Skip to content

Commit 70be52c

Browse files
committed
dbft: detach dBFT extensible payloads verifier from dBFT engine
1 parent 3ac8c58 commit 70be52c

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
// DBFT is the proof-of-authority consensus engine.
@@ -218,11 +211,8 @@ type DBFT struct {
218211
finished chan struct{}
219212

220213
// 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]
214+
backend *ethapi.Backend
215+
txAPI *ethapi.TransactionAPI
226216
// staticPool is a legacy pool instance for decrypted transaction verification,
227217
// which is initialized once per height at postBlock callback. It should be reset
228218
// before any reusage at the same height.
@@ -313,9 +303,6 @@ func New(chainCfg *params.ChainConfig, _ ethdb.Database, statisticsCfg Statistic
313303
quit: make(chan struct{}),
314304
finished: make(chan struct{}),
315305

316-
validatorsCache: lru.NewCache[uint64, []common.Address](validatorsCacheCap),
317-
dkgIndexCache: lru.NewCache[uint64, []int](validatorsCacheCap),
318-
319306
dkgSnapshot: NewSnapshot(),
320307
executeProofTaskChan: make(chan *taskList, 2), // The maximum number of task lists per epoch is 3
321308
loopWatchTaskChan: make(chan struct{}),
@@ -439,12 +426,16 @@ func (c *DBFT) getValidatorsCb(txs ...dbft.Transaction[common.Hash]) []dbft.Publ
439426
err error
440427
)
441428
if txs == nil {
442-
// getValidatorsSorted with empty args is used by dbft to fill the list of
429+
// GetValidatorsSorted with empty args is used by dbft to fill the list of
443430
// block's validators, thus should return validators from the current
444431
// epoch without recalculation.
445-
pKeys, err = c.getValidatorsSorted(&c.lastIndex, nil, nil)
432+
if c.backend != nil {
433+
pKeys, err = (*c.backend).GetValidatorsSorted(&c.lastIndex, nil, nil)
434+
} else {
435+
err = errNotInitializedBackend
436+
}
446437
}
447-
// getValidatorsSorted with non-empty args is used by dbft to fill block's
438+
// GetValidatorsSorted with non-empty args is used by dbft to fill block's
448439
// NextConsensus field, but DBFT doesn't provide WithGetConsensusAddress
449440
// callback and fills NextConsensus by itself via WithNewBlockFromContext
450441
// callback. Thus, leave pKeys empty if txes != nil.
@@ -1062,10 +1053,16 @@ func (c *DBFT) processPreBlockCb(b dbft.PreBlock[common.Hash]) error {
10621053
sharesCurr = make(map[int][]*tpke.DecryptionShare, ctx.M())
10631054
sharesPrev = make(map[int][]*tpke.DecryptionShare)
10641055
blockNum = pre.header.Number.Uint64() - 1
1056+
dkgIndex int
1057+
err error
10651058
)
10661059
for _, preC := range ctx.PreCommitPayloads {
10671060
if preC != nil && preC.ViewNumber() == ctx.ViewNumber {
1068-
dkgIndex, err := c.getDKGIndex(int(preC.ValidatorIndex()), blockNum)
1061+
if c.backend != nil {
1062+
dkgIndex, err = (*c.backend).GetDKGIndex(blockNum, int(preC.ValidatorIndex()))
1063+
} else {
1064+
err = errNotInitializedBackend
1065+
}
10691066
if err != nil {
10701067
return fmt.Errorf("get DKG index failed: ValidatorIndex %d, block height %d", int(preC.ValidatorIndex()), blockNum)
10711068
}
@@ -1408,8 +1405,13 @@ func (c *DBFT) getBlockWitness(pub *tpke.PublicKey, block *Block) ([]byte, error
14081405
for i := 0; i < len(vals); i++ {
14091406
if p := dctx.CommitPayloads[i]; p != nil && p.ViewNumber() == dctx.ViewNumber {
14101407
var err error
1408+
var dkgIndex int
14111409
blockNum := block.header.Number.Uint64() - 1
1412-
dkgIndex, err := c.getDKGIndex(i, blockNum)
1410+
if c.backend != nil {
1411+
dkgIndex, err = (*c.backend).GetDKGIndex(blockNum, i)
1412+
} else {
1413+
err = errNotInitializedBackend
1414+
}
14131415
if err != nil {
14141416
return nil, fmt.Errorf("get DKG index failed: ValidatorIndex %d, block height %d", i, blockNum)
14151417
}
@@ -1482,47 +1484,6 @@ func (c *DBFT) WithRequestTxs(f func(hashed []common.Hash)) {
14821484
func (c *DBFT) WithMux(mux *event.TypeMux) {
14831485
c.mux = mux
14841486
c.blockQueue.SetMux(mux)
1485-
1486-
go c.syncWatcher()
1487-
}
1488-
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-
}
15261487
}
15271488

15281489
// WithTxPool initializes transaction pool API for DBFT interactions with memory pool
@@ -2212,7 +2173,7 @@ func (c *DBFT) eventLoop() {
22122173
// been broadcasted the events are unregistered and the loop is exited. This to
22132174
// prevent a major security vuln where external parties can DOS you with blocks
22142175
// and halt your dBFT operation for as long as the DOS continues.
2215-
downloaderEvents := c.mux.Subscribe(downloader.DoneEvent{}, downloader.FailedEvent{})
2176+
downloaderEvents := c.mux.Subscribe(downloader.StartEvent{}, downloader.DoneEvent{}, downloader.FailedEvent{})
22162177
defer func() {
22172178
if !downloaderEvents.Closed() {
22182179
downloaderEvents.Unsubscribe()
@@ -2286,7 +2247,11 @@ events:
22862247
continue
22872248
}
22882249
switch ev.Data.(type) {
2250+
case downloader.StartEvent:
2251+
c.syncing.Store(true)
22892252
case downloader.FailedEvent:
2253+
c.syncing.Store(false)
2254+
22902255
latest := c.chain.CurrentHeader()
22912256
err := c.handleChainBlock(latest, false)
22922257
if err != nil {
@@ -2297,6 +2262,8 @@ events:
22972262
}
22982263

22992264
case downloader.DoneEvent:
2265+
c.syncing.Store(false)
2266+
23002267
// Stop reacting to downloader events.
23012268
downloaderEvents.Unsubscribe()
23022269

@@ -2435,7 +2402,10 @@ func payloadFromMessage(ep *dbftproto.Message, getBlockExtraVersion func(*big.In
24352402

24362403
func (c *DBFT) validatePayload(p *Payload) error {
24372404
h := c.chain.CurrentBlock().Number.Uint64()
2438-
validators, err := c.getValidatorsSorted(&h, nil, nil)
2405+
if c.backend == nil {
2406+
return errNotInitializedBackend
2407+
}
2408+
validators, err := (*c.backend).GetValidatorsSorted(&h, nil, nil)
24392409
if err != nil {
24402410
return fmt.Errorf("failed to get next block validators: %w", err)
24412411
}
@@ -2451,25 +2421,6 @@ func (c *DBFT) validatePayload(p *Payload) error {
24512421
return nil
24522422
}
24532423

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-
24732424
func (c *DBFT) newConsensusPayloadCb(ctx *dbft.Context[common.Hash], t dbft.MessageType, msg any) dbft.ConsensusPayload[common.Hash] {
24742425
var cp = new(Payload)
24752426
cp.BlockIndex = uint64(ctx.BlockIndex)
@@ -2537,7 +2488,10 @@ func (c *DBFT) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, pa
25372488

25382489
func (c *DBFT) calcDifficulty(signer common.Address, parent *types.Header) *big.Int {
25392490
h := parent.Number.Uint64()
2540-
vals, err := c.getValidatorsSorted(&h, nil, nil)
2491+
if c.backend == nil {
2492+
return nil
2493+
}
2494+
vals, err := (*c.backend).GetValidatorsSorted(&h, nil, nil)
25412495
if err != nil {
25422496
return nil
25432497
}
@@ -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
@@ -968,7 +968,7 @@ func getZKVersion(backend *ethapi.Backend, state *state.StateDB, header *types.H
968968
// readFromContract calls a contract with ABI-packed inputs.
969969
func readFromContract(res interface{}, backend *ethapi.Backend, contract common.Address, contractAbi abi.ABI, state *state.StateDB, header *types.Header, method string, args ...interface{}) error {
970970
if backend == nil {
971-
return errors.New("eth API backend is not initialized, DKG can't function properly")
971+
return errNotInitializedBackend
972972
}
973973
data, err := contractAbi.Pack(method, args...)
974974
if err != nil {
@@ -995,7 +995,7 @@ func readFromContract(res interface{}, backend *ethapi.Backend, contract common.
995995
// sendTransactionToKeyManagement sends a transaction to KeyManagement contract.
996996
func sendTransactionToKeyManagement(api *ethapi.TransactionAPI, signer common.Address, method string, zkVersion uint64, args ...interface{}) (*common.Hash, error) {
997997
if api == nil {
998-
return nil, errors.New("eth transaction API is not initialized, DKG can't function properly")
998+
return nil, errNotInitializedBackend
999999
}
10001000
// Choose different abi depends on the ZK settings.
10011001
var abi abi.ABI

0 commit comments

Comments
 (0)