Skip to content

Commit

Permalink
miner: fix miner competition (#57)
Browse files Browse the repository at this point in the history
### Description

- backporting ethereum/go-ethereum#27218 
- remove `commitWork` delay, delay exist the `Seal` stage, `commitWork`
delay causing timing confusion.
- change `Seal` stage delay, reduce chain reorganization

---------

Signed-off-by: 0xcb9ff9 <0xcb9ff9@proton.me>
  • Loading branch information
0xcb9ff9 committed Nov 10, 2023
1 parent 45f3636 commit e13fda7
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 219 deletions.
29 changes: 2 additions & 27 deletions consensus/drab/drab.go
Original file line number Diff line number Diff line change
Expand Up @@ -883,32 +883,7 @@ func (d *Drab) Authorize(val common.Address, signFn SignerFn, signTxFn SignerTxF

// Argument leftOver is the time reserved for block finalize(calculate root, distribute income...)
func (d *Drab) Delay(chain consensus.ChainReader, header *types.Header, leftOver *time.Duration) *time.Duration {
number := header.Number.Uint64()
snap, err := d.snapshot(chain, number-1, header.ParentHash, nil)
if err != nil {
return nil
}

delay := d.delayForHawaiiFork(snap, header)

if *leftOver >= time.Duration(d.config.BlockTime)*time.Second {
// ignore invalid leftOver
log.Error("Delay invalid argument", "leftOver", leftOver.String(), "BlockTime", d.config.BlockTime)
} else if *leftOver >= delay {
// no left time
delay = time.Duration(0)
return &delay
} else {
// delay
delay = delay - *leftOver
}

// The blocking time should be no more than half of period
half := time.Duration(d.config.BlockTime) * time.Second / 2
if delay > half {
delay = half
}
return &delay
return nil
}

// Seal implements consensus.Engine, attempting to create a sealed block using
Expand Down Expand Up @@ -947,7 +922,7 @@ func (d *Drab) Seal(chain consensus.ChainHeaderReader, block *types.Block, resul
// Signer is among recents, only wait if the current block doesn't shift it out
if limit := uint64(snap.blockLimit()); number < limit || seen+limit > number {
log.Info("Sealing found signed recently, must wait for others", "seen", seen, "limit", limit, "number", number)
return nil
return errRecentlySigned
}
}
}
Expand Down
51 changes: 42 additions & 9 deletions consensus/drab/hawaii.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,52 @@ import (
)

const (
wiggleTime = uint64(1) // second, Random delay (per signer) to allow concurrent signers
initialBackOffTime = uint64(1) // second
processBackOffTime = uint64(1) // second
wiggleTimeBeforeFork = 500 * time.Millisecond // Random delay (per signer) to allow concurrent signers
fixedBackOffTimeBeforeFork = 200 * time.Millisecond
wiggleTime = uint64(1) // second, Random delay (per signer) to allow concurrent signers
initialBackOffTime = uint64(1) // second
processBackOffTime = uint64(1) // second

wiggleTimeBeforeForkGranularity = 3 * time.Millisecond // Time granularity of the random delay
fixedBackOffTimeBeforeFork = 200 * time.Millisecond
)

var (
randDelaySeed = rand.New(rand.NewSource(time.Now().UnixNano()))
)

//// Consensus time schedule design:
/**
┌──────────────────────────────────────────────────────┐
│ block N │
├─────────────────┬───────────────┬────────────────────┤
Diff No Turn: │ fillTx time │ DelayLeftOver │ wait seal block │
└─────────────────┴───────────────┴────────────────────┘
┌──────────────────────────────────────────────────────┬─────────────────────────────────────────────────────────┐
│ block N │ block N+1 │
├─────────────────┬───────────────┬────────────────────┴─────────────────────┬───────────────────────────────────┴───────────┐
Diff Turn: │ fillTx time │ DelayLeftOver │ never seal block │ preempt seal block │
└─────────────────┴───────────────┼────────────────────┬─────────────────────┼───────────────────────────────────────────────┤
│ wait header time │ fixed backoff delay │ random blockTime*blockLimit range (step 11us)│
└────────────────────┴─────────────────────┴───────────────────────────────────────────────┘
**/

func (d *Drab) delayForHawaiiFork(snap *Snapshot, header *types.Header) time.Duration {
delay := time.Until(time.Unix(int64(header.Time), 0)) // nolint: gosimple
delay := time.Until(time.Unix(int64(header.Time), 0)) // time until the block is supposed to be mined
// if delay <= 0 we are late, so we should try to sign immediately
if delay <= 0 {
delay = 0
}

if header.Difficulty.Cmp(diffNoTurn) == 0 {
// It's not our turn explicitly to sign, delay it a bit
wiggle := time.Duration(snap.blockLimit()) * wiggleTimeBeforeFork
delay += fixedBackOffTimeBeforeFork + time.Duration(rand.Int63n(int64(wiggle)))
// It's not our turn explicitly to sign, delay it.
// Wait other validators have signed recently, if timeout we can try sign immediately.
backOffTime := time.Duration(d.config.BlockTime) * time.Second // fixed backoff time
// wiggle time is random delay (per signer) to allow concurrent signers
wiggle := time.Duration(snap.blockLimit()) *
wiggleTimeBeforeForkGranularity *
time.Duration(1+randDelaySeed.Int63n(int64(backOffTime/wiggleTimeBeforeForkGranularity)))

// delay = durationToBlockTimestamp + fixedBackOffTimeBeforeFork + randomRange(wiggleTimeBeforeForkGranularity, blockTime*blockLimit)
delay += backOffTime + fixedBackOffTimeBeforeFork + wiggle
}
return delay
}
Expand Down
6 changes: 6 additions & 0 deletions eth/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,9 @@ func (api *PublicDebugAPI) DumpBlock(blockNr rpc.BlockNumber) (state.Dump, error
// both the pending block as well as the pending state from
// the miner and operate on those
_, stateDb := api.eth.miner.Pending()
if stateDb == nil {
return state.Dump{}, errors.New("pending state is not available")
}
return stateDb.RawDump(opts), nil
}
var block *types.Block
Expand Down Expand Up @@ -369,6 +372,9 @@ func (api *PublicDebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, sta
// both the pending block as well as the pending state from
// the miner and operate on those
_, stateDb = api.eth.miner.Pending()
if stateDb == nil {
return state.IteratorDump{}, errors.New("pending state is not available")
}
} else {
var block *types.Block
if number == rpc.LatestBlockNumber {
Expand Down
9 changes: 9 additions & 0 deletions eth/api_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ func (b *EthAPIBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumb
// Pending block is only known by the miner
if number == rpc.PendingBlockNumber {
block := b.eth.miner.PendingBlock()
if block == nil {
return nil, errors.New("pending block is not available")
}
return block.Header(), nil
}
// Otherwise resolve and return the block
Expand Down Expand Up @@ -108,6 +111,9 @@ func (b *EthAPIBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumbe
// Pending block is only known by the miner
if number == rpc.PendingBlockNumber {
block := b.eth.miner.PendingBlock()
if block == nil {
return nil, errors.New("pending block is not available")
}
return block, nil
}
// Otherwise resolve and return the block
Expand Down Expand Up @@ -150,6 +156,9 @@ func (b *EthAPIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.B
// Pending state is only known by the miner
if number == rpc.PendingBlockNumber {
block, state := b.eth.miner.Pending()
if block == nil || state == nil {
return nil, nil, errors.New("pending state is not available")
}
return state, block.Header(), nil
}
// Otherwise resolve the block number and return its state
Expand Down
3 changes: 3 additions & 0 deletions eth/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,9 @@ func (s *Ethereum) shouldPreserve(header *types.Header) bool {
if _, ok := s.engine.(*parlia.Parlia); ok {
return false
}
if _, ok := s.engine.(*drab.Drab); ok {
return false
}
return s.isLocalBlock(header)
}

Expand Down
14 changes: 11 additions & 3 deletions miner/miner.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,18 +120,24 @@ func (miner *Miner) update() {
shouldStart = true
log.Info("Mining aborted due to sync")
}
miner.worker.syncing.Store(true)

case downloader.FailedEvent:
canStart = true
if shouldStart {
miner.SetEtherbase(miner.coinbase)
miner.worker.start()
}
miner.worker.syncing.Store(false)

case downloader.DoneEvent:
canStart = true
if shouldStart {
miner.SetEtherbase(miner.coinbase)
miner.worker.start()
}
miner.worker.syncing.Store(false)

// Stop reacting to downloader events
events.Unsubscribe()
}
Expand Down Expand Up @@ -188,7 +194,8 @@ func (miner *Miner) SetRecommitInterval(interval time.Duration) {
miner.worker.setRecommitInterval(interval)
}

// Pending returns the currently pending block and associated state.
// Pending returns the currently pending block and associated state. The returned
// values can be nil in case the pending block is not initialized
func (miner *Miner) Pending() (*types.Block, *state.StateDB) {
if miner.worker.isRunning() {
pendingBlock, pendingState := miner.worker.pending()
Expand All @@ -208,11 +215,11 @@ func (miner *Miner) Pending() (*types.Block, *state.StateDB) {
return block, stateDb
}

// PendingBlock returns the currently pending block.
// PendingBlock returns the currently pending block. The returned block can be
// nil in case the pending block is not initialized.
//
// Note, to access both the pending block and the pending state
// simultaneously, please use Pending(), as the pending state can
// change between multiple method calls
func (miner *Miner) PendingBlock() *types.Block {
if miner.worker.isRunning() {
pendingBlock := miner.worker.pendingBlock()
Expand All @@ -225,6 +232,7 @@ func (miner *Miner) PendingBlock() *types.Block {
}

// PendingBlockAndReceipts returns the currently pending block and corresponding receipts.
// The returned values can be nil in case the pending block is not initialized.
func (miner *Miner) PendingBlockAndReceipts() (*types.Block, types.Receipts) {
return miner.worker.pendingBlockAndReceipts()
}
Expand Down
Loading

0 comments on commit e13fda7

Please sign in to comment.