Skip to content

Commit 1d33099

Browse files
committed
dbft: detach dBFT extensible payloads verifier from dBFT engine
1 parent 16cd550 commit 1d33099

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
// staticPool is a legacy pool instance for decrypted transaction verification,
227216
// which is initialized once per height at postBlock callback. It should be reset
228217
// before any reusage at the same height.
@@ -313,9 +302,6 @@ func New(chainCfg *params.ChainConfig, _ ethdb.Database, statisticsCfg Statistic
313302
quit: make(chan struct{}),
314303
finished: make(chan struct{}),
315304

316-
validatorsCache: lru.NewCache[uint64, []common.Address](validatorsCacheCap),
317-
dkgIndexCache: lru.NewCache[uint64, []int](validatorsCacheCap),
318-
319305
dkgSnapshot: NewSnapshot(),
320306
executeProofTaskChan: make(chan *taskList, 2), // The maximum number of task lists per epoch is 3
321307
loopWatchTaskChan: make(chan struct{}),
@@ -442,7 +428,7 @@ func (c *DBFT) getValidatorsCb(txs ...dbft.Transaction[common.Hash]) []dbft.Publ
442428
// getValidatorsSorted with empty args is used by dbft to fill the list of
443429
// block's validators, thus should return validators from the current
444430
// epoch without recalculation.
445-
pKeys, err = c.getValidatorsSorted(&c.lastIndex, nil, nil)
431+
pKeys, err = c.extensibleVerifier.GetValidatorsSorted(&c.lastIndex, nil, nil)
446432
}
447433
// getValidatorsSorted with non-empty args is used by dbft to fill block's
448434
// NextConsensus field, but DBFT doesn't provide WithGetConsensusAddress
@@ -1065,7 +1051,7 @@ func (c *DBFT) processPreBlockCb(b dbft.PreBlock[common.Hash]) error {
10651051
)
10661052
for _, preC := range ctx.PreCommitPayloads {
10671053
if preC != nil && preC.ViewNumber() == ctx.ViewNumber {
1068-
dkgIndex, err := c.getDKGIndex(int(preC.ValidatorIndex()), blockNum)
1054+
dkgIndex, err := c.extensibleVerifier.GetDKGIndex(int(preC.ValidatorIndex()), blockNum)
10691055
if err != nil {
10701056
return fmt.Errorf("get DKG index failed: ValidatorIndex %d, block height %d", int(preC.ValidatorIndex()), blockNum)
10711057
}
@@ -1409,7 +1395,7 @@ func (c *DBFT) getBlockWitness(pub *tpke.PublicKey, block *Block) ([]byte, error
14091395
if p := dctx.CommitPayloads[i]; p != nil && p.ViewNumber() == dctx.ViewNumber {
14101396
var err error
14111397
blockNum := block.header.Number.Uint64() - 1
1412-
dkgIndex, err := c.getDKGIndex(i, blockNum)
1398+
dkgIndex, err := c.extensibleVerifier.GetDKGIndex(i, blockNum)
14131399
if err != nil {
14141400
return nil, fmt.Errorf("get DKG index failed: ValidatorIndex %d, block height %d", i, blockNum)
14151401
}
@@ -1482,47 +1468,11 @@ func (c *DBFT) WithRequestTxs(f func(hashed []common.Hash)) {
14821468
func (c *DBFT) WithMux(mux *event.TypeMux) {
14831469
c.mux = mux
14841470
c.blockQueue.SetMux(mux)
1485-
1486-
go c.syncWatcher()
14871471
}
14881472

1489-
// syncWatcher is a standalone loop aimed to be active irrespectively of dBFT engine
1490-
// activity. It tracks the first chain sync attempt till its end.
1491-
func (c *DBFT) syncWatcher() {
1492-
downloaderEvents := c.mux.Subscribe(downloader.StartEvent{}, downloader.DoneEvent{}, downloader.FailedEvent{})
1493-
defer func() {
1494-
if !downloaderEvents.Closed() {
1495-
downloaderEvents.Unsubscribe()
1496-
}
1497-
}()
1498-
dlEventCh := downloaderEvents.Chan()
1499-
1500-
events:
1501-
for {
1502-
select {
1503-
case <-c.quit:
1504-
break events
1505-
case ev := <-dlEventCh:
1506-
if ev == nil {
1507-
// Unsubscription done, stop listening.
1508-
dlEventCh = nil
1509-
break events
1510-
}
1511-
switch ev.Data.(type) {
1512-
case downloader.StartEvent:
1513-
c.syncing.Store(true)
1514-
1515-
case downloader.FailedEvent:
1516-
c.syncing.Store(false)
1517-
1518-
case downloader.DoneEvent:
1519-
c.syncing.Store(false)
1520-
1521-
// Stop reacting to downloader events.
1522-
downloaderEvents.Unsubscribe()
1523-
}
1524-
}
1525-
}
1473+
// WithExtensibleVerifier initializes ExtensibleVerifier for extensible payloads verification.
1474+
func (c *DBFT) WithExtensibleVerifier(ev *verifier.ExtensibleVerifier) {
1475+
c.extensibleVerifier = ev
15261476
}
15271477

15281478
// WithTxPool initializes transaction pool API for DBFT interactions with memory pool
@@ -2212,7 +2162,7 @@ func (c *DBFT) eventLoop() {
22122162
// been broadcasted the events are unregistered and the loop is exited. This to
22132163
// prevent a major security vuln where external parties can DOS you with blocks
22142164
// and halt your dBFT operation for as long as the DOS continues.
2215-
downloaderEvents := c.mux.Subscribe(downloader.DoneEvent{}, downloader.FailedEvent{})
2165+
downloaderEvents := c.mux.Subscribe(downloader.StartEvent{}, downloader.DoneEvent{}, downloader.FailedEvent{})
22162166
defer func() {
22172167
if !downloaderEvents.Closed() {
22182168
downloaderEvents.Unsubscribe()
@@ -2286,7 +2236,11 @@ events:
22862236
continue
22872237
}
22882238
switch ev.Data.(type) {
2239+
case downloader.StartEvent:
2240+
c.syncing.Store(true)
22892241
case downloader.FailedEvent:
2242+
c.syncing.Store(false)
2243+
22902244
latest := c.chain.CurrentHeader()
22912245
err := c.handleChainBlock(latest, false)
22922246
if err != nil {
@@ -2297,6 +2251,8 @@ events:
22972251
}
22982252

22992253
case downloader.DoneEvent:
2254+
c.syncing.Store(false)
2255+
23002256
// Stop reacting to downloader events.
23012257
downloaderEvents.Unsubscribe()
23022258

@@ -2435,7 +2391,7 @@ func payloadFromMessage(ep *dbftproto.Message, getBlockExtraVersion func(*big.In
24352391

24362392
func (c *DBFT) validatePayload(p *Payload) error {
24372393
h := c.chain.CurrentBlock().Number.Uint64()
2438-
validators, err := c.getValidatorsSorted(&h, nil, nil)
2394+
validators, err := c.extensibleVerifier.GetValidatorsSorted(&h, nil, nil)
24392395
if err != nil {
24402396
return fmt.Errorf("failed to get next block validators: %w", err)
24412397
}
@@ -2451,25 +2407,6 @@ func (c *DBFT) validatePayload(p *Payload) error {
24512407
return nil
24522408
}
24532409

2454-
// IsExtensibleAllowed determines if address is allowed to send extensible payloads
2455-
// (only consensus payloads for now) at the specified height.
2456-
func (c *DBFT) IsExtensibleAllowed(h uint64, u common.Address) error {
2457-
// Can't verify extensible sender if the node has an outdated state.
2458-
if c.syncing.Load() {
2459-
return dbftproto.ErrSyncing
2460-
}
2461-
// Only validators are included into extensible whitelist for now.
2462-
validators, err := c.getValidatorsSorted(&h, nil, nil)
2463-
if err != nil {
2464-
return fmt.Errorf("failed to get validators: %w", err)
2465-
}
2466-
_, found := slices.BinarySearchFunc(validators, u, common.Address.Cmp)
2467-
if !found {
2468-
return fmt.Errorf("address is not a validator")
2469-
}
2470-
return nil
2471-
}
2472-
24732410
func (c *DBFT) newConsensusPayloadCb(ctx *dbft.Context[common.Hash], t dbft.MessageType, msg any) dbft.ConsensusPayload[common.Hash] {
24742411
var cp = new(Payload)
24752412
cp.BlockIndex = uint64(ctx.BlockIndex)
@@ -2537,7 +2474,7 @@ func (c *DBFT) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, pa
25372474

25382475
func (c *DBFT) calcDifficulty(signer common.Address, parent *types.Header) *big.Int {
25392476
h := parent.Number.Uint64()
2540-
vals, err := c.getValidatorsSorted(&h, nil, nil)
2477+
vals, err := c.extensibleVerifier.GetValidatorsSorted(&h, nil, nil)
25412478
if err != nil {
25422479
return nil
25432480
}
@@ -2722,111 +2659,6 @@ func (c *DBFT) APIs(chain consensus.ChainHeaderReader) []rpc.API {
27222659
}}
27232660
}
27242661

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

2863-
nextVals, err := c.getValidatorsSorted(nil, s.Copy(), h)
2695+
nextVals, err := c.extensibleVerifier.GetValidatorsSorted(nil, s.Copy(), h)
28642696
if err != nil {
28652697
log.Crit("Failed to compute next block validators",
28662698
"err", err)

0 commit comments

Comments
 (0)