Skip to content

Commit 61489d0

Browse files
committed
dbft: detach dBFT extensible payloads verifier from dBFT engine
1 parent 05c56a3 commit 61489d0

File tree

10 files changed

+346
-244
lines changed

10 files changed

+346
-244
lines changed

consensus/dbft/dbft.go

Lines changed: 44 additions & 192 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"
@@ -87,11 +82,6 @@ const (
8782
// msgsChCap is a capacity of channel that accepts consensus messages from
8883
// dBFT protocol.
8984
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
9585
// crossEpochDecryptionStartRound is the number of DKG round (as denoted in KeyManagement
9686
// system contract) starting from which continuous cross-epoch Envelopes decryption is supported.
9787
// First DKG round setups sharing key, second DKG round setups resharing key, hence resharing
@@ -131,6 +121,9 @@ var (
131121

132122
// errUnauthorizedSigner is returned if a header is signed by a non-authorized entity.
133123
errUnauthorizedSigner = errors.New("unauthorized signer")
124+
125+
// errNotInitializedBackend is returned if the eth API backend is not initialized
126+
errNotInitializedBackend = errors.New("eth API backend is not initialized, DKG can't function properly")
134127
)
135128

136129
// errShutdown represents an error caused by engine shutdown initiated by user.
@@ -224,11 +217,8 @@ type DBFT struct {
224217
dkgTaskWatcherToCloseCh chan struct{}
225218

226219
// various native contract APIs that dBFT uses.
227-
backend *ethapi.Backend
228-
txAPI *ethapi.TransactionAPI
229-
validatorsCache *lru.Cache[uint64, []common.Address]
230-
// dkgIndexCache is a cache for storing the index array of the ordered validators
231-
dkgIndexCache *lru.Cache[uint64, []int]
220+
backend *ethapi.Backend
221+
txAPI *ethapi.TransactionAPI
232222
// staticPool is a legacy pool instance for decrypted transaction verification,
233223
// which is initialized once per height at postBlock callback. It should be reset
234224
// before any reusage at the same height.
@@ -320,9 +310,6 @@ func New(chainCfg *params.ChainConfig, _ ethdb.Database, statisticsCfg Statistic
320310
dkgTaskExecutorToCloseCh: make(chan struct{}),
321311
dkgTaskWatcherToCloseCh: make(chan struct{}),
322312

323-
validatorsCache: lru.NewCache[uint64, []common.Address](validatorsCacheCap),
324-
dkgIndexCache: lru.NewCache[uint64, []int](validatorsCacheCap),
325-
326313
dkgSnapshot: NewSnapshot(),
327314
dkgTaskExecutorCh: make(chan *taskList, 2), // The maximum number of task lists per epoch is 3
328315
dkgTaskWatcherCh: make(chan *taskList, 2),
@@ -445,12 +432,16 @@ func (c *DBFT) getValidatorsCb(txs ...dbft.Transaction[common.Hash]) []dbft.Publ
445432
err error
446433
)
447434
if txs == nil {
448-
// getValidatorsSorted with empty args is used by dbft to fill the list of
435+
// GetValidatorsSorted with empty args is used by dbft to fill the list of
449436
// block's validators, thus should return validators from the current
450437
// epoch without recalculation.
451-
pKeys, err = c.getValidatorsSorted(&c.lastIndex, nil, nil)
438+
if c.backend != nil {
439+
pKeys, err = (*c.backend).GetValidatorsSorted(&c.lastIndex, nil, nil)
440+
} else {
441+
err = errNotInitializedBackend
442+
}
452443
}
453-
// getValidatorsSorted with non-empty args is used by dbft to fill block's
444+
// GetValidatorsSorted with non-empty args is used by dbft to fill block's
454445
// NextConsensus field, but DBFT doesn't provide WithGetConsensusAddress
455446
// callback and fills NextConsensus by itself via WithNewBlockFromContext
456447
// callback. Thus, leave pKeys empty if txes != nil.
@@ -1064,10 +1055,16 @@ func (c *DBFT) processPreBlockCb(b dbft.PreBlock[common.Hash]) error {
10641055
sharesCurr = make(map[int][]*tpke.DecryptionShare, ctx.M())
10651056
sharesPrev = make(map[int][]*tpke.DecryptionShare)
10661057
blockNum = pre.header.Number.Uint64() - 1
1058+
dkgIndex int
1059+
err error
10671060
)
10681061
for _, preC := range ctx.PreCommitPayloads {
10691062
if preC != nil && preC.ViewNumber() == ctx.ViewNumber {
1070-
dkgIndex, err := c.getDKGIndex(int(preC.ValidatorIndex()), blockNum)
1063+
if c.backend != nil {
1064+
dkgIndex, err = (*c.backend).GetDKGIndex(blockNum, int(preC.ValidatorIndex()))
1065+
} else {
1066+
err = errNotInitializedBackend
1067+
}
10711068
if err != nil {
10721069
return fmt.Errorf("get DKG index failed: ValidatorIndex %d, block height %d", int(preC.ValidatorIndex()), blockNum)
10731070
}
@@ -1413,8 +1410,13 @@ func (c *DBFT) getBlockWitness(pub *tpke.PublicKey, block *Block) ([]byte, error
14131410
for i := 0; i < len(vals); i++ {
14141411
if p := dctx.CommitPayloads[i]; p != nil && p.ViewNumber() == dctx.ViewNumber {
14151412
var err error
1413+
var dkgIndex int
14161414
blockNum := block.header.Number.Uint64() - 1
1417-
dkgIndex, err := c.getDKGIndex(i, blockNum)
1415+
if c.backend != nil {
1416+
dkgIndex, err = (*c.backend).GetDKGIndex(blockNum, i)
1417+
} else {
1418+
err = errNotInitializedBackend
1419+
}
14181420
if err != nil {
14191421
return nil, fmt.Errorf("get DKG index failed: ValidatorIndex %d, block height %d", i, blockNum)
14201422
}
@@ -1487,47 +1489,6 @@ func (c *DBFT) WithRequestTxs(f func(hashed []common.Hash)) {
14871489
func (c *DBFT) WithMux(mux *event.TypeMux) {
14881490
c.mux = mux
14891491
c.blockQueue.SetMux(mux)
1490-
1491-
go c.syncWatcher()
1492-
}
1493-
1494-
// syncWatcher is a standalone loop aimed to be active irrespectively of dBFT engine
1495-
// activity. It tracks the first chain sync attempt till its end.
1496-
func (c *DBFT) syncWatcher() {
1497-
downloaderEvents := c.mux.Subscribe(downloader.StartEvent{}, downloader.DoneEvent{}, downloader.FailedEvent{})
1498-
defer func() {
1499-
if !downloaderEvents.Closed() {
1500-
downloaderEvents.Unsubscribe()
1501-
}
1502-
}()
1503-
dlEventCh := downloaderEvents.Chan()
1504-
1505-
events:
1506-
for {
1507-
select {
1508-
case <-c.quit:
1509-
break events
1510-
case ev := <-dlEventCh:
1511-
if ev == nil {
1512-
// Unsubscription done, stop listening.
1513-
dlEventCh = nil
1514-
break events
1515-
}
1516-
switch ev.Data.(type) {
1517-
case downloader.StartEvent:
1518-
c.syncing.Store(true)
1519-
1520-
case downloader.FailedEvent:
1521-
c.syncing.Store(false)
1522-
1523-
case downloader.DoneEvent:
1524-
c.syncing.Store(false)
1525-
1526-
// Stop reacting to downloader events.
1527-
downloaderEvents.Unsubscribe()
1528-
}
1529-
}
1530-
}
15311492
}
15321493

15331494
// WithTxPool initializes transaction pool API for DBFT interactions with memory pool
@@ -2234,7 +2195,7 @@ func (c *DBFT) eventLoop() {
22342195
// been broadcasted the events are unregistered and the loop is exited. This to
22352196
// prevent a major security vuln where external parties can DOS you with blocks
22362197
// and halt your dBFT operation for as long as the DOS continues.
2237-
downloaderEvents := c.mux.Subscribe(downloader.DoneEvent{}, downloader.FailedEvent{})
2198+
downloaderEvents := c.mux.Subscribe(downloader.StartEvent{}, downloader.DoneEvent{}, downloader.FailedEvent{})
22382199
dlEventCh := downloaderEvents.Chan()
22392200

22402201
events:
@@ -2294,10 +2255,16 @@ events:
22942255
continue
22952256
}
22962257
switch ev.Data.(type) {
2258+
case downloader.StartEvent:
2259+
c.syncing.Store(true)
22972260
case downloader.FailedEvent:
2261+
c.syncing.Store(false)
2262+
22982263
latest := c.chain.CurrentHeader()
22992264
c.handleChainBlock(latest, false)
23002265
case downloader.DoneEvent:
2266+
c.syncing.Store(false)
2267+
23012268
// Stop reacting to downloader events.
23022269
downloaderEvents.Unsubscribe()
23032270

@@ -2430,7 +2397,10 @@ func payloadFromMessage(ep *dbftproto.Message, getBlockExtraVersion func(*big.In
24302397

24312398
func (c *DBFT) validatePayload(p *Payload) error {
24322399
h := c.chain.CurrentBlock().Number.Uint64()
2433-
validators, err := c.getValidatorsSorted(&h, nil, nil)
2400+
if c.backend == nil {
2401+
return errNotInitializedBackend
2402+
}
2403+
validators, err := (*c.backend).GetValidatorsSorted(&h, nil, nil)
24342404
if err != nil {
24352405
return fmt.Errorf("failed to get next block validators: %w", err)
24362406
}
@@ -2446,25 +2416,6 @@ func (c *DBFT) validatePayload(p *Payload) error {
24462416
return nil
24472417
}
24482418

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

25342485
func (c *DBFT) calcDifficulty(signer common.Address, parent *types.Header) *big.Int {
25352486
h := parent.Number.Uint64()
2536-
vals, err := c.getValidatorsSorted(&h, nil, nil)
2487+
if c.backend == nil {
2488+
return nil
2489+
}
2490+
vals, err := (*c.backend).GetValidatorsSorted(&h, nil, nil)
25372491
if err != nil {
25382492
return nil
25392493
}
@@ -2722,111 +2676,6 @@ func (c *DBFT) APIs(chain consensus.ChainHeaderReader) []rpc.API {
27222676
}}
27232677
}
27242678

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-
28302679
// getGlobalPublicKey returns TPKE global public key. If state is provided, then this state
28312680
// is used to recalculate local key based on the KeyManagement contract state. If state is
28322681
// not provided, then the node's local keystore is used to retrieve global public key.
@@ -2860,7 +2709,10 @@ func (c *DBFT) getGlobalPublicKey(h *types.Header, s *state.StateDB) (*tpke.Publ
28602709
func (c *DBFT) getNextConsensus(h *types.Header, s *state.StateDB) (common.Hash, common.Hash) {
28612710
var multisig, threshold common.Hash
28622711

2863-
nextVals, err := c.getValidatorsSorted(nil, s.Copy(), h)
2712+
if c.backend == nil {
2713+
log.Crit("Can't calculate next consensus", "err", errNotInitializedBackend)
2714+
}
2715+
nextVals, err := (*c.backend).GetValidatorsSorted(nil, s.Copy(), h)
28642716
if err != nil {
28652717
log.Crit("Failed to compute next block validators",
28662718
"err", err)

consensus/dbft/dkg.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,7 +1045,7 @@ func getZKVersion(backend *ethapi.Backend, state *state.StateDB, header *types.H
10451045
// readFromContract calls a contract with ABI-packed inputs.
10461046
func readFromContract(res interface{}, backend *ethapi.Backend, contract common.Address, contractAbi abi.ABI, state *state.StateDB, header *types.Header, method string, args ...interface{}) error {
10471047
if backend == nil {
1048-
return errors.New("eth API backend is not initialized, DKG can't function properly")
1048+
return errNotInitializedBackend
10491049
}
10501050
data, err := contractAbi.Pack(method, args...)
10511051
if err != nil {
@@ -1072,7 +1072,7 @@ func readFromContract(res interface{}, backend *ethapi.Backend, contract common.
10721072
// sendTransactionToKeyManagement sends a transaction to KeyManagement contract.
10731073
func sendTransactionToKeyManagement(api *ethapi.TransactionAPI, signer common.Address, method string, zkVersion uint64, args ...interface{}) (*common.Hash, error) {
10741074
if api == nil {
1075-
return nil, errors.New("eth transaction API is not initialized, DKG can't function properly")
1075+
return nil, errNotInitializedBackend
10761076
}
10771077
// Choose different abi depends on the ZK settings.
10781078
var abi abi.ABI

0 commit comments

Comments
 (0)