Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 5 additions & 8 deletions consensus/bor/bor.go
Original file line number Diff line number Diff line change
Expand Up @@ -1104,7 +1104,10 @@ func (c *Bor) Prepare(chain consensus.ChainHeaderReader, header *types.Header, w
if currentSigner.signer != (common.Address{}) {
succession, err = snap.GetSignerSuccessionNumber(currentSigner.signer)
if err != nil {
return err
// If the signer is not in the active validator set, use succession 0
// so that the pending block header is still valid for RPC queries.
// Seal() will independently reject the block if unauthorized.
succession = 0
}
}

Expand Down Expand Up @@ -1154,15 +1157,9 @@ func (c *Bor) Prepare(chain consensus.ChainHeaderReader, header *types.Header, w

// Wait before start the block production if needed (previously this wait was on Seal)
if c.config.IsGiugliano(header.Number) && waitOnPrepare {
var successionNumber int
// if signer is not empty (RPC nodes have empty signer)
if currentSigner.signer != (common.Address{}) {
var err error
successionNumber, err = snap.GetSignerSuccessionNumber(currentSigner.signer)
if err != nil {
return err
}
if successionNumber == 0 {
if succession == 0 {
<-time.After(delay)
}
}
Expand Down
7 changes: 6 additions & 1 deletion miner/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -973,7 +973,12 @@ func (w *worker) taskLoop() {
w.pendingMu.Unlock()

if err := w.engine.Seal(w.chain, task.block, task.state.Witness(), w.resultCh, stopCh); err != nil {
log.Warn("Block sealing failed", "err", err)
switch err.(type) {
case *bor.UnauthorizedSignerError:
log.Debug("Block sealing skipped (not in validator set)", "err", err)
default:
log.Warn("Block sealing failed", "err", err)
}
w.pendingMu.Lock()
delete(w.pendingTasks, sealHash)
w.pendingMu.Unlock()
Expand Down
51 changes: 51 additions & 0 deletions miner/worker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,57 @@ import (
borSpan "github.com/ethereum/go-ethereum/consensus/bor/heimdall/span"
)

// TestPendingStateNotStaleForNonValidator verifies that a Bor node whose signer
// is NOT in the active validator set still keeps its pending snapshot fresh.
// Regression test: previously, Prepare() returned UnauthorizedSignerError for
// non-validators, which caused prepareWork to fail and the snapshot to never
// update, leading to stale trie errors on "pending" RPC queries.
func TestPendingStateNotStaleForNonValidator(t *testing.T) {
chainConfig := *params.BorUnittestChainConfig

engine, ctrl := getFakeBorFromConfig(t, &chainConfig)
defer engine.Close()
defer ctrl.Finish()

// Set up the worker normally, then re-authorize with a key NOT in the
// validator set. newTestWorkerBackend authorizes with testBankAddress
// (which IS the validator), so we must override AFTER backend creation.
nonValidatorKey, _ := crypto.GenerateKey()
nonValidatorAddr := crypto.PubkeyToAddress(nonValidatorKey.PublicKey)

db := rawdb.NewMemoryDatabase()
backend := newTestWorkerBackend(t, &chainConfig, engine, db)
w := newWorker(DefaultTestConfig(), &chainConfig, engine, backend, new(event.TypeMux), nil, false, false)
defer w.close()

// Override the signer to one NOT in the validator set.
engine.(*bor.Bor).Authorize(nonValidatorAddr, func(account accounts.Account, s string, data []byte) ([]byte, error) {
return crypto.Sign(crypto.Keccak256(data), nonValidatorKey)
})
w.setEtherbase(nonValidatorAddr)

// Start the worker. It will call commitWork which calls Prepare.
// Before the fix: Prepare fails with UnauthorizedSignerError, snapshot
// is never set, pending() returns nil.
// After the fix: Prepare succeeds (defaults succession to 0), snapshot
// is updated, pending() returns valid state.
w.start()

// Give the worker time to process the start event and run commitWork.
time.Sleep(1 * time.Second)

pendingBlock, _, pendingState := w.pending()
require.NotNil(t, pendingBlock, "pending block should not be nil for non-validator node")
require.NotNil(t, pendingState, "pending state should not be nil for non-validator node")

// The pending state must be readable without errors (not a stale trie).
balance := pendingState.GetBalance(testBankAddress)
require.False(t, balance.IsZero(),
"pending state balance for funded account should not be zero (would indicate stale trie)")
require.NoError(t, pendingState.Error(),
"pending state should have no database errors")
}

// nolint : paralleltest
func TestGenerateBlockAndImportClique(t *testing.T) {
testGenerateBlockAndImport(t, true, false)
Expand Down
Loading