Skip to content

Commit a059dc4

Browse files
committed
dbft: detach dBFT extensible payloads verifier from dBFT engine
1 parent 84177b7 commit a059dc4

File tree

3 files changed

+286
-197
lines changed

3 files changed

+286
-197
lines changed

consensus/dbft/dbft.go

Lines changed: 20 additions & 188 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,12 @@ package dbft
1919

2020
import (
2121
"bytes"
22-
"context"
2322
"encoding/binary"
2423
"encoding/hex"
2524
"errors"
2625
"fmt"
2726
"io"
2827
"math/big"
29-
"sort"
3028
"sync"
3129
"sync/atomic"
3230
"time"
@@ -35,15 +33,12 @@ import (
3533
"github.com/ethereum/go-ethereum/accounts/abi"
3634
"github.com/ethereum/go-ethereum/antimev"
3735
"github.com/ethereum/go-ethereum/common"
38-
"github.com/ethereum/go-ethereum/common/hexutil"
39-
"github.com/ethereum/go-ethereum/common/lru"
4036
"github.com/ethereum/go-ethereum/consensus"
4137
"github.com/ethereum/go-ethereum/consensus/dbft/dbftutil"
4238
"github.com/ethereum/go-ethereum/consensus/misc"
4339
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
4440
"github.com/ethereum/go-ethereum/core"
4541
"github.com/ethereum/go-ethereum/core/state"
46-
"github.com/ethereum/go-ethereum/core/systemcontracts"
4742
"github.com/ethereum/go-ethereum/core/txpool"
4843
"github.com/ethereum/go-ethereum/core/txpool/legacypool"
4944
"github.com/ethereum/go-ethereum/core/types"
@@ -52,6 +47,7 @@ import (
5247
"github.com/ethereum/go-ethereum/crypto/tpke"
5348
"github.com/ethereum/go-ethereum/eth/downloader"
5449
dbftproto "github.com/ethereum/go-ethereum/eth/protocols/dbft"
50+
"github.com/ethereum/go-ethereum/eth/verifier"
5551
"github.com/ethereum/go-ethereum/ethdb"
5652
"github.com/ethereum/go-ethereum/event"
5753
"github.com/ethereum/go-ethereum/internal/ethapi"
@@ -87,11 +83,6 @@ const (
8783
// msgsChCap is a capacity of channel that accepts consensus messages from
8884
// dBFT protocol.
8985
msgsChCap = 100
90-
// validatorsCacheCap is a capacity of validators cache. It's enough to store
91-
// validators for only three potentially subsequent heights, i.e. three latest
92-
// blocks to effectivaly verify dBFT payloads travelling through the network and
93-
// properly initialize dBFT at the latest height.
94-
validatorsCacheCap = 3
9586
// crossEpochDecryptionStartRound is the number of DKG round (as denoted in KeyManagement
9687
// system contract) starting from which continuous cross-epoch Envelopes decryption is supported.
9788
// First DKG round setups sharing key, second DKG round setups resharing key, hence resharing
@@ -218,11 +209,9 @@ type DBFT struct {
218209
finished chan struct{}
219210

220211
// various native contract APIs that dBFT uses.
221-
backend *ethapi.Backend
222-
txAPI *ethapi.TransactionAPI
223-
validatorsCache *lru.Cache[uint64, []common.Address]
224-
// dkgIndexCache is a cache for storing the index array of the ordered validators
225-
dkgIndexCache *lru.Cache[uint64, []int]
212+
backend *ethapi.Backend
213+
txAPI *ethapi.TransactionAPI
214+
extensibleVerifier *verifier.ExtensibleVerifier
226215

227216
// The fields below are for testing only
228217
fakeDiff bool // Skip difficulty verifications
@@ -309,9 +298,6 @@ func New(chainCfg *params.ChainConfig, _ ethdb.Database, statisticsCfg Statistic
309298
quit: make(chan struct{}),
310299
finished: make(chan struct{}),
311300

312-
validatorsCache: lru.NewCache[uint64, []common.Address](validatorsCacheCap),
313-
dkgIndexCache: lru.NewCache[uint64, []int](validatorsCacheCap),
314-
315301
dkgSnapshot: NewSnapshot(),
316302
executeProofTaskChan: make(chan *taskList, 2), // The maximum number of task lists per epoch is 3
317303
loopWatchTaskChan: make(chan struct{}),
@@ -438,7 +424,7 @@ func (c *DBFT) getValidatorsCb(txs ...dbft.Transaction[common.Hash]) []dbft.Publ
438424
// getValidatorsSorted with empty args is used by dbft to fill the list of
439425
// block's validators, thus should return validators from the current
440426
// epoch without recalculation.
441-
pKeys, err = c.getValidatorsSorted(&c.lastIndex, nil, nil)
427+
pKeys, err = c.extensibleVerifier.GetValidatorsSorted(&c.lastIndex, nil, nil)
442428
}
443429
// getValidatorsSorted with non-empty args is used by dbft to fill block's
444430
// NextConsensus field, but DBFT doesn't provide WithGetConsensusAddress
@@ -1058,7 +1044,7 @@ func (c *DBFT) processPreBlockCb(b dbft.PreBlock[common.Hash]) error {
10581044
)
10591045
for _, preC := range ctx.PreCommitPayloads {
10601046
if preC != nil && preC.ViewNumber() == ctx.ViewNumber {
1061-
dkgIndex, err := c.getDKGIndex(int(preC.ValidatorIndex()), blockNum)
1047+
dkgIndex, err := c.extensibleVerifier.GetDKGIndex(int(preC.ValidatorIndex()), blockNum)
10621048
if err != nil {
10631049
return fmt.Errorf("get DKG index failed: ValidatorIndex %d, block height %d", int(preC.ValidatorIndex()), blockNum)
10641050
}
@@ -1400,7 +1386,7 @@ func (c *DBFT) getBlockWitness(pub *tpke.PublicKey, block *Block) ([]byte, error
14001386
if p := dctx.CommitPayloads[i]; p != nil && p.ViewNumber() == dctx.ViewNumber {
14011387
var err error
14021388
blockNum := block.header.Number.Uint64() - 1
1403-
dkgIndex, err := c.getDKGIndex(i, blockNum)
1389+
dkgIndex, err := c.extensibleVerifier.GetDKGIndex(i, blockNum)
14041390
if err != nil {
14051391
return nil, fmt.Errorf("get DKG index failed: ValidatorIndex %d, block height %d", i, blockNum)
14061392
}
@@ -1473,47 +1459,11 @@ func (c *DBFT) WithRequestTxs(f func(hashed []common.Hash)) {
14731459
func (c *DBFT) WithMux(mux *event.TypeMux) {
14741460
c.mux = mux
14751461
c.blockQueue.SetMux(mux)
1476-
1477-
go c.syncWatcher()
14781462
}
14791463

1480-
// syncWatcher is a standalone loop aimed to be active irrespectively of dBFT engine
1481-
// activity. It tracks the first chain sync attempt till its end.
1482-
func (c *DBFT) syncWatcher() {
1483-
downloaderEvents := c.mux.Subscribe(downloader.StartEvent{}, downloader.DoneEvent{}, downloader.FailedEvent{})
1484-
defer func() {
1485-
if !downloaderEvents.Closed() {
1486-
downloaderEvents.Unsubscribe()
1487-
}
1488-
}()
1489-
dlEventCh := downloaderEvents.Chan()
1490-
1491-
events:
1492-
for {
1493-
select {
1494-
case <-c.quit:
1495-
break events
1496-
case ev := <-dlEventCh:
1497-
if ev == nil {
1498-
// Unsubscription done, stop listening.
1499-
dlEventCh = nil
1500-
break events
1501-
}
1502-
switch ev.Data.(type) {
1503-
case downloader.StartEvent:
1504-
c.syncing.Store(true)
1505-
1506-
case downloader.FailedEvent:
1507-
c.syncing.Store(false)
1508-
1509-
case downloader.DoneEvent:
1510-
c.syncing.Store(false)
1511-
1512-
// Stop reacting to downloader events.
1513-
downloaderEvents.Unsubscribe()
1514-
}
1515-
}
1516-
}
1464+
// WithExtensibleVerifier initializes ExtensibleVerifier for extensible payloads verification.
1465+
func (c *DBFT) WithExtensibleVerifier(ev *verifier.ExtensibleVerifier) {
1466+
c.extensibleVerifier = ev
15171467
}
15181468

15191469
// WithTxPool initializes transaction pool API for DBFT interactions with memory pool
@@ -2202,7 +2152,7 @@ func (c *DBFT) eventLoop() {
22022152
// been broadcasted the events are unregistered and the loop is exited. This to
22032153
// prevent a major security vuln where external parties can DOS you with blocks
22042154
// and halt your dBFT operation for as long as the DOS continues.
2205-
downloaderEvents := c.mux.Subscribe(downloader.DoneEvent{}, downloader.FailedEvent{})
2155+
downloaderEvents := c.mux.Subscribe(downloader.StartEvent{}, downloader.DoneEvent{}, downloader.FailedEvent{})
22062156
defer func() {
22072157
if !downloaderEvents.Closed() {
22082158
downloaderEvents.Unsubscribe()
@@ -2276,7 +2226,11 @@ events:
22762226
continue
22772227
}
22782228
switch ev.Data.(type) {
2229+
case downloader.StartEvent:
2230+
c.syncing.Store(true)
22792231
case downloader.FailedEvent:
2232+
c.syncing.Store(false)
2233+
22802234
latest := c.chain.CurrentHeader()
22812235
err := c.handleChainBlock(latest, false)
22822236
if err != nil {
@@ -2287,6 +2241,8 @@ events:
22872241
}
22882242

22892243
case downloader.DoneEvent:
2244+
c.syncing.Store(false)
2245+
22902246
// Stop reacting to downloader events.
22912247
downloaderEvents.Unsubscribe()
22922248

@@ -2425,7 +2381,7 @@ func payloadFromMessage(ep *dbftproto.Message, getBlockExtraVersion func(*big.In
24252381

24262382
func (c *DBFT) validatePayload(p *Payload) error {
24272383
h := c.chain.CurrentBlock().Number.Uint64()
2428-
validators, err := c.getValidatorsSorted(&h, nil, nil)
2384+
validators, err := c.extensibleVerifier.GetValidatorsSorted(&h, nil, nil)
24292385
if err != nil {
24302386
return fmt.Errorf("failed to get next block validators: %w", err)
24312387
}
@@ -2441,25 +2397,6 @@ func (c *DBFT) validatePayload(p *Payload) error {
24412397
return nil
24422398
}
24432399

2444-
// IsExtensibleAllowed determines if address is allowed to send extensible payloads
2445-
// (only consensus payloads for now) at the specified height.
2446-
func (c *DBFT) IsExtensibleAllowed(h uint64, u common.Address) error {
2447-
// Can't verify extensible sender if the node has an outdated state.
2448-
if c.syncing.Load() {
2449-
return dbftproto.ErrSyncing
2450-
}
2451-
// Only validators are included into extensible whitelist for now.
2452-
validators, err := c.getValidatorsSorted(&h, nil, nil)
2453-
if err != nil {
2454-
return fmt.Errorf("failed to get validators: %w", err)
2455-
}
2456-
_, found := slices.BinarySearchFunc(validators, u, common.Address.Cmp)
2457-
if !found {
2458-
return fmt.Errorf("address is not a validator")
2459-
}
2460-
return nil
2461-
}
2462-
24632400
func (c *DBFT) newConsensusPayloadCb(ctx *dbft.Context[common.Hash], t dbft.MessageType, msg any) dbft.ConsensusPayload[common.Hash] {
24642401
var cp = new(Payload)
24652402
cp.BlockIndex = uint64(ctx.BlockIndex)
@@ -2527,7 +2464,7 @@ func (c *DBFT) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, pa
25272464

25282465
func (c *DBFT) calcDifficulty(signer common.Address, parent *types.Header) *big.Int {
25292466
h := parent.Number.Uint64()
2530-
vals, err := c.getValidatorsSorted(&h, nil, nil)
2467+
vals, err := c.extensibleVerifier.GetValidatorsSorted(&h, nil, nil)
25312468
if err != nil {
25322469
return nil
25332470
}
@@ -2712,111 +2649,6 @@ func (c *DBFT) APIs(chain consensus.ChainHeaderReader) []rpc.API {
27122649
}}
27132650
}
27142651

2715-
// getValidatorsSorted returns validators chosen in the result of the latest
2716-
// finalized voting epoch. It calls Governance contract under the hood. The call
2717-
// is based on the provided state or (if not provided) on the state of the block
2718-
// with the specified height. Validators returned from this method are always
2719-
// sorted by bytes order (even if the list returned from governance contract is
2720-
// sorted in another way). This method uses cached values in case of validators
2721-
// requested by block height.
2722-
func (c *DBFT) getValidatorsSorted(blockNum *uint64, state *state.StateDB, header *types.Header) ([]common.Address, error) {
2723-
res, err := c.getValidators(blockNum, state, header)
2724-
if err != nil {
2725-
return nil, err
2726-
}
2727-
2728-
sortedList := slices.Clone(res)
2729-
slices.SortFunc(sortedList, common.Address.Cmp)
2730-
return sortedList, err
2731-
}
2732-
2733-
// getValidators returns validators chosen in the result of the latest finalized
2734-
// voting epoch. It calls Governance contract under the hood. The call is based
2735-
// on the provided state or (if not provided) on the state of the block with the
2736-
// specified height. Validators returned from this method are sorted in the original
2737-
// order used by Governance contract. This method uses cached values in case of
2738-
// validators requested by block height.
2739-
func (c *DBFT) getValidators(blockNum *uint64, state *state.StateDB, header *types.Header) ([]common.Address, error) {
2740-
if c.backend == nil {
2741-
return nil, errors.New("eth API backend is not initialized, dBFT can't function properly")
2742-
}
2743-
2744-
if state == nil && blockNum != nil {
2745-
vals, ok := c.validatorsCache.Get(*blockNum)
2746-
if ok {
2747-
return vals, nil
2748-
}
2749-
}
2750-
2751-
// Perform smart contract call.
2752-
method := "getCurrentConsensus" // latest finalized epoch validators.
2753-
data, err := systemcontracts.GovernanceABI.Pack(method)
2754-
if err != nil {
2755-
return nil, fmt.Errorf("failed to pack '%s': %w", method, err)
2756-
}
2757-
msgData := hexutil.Bytes(data)
2758-
gas := hexutil.Uint64(50_000_000) // more than enough for validators call processing.
2759-
args := ethapi.TransactionArgs{
2760-
Gas: &gas,
2761-
To: &systemcontracts.GovernanceProxyHash,
2762-
Data: &msgData,
2763-
}
2764-
2765-
ctx, cancel := context.WithCancel(context.Background())
2766-
// Cancel when we are finished consuming integers.
2767-
defer cancel()
2768-
var result *core.ExecutionResult
2769-
if state != nil {
2770-
result, err = ethapi.DoCallAtState(ctx, *c.backend, args, state, header, nil, nil, 0, 0)
2771-
} else if blockNum != nil {
2772-
blockNr := rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(*blockNum))
2773-
result, err = ethapi.DoCall(ctx, *c.backend, args, blockNr, nil, nil, 0, 0)
2774-
} else {
2775-
return nil, fmt.Errorf("failed to compute validators: both block number and state are nil")
2776-
}
2777-
if err != nil {
2778-
return nil, fmt.Errorf("failed to perform '%s' call: %w", method, err)
2779-
}
2780-
var res []common.Address
2781-
err = unpackContractExecutionResult(&res, result, systemcontracts.GovernanceABI, method)
2782-
if err != nil {
2783-
return nil, err
2784-
}
2785-
2786-
// Update cache in case if existing state was used for validators retrieval.
2787-
if state == nil && blockNum != nil {
2788-
_ = c.validatorsCache.Add(*blockNum, res)
2789-
}
2790-
2791-
return res, err
2792-
}
2793-
2794-
// getDKGIndex returns validator dkg index (original validator index +1) by validatorIndex (ordered validator index).
2795-
func (c *DBFT) getDKGIndex(validatorIndex int, blockNum uint64) (int, error) {
2796-
indices, ok := c.dkgIndexCache.Get(blockNum)
2797-
if !ok {
2798-
originValidators, err := c.getValidators(&blockNum, nil, nil)
2799-
if err != nil {
2800-
return -1, err
2801-
}
2802-
2803-
indices = make([]int, len(originValidators))
2804-
for i := range indices {
2805-
indices[i] = i
2806-
}
2807-
sort.Slice(indices, func(i, j int) bool {
2808-
return originValidators[indices[i]].Cmp(originValidators[indices[j]]) < 0
2809-
})
2810-
_ = c.dkgIndexCache.Add(blockNum, indices)
2811-
}
2812-
2813-
if validatorIndex < 0 || validatorIndex >= len(indices) {
2814-
return -1, fmt.Errorf("invalid validator index: validators count is %d, requested %d", len(indices), validatorIndex)
2815-
}
2816-
dkgIndex := indices[validatorIndex] + 1
2817-
return dkgIndex, nil
2818-
}
2819-
28202652
// getGlobalPublicKey returns TPKE global public key. If state is provided, then this state
28212653
// is used to recalculate local key based on the KeyManagement contract state. If state is
28222654
// not provided, then the node's local keystore is used to retrieve global public key.
@@ -2850,7 +2682,7 @@ func (c *DBFT) getGlobalPublicKey(h *types.Header, s *state.StateDB) (*tpke.Publ
28502682
func (c *DBFT) getNextConsensus(h *types.Header, s *state.StateDB) (common.Hash, common.Hash) {
28512683
var multisig, threshold common.Hash
28522684

2853-
nextVals, err := c.getValidatorsSorted(nil, s.Copy(), h)
2685+
nextVals, err := c.extensibleVerifier.GetValidatorsSorted(nil, s.Copy(), h)
28542686
if err != nil {
28552687
log.Crit("Failed to compute next block validators",
28562688
"err", err)

0 commit comments

Comments
 (0)