From a5b3f905e695e0791efac6e1b0e1de69010b2d3b Mon Sep 17 00:00:00 2001 From: envestcc Date: Wed, 19 Mar 2025 08:54:28 +0800 Subject: [PATCH 01/10] fork blockchain --- action/protocol/execution/protocol_test.go | 4 +- api/serverV2_integrity_test.go | 2 +- blockchain/blockchain.go | 165 +++++++++++++----- blockchain/integrity/benchmark_test.go | 2 +- blockchain/integrity/integrity_test.go | 35 ++-- blocksync/blocksync_test.go | 14 +- blocksync/buffer_test.go | 4 +- chainservice/builder.go | 11 +- consensus/scheme/rolldpos/rolldpos_test.go | 2 +- .../scheme/rolldpos/roundcalculator_test.go | 2 +- e2etest/bigint_test.go | 2 +- e2etest/contract_staking_test.go | 2 +- e2etest/local_test.go | 2 +- e2etest/local_transfer_test.go | 2 +- gasstation/gasstattion_test.go | 4 +- state/factory/blockpreparer.go | 85 +++++++++ state/factory/minter.go | 110 +++++++++++- state/factory/proposalpool.go | 148 ++++++++++++++++ test/mock/mock_blockchain/mock_blockchain.go | 98 ++++++++++- 19 files changed, 598 insertions(+), 96 deletions(-) create mode 100644 state/factory/blockpreparer.go create mode 100644 state/factory/proposalpool.go diff --git a/action/protocol/execution/protocol_test.go b/action/protocol/execution/protocol_test.go index a9fa5d005d..24478437a3 100644 --- a/action/protocol/execution/protocol_test.go +++ b/action/protocol/execution/protocol_test.go @@ -478,7 +478,7 @@ func (sct *SmartContractTest) prepareBlockchain( cfg.Chain, cfg.Genesis, dao, - factory.NewMinter(sf, ap), + factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockValidatorOption(block.NewValidator( sf, protocol.NewGenericValidator(sf, accountutil.AccountState), @@ -730,7 +730,7 @@ func TestProtocol_Handle(t *testing.T) { cfg.Chain, cfg.Genesis, dao, - factory.NewMinter(sf, ap), + factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockValidatorOption(block.NewValidator( sf, protocol.NewGenericValidator(sf, accountutil.AccountState), diff --git a/api/serverV2_integrity_test.go b/api/serverV2_integrity_test.go index 3dbdcfe080..88f59fdc1b 100644 --- a/api/serverV2_integrity_test.go +++ b/api/serverV2_integrity_test.go @@ -325,7 +325,7 @@ func setupChain(cfg testConfig) (blockchain.Blockchain, blockdao.BlockDAO, block cfg.chain, cfg.genesis, dao, - factory.NewMinter(sf, ap), + factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.chain.ProducerPrivateKey())), blockchain.BlockValidatorOption(block.NewValidator( sf, protocol.NewGenericValidator(sf, accountutil.AccountState), diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index efc32b84b9..8e77707df7 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -20,12 +20,12 @@ import ( "github.com/prometheus/client_golang/prometheus" "go.uber.org/zap" - "github.com/iotexproject/iotex-core/v2/action" "github.com/iotexproject/iotex-core/v2/action/protocol" "github.com/iotexproject/iotex-core/v2/blockchain/block" "github.com/iotexproject/iotex-core/v2/blockchain/blockdao" "github.com/iotexproject/iotex-core/v2/blockchain/filedao" "github.com/iotexproject/iotex-core/v2/blockchain/genesis" + "github.com/iotexproject/iotex-core/v2/db" "github.com/iotexproject/iotex-core/v2/pkg/lifecycle" "github.com/iotexproject/iotex-core/v2/pkg/log" "github.com/iotexproject/iotex-core/v2/pkg/prometheustimer" @@ -98,6 +98,8 @@ type ( CommitBlock(blk *block.Block) error // ValidateBlock validates a new block before adding it to the blockchain ValidateBlock(*block.Block, ...BlockValidationOption) error + // Fork returns a new blockchain instance with the given tip hash + Fork(hash.Hash256) (Blockchain, error) // AddSubscriber make you listen to every single produced block AddSubscriber(BlockCreationSubscriber) error @@ -108,8 +110,12 @@ type ( // BlockBuilderFactory is the factory interface of block builder BlockBuilderFactory interface { - // NewBlockBuilder creates block builder - NewBlockBuilder(context.Context, func(action.Envelope) (*action.SealedEnvelope, error)) (*block.Builder, error) + Mint(ctx context.Context) (*block.Block, error) + ReceiveBlock(*block.Block) error + Init(hash.Hash256) + AddProposal(*block.Block) error + Block(hash.Hash256) *block.Block + BlockByHeight(uint64) *block.Block } // blockchain implements the Blockchain interface @@ -126,6 +132,9 @@ type ( // used by account-based model bbf BlockBuilderFactory + // head is the head block of the blockchain + head *block.Header + headLocker sync.RWMutex } ) @@ -208,7 +217,7 @@ func NewBlockchain(cfg Config, g genesis.Genesis, dao blockdao.BlockDAO, bbf Blo } chain.lifecycle.Add(chain.dao) chain.lifecycle.Add(chain.pubSubManager) - + chain.pubSubManager.AddBlockListener(chain.bbf) return chain } @@ -238,7 +247,20 @@ func (bc *blockchain) Start(ctx context.Context) error { EvmNetworkID: bc.EvmNetworkID(), }, ), bc.genesis)) - return bc.lifecycle.OnStart(ctx) + if err := bc.lifecycle.OnStart(ctx); err != nil { + return err + } + tip, err := bc.dao.Height() + if err != nil { + return err + } + header, err := bc.dao.HeaderByHeight(tip) + if err != nil { + return err + } + bc.head = header + bc.bbf.Init(header.HashBlock()) + return nil } // Stop stops the blockchain. @@ -249,7 +271,20 @@ func (bc *blockchain) Stop(ctx context.Context) error { } func (bc *blockchain) BlockHeaderByHeight(height uint64) (*block.Header, error) { - return bc.dao.HeaderByHeight(height) + header, err := bc.dao.HeaderByHeight(height) + switch errors.Cause(err) { + case nil: + return header, nil + case db.ErrNotExist: + bc.mu.RLock() + defer bc.mu.RUnlock() + if blk := bc.draftBlockByHeight(height); blk != nil { + return &blk.Header, nil + } + return nil, err + default: + return nil, err + } } func (bc *blockchain) BlockFooterByHeight(height uint64) (*block.Footer, error) { @@ -258,24 +293,12 @@ func (bc *blockchain) BlockFooterByHeight(height uint64) (*block.Footer, error) // TipHash returns tip block's hash func (bc *blockchain) TipHash() hash.Hash256 { - tipHeight, err := bc.dao.Height() - if err != nil { - return hash.ZeroHash256 - } - tipHash, err := bc.dao.GetBlockHash(tipHeight) - if err != nil { - return hash.ZeroHash256 - } - return tipHash + return bc.tipHash() } // TipHeight returns tip block's height func (bc *blockchain) TipHeight() uint64 { - tipHeight, err := bc.dao.Height() - if err != nil { - log.L().Panic("failed to get tip height", zap.Error(err)) - } - return tipHeight + return bc.tipHeight() } // ValidateBlock validates a new block before adding it to the blockchain @@ -287,10 +310,7 @@ func (bc *blockchain) ValidateBlock(blk *block.Block, opts ...BlockValidationOpt if blk == nil { return ErrInvalidBlock } - tipHeight, err := bc.dao.Height() - if err != nil { - return err - } + tipHeight := bc.tipHeight() tip, err := bc.tipInfo(tipHeight) if err != nil { return err @@ -355,7 +375,14 @@ func (bc *blockchain) ValidateBlock(blk *block.Block, opts ...BlockValidationOpt if bc.blockValidator == nil { return nil } - return bc.blockValidator.Validate(ctx, blk) + err = bc.blockValidator.Validate(ctx, blk) + if err != nil { + return err + } + if err = bc.bbf.AddProposal(blk); err != nil { + log.L().Warn("failed to add block to proposal pool", zap.Error(err), zap.Uint64("height", blk.Height()), zap.Time("timestamp", blk.Timestamp())) + } + return nil } func (bc *blockchain) Context(ctx context.Context) (context.Context, error) { @@ -392,7 +419,10 @@ func (bc *blockchain) context(ctx context.Context, height uint64) (context.Conte if err != nil { return nil, err } + return bc.contextWithTipInfo(ctx, tip), nil +} +func (bc *blockchain) contextWithTipInfo(ctx context.Context, tip *protocol.TipInfo) context.Context { ctx = genesis.WithGenesisContext( protocol.WithBlockchainCtx( ctx, @@ -404,7 +434,7 @@ func (bc *blockchain) context(ctx context.Context, height uint64) (context.Conte ), bc.genesis, ) - return protocol.WithFeatureWithHeightCtx(ctx), nil + return protocol.WithFeatureWithHeightCtx(ctx) } func (bc *blockchain) MintNewBlock(timestamp time.Time) (*block.Block, error) { @@ -422,30 +452,23 @@ func (bc *blockchain) MintNewBlock(timestamp time.Time) (*block.Block, error) { return nil, err } tip := protocol.MustGetBlockchainCtx(ctx).Tip + // create a new block + log.L().Debug("Produce a new block.", zap.Uint64("height", newblockHeight), zap.Time("timestamp", timestamp)) ctx = bc.contextWithBlock(ctx, bc.config.ProducerAddress(), newblockHeight, timestamp, protocol.CalcBaseFee(genesis.MustExtractGenesisContext(ctx).Blockchain, &tip), protocol.CalcExcessBlobGas(tip.ExcessBlobGas, tip.BlobGasUsed)) ctx = protocol.WithFeatureCtx(ctx) // run execution and update state trie root hash - minterPrivateKey := bc.config.ProducerPrivateKey() - blockBuilder, err := bc.bbf.NewBlockBuilder( - ctx, - func(elp action.Envelope) (*action.SealedEnvelope, error) { - return action.Sign(elp, minterPrivateKey) - }, - ) - if err != nil { - return nil, errors.Wrapf(err, "failed to create block builder at new block height %d", newblockHeight) - } - blk, err := blockBuilder.SignAndBuild(minterPrivateKey) + blk, err := bc.bbf.Mint(ctx) if err != nil { return nil, errors.Wrapf(err, "failed to create block") } _blockMtc.WithLabelValues("MintGas").Set(float64(blk.GasUsed())) _blockMtc.WithLabelValues("MintActions").Set(float64(len(blk.Actions))) - return &blk, nil + return blk, nil } // CommitBlock validates and appends a block to the chain func (bc *blockchain) CommitBlock(blk *block.Block) error { + log.L().Debug("Commit a block.", zap.Uint64("height", blk.Height()), zap.String("ioAddr", bc.config.ProducerAddress().String())) bc.mu.Lock() defer bc.mu.Unlock() timer := bc.timerFactory.NewTimer("CommitBlock") @@ -466,6 +489,31 @@ func (bc *blockchain) RemoveSubscriber(s BlockCreationSubscriber) error { return bc.pubSubManager.RemoveBlockListener(s) } +func (bc *blockchain) Fork(hash hash.Hash256) (Blockchain, error) { + if bc.tipHash() == hash { + return bc, nil + } + bc.mu.RLock() + defer bc.mu.RUnlock() + headBlk := bc.bbf.Block(hash) + if headBlk == nil { + return nil, errors.Errorf("block %x is not in the proposal pool, tip height %d", hash, bc.tipHeight()) + } + fork := &blockchain{ + dao: bc.dao, + config: bc.config, + genesis: bc.genesis, + blockValidator: bc.blockValidator, + lifecycle: bc.lifecycle, + clk: bc.clk, + pubSubManager: bc.pubSubManager, + timerFactory: bc.timerFactory, + bbf: bc.bbf, + head: &headBlk.Header, + } + return fork, nil +} + //====================================== // internal functions //===================================== @@ -486,11 +534,10 @@ func (bc *blockchain) tipInfo(tipHeight uint64) (*protocol.TipInfo, error) { Timestamp: time.Unix(bc.genesis.Timestamp, 0), }, nil } - header, err := bc.dao.HeaderByHeight(tipHeight) + header, err := bc.blockByHeight(tipHeight) if err != nil { return nil, err } - return &protocol.TipInfo{ Height: tipHeight, GasUsed: header.GasUsed(), @@ -526,6 +573,9 @@ func (bc *blockchain) commitBlock(blk *block.Block) error { default: return err } + bc.headLocker.Lock() + bc.head = &blk.Header + bc.headLocker.Unlock() blkHash := blk.HashBlock() if blk.Height()%100 == 0 { blk.HeaderLogger(log.L()).Info("Committed a block.", log.Hex("tipHash", blkHash[:])) @@ -548,3 +598,38 @@ func (bc *blockchain) emitToSubscribers(blk *block.Block) { } bc.pubSubManager.SendBlockToSubscribers(blk) } + +func (bc *blockchain) draftBlockByHeight(height uint64) *block.Block { + for blk := bc.bbf.Block(bc.tipHash()); blk != nil && blk.Height() > height; blk = bc.bbf.Block(blk.PrevHash()) { + if blk.Height() == height { + return blk + } + } + return nil +} + +func (bc *blockchain) blockByHeight(height uint64) (*block.Block, error) { + daoHeight, err := bc.dao.Height() + if err != nil { + return nil, err + } + if height > daoHeight { + if blk := bc.draftBlockByHeight(height); blk != nil { + return blk, nil + } + return nil, errors.Wrapf(db.ErrNotExist, "block %d not found", height) + } + return bc.dao.GetBlockByHeight(height) +} + +func (bc *blockchain) tipHeight() uint64 { + bc.headLocker.RLock() + defer bc.headLocker.RUnlock() + return bc.head.Height() +} + +func (bc *blockchain) tipHash() hash.Hash256 { + bc.headLocker.Lock() + defer bc.headLocker.Unlock() + return bc.head.HashBlock() +} diff --git a/blockchain/integrity/benchmark_test.go b/blockchain/integrity/benchmark_test.go index d74e80cb89..1ac1d73df8 100644 --- a/blockchain/integrity/benchmark_test.go +++ b/blockchain/integrity/benchmark_test.go @@ -273,7 +273,7 @@ func newChainInDB() (blockchain.Blockchain, actpool.ActPool, error) { cfg.Chain, cfg.Genesis, dao, - factory.NewMinter(sf, ap), + factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockValidatorOption(block.NewValidator( sf, protocol.NewGenericValidator(sf, accountutil.AccountState), diff --git a/blockchain/integrity/integrity_test.go b/blockchain/integrity/integrity_test.go index 34f36f52dd..66c12d48b3 100644 --- a/blockchain/integrity/integrity_test.go +++ b/blockchain/integrity/integrity_test.go @@ -501,7 +501,7 @@ func TestCreateBlockchain(t *testing.T) { cfg.Chain, cfg.Genesis, dao, - factory.NewMinter(sf, ap), + factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockValidatorOption(block.NewValidator( sf, protocol.NewGenericValidator(sf, accountutil.AccountState), @@ -557,7 +557,7 @@ func TestGetBlockHash(t *testing.T) { cfg.Chain, cfg.Genesis, dao, - factory.NewMinter(sf, ap), + factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockValidatorOption(block.NewValidator( sf, protocol.NewGenericValidator(sf, accountutil.AccountState), @@ -722,7 +722,7 @@ func TestBlockchain_MintNewBlock(t *testing.T) { cfg.Chain, cfg.Genesis, dao, - factory.NewMinter(sf, ap), + factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockValidatorOption(block.NewValidator( sf, protocol.NewGenericValidator(sf, accountutil.AccountState), @@ -792,7 +792,7 @@ func TestBlockchain_MintNewBlock_PopAccount(t *testing.T) { cfg.Chain, cfg.Genesis, dao, - factory.NewMinter(sf, ap), + factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockValidatorOption(block.NewValidator( sf, protocol.NewGenericValidator(sf, accountutil.AccountState), @@ -935,7 +935,7 @@ func createChain(cfg config.Config, inMem bool) (blockchain.Blockchain, factory. cfg.Chain, cfg.Genesis, dao, - factory.NewMinter(sf, ap), + factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockValidatorOption(block.NewValidator( sf, protocol.NewGenericValidator(sf, accountutil.AccountState), @@ -1480,7 +1480,7 @@ func TestConstantinople(t *testing.T) { cfg.Chain, cfg.Genesis, dao, - factory.NewMinter(sf, ap), + factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockValidatorOption(block.NewValidator( sf, protocol.NewGenericValidator(sf, accountutil.AccountState), @@ -1578,7 +1578,7 @@ func TestConstantinople(t *testing.T) { blkHash, err := dao.GetBlockHash(v.height) require.NoError(err) - require.Equal(v.blkHash, hex.EncodeToString(blkHash[:])) + require.Equal(v.blkHash, hex.EncodeToString(blkHash[:]), "height %d", v.height) if v.topic != nil { funcSig := hash.Hash256b([]byte("Set(uint256)")) @@ -1690,6 +1690,7 @@ func TestConstantinople(t *testing.T) { cfg.Genesis.BeringBlockHeight = 8 cfg.Genesis.GreenlandBlockHeight = 9 cfg.Genesis.InitBalanceMap[identityset.Address(27).String()] = unit.ConvertIotxToRau(10000000000).String() + block.LoadGenesisHash(&cfg.Genesis) t.Run("test Constantinople contract", func(t *testing.T) { testValidateBlockchain(cfg, t) @@ -1734,7 +1735,7 @@ func TestLoadBlockchainfromDB(t *testing.T) { cfg.Chain, cfg.Genesis, dao, - factory.NewMinter(sf, ap), + factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockValidatorOption(block.NewValidator( sf, protocol.NewGenericValidator(sf, accountutil.AccountState), @@ -1763,7 +1764,7 @@ func TestLoadBlockchainfromDB(t *testing.T) { cfg.Chain, cfg.Genesis, dao, - factory.NewMinter(sf, ap), + factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockValidatorOption(block.NewValidator( sf, protocol.NewGenericValidator(sf, accountutil.AccountState), @@ -2044,7 +2045,7 @@ func TestBlockchainInitialCandidate(t *testing.T) { cfg.Chain, cfg.Genesis, dao, - factory.NewMinter(sf, ap), + factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockValidatorOption(sf), ) rolldposProtocol := rolldpos.NewProtocol( @@ -2087,7 +2088,7 @@ func TestBlockchain_AccountState(t *testing.T) { store, err := filedao.NewFileDAOInMemForTest() require.NoError(err) dao := blockdao.NewBlockDAOWithIndexersAndCache(store, []blockdao.BlockIndexer{sf}, cfg.DB.MaxCacheSize) - bc := blockchain.NewBlockchain(cfg.Chain, cfg.Genesis, dao, factory.NewMinter(sf, ap)) + bc := blockchain.NewBlockchain(cfg.Chain, cfg.Genesis, dao, factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey()))) require.NoError(bc.Start(ctx)) require.NotNil(bc) defer func() { @@ -2119,7 +2120,7 @@ func TestNewAccountAction(t *testing.T) { store, err := filedao.NewFileDAOInMemForTest() require.NoError(err) dao := blockdao.NewBlockDAOWithIndexersAndCache(store, []blockdao.BlockIndexer{sf}, cfg.DB.MaxCacheSize) - bc := blockchain.NewBlockchain(cfg.Chain, cfg.Genesis, dao, factory.NewMinter(sf, ap)) + bc := blockchain.NewBlockchain(cfg.Chain, cfg.Genesis, dao, factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey()))) require.NoError(bc.Start(ctx)) require.NotNil(bc) defer func() { @@ -2164,7 +2165,7 @@ func TestNewAccountAction(t *testing.T) { store, err := filedao.NewFileDAOInMemForTest() require.NoError(err) dao1 := blockdao.NewBlockDAOWithIndexersAndCache(store, []blockdao.BlockIndexer{sf1}, cfg.DB.MaxCacheSize) - bc1 := blockchain.NewBlockchain(cfg.Chain, cfg.Genesis, dao1, factory.NewMinter(sf1, ap)) + bc1 := blockchain.NewBlockchain(cfg.Chain, cfg.Genesis, dao1, factory.NewMinter(sf1, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey()))) require.NoError(bc1.Start(ctx)) require.NotNil(bc1) defer func() { @@ -2233,7 +2234,7 @@ func TestBlocks(t *testing.T) { dao := blockdao.NewBlockDAOWithIndexersAndCache(store, []blockdao.BlockIndexer{sf}, dbcfg.MaxCacheSize) // Create a blockchain from scratch - bc := blockchain.NewBlockchain(cfg.Chain, cfg.Genesis, dao, factory.NewMinter(sf, ap)) + bc := blockchain.NewBlockchain(cfg.Chain, cfg.Genesis, dao, factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey()))) require.NoError(bc.Start(context.Background())) defer func() { require.NoError(bc.Stop(context.Background())) @@ -2311,7 +2312,7 @@ func TestActions(t *testing.T) { cfg.Chain, cfg.Genesis, dao, - factory.NewMinter(sf, ap), + factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockValidatorOption(block.NewValidator( sf, protocol.NewGenericValidator(sf, accountutil.AccountState), @@ -2371,7 +2372,7 @@ func TestBlockchain_AddRemoveSubscriber(t *testing.T) { store, err := filedao.NewFileDAOInMemForTest() req.NoError(err) dao := blockdao.NewBlockDAOWithIndexersAndCache(store, []blockdao.BlockIndexer{sf}, cfg.DB.MaxCacheSize) - bc := blockchain.NewBlockchain(cfg.Chain, cfg.Genesis, dao, factory.NewMinter(sf, ap)) + bc := blockchain.NewBlockchain(cfg.Chain, cfg.Genesis, dao, factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey()))) // mock ctrl := gomock.NewController(t) mb := mock_blockcreationsubscriber.NewMockBlockCreationSubscriber(ctrl) @@ -2606,7 +2607,7 @@ func newChain(t *testing.T, stateTX bool) (blockchain.Blockchain, factory.Factor cfg.Chain, cfg.Genesis, dao, - factory.NewMinter(sf, ap), + factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockValidatorOption(block.NewValidator( sf, protocol.NewGenericValidator(sf, accountutil.AccountState), diff --git a/blocksync/blocksync_test.go b/blocksync/blocksync_test.go index 26808a22b9..d5edebde28 100644 --- a/blocksync/blocksync_test.go +++ b/blocksync/blocksync_test.go @@ -208,7 +208,7 @@ func TestBlockSyncerProcessBlockTipHeight(t *testing.T) { cfg.Chain, cfg.Genesis, dao, - factory.NewMinter(sf, ap), + factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockValidatorOption(block.NewValidator(sf, ap)), ) require.NoError(chain.Start(ctx)) @@ -274,7 +274,7 @@ func TestBlockSyncerProcessBlockOutOfOrder(t *testing.T) { cfg.Chain, cfg.Genesis, dao, - factory.NewMinter(sf, ap1), + factory.NewMinter(sf, ap1, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockValidatorOption(block.NewValidator(sf, ap1)), ) require.NotNil(chain1) @@ -301,7 +301,7 @@ func TestBlockSyncerProcessBlockOutOfOrder(t *testing.T) { cfg.Chain, cfg.Genesis, dao2, - factory.NewMinter(sf2, ap2), + factory.NewMinter(sf2, ap2, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockValidatorOption(block.NewValidator(sf2, ap2)), ) require.NotNil(chain2) @@ -376,7 +376,7 @@ func TestBlockSyncerProcessBlock(t *testing.T) { cfg.Chain, cfg.Genesis, dao, - factory.NewMinter(sf, ap1), + factory.NewMinter(sf, ap1, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockValidatorOption(block.NewValidator(sf, ap1)), ) require.NoError(chain1.Start(ctx)) @@ -402,7 +402,7 @@ func TestBlockSyncerProcessBlock(t *testing.T) { cfg.Chain, cfg.Genesis, dao2, - factory.NewMinter(sf2, ap2), + factory.NewMinter(sf2, ap2, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockValidatorOption(block.NewValidator(sf2, ap2)), ) require.NoError(chain2.Start(ctx)) @@ -470,7 +470,7 @@ func TestBlockSyncerSync(t *testing.T) { cfg.Chain, cfg.Genesis, dao, - factory.NewMinter(sf, ap), + factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockValidatorOption(block.NewValidator(sf, ap)), ) require.NoError(chain.Start(ctx)) @@ -523,7 +523,7 @@ func newTestConfig() (testConfig, error) { cfg := testConfig{ BlockSync: DefaultConfig, - Genesis: genesis.TestDefault(), + Genesis: genesis.Default, Chain: blockchain.DefaultConfig, ActPool: actpool.DefaultConfig, } diff --git a/blocksync/buffer_test.go b/blocksync/buffer_test.go index 0d05f1d695..03674c9a13 100644 --- a/blocksync/buffer_test.go +++ b/blocksync/buffer_test.go @@ -55,7 +55,7 @@ func TestBlockBufferFlush(t *testing.T) { cfg.Chain, cfg.Genesis, dao, - factory.NewMinter(sf, ap), + factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockValidatorOption(block.NewValidator(sf, ap)), ) require.NoError(chain.Start(ctx)) @@ -149,7 +149,7 @@ func TestBlockBufferGetBlocksIntervalsToSync(t *testing.T) { cfg.Chain, cfg.Genesis, dao, - factory.NewMinter(sf, ap), + factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), ) require.NotNil(chain) require.NoError(chain.Start(ctx)) diff --git a/chainservice/builder.go b/chainservice/builder.go index 65a0d531a7..d457fe29e3 100644 --- a/chainservice/builder.go +++ b/chainservice/builder.go @@ -514,7 +514,7 @@ func (builder *Builder) createBlockchain(forSubChain, forTest bool) blockchain.B chainOpts = append(chainOpts, blockchain.BlockValidatorOption(builder.cs.factory)) } - var mintOpts []factory.MintOption + mintOpts := []factory.MintOption{factory.WithPrivateKeyOption(builder.cfg.Chain.ProducerPrivateKey())} if builder.cfg.Consensus.Scheme == config.RollDPoSScheme { mintOpts = append(mintOpts, factory.WithTimeoutOption(builder.cfg.Chain.MintTimeout)) } @@ -785,7 +785,14 @@ func (builder *Builder) registerRollDPoSProtocol() error { func (builder *Builder) buildBlockTimeCalculator() (err error) { consensusCfg := consensusfsm.NewConsensusConfig(builder.cfg.Consensus.RollDPoS.FSM, builder.cfg.DardanellesUpgrade, builder.cfg.Genesis, builder.cfg.Consensus.RollDPoS.Delay) dao := builder.cs.BlockDAO() - builder.cs.blockTimeCalculator, err = blockutil.NewBlockTimeCalculator(consensusCfg.BlockInterval, builder.cs.Blockchain().TipHeight, func(height uint64) (time.Time, error) { + builder.cs.blockTimeCalculator, err = blockutil.NewBlockTimeCalculator(consensusCfg.BlockInterval, func() uint64 { + tip, err := dao.Height() + if err != nil { + log.L().Error("failed to get tip height", zap.Error(err)) + return 0 + } + return tip + }, func(height uint64) (time.Time, error) { blk, err := dao.GetBlockByHeight(height) if err != nil { return time.Time{}, err diff --git a/consensus/scheme/rolldpos/rolldpos_test.go b/consensus/scheme/rolldpos/rolldpos_test.go index d93db94f62..42edbf8b7e 100644 --- a/consensus/scheme/rolldpos/rolldpos_test.go +++ b/consensus/scheme/rolldpos/rolldpos_test.go @@ -468,7 +468,7 @@ func TestRollDPoSConsensus(t *testing.T) { bc, g, dao, - factory.NewMinter(sf, actPool), + factory.NewMinter(sf, actPool, factory.WithPrivateKeyOption(chainAddrs[i].priKey)), blockchain.BlockValidatorOption(block.NewValidator( sf, protocol.NewGenericValidator(sf, accountutil.AccountState), diff --git a/consensus/scheme/rolldpos/roundcalculator_test.go b/consensus/scheme/rolldpos/roundcalculator_test.go index 99cc8726da..9da3c5c54f 100644 --- a/consensus/scheme/rolldpos/roundcalculator_test.go +++ b/consensus/scheme/rolldpos/roundcalculator_test.go @@ -193,7 +193,7 @@ func makeChain(t *testing.T) (blockchain.Blockchain, factory.Factory, actpool.Ac cfg, g, dao, - factory.NewMinter(sf, ap), + factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(sk)), blockchain.BlockValidatorOption(block.NewValidator( sf, protocol.NewGenericValidator(sf, accountutil.AccountState), diff --git a/e2etest/bigint_test.go b/e2etest/bigint_test.go index 9148a7ff4f..b9c99e1fa2 100644 --- a/e2etest/bigint_test.go +++ b/e2etest/bigint_test.go @@ -106,7 +106,7 @@ func prepareBlockchain(ctx context.Context, _executor string, r *require.Asserti cfg.Chain, cfg.Genesis, dao, - factory.NewMinter(sf, ap), + factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockValidatorOption(block.NewValidator( sf, genericValidator, diff --git a/e2etest/contract_staking_test.go b/e2etest/contract_staking_test.go index 86bc4552e0..ec8fa92df0 100644 --- a/e2etest/contract_staking_test.go +++ b/e2etest/contract_staking_test.go @@ -1984,7 +1984,7 @@ func prepareContractStakingBlockchain(ctx context.Context, cfg config.Config, r cfg.Chain, cfg.Genesis, dao, - factory.NewMinter(sf, ap), + factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockValidatorOption(block.NewValidator( sf, protocol.NewGenericValidator(sf, accountutil.AccountState), diff --git a/e2etest/local_test.go b/e2etest/local_test.go index 7ffa74d865..93f8cbfb83 100644 --- a/e2etest/local_test.go +++ b/e2etest/local_test.go @@ -167,7 +167,7 @@ func TestLocalCommit(t *testing.T) { cfg.Chain, cfg.Genesis, dao, - factory.NewMinter(sf2, ap2), + factory.NewMinter(sf2, ap2, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockValidatorOption(block.NewValidator( sf2, protocol.NewGenericValidator(sf2, accountutil.AccountState), diff --git a/e2etest/local_transfer_test.go b/e2etest/local_transfer_test.go index db1f466ad0..762b6ec75b 100644 --- a/e2etest/local_transfer_test.go +++ b/e2etest/local_transfer_test.go @@ -679,7 +679,7 @@ func TestEnforceChainID(t *testing.T) { cfg.Chain, cfg.Genesis, blkMemDao, - factory.NewMinter(sf, ap), + factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), ) require.NoError(bc.Start(ctx)) diff --git a/gasstation/gasstattion_test.go b/gasstation/gasstattion_test.go index 9780c190de..f840ec3782 100644 --- a/gasstation/gasstattion_test.go +++ b/gasstation/gasstattion_test.go @@ -93,7 +93,7 @@ func TestSuggestGasPriceForUserAction(t *testing.T) { cfg.Chain, cfg.Genesis, blkMemDao, - factory.NewMinter(sf, ap), + factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockValidatorOption(block.NewValidator( sf, protocol.NewGenericValidator(sf, accountutil.AccountState), @@ -164,7 +164,7 @@ func TestSuggestGasPriceForSystemAction(t *testing.T) { cfg.Chain, cfg.Genesis, blkMemDao, - factory.NewMinter(sf, ap), + factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockValidatorOption(block.NewValidator( sf, protocol.NewGenericValidator(sf, accountutil.AccountState), diff --git a/state/factory/blockpreparer.go b/state/factory/blockpreparer.go new file mode 100644 index 0000000000..61399c40d0 --- /dev/null +++ b/state/factory/blockpreparer.go @@ -0,0 +1,85 @@ +package factory + +import ( + "context" + "sync" + "time" + + "github.com/iotexproject/go-pkgs/hash" + "github.com/pkg/errors" + "go.uber.org/zap" + + "github.com/iotexproject/iotex-core/v2/blockchain/block" + "github.com/iotexproject/iotex-core/v2/pkg/log" +) + +type ( + blockPreparer struct { + tasks map[hash.Hash256]map[int64]chan struct{} + results map[hash.Hash256]map[int64]*mintResult + mu sync.Mutex + } + mintResult struct { + blk *block.Block + err error + } +) + +func newBlockPreparer() *blockPreparer { + return &blockPreparer{ + tasks: make(map[hash.Hash256]map[int64]chan struct{}), + results: make(map[hash.Hash256]map[int64]*mintResult), + } +} + +func (d *blockPreparer) PrepareOrWait(ctx context.Context, prevHash []byte, timestamp time.Time, fn func() (*block.Block, error)) (*block.Block, error) { + d.mu.Lock() + task := d.prepare(prevHash, timestamp, fn) + d.mu.Unlock() + + select { + case <-task: + case <-ctx.Done(): + var null *block.Block + return null, errors.Wrapf(ctx.Err(), "wait for draft block timeout %v", timestamp) + } + + d.mu.Lock() + res := d.results[hash.Hash256(prevHash)][timestamp.UnixNano()] + d.mu.Unlock() + return res.blk, res.err +} + +func (d *blockPreparer) prepare(prevHash []byte, timestamp time.Time, mintFn func() (*block.Block, error)) chan struct{} { + if forks, ok := d.tasks[hash.BytesToHash256(prevHash)]; ok { + if ch, ok := forks[timestamp.UnixNano()]; ok { + log.L().Debug("draft block already exists", log.Hex("prevHash", prevHash)) + return ch + } + } else { + d.tasks[hash.BytesToHash256(prevHash)] = make(map[int64]chan struct{}) + } + task := make(chan struct{}) + d.tasks[hash.BytesToHash256(prevHash)][timestamp.UnixNano()] = task + + go func() { + blk, err := mintFn() + d.mu.Lock() + if _, ok := d.results[hash.BytesToHash256(prevHash)]; !ok { + d.results[hash.BytesToHash256(prevHash)] = make(map[int64]*mintResult) + } + d.results[hash.BytesToHash256(prevHash)][timestamp.UnixNano()] = &mintResult{blk: blk, err: err} + d.mu.Unlock() + close(task) + log.L().Debug("prepare mint returned", zap.Error(err)) + }() + + return task +} + +func (d *blockPreparer) ReceiveBlock(blk *block.Block) error { + d.mu.Lock() + delete(d.tasks, blk.PrevHash()) + d.mu.Unlock() + return nil +} diff --git a/state/factory/minter.go b/state/factory/minter.go index ef5b301d64..681976b583 100644 --- a/state/factory/minter.go +++ b/state/factory/minter.go @@ -7,13 +7,20 @@ package factory import ( "context" + "sync" "time" + "go.uber.org/zap" + + "github.com/iotexproject/go-pkgs/crypto" + "github.com/iotexproject/go-pkgs/hash" + "github.com/iotexproject/iotex-core/v2/action" "github.com/iotexproject/iotex-core/v2/action/protocol" "github.com/iotexproject/iotex-core/v2/actpool" "github.com/iotexproject/iotex-core/v2/blockchain" "github.com/iotexproject/iotex-core/v2/blockchain/block" + "github.com/iotexproject/iotex-core/v2/pkg/log" ) type MintOption func(*minter) @@ -25,26 +32,113 @@ func WithTimeoutOption(timeout time.Duration) MintOption { } } +func WithPrivateKeyOption(sk crypto.PrivateKey) MintOption { + return func(m *minter) { + m.sk = sk + } +} + type minter struct { - f Factory - ap actpool.ActPool - timeout time.Duration + f Factory + ap actpool.ActPool + timeout time.Duration + blockPreparer *blockPreparer + proposalPool *proposalPool + sk crypto.PrivateKey + mu sync.Mutex } // NewMinter creates a wrapper instance func NewMinter(f Factory, ap actpool.ActPool, opts ...MintOption) blockchain.BlockBuilderFactory { - m := &minter{f: f, ap: ap} + m := &minter{ + f: f, + ap: ap, + blockPreparer: newBlockPreparer(), + proposalPool: newProposalPool(), + } for _, opt := range opts { opt(m) } return m } -// NewBlockBuilder implements the BlockMinter interface -func (m *minter) NewBlockBuilder(ctx context.Context, sign func(action.Envelope) (*action.SealedEnvelope, error)) (*block.Builder, error) { +func (m *minter) Init(root hash.Hash256) { + m.proposalPool.Init(root) +} + +func (m *minter) Mint(ctx context.Context) (*block.Block, error) { + bcCtx := protocol.MustGetBlockchainCtx(ctx) + blkCtx := protocol.MustGetBlockCtx(ctx) + + // create a new block + blk, err := m.blockPreparer.PrepareOrWait(ctx, bcCtx.Tip.Hash[:], blkCtx.BlockTimeStamp, func() (*block.Block, error) { + blk, err := m.mint(ctx) + if err != nil { + return nil, err + } + if err = m.proposalPool.AddBlock(blk); err != nil { + log.L().Error("failed to add block to proposal pool", zap.Error(err)) + } + return blk, nil + }) + if err != nil { + return nil, err + } + return blk, nil +} + +func (m *minter) AddProposal(blk *block.Block) error { + return m.proposalPool.AddBlock(blk) +} + +func (m *minter) ReceiveBlock(blk *block.Block) error { + prevHash := blk.PrevHash() + l := log.L().With(zap.Uint64("height", blk.Height()), log.Hex("prevHash", prevHash[:]), zap.Time("timestamp", blk.Timestamp())) + if err := m.blockPreparer.ReceiveBlock(blk); err != nil { + l.Error("failed to receive block", zap.Error(err)) + } + if err := m.proposalPool.ReceiveBlock(blk); err != nil { + l.Error("failed to receive block", zap.Error(err)) + } + return nil +} + +func (m *minter) Block(hash hash.Hash256) *block.Block { + return m.proposalPool.BlockByHash(hash) +} + +func (m *minter) BlockByHeight(height uint64) *block.Block { + return m.proposalPool.Block(height) +} + +func (m *minter) mint(ctx context.Context) (*block.Block, error) { + builder, err := m.newBlockBuilder(ctx, func(e action.Envelope) (*action.SealedEnvelope, error) { + return action.Sign(e, m.sk) + }) + if err != nil { + return nil, err + } + blk, err := builder.SignAndBuild(m.sk) + if err != nil { + return nil, err + } + return &blk, nil +} + +func (m *minter) newBlockBuilder(ctx context.Context, sign func(action.Envelope) (*action.SealedEnvelope, error)) (*block.Builder, error) { if m.timeout > 0 { - var cancel context.CancelFunc - ctx, cancel = context.WithDeadline(ctx, protocol.MustGetBlockCtx(ctx).BlockTimeStamp.Add(m.timeout)) + // set deadline for NewBlockBuilder + // ensure that minting finishes before `block start time + timeout` and that its duration does not exceed the timeout. + var ( + cancel context.CancelFunc + now = time.Now() + blkTs = protocol.MustGetBlockCtx(ctx).BlockTimeStamp + ddl = blkTs.Add(m.timeout) + ) + if now.Before(blkTs) { + ddl = now.Add(m.timeout) + } + ctx, cancel = context.WithDeadline(ctx, ddl) defer cancel() } return m.f.NewBlockBuilder(ctx, m.ap, sign) diff --git a/state/factory/proposalpool.go b/state/factory/proposalpool.go new file mode 100644 index 0000000000..94b527937c --- /dev/null +++ b/state/factory/proposalpool.go @@ -0,0 +1,148 @@ +package factory + +import ( + "sync" + "time" + + "github.com/iotexproject/go-pkgs/hash" + "github.com/pkg/errors" + "go.uber.org/zap" + + "github.com/iotexproject/iotex-core/v2/blockchain/block" + "github.com/iotexproject/iotex-core/v2/pkg/log" +) + +// proposalPool is a pool of draft blocks +type proposalPool struct { + // blocks is a map of draft blocks + // key is the hash of the block + blocks map[hash.Hash256]*block.Block + // forks is a map of draft blocks that are forks + // key is the hash of the tip block of the fork + // value is the timestamp of the block + forks map[hash.Hash256]time.Time + // root is the hash of the tip block of the blockchain + root hash.Hash256 + mu sync.Mutex +} + +func newProposalPool() *proposalPool { + return &proposalPool{ + blocks: make(map[hash.Hash256]*block.Block), + forks: make(map[hash.Hash256]time.Time), + } +} + +func (d *proposalPool) Init(root hash.Hash256) { + d.mu.Lock() + defer d.mu.Unlock() + d.root = root + log.L().Info("proposal pool initialized", log.Hex("root", root[:])) +} + +// AddBlock adds a block to the draft pool +func (d *proposalPool) AddBlock(blk *block.Block) error { + d.mu.Lock() + defer d.mu.Unlock() + // nothing to do if the block already exists + hash := blk.HashBlock() + if _, ok := d.blocks[hash]; ok { + return nil + } + // it must be a new tip of any fork, or make a new fork + prevHash := blk.PrevHash() + if _, ok := d.forks[prevHash]; ok { + delete(d.forks, prevHash) + } else if prevHash != d.root { + return errors.Errorf("block %x is not a tip of any fork", prevHash[:]) + } + d.forks[hash] = blk.Timestamp() + d.blocks[hash] = blk + log.L().Info("added block to draft pool", log.Hex("hash", hash[:]), zap.Uint64("height", blk.Height()), zap.Time("timestamp", blk.Timestamp())) + return nil +} + +// ReceiveBlock a block has been confirmed and remove all the blocks that is previous to it, or other forks +func (d *proposalPool) ReceiveBlock(blk *block.Block) error { + d.mu.Lock() + defer d.mu.Unlock() + + // find the fork that contains this block + fork, err := d.forkAt(blk) + if err != nil { + return err + } + + // remove blocks in other forks or older blocks in the same fork + for f := range d.forks { + start := d.blocks[f] + if f == fork { + start = blk + } + for b := start; b != nil; b = d.blocks[b.PrevHash()] { + ha := b.HashBlock() + log.L().Info("remove block from draft pool", log.Hex("hash", ha[:]), zap.Uint64("height", b.Height()), zap.Time("timestamp", b.Timestamp())) + delete(d.blocks, b.HashBlock()) + } + } + // reset forks to only this one + if forkTime, ok := d.forks[fork]; ok && d.blocks[fork] != nil { + d.forks = map[hash.Hash256]time.Time{fork: forkTime} + } else { + d.forks = map[hash.Hash256]time.Time{} + } + d.root = blk.HashBlock() + return nil +} + +// Block returns the latest block at the given height +func (d *proposalPool) Block(height uint64) *block.Block { + d.mu.Lock() + defer d.mu.Unlock() + var blk *block.Block + for _, b := range d.blocks { + if b.Height() != height { + continue + } else if blk == nil { + blk = b + } else if b.Timestamp().After(blk.Timestamp()) { + blk = b + } + } + return blk +} + +// BlockByHash returns the block by hash +func (d *proposalPool) BlockByHash(hash hash.Hash256) *block.Block { + d.mu.Lock() + defer d.mu.Unlock() + return d.blocks[hash] +} + +func (d *proposalPool) BlockByTime(prevHash hash.Hash256, timestamp time.Time) *block.Block { + d.mu.Lock() + defer d.mu.Unlock() + for _, b := range d.blocks { + if b.PrevHash() == prevHash && b.Timestamp().Equal(timestamp) { + return b + } + } + return nil +} + +func (d *proposalPool) forkAt(blk *block.Block) (hash.Hash256, error) { + blkHash := blk.HashBlock() + // If this block isn't in the pool, just return it + if _, ok := d.blocks[blkHash]; !ok { + return blkHash, nil + } + // Otherwise, find which fork chain contains it + for forkTip := range d.forks { + for b := d.blocks[forkTip]; b != nil; b = d.blocks[b.PrevHash()] { + if blkHash == b.HashBlock() { + return forkTip, nil + } + } + } + return hash.ZeroHash256, errors.Errorf("block %x exists in the draft pool but not in the fork pool, this should not happen", blkHash[:]) +} diff --git a/test/mock/mock_blockchain/mock_blockchain.go b/test/mock/mock_blockchain/mock_blockchain.go index 42b5a75e15..19e397a37f 100644 --- a/test/mock/mock_blockchain/mock_blockchain.go +++ b/test/mock/mock_blockchain/mock_blockchain.go @@ -11,7 +11,6 @@ import ( gomock "github.com/golang/mock/gomock" hash "github.com/iotexproject/go-pkgs/hash" - action "github.com/iotexproject/iotex-core/v2/action" blockchain "github.com/iotexproject/iotex-core/v2/blockchain" block "github.com/iotexproject/iotex-core/v2/blockchain/block" genesis "github.com/iotexproject/iotex-core/v2/blockchain/genesis" @@ -170,6 +169,21 @@ func (mr *MockBlockchainMockRecorder) EvmNetworkID() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EvmNetworkID", reflect.TypeOf((*MockBlockchain)(nil).EvmNetworkID)) } +// Fork mocks base method. +func (m *MockBlockchain) Fork(arg0 hash.Hash256) (blockchain.Blockchain, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Fork", arg0) + ret0, _ := ret[0].(blockchain.Blockchain) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Fork indicates an expected call of Fork. +func (mr *MockBlockchainMockRecorder) Fork(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Fork", reflect.TypeOf((*MockBlockchain)(nil).Fork), arg0) +} + // Genesis mocks base method. func (m *MockBlockchain) Genesis() genesis.Genesis { m.ctrl.T.Helper() @@ -311,17 +325,85 @@ func (m *MockBlockBuilderFactory) EXPECT() *MockBlockBuilderFactoryMockRecorder return m.recorder } -// NewBlockBuilder mocks base method. -func (m *MockBlockBuilderFactory) NewBlockBuilder(arg0 context.Context, arg1 func(action.Envelope) (*action.SealedEnvelope, error)) (*block.Builder, error) { +// AddProposal mocks base method. +func (m *MockBlockBuilderFactory) AddProposal(arg0 *block.Block) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddProposal", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddProposal indicates an expected call of AddProposal. +func (mr *MockBlockBuilderFactoryMockRecorder) AddProposal(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddProposal", reflect.TypeOf((*MockBlockBuilderFactory)(nil).AddProposal), arg0) +} + +// Block mocks base method. +func (m *MockBlockBuilderFactory) Block(arg0 hash.Hash256) *block.Block { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Block", arg0) + ret0, _ := ret[0].(*block.Block) + return ret0 +} + +// Block indicates an expected call of Block. +func (mr *MockBlockBuilderFactoryMockRecorder) Block(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Block", reflect.TypeOf((*MockBlockBuilderFactory)(nil).Block), arg0) +} + +// BlockByHeight mocks base method. +func (m *MockBlockBuilderFactory) BlockByHeight(arg0 uint64) *block.Block { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "NewBlockBuilder", arg0, arg1) - ret0, _ := ret[0].(*block.Builder) + ret := m.ctrl.Call(m, "BlockByHeight", arg0) + ret0, _ := ret[0].(*block.Block) + return ret0 +} + +// BlockByHeight indicates an expected call of BlockByHeight. +func (mr *MockBlockBuilderFactoryMockRecorder) BlockByHeight(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BlockByHeight", reflect.TypeOf((*MockBlockBuilderFactory)(nil).BlockByHeight), arg0) +} + +// Init mocks base method. +func (m *MockBlockBuilderFactory) Init(arg0 hash.Hash256) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Init", arg0) +} + +// Init indicates an expected call of Init. +func (mr *MockBlockBuilderFactoryMockRecorder) Init(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockBlockBuilderFactory)(nil).Init), arg0) +} + +// Mint mocks base method. +func (m *MockBlockBuilderFactory) Mint(ctx context.Context) (*block.Block, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Mint", ctx) + ret0, _ := ret[0].(*block.Block) ret1, _ := ret[1].(error) return ret0, ret1 } -// NewBlockBuilder indicates an expected call of NewBlockBuilder. -func (mr *MockBlockBuilderFactoryMockRecorder) NewBlockBuilder(arg0, arg1 interface{}) *gomock.Call { +// Mint indicates an expected call of Mint. +func (mr *MockBlockBuilderFactoryMockRecorder) Mint(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Mint", reflect.TypeOf((*MockBlockBuilderFactory)(nil).Mint), ctx) +} + +// ReceiveBlock mocks base method. +func (m *MockBlockBuilderFactory) ReceiveBlock(arg0 *block.Block) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ReceiveBlock", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// ReceiveBlock indicates an expected call of ReceiveBlock. +func (mr *MockBlockBuilderFactoryMockRecorder) ReceiveBlock(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewBlockBuilder", reflect.TypeOf((*MockBlockBuilderFactory)(nil).NewBlockBuilder), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReceiveBlock", reflect.TypeOf((*MockBlockBuilderFactory)(nil).ReceiveBlock), arg0) } From 356a9d1a851da7c7c4d74c5ceebb0fa815f2bc01 Mon Sep 17 00:00:00 2001 From: envestcc Date: Wed, 19 Mar 2025 23:03:01 +0800 Subject: [PATCH 02/10] fix fork blockchain --- blockchain/blockchain.go | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index 8e77707df7..f288773c21 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -259,7 +259,12 @@ func (bc *blockchain) Start(ctx context.Context) error { return err } bc.head = header - bc.bbf.Init(header.HashBlock()) + headHash := header.HashBlock() + if tip == 0 { + headHash = bc.genesis.Hash() + } + bc.bbf.Init(headHash) + log.L().Debug("blockchain head at", zap.Uint64("height", tip), log.Hex("hash", headHash[:])) return nil } @@ -442,10 +447,7 @@ func (bc *blockchain) MintNewBlock(timestamp time.Time) (*block.Block, error) { defer bc.mu.RUnlock() mintNewBlockTimer := bc.timerFactory.NewTimer("MintNewBlock") defer mintNewBlockTimer.End() - tipHeight, err := bc.dao.Height() - if err != nil { - return nil, err - } + tipHeight := bc.tipHeight() newblockHeight := tipHeight + 1 ctx, err := bc.context(context.Background(), tipHeight) if err != nil { @@ -453,7 +455,7 @@ func (bc *blockchain) MintNewBlock(timestamp time.Time) (*block.Block, error) { } tip := protocol.MustGetBlockchainCtx(ctx).Tip // create a new block - log.L().Debug("Produce a new block.", zap.Uint64("height", newblockHeight), zap.Time("timestamp", timestamp)) + log.L().Debug("Produce a new block.", zap.Uint64("height", newblockHeight), zap.Time("timestamp", timestamp), log.Hex("prevHash", tip.Hash[:])) ctx = bc.contextWithBlock(ctx, bc.config.ProducerAddress(), newblockHeight, timestamp, protocol.CalcBaseFee(genesis.MustExtractGenesisContext(ctx).Blockchain, &tip), protocol.CalcExcessBlobGas(tip.ExcessBlobGas, tip.BlobGasUsed)) ctx = protocol.WithFeatureCtx(ctx) // run execution and update state trie root hash @@ -495,9 +497,16 @@ func (bc *blockchain) Fork(hash hash.Hash256) (Blockchain, error) { } bc.mu.RLock() defer bc.mu.RUnlock() + var header *block.Header headBlk := bc.bbf.Block(hash) if headBlk == nil { - return nil, errors.Errorf("block %x is not in the proposal pool, tip height %d", hash, bc.tipHeight()) + daoHeader, err := bc.dao.Header(hash) + if err != nil { + return nil, errors.Wrapf(err, "failed to get block %x to fork", hash) + } + header = daoHeader + } else { + header = &headBlk.Header } fork := &blockchain{ dao: bc.dao, @@ -509,7 +518,7 @@ func (bc *blockchain) Fork(hash hash.Hash256) (Blockchain, error) { pubSubManager: bc.pubSubManager, timerFactory: bc.timerFactory, bbf: bc.bbf, - head: &headBlk.Header, + head: header, } return fork, nil } @@ -600,7 +609,7 @@ func (bc *blockchain) emitToSubscribers(blk *block.Block) { } func (bc *blockchain) draftBlockByHeight(height uint64) *block.Block { - for blk := bc.bbf.Block(bc.tipHash()); blk != nil && blk.Height() > height; blk = bc.bbf.Block(blk.PrevHash()) { + for blk := bc.bbf.Block(bc.tipHash()); blk != nil && blk.Height() >= height; blk = bc.bbf.Block(blk.PrevHash()) { if blk.Height() == height { return blk } @@ -631,5 +640,8 @@ func (bc *blockchain) tipHeight() uint64 { func (bc *blockchain) tipHash() hash.Hash256 { bc.headLocker.Lock() defer bc.headLocker.Unlock() + if bc.head.Height() == 0 { + return bc.genesis.Hash() + } return bc.head.HashBlock() } From 327ea42ca8d7121fc89e6ea9ee13223509f580c7 Mon Sep 17 00:00:00 2001 From: envestcc Date: Wed, 19 Mar 2025 11:40:46 +0800 Subject: [PATCH 03/10] add prevhash for roundctx --- consensus/scheme/rolldpos/rolldpos.go | 14 ++++++++------ consensus/scheme/rolldpos/rolldpos_test.go | 3 ++- consensus/scheme/rolldpos/rolldposctx.go | 15 +++++++++------ consensus/scheme/rolldpos/roundcalculator.go | 17 ++++++++++++----- .../scheme/rolldpos/roundcalculator_test.go | 15 ++++++++------- consensus/scheme/rolldpos/roundctx.go | 9 +++++++++ 6 files changed, 48 insertions(+), 25 deletions(-) diff --git a/consensus/scheme/rolldpos/rolldpos.go b/consensus/scheme/rolldpos/rolldpos.go index 5ca1cdf3fe..50d514b04a 100644 --- a/consensus/scheme/rolldpos/rolldpos.go +++ b/consensus/scheme/rolldpos/rolldpos.go @@ -12,6 +12,7 @@ import ( "github.com/facebookgo/clock" "github.com/iotexproject/go-fsm" "github.com/iotexproject/go-pkgs/crypto" + "github.com/iotexproject/go-pkgs/hash" "github.com/iotexproject/iotex-proto/golang/iotextypes" "github.com/pkg/errors" "go.uber.org/zap" @@ -59,7 +60,7 @@ type ( // ValidateBlock validates a new block before adding it to the blockchain ValidateBlock(blk *block.Block) error // TipHeight returns tip block's height - TipHeight() uint64 + Tip() (uint64, hash.Hash256) // ChainAddress returns chain address on parent chain, the root chain return empty. ChainAddress() string } @@ -135,8 +136,8 @@ func (cm *chainManager) ValidateBlock(blk *block.Block) error { } // TipHeight returns tip block's height -func (cm *chainManager) TipHeight() uint64 { - return cm.bc.TipHeight() +func (cm *chainManager) Tip() (uint64, hash.Hash256) { + return cm.bc.TipHeight(), cm.bc.TipHash() } // ChainAddress returns chain address on parent chain, the root chain return empty. @@ -244,7 +245,8 @@ func (r *RollDPoS) Calibrate(height uint64) { // ValidateBlockFooter validates the signatures in the block footer func (r *RollDPoS) ValidateBlockFooter(blk *block.Block) error { height := blk.Height() - round, err := r.ctx.RoundCalculator().NewRound(height, r.ctx.BlockInterval(height), blk.Timestamp(), nil) + prevHash := blk.PrevHash() + round, err := r.ctx.RoundCalculator().NewRound(height, r.ctx.BlockInterval(height), blk.Timestamp(), nil, prevHash[:]) if err != nil { return err } @@ -276,8 +278,8 @@ func (r *RollDPoS) ValidateBlockFooter(blk *block.Block) error { // Metrics returns RollDPoS consensus metrics func (r *RollDPoS) Metrics() (scheme.ConsensusMetrics, error) { var metrics scheme.ConsensusMetrics - height := r.ctx.Chain().TipHeight() - round, err := r.ctx.RoundCalculator().NewRound(height+1, r.ctx.BlockInterval(height), r.ctx.Clock().Now(), nil) + height, tipHash := r.ctx.Chain().Tip() + round, err := r.ctx.RoundCalculator().NewRound(height+1, r.ctx.BlockInterval(height), r.ctx.Clock().Now(), nil, tipHash[:]) if err != nil { return metrics, errors.Wrap(err, "error when calculating round") } diff --git a/consensus/scheme/rolldpos/rolldpos_test.go b/consensus/scheme/rolldpos/rolldpos_test.go index 42edbf8b7e..695955ed2a 100644 --- a/consensus/scheme/rolldpos/rolldpos_test.go +++ b/consensus/scheme/rolldpos/rolldpos_test.go @@ -288,6 +288,7 @@ func TestRollDPoS_Metrics(t *testing.T) { footer := &block.Footer{} bc := mock_blockchain.NewMockBlockchain(ctrl) bc.EXPECT().TipHeight().Return(blockHeight).Times(1) + bc.EXPECT().TipHash().Return(hash.ZeroHash256).Times(1) bc.EXPECT().BlockFooterByHeight(blockHeight).Return(footer, nil).Times(2) sk1 := identityset.PrivateKey(1) @@ -338,7 +339,7 @@ func TestRollDPoS_Metrics(t *testing.T) { ctx := r.ctx.(*rollDPoSCtx) clock.Add(ctx.BlockInterval(blockHeight)) require.NoError(t, ctx.Start(context.Background())) - ctx.round, err = ctx.roundCalc.UpdateRound(ctx.round, blockHeight+1, ctx.BlockInterval(blockHeight+1), clock.Now(), 2*time.Second) + ctx.round, err = ctx.roundCalc.UpdateRound(ctx.round, blockHeight+1, ctx.BlockInterval(blockHeight+1), clock.Now(), 2*time.Second, hash.ZeroHash256[:]) require.NoError(t, err) m, err := r.Metrics() diff --git a/consensus/scheme/rolldpos/rolldposctx.go b/consensus/scheme/rolldpos/rolldposctx.go index 62c9a4e3ef..db319d8410 100644 --- a/consensus/scheme/rolldpos/rolldposctx.go +++ b/consensus/scheme/rolldpos/rolldposctx.go @@ -13,6 +13,7 @@ import ( "github.com/facebookgo/clock" fsm "github.com/iotexproject/go-fsm" "github.com/iotexproject/go-pkgs/crypto" + "github.com/iotexproject/go-pkgs/hash" "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" "go.uber.org/zap" @@ -181,7 +182,7 @@ func (ctx *rollDPoSCtx) Start(c context.Context) (err error) { } eManager, err = newEndorsementManager(ctx.eManagerDB, ctx.blockDeserializer) } - ctx.round, err = ctx.roundCalc.NewRoundWithToleration(0, ctx.BlockInterval(0), ctx.clock.Now(), eManager, ctx.toleratedOvertime) + ctx.round, err = ctx.roundCalc.NewRoundWithToleration(0, ctx.BlockInterval(0), ctx.clock.Now(), eManager, ctx.toleratedOvertime, hash.ZeroHash256[:]) return err } @@ -247,7 +248,8 @@ func (ctx *rollDPoSCtx) CheckBlockProposer( if endorserAddr == nil { return errors.New("failed to get address") } - if proposer := ctx.roundCalc.Proposer(height, ctx.BlockInterval(height), en.Timestamp()); proposer != endorserAddr.String() { + prevHash := proposal.block.PrevHash() + if proposer := ctx.roundCalc.Proposer(height, ctx.BlockInterval(height), en.Timestamp(), prevHash[:]); proposer != endorserAddr.String() { return errors.Errorf( "%s is not proposer of the corresponding round, %s expected", endorserAddr.String(), @@ -255,14 +257,14 @@ func (ctx *rollDPoSCtx) CheckBlockProposer( ) } proposerAddr := proposal.ProposerAddress() - if ctx.roundCalc.Proposer(height, ctx.BlockInterval(height), proposal.block.Timestamp()) != proposerAddr { + if ctx.roundCalc.Proposer(height, ctx.BlockInterval(height), proposal.block.Timestamp(), prevHash[:]) != proposerAddr { return errors.Errorf("%s is not proposer of the corresponding round", proposerAddr) } if !proposal.block.VerifySignature() { return errors.Errorf("invalid block signature") } if proposerAddr != endorserAddr.String() { - round, err := ctx.roundCalc.NewRound(height, ctx.BlockInterval(height), en.Timestamp(), nil) + round, err := ctx.roundCalc.NewRound(height, ctx.BlockInterval(height), en.Timestamp(), nil, prevHash[:]) if err != nil { return err } @@ -328,8 +330,9 @@ func (ctx *rollDPoSCtx) Logger() *zap.Logger { func (ctx *rollDPoSCtx) Prepare() error { ctx.mutex.Lock() defer ctx.mutex.Unlock() - height := ctx.chain.TipHeight() + 1 - newRound, err := ctx.roundCalc.UpdateRound(ctx.round, height, ctx.BlockInterval(height), ctx.clock.Now(), ctx.toleratedOvertime) + tipHeight, tipHash := ctx.chain.Tip() + height := tipHeight + 1 + newRound, err := ctx.roundCalc.UpdateRound(ctx.round, height, ctx.BlockInterval(height), ctx.clock.Now(), ctx.toleratedOvertime, tipHash[:]) if err != nil { return err } diff --git a/consensus/scheme/rolldpos/roundcalculator.go b/consensus/scheme/rolldpos/roundcalculator.go index 4fc21ac9a6..8133dc40c9 100644 --- a/consensus/scheme/rolldpos/roundcalculator.go +++ b/consensus/scheme/rolldpos/roundcalculator.go @@ -10,6 +10,8 @@ import ( "github.com/pkg/errors" + "github.com/iotexproject/go-pkgs/hash" + "github.com/iotexproject/iotex-core/v2/action/protocol/rolldpos" "github.com/iotexproject/iotex-core/v2/endorsement" ) @@ -26,7 +28,7 @@ type roundCalculator struct { } // UpdateRound updates previous roundCtx -func (c *roundCalculator) UpdateRound(round *roundCtx, height uint64, blockInterval time.Duration, now time.Time, toleratedOvertime time.Duration) (*roundCtx, error) { +func (c *roundCalculator) UpdateRound(round *roundCtx, height uint64, blockInterval time.Duration, now time.Time, toleratedOvertime time.Duration, prevHash []byte) (*roundCtx, error) { epochNum := round.EpochNum() epochStartHeight := round.EpochStartHeight() delegates := round.Delegates() @@ -86,6 +88,7 @@ func (c *roundCalculator) UpdateRound(round *roundCtx, height uint64, blockInter height: height, roundNum: roundNum, + prevHash: hash.Hash256(prevHash), proposer: proposer, roundStartTime: roundStartTime, nextRoundStartTime: roundStartTime.Add(blockInterval), @@ -97,8 +100,8 @@ func (c *roundCalculator) UpdateRound(round *roundCtx, height uint64, blockInter } // Proposer returns the block producer of the round -func (c *roundCalculator) Proposer(height uint64, blockInterval time.Duration, roundStartTime time.Time) string { - round, err := c.newRound(height, blockInterval, roundStartTime, nil, 0) +func (c *roundCalculator) Proposer(height uint64, blockInterval time.Duration, roundStartTime time.Time, prevHash []byte) string { + round, err := c.newRound(height, blockInterval, roundStartTime, nil, 0, prevHash) if err != nil { return "" } @@ -196,8 +199,9 @@ func (c *roundCalculator) NewRoundWithToleration( now time.Time, eManager *endorsementManager, toleratedOvertime time.Duration, + prevHash []byte, ) (round *roundCtx, err error) { - return c.newRound(height, blockInterval, now, eManager, toleratedOvertime) + return c.newRound(height, blockInterval, now, eManager, toleratedOvertime, prevHash) } // NewRound starts new round and returns roundCtx @@ -206,8 +210,9 @@ func (c *roundCalculator) NewRound( blockInterval time.Duration, now time.Time, eManager *endorsementManager, + prevHash []byte, ) (round *roundCtx, err error) { - return c.newRound(height, blockInterval, now, eManager, 0) + return c.newRound(height, blockInterval, now, eManager, 0, prevHash) } func (c *roundCalculator) newRound( @@ -216,6 +221,7 @@ func (c *roundCalculator) newRound( now time.Time, eManager *endorsementManager, toleratedOvertime time.Duration, + prevHash []byte, ) (round *roundCtx, err error) { epochNum := uint64(0) epochStartHeight := uint64(0) @@ -253,6 +259,7 @@ func (c *roundCalculator) newRound( height: height, roundNum: roundNum, + prevHash: hash.Hash256(prevHash), proposer: proposer, eManager: eManager, roundStartTime: roundStartTime, diff --git a/consensus/scheme/rolldpos/roundcalculator_test.go b/consensus/scheme/rolldpos/roundcalculator_test.go index 9da3c5c54f..04396d7f64 100644 --- a/consensus/scheme/rolldpos/roundcalculator_test.go +++ b/consensus/scheme/rolldpos/roundcalculator_test.go @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/iotexproject/go-pkgs/crypto" + "github.com/iotexproject/go-pkgs/hash" "github.com/pkg/errors" "github.com/iotexproject/iotex-core/v2/action/protocol" @@ -38,23 +39,23 @@ import ( func TestUpdateRound(t *testing.T) { require := require.New(t) rc := makeRoundCalculator(t) - ra, err := rc.NewRound(51, time.Second, time.Unix(1562382522, 0), nil) + ra, err := rc.NewRound(51, time.Second, time.Unix(1562382522, 0), nil, hash.ZeroHash256[:]) require.NoError(err) // height < round.Height() - _, err = rc.UpdateRound(ra, 50, time.Second, time.Unix(1562382492, 0), time.Second) + _, err = rc.UpdateRound(ra, 50, time.Second, time.Unix(1562382492, 0), time.Second, hash.ZeroHash256[:]) require.Error(err) // height == round.Height() and now.Before(round.StartTime()) - _, err = rc.UpdateRound(ra, 51, time.Second, time.Unix(1562382522, 0), time.Second) + _, err = rc.UpdateRound(ra, 51, time.Second, time.Unix(1562382522, 0), time.Second, hash.ZeroHash256[:]) require.NoError(err) // height >= round.NextEpochStartHeight() Delegates error - _, err = rc.UpdateRound(ra, 500, time.Second, time.Unix(1562382092, 0), time.Second) + _, err = rc.UpdateRound(ra, 500, time.Second, time.Unix(1562382092, 0), time.Second, hash.ZeroHash256[:]) require.Error(err) // (51+100)%24 - ra, err = rc.UpdateRound(ra, 51, time.Second, time.Unix(1562382522, 0), time.Second) + ra, err = rc.UpdateRound(ra, 51, time.Second, time.Unix(1562382522, 0), time.Second, hash.ZeroHash256[:]) require.NoError(err) require.Equal(identityset.Address(10).String(), ra.proposer) } @@ -77,7 +78,7 @@ func TestNewRound(t *testing.T) { require.NoError(err) require.Equal(validDelegates[2], proposer) - ra, err := rc.NewRound(51, time.Second, time.Unix(1562382592, 0), nil) + ra, err := rc.NewRound(51, time.Second, time.Unix(1562382592, 0), nil, hash.ZeroHash256[:]) require.NoError(err) require.Equal(uint32(170), ra.roundNum) require.Equal(uint64(51), ra.height) @@ -85,7 +86,7 @@ func TestNewRound(t *testing.T) { require.Equal(identityset.Address(7).String(), ra.proposer) rc.timeBasedRotation = true - ra, err = rc.NewRound(51, time.Second, time.Unix(1562382592, 0), nil) + ra, err = rc.NewRound(51, time.Second, time.Unix(1562382592, 0), nil, hash.ZeroHash256[:]) require.NoError(err) require.Equal(uint32(170), ra.roundNum) require.Equal(uint64(51), ra.height) diff --git a/consensus/scheme/rolldpos/roundctx.go b/consensus/scheme/rolldpos/roundctx.go index b50e8d9166..ddaff82b78 100644 --- a/consensus/scheme/rolldpos/roundctx.go +++ b/consensus/scheme/rolldpos/roundctx.go @@ -11,8 +11,11 @@ import ( "github.com/pkg/errors" "go.uber.org/zap" + "github.com/iotexproject/go-pkgs/hash" + "github.com/iotexproject/iotex-core/v2/blockchain/block" "github.com/iotexproject/iotex-core/v2/endorsement" + "github.com/iotexproject/iotex-core/v2/pkg/log" ) // ErrInsufficientEndorsements represents the error that not enough endorsements @@ -36,6 +39,7 @@ type roundCtx struct { height uint64 roundNum uint32 + prevHash hash.Hash256 proposer string roundStartTime time.Time nextRoundStartTime time.Time @@ -52,6 +56,7 @@ func (ctx *roundCtx) Log(l *zap.Logger) *zap.Logger { zap.Uint64("epoch", ctx.epochNum), zap.Uint32("round", ctx.roundNum), zap.String("proposer", ctx.proposer), + log.Hex("prevHash", ctx.prevHash[:]), ) } @@ -87,6 +92,10 @@ func (ctx *roundCtx) Number() uint32 { return ctx.roundNum } +func (ctx *roundCtx) PrevHash() hash.Hash256 { + return ctx.prevHash +} + func (ctx *roundCtx) Proposer() string { return ctx.proposer } From d0ff280f248227c324542216b2ab996885659094 Mon Sep 17 00:00:00 2001 From: envestcc Date: Wed, 19 Mar 2025 12:14:35 +0800 Subject: [PATCH 04/10] fork chainmanager --- consensus/consensus.go | 2 +- consensus/scheme/rolldpos/chainmanager.go | 124 ++++++++++++++++++++++ consensus/scheme/rolldpos/rolldpos.go | 83 --------------- state/factory/factory.go | 5 + state/factory/statedb.go | 4 + 5 files changed, 134 insertions(+), 84 deletions(-) create mode 100644 consensus/scheme/rolldpos/chainmanager.go diff --git a/consensus/consensus.go b/consensus/consensus.go index 76bcdd4fd2..e41f0cd25e 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -136,7 +136,7 @@ func NewConsensus( SetAddr(cfg.Chain.ProducerAddress().String()). SetPriKey(cfg.Chain.ProducerPrivateKey()). SetConfig(cfg). - SetChainManager(rolldpos.NewChainManager(bc)). + SetChainManager(rolldpos.NewChainManager(bc, sf)). SetBlockDeserializer(block.NewDeserializer(bc.EvmNetworkID())). SetClock(clock). SetBroadcast(ops.broadcastHandler). diff --git a/consensus/scheme/rolldpos/chainmanager.go b/consensus/scheme/rolldpos/chainmanager.go new file mode 100644 index 0000000000..97fafc98a5 --- /dev/null +++ b/consensus/scheme/rolldpos/chainmanager.go @@ -0,0 +1,124 @@ +package rolldpos + +import ( + "time" + + "github.com/iotexproject/go-pkgs/hash" + "github.com/pkg/errors" + + "github.com/iotexproject/iotex-core/v2/action/protocol" + "github.com/iotexproject/iotex-core/v2/blockchain" + "github.com/iotexproject/iotex-core/v2/blockchain/block" +) + +type ( + // ChainManager defines the blockchain interface + ChainManager interface { + // BlockProposeTime return propose time by height + BlockProposeTime(uint64) (time.Time, error) + // BlockCommitTime return commit time by height + BlockCommitTime(uint64) (time.Time, error) + // MintNewBlock creates a new block with given actions + // Note: the coinbase transfer will be added to the given transfers when minting a new block + MintNewBlock(timestamp time.Time) (*block.Block, error) + // CommitBlock validates and appends a block to the chain + CommitBlock(blk *block.Block) error + // ValidateBlock validates a new block before adding it to the blockchain + ValidateBlock(blk *block.Block) error + // TipHeight returns tip block's height + Tip() (uint64, hash.Hash256) + // ChainAddress returns chain address on parent chain, the root chain return empty. + ChainAddress() string + // StateReader returns the state reader + StateReader() (protocol.StateReader, error) + // Fork creates a new chain manager with the given hash + Fork(hash hash.Hash256) (ChainManager, error) + } + + StateReaderFactory interface { + StateReaderAt(*block.Header) (protocol.StateReader, error) + } + + chainManager struct { + bc blockchain.Blockchain + srf StateReaderFactory + } +) + +// NewChainManager creates a chain manager +func NewChainManager(bc blockchain.Blockchain, srf StateReaderFactory) ChainManager { + return &chainManager{ + bc: bc, + srf: srf, + } +} + +// BlockProposeTime return propose time by height +func (cm *chainManager) BlockProposeTime(height uint64) (time.Time, error) { + if height == 0 { + return time.Unix(cm.bc.Genesis().Timestamp, 0), nil + } + header, err := cm.bc.BlockHeaderByHeight(height) + if err != nil { + return time.Time{}, errors.Wrapf( + err, "error when getting the block at height: %d", + height, + ) + } + return header.Timestamp(), nil +} + +// BlockCommitTime return commit time by height +func (cm *chainManager) BlockCommitTime(height uint64) (time.Time, error) { + footer, err := cm.bc.BlockFooterByHeight(height) + if err != nil { + return time.Time{}, errors.Wrapf( + err, "error when getting the block at height: %d", + height, + ) + } + return footer.CommitTime(), nil +} + +// MintNewBlock creates a new block with given actions +func (cm *chainManager) MintNewBlock(timestamp time.Time) (*block.Block, error) { + return cm.bc.MintNewBlock(timestamp) +} + +// CommitBlock validates and appends a block to the chain +func (cm *chainManager) CommitBlock(blk *block.Block) error { + return cm.bc.CommitBlock(blk) +} + +// ValidateBlock validates a new block before adding it to the blockchain +func (cm *chainManager) ValidateBlock(blk *block.Block) error { + return cm.bc.ValidateBlock(blk) +} + +// TipHeight returns tip block's height +func (cm *chainManager) Tip() (uint64, hash.Hash256) { + return cm.bc.TipHeight(), cm.bc.TipHash() +} + +// ChainAddress returns chain address on parent chain, the root chain return empty. +func (cm *chainManager) ChainAddress() string { + return cm.bc.ChainAddress() +} + +// StateReader returns the state reader +func (cm *chainManager) StateReader() (protocol.StateReader, error) { + header, err := cm.bc.BlockHeaderByHeight(cm.bc.TipHeight()) + if err != nil { + return nil, err + } + return cm.srf.StateReaderAt(header) +} + +// Fork creates a new chain manager with the given hash +func (cm *chainManager) Fork(hash hash.Hash256) (ChainManager, error) { + fork, err := cm.bc.Fork(hash) + if err != nil { + return nil, err + } + return NewChainManager(fork, cm.srf), nil +} diff --git a/consensus/scheme/rolldpos/rolldpos.go b/consensus/scheme/rolldpos/rolldpos.go index 50d514b04a..139aa65a23 100644 --- a/consensus/scheme/rolldpos/rolldpos.go +++ b/consensus/scheme/rolldpos/rolldpos.go @@ -12,7 +12,6 @@ import ( "github.com/facebookgo/clock" "github.com/iotexproject/go-fsm" "github.com/iotexproject/go-pkgs/crypto" - "github.com/iotexproject/go-pkgs/hash" "github.com/iotexproject/iotex-proto/golang/iotextypes" "github.com/pkg/errors" "go.uber.org/zap" @@ -45,29 +44,6 @@ type ( Delay time.Duration `yaml:"delay"` ConsensusDBPath string `yaml:"consensusDBPath"` } - - // ChainManager defines the blockchain interface - ChainManager interface { - // BlockProposeTime return propose time by height - BlockProposeTime(uint64) (time.Time, error) - // BlockCommitTime return commit time by height - BlockCommitTime(uint64) (time.Time, error) - // MintNewBlock creates a new block with given actions - // Note: the coinbase transfer will be added to the given transfers when minting a new block - MintNewBlock(timestamp time.Time) (*block.Block, error) - // CommitBlock validates and appends a block to the chain - CommitBlock(blk *block.Block) error - // ValidateBlock validates a new block before adding it to the blockchain - ValidateBlock(blk *block.Block) error - // TipHeight returns tip block's height - Tip() (uint64, hash.Hash256) - // ChainAddress returns chain address on parent chain, the root chain return empty. - ChainAddress() string - } - - chainManager struct { - bc blockchain.Blockchain - } ) // DefaultConfig is the default config @@ -86,65 +62,6 @@ var DefaultConfig = Config{ ConsensusDBPath: "/var/data/consensus.db", } -// NewChainManager creates a chain manager -func NewChainManager(bc blockchain.Blockchain) ChainManager { - return &chainManager{ - bc: bc, - } -} - -// BlockProposeTime return propose time by height -func (cm *chainManager) BlockProposeTime(height uint64) (time.Time, error) { - if height == 0 { - return time.Unix(cm.bc.Genesis().Timestamp, 0), nil - } - header, err := cm.bc.BlockHeaderByHeight(height) - if err != nil { - return time.Time{}, errors.Wrapf( - err, "error when getting the block at height: %d", - height, - ) - } - return header.Timestamp(), nil -} - -// BlockCommitTime return commit time by height -func (cm *chainManager) BlockCommitTime(height uint64) (time.Time, error) { - footer, err := cm.bc.BlockFooterByHeight(height) - if err != nil { - return time.Time{}, errors.Wrapf( - err, "error when getting the block at height: %d", - height, - ) - } - return footer.CommitTime(), nil -} - -// MintNewBlock creates a new block with given actions -func (cm *chainManager) MintNewBlock(timestamp time.Time) (*block.Block, error) { - return cm.bc.MintNewBlock(timestamp) -} - -// CommitBlock validates and appends a block to the chain -func (cm *chainManager) CommitBlock(blk *block.Block) error { - return cm.bc.CommitBlock(blk) -} - -// ValidateBlock validates a new block before adding it to the blockchain -func (cm *chainManager) ValidateBlock(blk *block.Block) error { - return cm.bc.ValidateBlock(blk) -} - -// TipHeight returns tip block's height -func (cm *chainManager) Tip() (uint64, hash.Hash256) { - return cm.bc.TipHeight(), cm.bc.TipHash() -} - -// ChainAddress returns chain address on parent chain, the root chain return empty. -func (cm *chainManager) ChainAddress() string { - return cm.bc.ChainAddress() -} - // RollDPoS is Roll-DPoS consensus main entrance type RollDPoS struct { cfsm *consensusfsm.ConsensusFSM diff --git a/state/factory/factory.go b/state/factory/factory.go index 274cc41b7f..693910d882 100644 --- a/state/factory/factory.go +++ b/state/factory/factory.go @@ -88,6 +88,7 @@ type ( PutBlock(context.Context, *block.Block) error WorkingSet(context.Context) (protocol.StateManager, error) WorkingSetAtHeight(context.Context, uint64, ...*action.SealedEnvelope) (protocol.StateManager, error) + StateReaderAt(header *block.Header) (protocol.StateReader, error) } // factory implements StateFactory interface, tracks changes to account/contract and batch-commits to DB @@ -539,6 +540,10 @@ func (sf *factory) ReadView(name string) (interface{}, error) { return sf.protocolView.Read(name) } +func (sf *factory) StateReaderAt(header *block.Header) (protocol.StateReader, error) { + panic("implement me") +} + //====================================== // private trie constructor functions //====================================== diff --git a/state/factory/statedb.go b/state/factory/statedb.go index 03634eb060..df83052d49 100644 --- a/state/factory/statedb.go +++ b/state/factory/statedb.go @@ -378,6 +378,10 @@ func (sdb *stateDB) ReadView(name string) (interface{}, error) { return sdb.protocolView.Read(name) } +func (sdb *stateDB) StateReaderAt(header *block.Header) (protocol.StateReader, error) { + panic("implement me") +} + //====================================== // private trie constructor functions //====================================== From 400b97d478b353d99b6c9bb31776a34ee2b8c8e3 Mon Sep 17 00:00:00 2001 From: envestcc Date: Wed, 19 Mar 2025 12:30:43 +0800 Subject: [PATCH 05/10] delegate at hash --- consensus/consensus.go | 20 +++++++++++++++----- consensus/scheme/rolldpos/rolldposctx.go | 4 ++-- consensus/scheme/rolldpos/roundcalculator.go | 20 ++++++++++---------- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/consensus/consensus.go b/consensus/consensus.go index e41f0cd25e..bb67acc810 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -9,6 +9,7 @@ import ( "context" "github.com/facebookgo/clock" + "github.com/iotexproject/go-pkgs/hash" "github.com/iotexproject/iotex-proto/golang/iotextypes" "github.com/pkg/errors" "go.uber.org/zap" @@ -100,7 +101,16 @@ func NewConsensus( var err error switch cfg.Scheme { case RollDPoSScheme: - delegatesByEpochFunc := func(epochNum uint64) ([]string, error) { + chanMgr := rolldpos.NewChainManager(bc, sf) + delegatesByEpochFunc := func(epochNum uint64, prevHash []byte) ([]string, error) { + fork, serr := chanMgr.Fork(hash.Hash256(prevHash)) + if serr != nil { + return nil, serr + } + forkSF, serr := fork.StateReader() + if serr != nil { + return nil, serr + } re := protocol.NewRegistry() if err := ops.rp.Register(re); err != nil { return nil, err @@ -110,15 +120,15 @@ func NewConsensus( cfg.Genesis, ) ctx = protocol.WithFeatureWithHeightCtx(ctx) - tipHeight := bc.TipHeight() + tipHeight, _ := fork.Tip() tipEpochNum := ops.rp.GetEpochNum(tipHeight) var candidatesList state.CandidateList var err error switch epochNum { case tipEpochNum: - candidatesList, err = ops.pp.Delegates(ctx, sf) + candidatesList, err = ops.pp.Delegates(ctx, forkSF) case tipEpochNum + 1: - candidatesList, err = ops.pp.NextDelegates(ctx, sf) + candidatesList, err = ops.pp.NextDelegates(ctx, forkSF) default: err = errors.Errorf("invalid epoch number %d compared to tip epoch number %d", epochNum, tipEpochNum) } @@ -136,7 +146,7 @@ func NewConsensus( SetAddr(cfg.Chain.ProducerAddress().String()). SetPriKey(cfg.Chain.ProducerPrivateKey()). SetConfig(cfg). - SetChainManager(rolldpos.NewChainManager(bc, sf)). + SetChainManager(chanMgr). SetBlockDeserializer(block.NewDeserializer(bc.EvmNetworkID())). SetClock(clock). SetBroadcast(ops.broadcastHandler). diff --git a/consensus/scheme/rolldpos/rolldposctx.go b/consensus/scheme/rolldpos/rolldposctx.go index db319d8410..de7ec75131 100644 --- a/consensus/scheme/rolldpos/rolldposctx.go +++ b/consensus/scheme/rolldpos/rolldposctx.go @@ -71,7 +71,7 @@ func init() { type ( // NodesSelectionByEpochFunc defines a function to select nodes - NodesSelectionByEpochFunc func(uint64) ([]string, error) + NodesSelectionByEpochFunc func(uint64, []byte) ([]string, error) // RDPoSCtx is the context of RollDPoS RDPoSCtx interface { @@ -222,7 +222,7 @@ func (ctx *rollDPoSCtx) CheckVoteEndorser( if endorserAddr == nil { return errors.New("failed to get address") } - if !ctx.roundCalc.IsDelegate(endorserAddr.String(), height) { + if !ctx.roundCalc.IsDelegate(endorserAddr.String(), height, ctx.round.prevHash[:]) { return errors.Errorf("%s is not delegate of the corresponding round", endorserAddr) } diff --git a/consensus/scheme/rolldpos/roundcalculator.go b/consensus/scheme/rolldpos/roundcalculator.go index 8133dc40c9..5cd3478255 100644 --- a/consensus/scheme/rolldpos/roundcalculator.go +++ b/consensus/scheme/rolldpos/roundcalculator.go @@ -46,10 +46,10 @@ func (c *roundCalculator) UpdateRound(round *roundCtx, height uint64, blockInter epochNum = c.rp.GetEpochNum(height) epochStartHeight = c.rp.GetEpochHeight(epochNum) var err error - if delegates, err = c.Delegates(height); err != nil { + if delegates, err = c.Delegates(height, prevHash); err != nil { return nil, err } - if proposers, err = c.Proposers(height); err != nil { + if proposers, err = c.Proposers(height, prevHash); err != nil { return nil, err } } @@ -109,8 +109,8 @@ func (c *roundCalculator) Proposer(height uint64, blockInterval time.Duration, r return round.Proposer() } -func (c *roundCalculator) IsDelegate(addr string, height uint64) bool { - delegates, err := c.Delegates(height) +func (c *roundCalculator) IsDelegate(addr string, height uint64, prevHash []byte) bool { + delegates, err := c.Delegates(height, prevHash) if err != nil { return false } @@ -181,15 +181,15 @@ func (c *roundCalculator) roundInfo( } // Delegates returns list of delegates at given height -func (c *roundCalculator) Delegates(height uint64) ([]string, error) { +func (c *roundCalculator) Delegates(height uint64, prevHash []byte) ([]string, error) { epochNum := c.rp.GetEpochNum(height) - return c.delegatesByEpochFunc(epochNum) + return c.delegatesByEpochFunc(epochNum, prevHash) } // Proposers returns list of candidate proposers at given height -func (c *roundCalculator) Proposers(height uint64) ([]string, error) { +func (c *roundCalculator) Proposers(height uint64, prevHash []byte) ([]string, error) { epochNum := c.rp.GetEpochNum(height) - return c.proposersByEpochFunc(epochNum) + return c.proposersByEpochFunc(epochNum, prevHash) } // NewRoundWithToleration starts new round with tolerated over time @@ -232,10 +232,10 @@ func (c *roundCalculator) newRound( if height != 0 { epochNum = c.rp.GetEpochNum(height) epochStartHeight = c.rp.GetEpochHeight(epochNum) - if delegates, err = c.Delegates(height); err != nil { + if delegates, err = c.Delegates(height, prevHash); err != nil { return } - if proposers, err = c.Proposers(height); err != nil { + if proposers, err = c.Proposers(height, prevHash); err != nil { return } if roundNum, roundStartTime, err = c.roundInfo(height, blockInterval, now, toleratedOvertime); err != nil { From 9ac71958c399ea9ba7aa8a5498b66c609c9d9981 Mon Sep 17 00:00:00 2001 From: envestcc Date: Wed, 19 Mar 2025 12:38:17 +0800 Subject: [PATCH 06/10] prepare next proposal --- consensus/consensusfsm/context.go | 1 + consensus/consensusfsm/fsm.go | 6 ++++ consensus/scheme/rolldpos/rolldposctx.go | 39 ++++++++++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/consensus/consensusfsm/context.go b/consensus/consensusfsm/context.go index 2acbd6d866..65f568258b 100644 --- a/consensus/consensusfsm/context.go +++ b/consensus/consensusfsm/context.go @@ -34,6 +34,7 @@ type Context interface { Prepare() error IsDelegate() bool Proposal() (interface{}, error) + PrepareNextProposal(any) error WaitUntilRoundStart() time.Duration PreCommitEndorsement() interface{} NewProposalEndorsement(interface{}) (interface{}, error) diff --git a/consensus/consensusfsm/fsm.go b/consensus/consensusfsm/fsm.go index 7f02c92920..b4d0680920 100644 --- a/consensus/consensusfsm/fsm.go +++ b/consensus/consensusfsm/fsm.go @@ -469,6 +469,12 @@ func (m *ConsensusFSM) processBlock(block interface{}) error { } m.ProduceReceiveProposalEndorsementEvent(en) m.ctx.Broadcast(en) + if block == nil { + return nil + } + if err = m.ctx.PrepareNextProposal(block); err != nil { + m.ctx.Logger().Warn("Failed to prepare next proposal", zap.Error(err)) + } return nil } diff --git a/consensus/scheme/rolldpos/rolldposctx.go b/consensus/scheme/rolldpos/rolldposctx.go index de7ec75131..eb6f65173d 100644 --- a/consensus/scheme/rolldpos/rolldposctx.go +++ b/consensus/scheme/rolldpos/rolldposctx.go @@ -373,6 +373,45 @@ func (ctx *rollDPoSCtx) Proposal() (interface{}, error) { return ctx.mintNewBlock() } +func (ctx *rollDPoSCtx) PrepareNextProposal(msg any) error { + // retrieve the block from the message + ecm, ok := msg.(*EndorsedConsensusMessage) + if !ok { + return errors.New("invalid endorsed block") + } + proposal, ok := ecm.Document().(*blockProposal) + if !ok { + return errors.New("invalid endorsed block") + } + var ( + blk = proposal.block + height = blk.Height() + 1 + interval = ctx.BlockInterval(height) + startTime = blk.Timestamp().Add(interval) + prevHash = blk.HashBlock() + err error + ) + fork, err := ctx.chain.Fork(prevHash) + if err != nil { + return errors.Wrapf(err, "failed to check fork at block %d, hash %x", blk.Height(), prevHash[:]) + } + // check if the current node is the next proposer + nextProposer := ctx.roundCalc.Proposer(height, interval, startTime, prevHash[:]) + if ctx.encodedAddr != nextProposer { + return nil + } + ctx.logger().Debug("prepare next proposal", log.Hex("prevHash", prevHash[:]), zap.Uint64("height", ctx.round.height+1), zap.Time("timestamp", startTime), zap.String("ioAddr", ctx.encodedAddr), zap.String("nextproposer", nextProposer)) + go func() { + blk, err := fork.MintNewBlock(startTime) + if err != nil { + ctx.logger().Error("failed to mint new block", zap.Error(err)) + return + } + ctx.logger().Debug("prepared a new block", zap.Uint64("height", blk.Height()), zap.Time("timestamp", blk.Timestamp())) + }() + return nil +} + func (ctx *rollDPoSCtx) WaitUntilRoundStart() time.Duration { ctx.mutex.RLock() defer ctx.mutex.RUnlock() From fd404667edb94b40128af2a52e36b197e661328a Mon Sep 17 00:00:00 2001 From: envestcc Date: Wed, 19 Mar 2025 13:04:25 +0800 Subject: [PATCH 07/10] fix test --- consensus/consensusfsm/fsm_test.go | 1 + consensus/consensusfsm/mock_context_test.go | 14 ++++++++++ consensus/scheme/rolldpos/rolldpos_test.go | 23 +++++++++------- consensus/scheme/rolldpos/rolldposctx_test.go | 27 ++++++++++--------- .../scheme/rolldpos/roundcalculator_test.go | 10 +++---- test/mock/mock_factory/mock_factory.go | 15 +++++++++++ 6 files changed, 62 insertions(+), 28 deletions(-) diff --git a/consensus/consensusfsm/fsm_test.go b/consensus/consensusfsm/fsm_test.go index 15bdc258e1..ddb88b9baf 100644 --- a/consensus/consensusfsm/fsm_test.go +++ b/consensus/consensusfsm/fsm_test.go @@ -236,6 +236,7 @@ func TestStateTransitionFunctions(t *testing.T) { t.Run("success", func(t *testing.T) { mockCtx.EXPECT().NewProposalEndorsement(gomock.Any()).Return(NewMockEndorsement(ctrl), nil).Times(1) mockCtx.EXPECT().Broadcast(gomock.Any()).Return().Times(1) + mockCtx.EXPECT().PrepareNextProposal(gomock.Any()).Return(nil).Times(1) state, err := cfsm.onReceiveBlock(&ConsensusEvent{data: NewMockEndorsement(ctrl)}) require.NoError(err) require.Equal(sAcceptProposalEndorsement, state) diff --git a/consensus/consensusfsm/mock_context_test.go b/consensus/consensusfsm/mock_context_test.go index 2108864683..2a954677c2 100644 --- a/consensus/consensusfsm/mock_context_test.go +++ b/consensus/consensusfsm/mock_context_test.go @@ -373,6 +373,20 @@ func (mr *MockContextMockRecorder) Prepare() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Prepare", reflect.TypeOf((*MockContext)(nil).Prepare)) } +// PrepareNextProposal mocks base method. +func (m *MockContext) PrepareNextProposal(arg0 any) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PrepareNextProposal", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// PrepareNextProposal indicates an expected call of PrepareNextProposal. +func (mr *MockContextMockRecorder) PrepareNextProposal(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrepareNextProposal", reflect.TypeOf((*MockContext)(nil).PrepareNextProposal), arg0) +} + // Proposal mocks base method. func (m *MockContext) Proposal() (interface{}, error) { m.ctrl.T.Helper() diff --git a/consensus/scheme/rolldpos/rolldpos_test.go b/consensus/scheme/rolldpos/rolldpos_test.go index 695955ed2a..401f6c9439 100644 --- a/consensus/scheme/rolldpos/rolldpos_test.go +++ b/consensus/scheme/rolldpos/rolldpos_test.go @@ -44,6 +44,7 @@ import ( "github.com/iotexproject/iotex-core/v2/state/factory" "github.com/iotexproject/iotex-core/v2/test/identityset" "github.com/iotexproject/iotex-core/v2/test/mock/mock_blockchain" + "github.com/iotexproject/iotex-core/v2/test/mock/mock_factory" "github.com/iotexproject/iotex-core/v2/testutil" ) @@ -71,14 +72,14 @@ func TestNewRollDPoS(t *testing.T) { g.NumDelegates, g.NumSubEpochs, ) - delegatesByEpoch := func(uint64) ([]string, error) { return nil, nil } + delegatesByEpoch := func(uint64, []byte) ([]string, error) { return nil, nil } t.Run("normal", func(t *testing.T) { sk := identityset.PrivateKey(0) r, err := NewRollDPoSBuilder(). SetConfig(builderCfg). SetAddr(identityset.Address(0).String()). SetPriKey(sk). - SetChainManager(NewChainManager(mock_blockchain.NewMockBlockchain(ctrl))). + SetChainManager(NewChainManager(mock_blockchain.NewMockBlockchain(ctrl), mock_factory.NewMockFactory(ctrl))). SetBroadcast(func(_ proto.Message) error { return nil }). @@ -95,7 +96,7 @@ func TestNewRollDPoS(t *testing.T) { SetConfig(builderCfg). SetAddr(identityset.Address(0).String()). SetPriKey(sk). - SetChainManager(NewChainManager(mock_blockchain.NewMockBlockchain(ctrl))). + SetChainManager(NewChainManager(mock_blockchain.NewMockBlockchain(ctrl), mock_factory.NewMockFactory(ctrl))). SetBroadcast(func(_ proto.Message) error { return nil }). @@ -116,7 +117,7 @@ func TestNewRollDPoS(t *testing.T) { SetConfig(builderCfg). SetAddr(identityset.Address(0).String()). SetPriKey(sk). - SetChainManager(NewChainManager(mock_blockchain.NewMockBlockchain(ctrl))). + SetChainManager(NewChainManager(mock_blockchain.NewMockBlockchain(ctrl), mock_factory.NewMockFactory(ctrl))). SetBroadcast(func(_ proto.Message) error { return nil }). @@ -217,12 +218,13 @@ func TestValidateBlockFooter(t *testing.T) { SystemActive: true, } bc.EXPECT().Genesis().Return(g).Times(5) + sf := mock_factory.NewMockFactory(ctrl) rp := rolldpos.NewProtocol( g.NumCandidateDelegates, g.NumDelegates, g.NumSubEpochs, ) - delegatesByEpoch := func(uint64) ([]string, error) { + delegatesByEpoch := func(uint64, []byte) ([]string, error) { return []string{ candidates[0], candidates[1], @@ -234,7 +236,7 @@ func TestValidateBlockFooter(t *testing.T) { SetConfig(builderCfg). SetAddr(identityset.Address(1).String()). SetPriKey(sk1). - SetChainManager(NewChainManager(bc)). + SetChainManager(NewChainManager(bc, sf)). SetBroadcast(func(_ proto.Message) error { return nil }). @@ -308,12 +310,13 @@ func TestRollDPoS_Metrics(t *testing.T) { SystemActive: true, } bc.EXPECT().Genesis().Return(g).Times(2) + sf := mock_factory.NewMockFactory(ctrl) rp := rolldpos.NewProtocol( g.NumCandidateDelegates, g.NumDelegates, g.NumSubEpochs, ) - delegatesByEpoch := func(uint64) ([]string, error) { + delegatesByEpoch := func(uint64, []byte) ([]string, error) { return []string{ candidates[0], candidates[1], @@ -325,7 +328,7 @@ func TestRollDPoS_Metrics(t *testing.T) { SetConfig(builderCfg). SetAddr(identityset.Address(1).String()). SetPriKey(sk1). - SetChainManager(NewChainManager(bc)). + SetChainManager(NewChainManager(bc, sf)). SetBroadcast(func(_ proto.Message) error { return nil }). @@ -432,7 +435,7 @@ func TestRollDPoSConsensus(t *testing.T) { chainAddrs[i] = addressMap[rawAddress] } - delegatesByEpochFunc := func(_ uint64) ([]string, error) { + delegatesByEpochFunc := func(_ uint64, _ []byte) ([]string, error) { candidates := make([]string, 0, numNodes) for _, addr := range chainAddrs { candidates = append(candidates, addr.encodedAddr) @@ -487,7 +490,7 @@ func TestRollDPoSConsensus(t *testing.T) { SetAddr(chainAddrs[i].encodedAddr). SetPriKey(chainAddrs[i].priKey). SetConfig(builderCfg). - SetChainManager(NewChainManager(chain)). + SetChainManager(NewChainManager(chain, sf)). SetBroadcast(p2p.Broadcast). SetDelegatesByEpochFunc(delegatesByEpochFunc). SetProposersByEpochFunc(delegatesByEpochFunc). diff --git a/consensus/scheme/rolldpos/rolldposctx_test.go b/consensus/scheme/rolldpos/rolldposctx_test.go index 38f07af2cf..e036f1441e 100644 --- a/consensus/scheme/rolldpos/rolldposctx_test.go +++ b/consensus/scheme/rolldpos/rolldposctx_test.go @@ -27,7 +27,7 @@ import ( "github.com/iotexproject/iotex-core/v2/test/identityset" ) -var dummyCandidatesByHeightFunc = func(uint64) ([]string, error) { return nil, nil } +var dummyCandidatesByHeightFunc = func(uint64, []byte) ([]string, error) { return nil, nil } func TestRollDPoSCtx(t *testing.T) { require := require.New(t) @@ -35,7 +35,7 @@ func TestRollDPoSCtx(t *testing.T) { g := genesis.TestDefault() dbConfig := db.DefaultConfig dbConfig.DbPath = DefaultConfig.ConsensusDBPath - b, _, _, _, _ := makeChain(t) + b, sf, _, _, _ := makeChain(t) t.Run("case 1:panic because of chain is nil", func(t *testing.T) { _, err := NewRollDPoSCtx(consensusfsm.NewConsensusConfig(cfg.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, cfg.Delay), dbConfig, true, time.Second, true, nil, block.NewDeserializer(0), nil, nil, dummyCandidatesByHeightFunc, dummyCandidatesByHeightFunc, "", nil, nil, 0) @@ -43,7 +43,7 @@ func TestRollDPoSCtx(t *testing.T) { }) t.Run("case 2:panic because of rp is nil", func(t *testing.T) { - _, err := NewRollDPoSCtx(consensusfsm.NewConsensusConfig(cfg.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, cfg.Delay), dbConfig, true, time.Second, true, NewChainManager(b), block.NewDeserializer(0), nil, nil, dummyCandidatesByHeightFunc, dummyCandidatesByHeightFunc, "", nil, nil, 0) + _, err := NewRollDPoSCtx(consensusfsm.NewConsensusConfig(cfg.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, cfg.Delay), dbConfig, true, time.Second, true, NewChainManager(b, sf), block.NewDeserializer(0), nil, nil, dummyCandidatesByHeightFunc, dummyCandidatesByHeightFunc, "", nil, nil, 0) require.Error(err) }) @@ -53,7 +53,7 @@ func TestRollDPoSCtx(t *testing.T) { g.NumSubEpochs, ) t.Run("case 3:panic because of clock is nil", func(t *testing.T) { - _, err := NewRollDPoSCtx(consensusfsm.NewConsensusConfig(cfg.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, cfg.Delay), dbConfig, true, time.Second, true, NewChainManager(b), block.NewDeserializer(0), rp, nil, dummyCandidatesByHeightFunc, dummyCandidatesByHeightFunc, "", nil, nil, 0) + _, err := NewRollDPoSCtx(consensusfsm.NewConsensusConfig(cfg.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, cfg.Delay), dbConfig, true, time.Second, true, NewChainManager(b, sf), block.NewDeserializer(0), rp, nil, dummyCandidatesByHeightFunc, dummyCandidatesByHeightFunc, "", nil, nil, 0) require.Error(err) }) @@ -63,19 +63,19 @@ func TestRollDPoSCtx(t *testing.T) { cfg.FSM.AcceptLockEndorsementTTL = time.Second cfg.FSM.CommitTTL = time.Second t.Run("case 4:panic because of fsm time bigger than block interval", func(t *testing.T) { - _, err := NewRollDPoSCtx(consensusfsm.NewConsensusConfig(cfg.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, cfg.Delay), dbConfig, true, time.Second, true, NewChainManager(b), block.NewDeserializer(0), rp, nil, dummyCandidatesByHeightFunc, dummyCandidatesByHeightFunc, "", nil, c, 0) + _, err := NewRollDPoSCtx(consensusfsm.NewConsensusConfig(cfg.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, cfg.Delay), dbConfig, true, time.Second, true, NewChainManager(b, sf), block.NewDeserializer(0), rp, nil, dummyCandidatesByHeightFunc, dummyCandidatesByHeightFunc, "", nil, c, 0) require.Error(err) }) g.Blockchain.BlockInterval = time.Second * 20 t.Run("case 5:panic because of nil CandidatesByHeight function", func(t *testing.T) { - _, err := NewRollDPoSCtx(consensusfsm.NewConsensusConfig(cfg.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, cfg.Delay), dbConfig, true, time.Second, true, NewChainManager(b), block.NewDeserializer(0), rp, nil, nil, nil, "", nil, c, 0) + _, err := NewRollDPoSCtx(consensusfsm.NewConsensusConfig(cfg.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, cfg.Delay), dbConfig, true, time.Second, true, NewChainManager(b, sf), block.NewDeserializer(0), rp, nil, nil, nil, "", nil, c, 0) require.Error(err) }) t.Run("case 6:normal", func(t *testing.T) { bh := g.BeringBlockHeight - rctx, err := NewRollDPoSCtx(consensusfsm.NewConsensusConfig(cfg.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, cfg.Delay), dbConfig, true, time.Second, true, NewChainManager(b), block.NewDeserializer(0), rp, nil, dummyCandidatesByHeightFunc, dummyCandidatesByHeightFunc, "", nil, c, bh) + rctx, err := NewRollDPoSCtx(consensusfsm.NewConsensusConfig(cfg.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, cfg.Delay), dbConfig, true, time.Second, true, NewChainManager(b, sf), block.NewDeserializer(0), rp, nil, dummyCandidatesByHeightFunc, dummyCandidatesByHeightFunc, "", nil, c, bh) require.NoError(err) require.Equal(bh, rctx.RoundCalculator().beringHeight) require.NotNil(rctx) @@ -88,7 +88,7 @@ func TestCheckVoteEndorser(t *testing.T) { c := clock.New() g := genesis.TestDefault() g.Blockchain.BlockInterval = time.Second * 20 - delegatesByEpochFunc := func(epochnum uint64) ([]string, error) { + delegatesByEpochFunc := func(epochnum uint64, _ []byte) ([]string, error) { re := protocol.NewRegistry() if err := rp.Register(re); err != nil { return nil, err @@ -129,7 +129,7 @@ func TestCheckVoteEndorser(t *testing.T) { true, time.Second, true, - NewChainManager(b), + NewChainManager(b, sf), block.NewDeserializer(0), rp, nil, @@ -142,6 +142,7 @@ func TestCheckVoteEndorser(t *testing.T) { ) require.NoError(err) require.NotNil(rctx) + require.NoError(rctx.Start(context.Background())) // case 1:endorser nil caused panic require.Panics(func() { rctx.CheckVoteEndorser(0, nil, nil) }, "") @@ -161,7 +162,7 @@ func TestCheckBlockProposer(t *testing.T) { b, sf, _, rp, pp := makeChain(t) c := clock.New() g.Blockchain.BlockInterval = time.Second * 20 - delegatesByEpochFunc := func(epochnum uint64) ([]string, error) { + delegatesByEpochFunc := func(epochnum uint64, _ []byte) ([]string, error) { re := protocol.NewRegistry() if err := rp.Register(re); err != nil { return nil, err @@ -202,7 +203,7 @@ func TestCheckBlockProposer(t *testing.T) { true, time.Second, true, - NewChainManager(b), + NewChainManager(b, sf), block.NewDeserializer(0), rp, nil, @@ -273,7 +274,7 @@ func TestNotProducingMultipleBlocks(t *testing.T) { c := clock.New() g := genesis.TestDefault() g.Blockchain.BlockInterval = time.Second * 20 - delegatesByEpoch := func(epochnum uint64) ([]string, error) { + delegatesByEpoch := func(epochnum uint64, _ []byte) ([]string, error) { re := protocol.NewRegistry() if err := rp.Register(re); err != nil { return nil, err @@ -314,7 +315,7 @@ func TestNotProducingMultipleBlocks(t *testing.T) { true, time.Second, true, - NewChainManager(b), + NewChainManager(b, sf), block.NewDeserializer(0), rp, nil, diff --git a/consensus/scheme/rolldpos/roundcalculator_test.go b/consensus/scheme/rolldpos/roundcalculator_test.go index 04396d7f64..01d2f8f717 100644 --- a/consensus/scheme/rolldpos/roundcalculator_test.go +++ b/consensus/scheme/rolldpos/roundcalculator_test.go @@ -97,12 +97,12 @@ func TestDelegates(t *testing.T) { require := require.New(t) rc := makeRoundCalculator(t) - dels, err := rc.Delegates(51) + dels, err := rc.Delegates(51, hash.ZeroHash256[:]) require.NoError(err) require.Equal(rc.rp.NumDelegates(), uint64(len(dels))) - require.False(rc.IsDelegate(identityset.Address(25).String(), 51)) - require.True(rc.IsDelegate(identityset.Address(0).String(), 51)) + require.False(rc.IsDelegate(identityset.Address(25).String(), 51, hash.ZeroHash256[:])) + require.True(rc.IsDelegate(identityset.Address(0).String(), 51, hash.ZeroHash256[:])) } func TestRoundInfo(t *testing.T) { @@ -228,7 +228,7 @@ func makeChain(t *testing.T) (blockchain.Blockchain, factory.Factory, actpool.Ac func makeRoundCalculator(t *testing.T) *roundCalculator { bc, sf, _, rp, pp := makeChain(t) - delegatesByEpoch := func(epochNum uint64) ([]string, error) { + delegatesByEpoch := func(epochNum uint64, _ []byte) ([]string, error) { re := protocol.NewRegistry() if err := rp.Register(re); err != nil { return nil, err @@ -266,7 +266,7 @@ func makeRoundCalculator(t *testing.T) *roundCalculator { return addrs, nil } return &roundCalculator{ - NewChainManager(bc), + NewChainManager(bc, sf), true, rp, delegatesByEpoch, diff --git a/test/mock/mock_factory/mock_factory.go b/test/mock/mock_factory/mock_factory.go index c383989d17..9bc0b23bd5 100644 --- a/test/mock/mock_factory/mock_factory.go +++ b/test/mock/mock_factory/mock_factory.go @@ -146,6 +146,21 @@ func (mr *MockFactoryMockRecorder) State(arg0 interface{}, arg1 ...interface{}) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "State", reflect.TypeOf((*MockFactory)(nil).State), varargs...) } +// StateReaderAt mocks base method. +func (m *MockFactory) StateReaderAt(header *block.Header) (protocol.StateReader, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateReaderAt", header) + ret0, _ := ret[0].(protocol.StateReader) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateReaderAt indicates an expected call of StateReaderAt. +func (mr *MockFactoryMockRecorder) StateReaderAt(header interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateReaderAt", reflect.TypeOf((*MockFactory)(nil).StateReaderAt), header) +} + // States mocks base method. func (m *MockFactory) States(arg0 ...protocol.StateOption) (uint64, state.Iterator, error) { m.ctrl.T.Helper() From ad969362b3b0cf46ba9554da1917a067d89b1d3a Mon Sep 17 00:00:00 2001 From: envestcc Date: Wed, 19 Mar 2025 23:05:12 +0800 Subject: [PATCH 08/10] fork roundcalc --- consensus/scheme/rolldpos/rolldpos.go | 10 +++- consensus/scheme/rolldpos/rolldpos_test.go | 2 +- consensus/scheme/rolldpos/rolldposctx.go | 30 +++++++--- consensus/scheme/rolldpos/roundcalculator.go | 56 +++++++++++++------ .../scheme/rolldpos/roundcalculator_test.go | 21 ++++--- 5 files changed, 77 insertions(+), 42 deletions(-) diff --git a/consensus/scheme/rolldpos/rolldpos.go b/consensus/scheme/rolldpos/rolldpos.go index 139aa65a23..ed301a07cc 100644 --- a/consensus/scheme/rolldpos/rolldpos.go +++ b/consensus/scheme/rolldpos/rolldpos.go @@ -163,7 +163,11 @@ func (r *RollDPoS) Calibrate(height uint64) { func (r *RollDPoS) ValidateBlockFooter(blk *block.Block) error { height := blk.Height() prevHash := blk.PrevHash() - round, err := r.ctx.RoundCalculator().NewRound(height, r.ctx.BlockInterval(height), blk.Timestamp(), nil, prevHash[:]) + roundCalc, err := r.ctx.RoundCalculator().Fork(prevHash) + if err != nil { + return errors.Wrapf(err, "failed to fork round calculator at height %d with prevHash %x", height, prevHash) + } + round, err := roundCalc.NewRound(height, r.ctx.BlockInterval(height), blk.Timestamp(), nil) if err != nil { return err } @@ -195,8 +199,8 @@ func (r *RollDPoS) ValidateBlockFooter(blk *block.Block) error { // Metrics returns RollDPoS consensus metrics func (r *RollDPoS) Metrics() (scheme.ConsensusMetrics, error) { var metrics scheme.ConsensusMetrics - height, tipHash := r.ctx.Chain().Tip() - round, err := r.ctx.RoundCalculator().NewRound(height+1, r.ctx.BlockInterval(height), r.ctx.Clock().Now(), nil, tipHash[:]) + height, _ := r.ctx.Chain().Tip() + round, err := r.ctx.RoundCalculator().NewRound(height+1, r.ctx.BlockInterval(height), r.ctx.Clock().Now(), nil) if err != nil { return metrics, errors.Wrap(err, "error when calculating round") } diff --git a/consensus/scheme/rolldpos/rolldpos_test.go b/consensus/scheme/rolldpos/rolldpos_test.go index 401f6c9439..cf53f63ca6 100644 --- a/consensus/scheme/rolldpos/rolldpos_test.go +++ b/consensus/scheme/rolldpos/rolldpos_test.go @@ -342,7 +342,7 @@ func TestRollDPoS_Metrics(t *testing.T) { ctx := r.ctx.(*rollDPoSCtx) clock.Add(ctx.BlockInterval(blockHeight)) require.NoError(t, ctx.Start(context.Background())) - ctx.round, err = ctx.roundCalc.UpdateRound(ctx.round, blockHeight+1, ctx.BlockInterval(blockHeight+1), clock.Now(), 2*time.Second, hash.ZeroHash256[:]) + ctx.round, err = ctx.roundCalc.UpdateRound(ctx.round, blockHeight+1, ctx.BlockInterval(blockHeight+1), clock.Now(), 2*time.Second) require.NoError(t, err) m, err := r.Metrics() diff --git a/consensus/scheme/rolldpos/rolldposctx.go b/consensus/scheme/rolldpos/rolldposctx.go index eb6f65173d..e37f2b73cc 100644 --- a/consensus/scheme/rolldpos/rolldposctx.go +++ b/consensus/scheme/rolldpos/rolldposctx.go @@ -13,7 +13,6 @@ import ( "github.com/facebookgo/clock" fsm "github.com/iotexproject/go-fsm" "github.com/iotexproject/go-pkgs/crypto" - "github.com/iotexproject/go-pkgs/hash" "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" "go.uber.org/zap" @@ -182,7 +181,7 @@ func (ctx *rollDPoSCtx) Start(c context.Context) (err error) { } eManager, err = newEndorsementManager(ctx.eManagerDB, ctx.blockDeserializer) } - ctx.round, err = ctx.roundCalc.NewRoundWithToleration(0, ctx.BlockInterval(0), ctx.clock.Now(), eManager, ctx.toleratedOvertime, hash.ZeroHash256[:]) + ctx.round, err = ctx.roundCalc.NewRoundWithToleration(0, ctx.BlockInterval(0), ctx.clock.Now(), eManager, ctx.toleratedOvertime) return err } @@ -222,7 +221,11 @@ func (ctx *rollDPoSCtx) CheckVoteEndorser( if endorserAddr == nil { return errors.New("failed to get address") } - if !ctx.roundCalc.IsDelegate(endorserAddr.String(), height, ctx.round.prevHash[:]) { + roundCalc, err := ctx.roundCalc.Fork(ctx.round.prevHash) + if err != nil { + return errors.Wrapf(err, "failed to fork at block %d, hash %x", height, ctx.round.prevHash[:]) + } + if !roundCalc.IsDelegate(endorserAddr.String(), height) { return errors.Errorf("%s is not delegate of the corresponding round", endorserAddr) } @@ -249,7 +252,11 @@ func (ctx *rollDPoSCtx) CheckBlockProposer( return errors.New("failed to get address") } prevHash := proposal.block.PrevHash() - if proposer := ctx.roundCalc.Proposer(height, ctx.BlockInterval(height), en.Timestamp(), prevHash[:]); proposer != endorserAddr.String() { + roundCalc, err := ctx.roundCalc.Fork(prevHash) + if err != nil { + return errors.Wrapf(err, "failed to fork at block %d, hash %x", proposal.block.Height(), prevHash[:]) + } + if proposer := roundCalc.Proposer(height, ctx.BlockInterval(height), en.Timestamp()); proposer != endorserAddr.String() { return errors.Errorf( "%s is not proposer of the corresponding round, %s expected", endorserAddr.String(), @@ -257,14 +264,14 @@ func (ctx *rollDPoSCtx) CheckBlockProposer( ) } proposerAddr := proposal.ProposerAddress() - if ctx.roundCalc.Proposer(height, ctx.BlockInterval(height), proposal.block.Timestamp(), prevHash[:]) != proposerAddr { + if roundCalc.Proposer(height, ctx.BlockInterval(height), proposal.block.Timestamp()) != proposerAddr { return errors.Errorf("%s is not proposer of the corresponding round", proposerAddr) } if !proposal.block.VerifySignature() { return errors.Errorf("invalid block signature") } if proposerAddr != endorserAddr.String() { - round, err := ctx.roundCalc.NewRound(height, ctx.BlockInterval(height), en.Timestamp(), nil, prevHash[:]) + round, err := roundCalc.NewRound(height, ctx.BlockInterval(height), en.Timestamp(), nil) if err != nil { return err } @@ -330,9 +337,9 @@ func (ctx *rollDPoSCtx) Logger() *zap.Logger { func (ctx *rollDPoSCtx) Prepare() error { ctx.mutex.Lock() defer ctx.mutex.Unlock() - tipHeight, tipHash := ctx.chain.Tip() + tipHeight, _ := ctx.chain.Tip() height := tipHeight + 1 - newRound, err := ctx.roundCalc.UpdateRound(ctx.round, height, ctx.BlockInterval(height), ctx.clock.Now(), ctx.toleratedOvertime, tipHash[:]) + newRound, err := ctx.roundCalc.UpdateRound(ctx.round, height, ctx.BlockInterval(height), ctx.clock.Now(), ctx.toleratedOvertime) if err != nil { return err } @@ -391,12 +398,17 @@ func (ctx *rollDPoSCtx) PrepareNextProposal(msg any) error { prevHash = blk.HashBlock() err error ) + ctx.logger().Debug("prepare next proposal", log.Hex("prevHash", prevHash[:]), zap.Uint64("height", height), zap.Time("timestamp", startTime), zap.String("ioAddr", ctx.encodedAddr)) fork, err := ctx.chain.Fork(prevHash) if err != nil { return errors.Wrapf(err, "failed to check fork at block %d, hash %x", blk.Height(), prevHash[:]) } + roundCalc, err := ctx.roundCalc.Fork(prevHash) + if err != nil { + return errors.Wrapf(err, "failed to fork at block %d, hash %x", blk.Height(), prevHash[:]) + } // check if the current node is the next proposer - nextProposer := ctx.roundCalc.Proposer(height, interval, startTime, prevHash[:]) + nextProposer := roundCalc.Proposer(height, interval, startTime) if ctx.encodedAddr != nextProposer { return nil } diff --git a/consensus/scheme/rolldpos/roundcalculator.go b/consensus/scheme/rolldpos/roundcalculator.go index 5cd3478255..e4a43b5e7b 100644 --- a/consensus/scheme/rolldpos/roundcalculator.go +++ b/consensus/scheme/rolldpos/roundcalculator.go @@ -9,11 +9,13 @@ import ( "time" "github.com/pkg/errors" + "go.uber.org/zap" "github.com/iotexproject/go-pkgs/hash" "github.com/iotexproject/iotex-core/v2/action/protocol/rolldpos" "github.com/iotexproject/iotex-core/v2/endorsement" + "github.com/iotexproject/iotex-core/v2/pkg/log" ) var errInvalidCurrentTime = errors.New("invalid current time") @@ -28,7 +30,7 @@ type roundCalculator struct { } // UpdateRound updates previous roundCtx -func (c *roundCalculator) UpdateRound(round *roundCtx, height uint64, blockInterval time.Duration, now time.Time, toleratedOvertime time.Duration, prevHash []byte) (*roundCtx, error) { +func (c *roundCalculator) UpdateRound(round *roundCtx, height uint64, blockInterval time.Duration, now time.Time, toleratedOvertime time.Duration) (*roundCtx, error) { epochNum := round.EpochNum() epochStartHeight := round.EpochStartHeight() delegates := round.Delegates() @@ -46,10 +48,10 @@ func (c *roundCalculator) UpdateRound(round *roundCtx, height uint64, blockInter epochNum = c.rp.GetEpochNum(height) epochStartHeight = c.rp.GetEpochHeight(epochNum) var err error - if delegates, err = c.Delegates(height, prevHash); err != nil { + if delegates, err = c.Delegates(height); err != nil { return nil, err } - if proposers, err = c.Proposers(height, prevHash); err != nil { + if proposers, err = c.Proposers(height); err != nil { return nil, err } } @@ -79,6 +81,7 @@ func (c *roundCalculator) UpdateRound(round *roundCtx, height uint64, blockInter return nil, err } } + _, prevHash := c.chain.Tip() return &roundCtx{ epochNum: epochNum, epochStartHeight: epochStartHeight, @@ -100,18 +103,20 @@ func (c *roundCalculator) UpdateRound(round *roundCtx, height uint64, blockInter } // Proposer returns the block producer of the round -func (c *roundCalculator) Proposer(height uint64, blockInterval time.Duration, roundStartTime time.Time, prevHash []byte) string { - round, err := c.newRound(height, blockInterval, roundStartTime, nil, 0, prevHash) +func (c *roundCalculator) Proposer(height uint64, blockInterval time.Duration, roundStartTime time.Time) string { + round, err := c.newRound(height, blockInterval, roundStartTime, nil, 0) if err != nil { + log.L().Warn("Failed to get proposer", zap.Error(err)) return "" } return round.Proposer() } -func (c *roundCalculator) IsDelegate(addr string, height uint64, prevHash []byte) bool { - delegates, err := c.Delegates(height, prevHash) +func (c *roundCalculator) IsDelegate(addr string, height uint64) bool { + delegates, err := c.Delegates(height) if err != nil { + log.L().Warn("Failed to get delegates", zap.Error(err)) return false } for _, d := range delegates { @@ -181,15 +186,17 @@ func (c *roundCalculator) roundInfo( } // Delegates returns list of delegates at given height -func (c *roundCalculator) Delegates(height uint64, prevHash []byte) ([]string, error) { +func (c *roundCalculator) Delegates(height uint64) ([]string, error) { epochNum := c.rp.GetEpochNum(height) - return c.delegatesByEpochFunc(epochNum, prevHash) + _, prevHash := c.chain.Tip() + return c.delegatesByEpochFunc(epochNum, prevHash[:]) } // Proposers returns list of candidate proposers at given height -func (c *roundCalculator) Proposers(height uint64, prevHash []byte) ([]string, error) { +func (c *roundCalculator) Proposers(height uint64) ([]string, error) { epochNum := c.rp.GetEpochNum(height) - return c.proposersByEpochFunc(epochNum, prevHash) + _, prevHash := c.chain.Tip() + return c.proposersByEpochFunc(epochNum, prevHash[:]) } // NewRoundWithToleration starts new round with tolerated over time @@ -199,9 +206,8 @@ func (c *roundCalculator) NewRoundWithToleration( now time.Time, eManager *endorsementManager, toleratedOvertime time.Duration, - prevHash []byte, ) (round *roundCtx, err error) { - return c.newRound(height, blockInterval, now, eManager, toleratedOvertime, prevHash) + return c.newRound(height, blockInterval, now, eManager, toleratedOvertime) } // NewRound starts new round and returns roundCtx @@ -210,9 +216,8 @@ func (c *roundCalculator) NewRound( blockInterval time.Duration, now time.Time, eManager *endorsementManager, - prevHash []byte, ) (round *roundCtx, err error) { - return c.newRound(height, blockInterval, now, eManager, 0, prevHash) + return c.newRound(height, blockInterval, now, eManager, 0) } func (c *roundCalculator) newRound( @@ -221,7 +226,6 @@ func (c *roundCalculator) newRound( now time.Time, eManager *endorsementManager, toleratedOvertime time.Duration, - prevHash []byte, ) (round *roundCtx, err error) { epochNum := uint64(0) epochStartHeight := uint64(0) @@ -232,10 +236,10 @@ func (c *roundCalculator) newRound( if height != 0 { epochNum = c.rp.GetEpochNum(height) epochStartHeight = c.rp.GetEpochHeight(epochNum) - if delegates, err = c.Delegates(height, prevHash); err != nil { + if delegates, err = c.Delegates(height); err != nil { return } - if proposers, err = c.Proposers(height, prevHash); err != nil { + if proposers, err = c.Proposers(height); err != nil { return } if roundNum, roundStartTime, err = c.roundInfo(height, blockInterval, now, toleratedOvertime); err != nil { @@ -250,6 +254,7 @@ func (c *roundCalculator) newRound( return nil, err } } + _, prevHash := c.chain.Tip() round = &roundCtx{ epochNum: epochNum, epochStartHeight: epochStartHeight, @@ -290,3 +295,18 @@ func (c *roundCalculator) calculateProposer( proposer = proposers[idx%numProposers] return } + +func (c *roundCalculator) Fork(hash hash.Hash256) (*roundCalculator, error) { + fork, err := c.chain.Fork(hash) + if err != nil { + return nil, err + } + return &roundCalculator{ + chain: fork, + timeBasedRotation: c.timeBasedRotation, + rp: c.rp, + delegatesByEpochFunc: c.delegatesByEpochFunc, + proposersByEpochFunc: c.proposersByEpochFunc, + beringHeight: c.beringHeight, + }, nil +} diff --git a/consensus/scheme/rolldpos/roundcalculator_test.go b/consensus/scheme/rolldpos/roundcalculator_test.go index 01d2f8f717..75d329edd6 100644 --- a/consensus/scheme/rolldpos/roundcalculator_test.go +++ b/consensus/scheme/rolldpos/roundcalculator_test.go @@ -13,7 +13,6 @@ import ( "github.com/stretchr/testify/require" "github.com/iotexproject/go-pkgs/crypto" - "github.com/iotexproject/go-pkgs/hash" "github.com/pkg/errors" "github.com/iotexproject/iotex-core/v2/action/protocol" @@ -39,23 +38,23 @@ import ( func TestUpdateRound(t *testing.T) { require := require.New(t) rc := makeRoundCalculator(t) - ra, err := rc.NewRound(51, time.Second, time.Unix(1562382522, 0), nil, hash.ZeroHash256[:]) + ra, err := rc.NewRound(51, time.Second, time.Unix(1562382522, 0), nil) require.NoError(err) // height < round.Height() - _, err = rc.UpdateRound(ra, 50, time.Second, time.Unix(1562382492, 0), time.Second, hash.ZeroHash256[:]) + _, err = rc.UpdateRound(ra, 50, time.Second, time.Unix(1562382492, 0), time.Second) require.Error(err) // height == round.Height() and now.Before(round.StartTime()) - _, err = rc.UpdateRound(ra, 51, time.Second, time.Unix(1562382522, 0), time.Second, hash.ZeroHash256[:]) + _, err = rc.UpdateRound(ra, 51, time.Second, time.Unix(1562382522, 0), time.Second) require.NoError(err) // height >= round.NextEpochStartHeight() Delegates error - _, err = rc.UpdateRound(ra, 500, time.Second, time.Unix(1562382092, 0), time.Second, hash.ZeroHash256[:]) + _, err = rc.UpdateRound(ra, 500, time.Second, time.Unix(1562382092, 0), time.Second) require.Error(err) // (51+100)%24 - ra, err = rc.UpdateRound(ra, 51, time.Second, time.Unix(1562382522, 0), time.Second, hash.ZeroHash256[:]) + ra, err = rc.UpdateRound(ra, 51, time.Second, time.Unix(1562382522, 0), time.Second) require.NoError(err) require.Equal(identityset.Address(10).String(), ra.proposer) } @@ -78,7 +77,7 @@ func TestNewRound(t *testing.T) { require.NoError(err) require.Equal(validDelegates[2], proposer) - ra, err := rc.NewRound(51, time.Second, time.Unix(1562382592, 0), nil, hash.ZeroHash256[:]) + ra, err := rc.NewRound(51, time.Second, time.Unix(1562382592, 0), nil) require.NoError(err) require.Equal(uint32(170), ra.roundNum) require.Equal(uint64(51), ra.height) @@ -86,7 +85,7 @@ func TestNewRound(t *testing.T) { require.Equal(identityset.Address(7).String(), ra.proposer) rc.timeBasedRotation = true - ra, err = rc.NewRound(51, time.Second, time.Unix(1562382592, 0), nil, hash.ZeroHash256[:]) + ra, err = rc.NewRound(51, time.Second, time.Unix(1562382592, 0), nil) require.NoError(err) require.Equal(uint32(170), ra.roundNum) require.Equal(uint64(51), ra.height) @@ -97,12 +96,12 @@ func TestDelegates(t *testing.T) { require := require.New(t) rc := makeRoundCalculator(t) - dels, err := rc.Delegates(51, hash.ZeroHash256[:]) + dels, err := rc.Delegates(51) require.NoError(err) require.Equal(rc.rp.NumDelegates(), uint64(len(dels))) - require.False(rc.IsDelegate(identityset.Address(25).String(), 51, hash.ZeroHash256[:])) - require.True(rc.IsDelegate(identityset.Address(0).String(), 51, hash.ZeroHash256[:])) + require.False(rc.IsDelegate(identityset.Address(25).String(), 51)) + require.True(rc.IsDelegate(identityset.Address(0).String(), 51)) } func TestRoundInfo(t *testing.T) { From 09ba553e3f3d7d12d8202633328c5c1978fe1096 Mon Sep 17 00:00:00 2001 From: envestcc Date: Thu, 20 Mar 2025 15:55:57 +0800 Subject: [PATCH 09/10] move drafts from blockchain to chainmanager --- action/protocol/context.go | 22 ++ blockchain/blockchain.go | 164 ++-------- consensus/consensus.go | 21 +- consensus/scheme/rolldpos/chainmanager.go | 305 +++++++++++++++--- consensus/scheme/rolldpos/rolldpos.go | 2 +- consensus/scheme/rolldpos/rolldpos_test.go | 39 ++- consensus/scheme/rolldpos/rolldposctx.go | 24 +- consensus/scheme/rolldpos/rolldposctx_test.go | 16 +- consensus/scheme/rolldpos/roundcalculator.go | 8 +- .../scheme/rolldpos/roundcalculator_test.go | 2 +- 10 files changed, 390 insertions(+), 213 deletions(-) diff --git a/action/protocol/context.go b/action/protocol/context.go index f5030c3275..3849ccd3a0 100644 --- a/action/protocol/context.go +++ b/action/protocol/context.go @@ -35,6 +35,8 @@ type ( vmConfigContextKey struct{} + consensusRoundContextKey struct{} + // TipInfo contains the tip block information TipInfo struct { Height uint64 @@ -162,6 +164,14 @@ type ( LoadCandidatesLegacy CheckFunc CandCenterHasAlias CheckFunc } + + ConsensusRoundCtx struct { + Height uint64 + Round uint32 + StartTime time.Time + EncodedProposer string + PrevHash hash.Hash256 + } ) // WithRegistry adds registry to context @@ -395,3 +405,15 @@ func GetVMConfigCtx(ctx context.Context) (vm.Config, bool) { cfg, ok := ctx.Value(vmConfigContextKey{}).(vm.Config) return cfg, ok } + +func WithConsensusRoundCtx(ctx context.Context, round ConsensusRoundCtx) context.Context { + return context.WithValue(ctx, consensusRoundContextKey{}, round) +} + +func MustGetConsensusRoundCtx(ctx context.Context) ConsensusRoundCtx { + round, ok := ctx.Value(consensusRoundContextKey{}).(ConsensusRoundCtx) + if !ok { + log.S().Panic("Miss consensus round context") + } + return round +} diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index f288773c21..b249ee4c90 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -25,7 +25,6 @@ import ( "github.com/iotexproject/iotex-core/v2/blockchain/blockdao" "github.com/iotexproject/iotex-core/v2/blockchain/filedao" "github.com/iotexproject/iotex-core/v2/blockchain/genesis" - "github.com/iotexproject/iotex-core/v2/db" "github.com/iotexproject/iotex-core/v2/pkg/lifecycle" "github.com/iotexproject/iotex-core/v2/pkg/log" "github.com/iotexproject/iotex-core/v2/pkg/prometheustimer" @@ -98,8 +97,6 @@ type ( CommitBlock(blk *block.Block) error // ValidateBlock validates a new block before adding it to the blockchain ValidateBlock(*block.Block, ...BlockValidationOption) error - // Fork returns a new blockchain instance with the given tip hash - Fork(hash.Hash256) (Blockchain, error) // AddSubscriber make you listen to every single produced block AddSubscriber(BlockCreationSubscriber) error @@ -111,11 +108,6 @@ type ( // BlockBuilderFactory is the factory interface of block builder BlockBuilderFactory interface { Mint(ctx context.Context) (*block.Block, error) - ReceiveBlock(*block.Block) error - Init(hash.Hash256) - AddProposal(*block.Block) error - Block(hash.Hash256) *block.Block - BlockByHeight(uint64) *block.Block } // blockchain implements the Blockchain interface @@ -132,9 +124,6 @@ type ( // used by account-based model bbf BlockBuilderFactory - // head is the head block of the blockchain - head *block.Header - headLocker sync.RWMutex } ) @@ -217,7 +206,6 @@ func NewBlockchain(cfg Config, g genesis.Genesis, dao blockdao.BlockDAO, bbf Blo } chain.lifecycle.Add(chain.dao) chain.lifecycle.Add(chain.pubSubManager) - chain.pubSubManager.AddBlockListener(chain.bbf) return chain } @@ -247,25 +235,7 @@ func (bc *blockchain) Start(ctx context.Context) error { EvmNetworkID: bc.EvmNetworkID(), }, ), bc.genesis)) - if err := bc.lifecycle.OnStart(ctx); err != nil { - return err - } - tip, err := bc.dao.Height() - if err != nil { - return err - } - header, err := bc.dao.HeaderByHeight(tip) - if err != nil { - return err - } - bc.head = header - headHash := header.HashBlock() - if tip == 0 { - headHash = bc.genesis.Hash() - } - bc.bbf.Init(headHash) - log.L().Debug("blockchain head at", zap.Uint64("height", tip), log.Hex("hash", headHash[:])) - return nil + return bc.lifecycle.OnStart(ctx) } // Stop stops the blockchain. @@ -276,20 +246,7 @@ func (bc *blockchain) Stop(ctx context.Context) error { } func (bc *blockchain) BlockHeaderByHeight(height uint64) (*block.Header, error) { - header, err := bc.dao.HeaderByHeight(height) - switch errors.Cause(err) { - case nil: - return header, nil - case db.ErrNotExist: - bc.mu.RLock() - defer bc.mu.RUnlock() - if blk := bc.draftBlockByHeight(height); blk != nil { - return &blk.Header, nil - } - return nil, err - default: - return nil, err - } + return bc.dao.HeaderByHeight(height) } func (bc *blockchain) BlockFooterByHeight(height uint64) (*block.Footer, error) { @@ -298,12 +255,24 @@ func (bc *blockchain) BlockFooterByHeight(height uint64) (*block.Footer, error) // TipHash returns tip block's hash func (bc *blockchain) TipHash() hash.Hash256 { - return bc.tipHash() + tipHeight, err := bc.dao.Height() + if err != nil { + return hash.ZeroHash256 + } + tipHash, err := bc.dao.GetBlockHash(tipHeight) + if err != nil { + return hash.ZeroHash256 + } + return tipHash } // TipHeight returns tip block's height func (bc *blockchain) TipHeight() uint64 { - return bc.tipHeight() + tipHeight, err := bc.dao.Height() + if err != nil { + log.L().Panic("failed to get tip height", zap.Error(err)) + } + return tipHeight } // ValidateBlock validates a new block before adding it to the blockchain @@ -315,7 +284,10 @@ func (bc *blockchain) ValidateBlock(blk *block.Block, opts ...BlockValidationOpt if blk == nil { return ErrInvalidBlock } - tipHeight := bc.tipHeight() + tipHeight, err := bc.dao.Height() + if err != nil { + return err + } tip, err := bc.tipInfo(tipHeight) if err != nil { return err @@ -380,14 +352,7 @@ func (bc *blockchain) ValidateBlock(blk *block.Block, opts ...BlockValidationOpt if bc.blockValidator == nil { return nil } - err = bc.blockValidator.Validate(ctx, blk) - if err != nil { - return err - } - if err = bc.bbf.AddProposal(blk); err != nil { - log.L().Warn("failed to add block to proposal pool", zap.Error(err), zap.Uint64("height", blk.Height()), zap.Time("timestamp", blk.Timestamp())) - } - return nil + return bc.blockValidator.Validate(ctx, blk) } func (bc *blockchain) Context(ctx context.Context) (context.Context, error) { @@ -424,10 +389,6 @@ func (bc *blockchain) context(ctx context.Context, height uint64) (context.Conte if err != nil { return nil, err } - return bc.contextWithTipInfo(ctx, tip), nil -} - -func (bc *blockchain) contextWithTipInfo(ctx context.Context, tip *protocol.TipInfo) context.Context { ctx = genesis.WithGenesisContext( protocol.WithBlockchainCtx( ctx, @@ -439,7 +400,7 @@ func (bc *blockchain) contextWithTipInfo(ctx context.Context, tip *protocol.TipI ), bc.genesis, ) - return protocol.WithFeatureWithHeightCtx(ctx) + return protocol.WithFeatureWithHeightCtx(ctx), nil } func (bc *blockchain) MintNewBlock(timestamp time.Time) (*block.Block, error) { @@ -447,7 +408,10 @@ func (bc *blockchain) MintNewBlock(timestamp time.Time) (*block.Block, error) { defer bc.mu.RUnlock() mintNewBlockTimer := bc.timerFactory.NewTimer("MintNewBlock") defer mintNewBlockTimer.End() - tipHeight := bc.tipHeight() + tipHeight, err := bc.dao.Height() + if err != nil { + return nil, err + } newblockHeight := tipHeight + 1 ctx, err := bc.context(context.Background(), tipHeight) if err != nil { @@ -491,38 +455,6 @@ func (bc *blockchain) RemoveSubscriber(s BlockCreationSubscriber) error { return bc.pubSubManager.RemoveBlockListener(s) } -func (bc *blockchain) Fork(hash hash.Hash256) (Blockchain, error) { - if bc.tipHash() == hash { - return bc, nil - } - bc.mu.RLock() - defer bc.mu.RUnlock() - var header *block.Header - headBlk := bc.bbf.Block(hash) - if headBlk == nil { - daoHeader, err := bc.dao.Header(hash) - if err != nil { - return nil, errors.Wrapf(err, "failed to get block %x to fork", hash) - } - header = daoHeader - } else { - header = &headBlk.Header - } - fork := &blockchain{ - dao: bc.dao, - config: bc.config, - genesis: bc.genesis, - blockValidator: bc.blockValidator, - lifecycle: bc.lifecycle, - clk: bc.clk, - pubSubManager: bc.pubSubManager, - timerFactory: bc.timerFactory, - bbf: bc.bbf, - head: header, - } - return fork, nil -} - //====================================== // internal functions //===================================== @@ -543,10 +475,11 @@ func (bc *blockchain) tipInfo(tipHeight uint64) (*protocol.TipInfo, error) { Timestamp: time.Unix(bc.genesis.Timestamp, 0), }, nil } - header, err := bc.blockByHeight(tipHeight) + header, err := bc.dao.HeaderByHeight(tipHeight) if err != nil { return nil, err } + return &protocol.TipInfo{ Height: tipHeight, GasUsed: header.GasUsed(), @@ -582,9 +515,6 @@ func (bc *blockchain) commitBlock(blk *block.Block) error { default: return err } - bc.headLocker.Lock() - bc.head = &blk.Header - bc.headLocker.Unlock() blkHash := blk.HashBlock() if blk.Height()%100 == 0 { blk.HeaderLogger(log.L()).Info("Committed a block.", log.Hex("tipHash", blkHash[:])) @@ -607,41 +537,3 @@ func (bc *blockchain) emitToSubscribers(blk *block.Block) { } bc.pubSubManager.SendBlockToSubscribers(blk) } - -func (bc *blockchain) draftBlockByHeight(height uint64) *block.Block { - for blk := bc.bbf.Block(bc.tipHash()); blk != nil && blk.Height() >= height; blk = bc.bbf.Block(blk.PrevHash()) { - if blk.Height() == height { - return blk - } - } - return nil -} - -func (bc *blockchain) blockByHeight(height uint64) (*block.Block, error) { - daoHeight, err := bc.dao.Height() - if err != nil { - return nil, err - } - if height > daoHeight { - if blk := bc.draftBlockByHeight(height); blk != nil { - return blk, nil - } - return nil, errors.Wrapf(db.ErrNotExist, "block %d not found", height) - } - return bc.dao.GetBlockByHeight(height) -} - -func (bc *blockchain) tipHeight() uint64 { - bc.headLocker.RLock() - defer bc.headLocker.RUnlock() - return bc.head.Height() -} - -func (bc *blockchain) tipHash() hash.Hash256 { - bc.headLocker.Lock() - defer bc.headLocker.Unlock() - if bc.head.Height() == 0 { - return bc.genesis.Hash() - } - return bc.head.HashBlock() -} diff --git a/consensus/consensus.go b/consensus/consensus.go index bb67acc810..cc3b102907 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -25,7 +25,6 @@ import ( "github.com/iotexproject/iotex-core/v2/pkg/lifecycle" "github.com/iotexproject/iotex-core/v2/pkg/log" "github.com/iotexproject/iotex-core/v2/state" - "github.com/iotexproject/iotex-core/v2/state/factory" ) // Consensus is the interface for handling IotxConsensus view change. @@ -50,6 +49,7 @@ type optionParams struct { broadcastHandler scheme.Broadcast pp poll.Protocol rp *rp.Protocol + bbf rolldpos.BlockBuilderFactory } // Option sets Consensus construction parameter. @@ -79,11 +79,19 @@ func WithPollProtocol(pp poll.Protocol) Option { } } +// WithBlockBuilderFactory is an option to set block builder factory +func WithBlockBuilderFactory(bbf rolldpos.BlockBuilderFactory) Option { + return func(ops *optionParams) error { + ops.bbf = bbf + return nil + } +} + // NewConsensus creates a IotxConsensus struct. func NewConsensus( cfg rolldpos.BuilderConfig, bc blockchain.Blockchain, - sf factory.Factory, + sf rolldpos.StateReaderFactory, opts ...Option, ) (Consensus, error) { var ops optionParams @@ -101,16 +109,13 @@ func NewConsensus( var err error switch cfg.Scheme { case RollDPoSScheme: - chanMgr := rolldpos.NewChainManager(bc, sf) + chanMgr := rolldpos.NewChainManager(bc, sf, ops.bbf) delegatesByEpochFunc := func(epochNum uint64, prevHash []byte) ([]string, error) { fork, serr := chanMgr.Fork(hash.Hash256(prevHash)) if serr != nil { return nil, serr } - forkSF, serr := fork.StateReader() - if serr != nil { - return nil, serr - } + forkSF := fork.StateReader() re := protocol.NewRegistry() if err := ops.rp.Register(re); err != nil { return nil, err @@ -120,7 +125,7 @@ func NewConsensus( cfg.Genesis, ) ctx = protocol.WithFeatureWithHeightCtx(ctx) - tipHeight, _ := fork.Tip() + tipHeight := fork.TipHeight() tipEpochNum := ops.rp.GetEpochNum(tipHeight) var candidatesList state.CandidateList var err error diff --git a/consensus/scheme/rolldpos/chainmanager.go b/consensus/scheme/rolldpos/chainmanager.go index 97fafc98a5..7cf4480bec 100644 --- a/consensus/scheme/rolldpos/chainmanager.go +++ b/consensus/scheme/rolldpos/chainmanager.go @@ -1,75 +1,145 @@ package rolldpos import ( + "context" + "strconv" "time" "github.com/iotexproject/go-pkgs/hash" + "github.com/iotexproject/iotex-address/address" "github.com/pkg/errors" + "github.com/prometheus/client_golang/prometheus" + "go.uber.org/zap" "github.com/iotexproject/iotex-core/v2/action/protocol" "github.com/iotexproject/iotex-core/v2/blockchain" "github.com/iotexproject/iotex-core/v2/blockchain/block" + "github.com/iotexproject/iotex-core/v2/blockchain/genesis" + "github.com/iotexproject/iotex-core/v2/db" + "github.com/iotexproject/iotex-core/v2/pkg/log" + "github.com/iotexproject/iotex-core/v2/pkg/prometheustimer" +) + +var ( + blockMtc = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "iotex_blockmint_metrics", + Help: "Block mint metrics.", + }, + []string{"type"}, + ) ) type ( // ChainManager defines the blockchain interface ChainManager interface { + Start(ctx context.Context) error + ForkChain + // Fork creates a new chain manager with the given hash + Fork(hash hash.Hash256) (ChainManager, error) + } + // ForkChain defines the fork chain interface + ForkChain interface { // BlockProposeTime return propose time by height BlockProposeTime(uint64) (time.Time, error) // BlockCommitTime return commit time by height BlockCommitTime(uint64) (time.Time, error) - // MintNewBlock creates a new block with given actions - // Note: the coinbase transfer will be added to the given transfers when minting a new block - MintNewBlock(timestamp time.Time) (*block.Block, error) - // CommitBlock validates and appends a block to the chain - CommitBlock(blk *block.Block) error - // ValidateBlock validates a new block before adding it to the blockchain - ValidateBlock(blk *block.Block) error // TipHeight returns tip block's height - Tip() (uint64, hash.Hash256) + TipHeight() uint64 + // TipHash returns tip block's hash + TipHash() hash.Hash256 // ChainAddress returns chain address on parent chain, the root chain return empty. ChainAddress() string // StateReader returns the state reader - StateReader() (protocol.StateReader, error) - // Fork creates a new chain manager with the given hash - Fork(hash hash.Hash256) (ChainManager, error) + StateReader() protocol.StateReader + // MintNewBlock creates a new block with given actions + // Note: the coinbase transfer will be added to the given transfers when minting a new block + MintNewBlock(ctx context.Context) (*block.Block, error) + // ValidateBlock validates a new block before adding it to the blockchain + ValidateBlock(blk *block.Block) error + // CommitBlock validates and appends a block to the chain + CommitBlock(blk *block.Block) error } StateReaderFactory interface { StateReaderAt(*block.Header) (protocol.StateReader, error) } + // BlockBuilderFactory is the factory interface of block builder + BlockBuilderFactory interface { + Mint(ctx context.Context) (*block.Block, error) + ReceiveBlock(*block.Block) error + Init(hash.Hash256) + AddProposal(*block.Block) error + Block(hash.Hash256) *block.Block + } + chainManager struct { - bc blockchain.Blockchain + *forkChain srf StateReaderFactory } + + forkChain struct { + bc blockchain.Blockchain + head *block.Header + sr protocol.StateReader + timerFactory *prometheustimer.TimerFactory + bbf BlockBuilderFactory + } ) +func init() { + prometheus.MustRegister(blockMtc) +} + +func newForkChain(bc blockchain.Blockchain, head *block.Header, sr protocol.StateReader, bbf BlockBuilderFactory) *forkChain { + timerFactory, err := prometheustimer.New( + "iotex_blockchain_perf", + "Performance of blockchain module", + []string{"topic", "chainID"}, + []string{"default", strconv.FormatUint(uint64(bc.ChainID()), 10)}, + ) + if err != nil { + log.L().Panic("Failed to generate prometheus timer factory.", zap.Error(err)) + } + return &forkChain{ + bc: bc, + head: head, + sr: sr, + bbf: bbf, + timerFactory: timerFactory, + } +} + // NewChainManager creates a chain manager -func NewChainManager(bc blockchain.Blockchain, srf StateReaderFactory) ChainManager { +func NewChainManager(bc blockchain.Blockchain, srf StateReaderFactory, bbf BlockBuilderFactory) ChainManager { return &chainManager{ - bc: bc, - srf: srf, + forkChain: newForkChain(bc, nil, nil, bbf), + srf: srf, } } // BlockProposeTime return propose time by height -func (cm *chainManager) BlockProposeTime(height uint64) (time.Time, error) { +func (cm *forkChain) BlockProposeTime(height uint64) (time.Time, error) { if height == 0 { return time.Unix(cm.bc.Genesis().Timestamp, 0), nil } header, err := cm.bc.BlockHeaderByHeight(height) - if err != nil { - return time.Time{}, errors.Wrapf( - err, "error when getting the block at height: %d", - height, - ) + switch errors.Cause(err) { + case nil: + return header.Timestamp(), nil + case db.ErrNotExist: + if blk := cm.draftBlockByHeight(height); blk != nil { + return blk.Timestamp(), nil + } + return time.Time{}, err + default: + return time.Time{}, err } - return header.Timestamp(), nil } // BlockCommitTime return commit time by height -func (cm *chainManager) BlockCommitTime(height uint64) (time.Time, error) { +func (cm *forkChain) BlockCommitTime(height uint64) (time.Time, error) { footer, err := cm.bc.BlockFooterByHeight(height) if err != nil { return time.Time{}, errors.Wrapf( @@ -81,44 +151,191 @@ func (cm *chainManager) BlockCommitTime(height uint64) (time.Time, error) { } // MintNewBlock creates a new block with given actions -func (cm *chainManager) MintNewBlock(timestamp time.Time) (*block.Block, error) { - return cm.bc.MintNewBlock(timestamp) -} - -// CommitBlock validates and appends a block to the chain -func (cm *chainManager) CommitBlock(blk *block.Block) error { - return cm.bc.CommitBlock(blk) +func (fc *forkChain) MintNewBlock(ctx context.Context) (*block.Block, error) { + mintNewBlockTimer := fc.timerFactory.NewTimer("MintBlock") + defer mintNewBlockTimer.End() + var ( + roundCtx = protocol.MustGetConsensusRoundCtx(ctx) + tipHeight = fc.head.Height() + tipHash = fc.tipHash() + newblockHeight = tipHeight + 1 + timestamp = roundCtx.StartTime + producer address.Address + err error + ) + // safety check + if roundCtx.Height != tipHeight { + return nil, errors.Errorf("invalid height, expecting %d, got %d", tipHeight, roundCtx.Height) + } + if roundCtx.PrevHash != tipHash { + return nil, errors.Errorf("invalid prev hash, expecting %x, got %x", tipHash, roundCtx.PrevHash) + } + producer, err = address.FromString(roundCtx.EncodedProposer) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse producer address %s", roundCtx.EncodedProposer) + } + ctx = fc.mintContext(context.Background(), roundCtx.StartTime, producer) + // create a new block + log.L().Debug("Produce a new block.", zap.Uint64("height", newblockHeight), zap.Time("timestamp", timestamp), log.Hex("prevHash", tipHash[:])) + // run execution and update state trie root hash + blk, err := fc.bbf.Mint(ctx) + if err != nil { + return nil, errors.Wrapf(err, "failed to create block") + } + if err = fc.bbf.AddProposal(blk); err != nil { + blkHash := blk.HashBlock() + log.L().Error("failed to add proposal", zap.Error(err), zap.Uint64("height", blk.Height()), log.Hex("hash", blkHash[:])) + } + blockMtc.WithLabelValues("MintGas").Set(float64(blk.GasUsed())) + blockMtc.WithLabelValues("MintActions").Set(float64(len(blk.Actions))) + return blk, nil } -// ValidateBlock validates a new block before adding it to the blockchain -func (cm *chainManager) ValidateBlock(blk *block.Block) error { - return cm.bc.ValidateBlock(blk) +// TipHeight returns tip block's height +func (cm *forkChain) TipHeight() uint64 { + return cm.head.Height() } -// TipHeight returns tip block's height -func (cm *chainManager) Tip() (uint64, hash.Hash256) { - return cm.bc.TipHeight(), cm.bc.TipHash() +// TipHash returns tip block's hash +func (cm *forkChain) TipHash() hash.Hash256 { + return cm.tipHash() } // ChainAddress returns chain address on parent chain, the root chain return empty. -func (cm *chainManager) ChainAddress() string { +func (cm *forkChain) ChainAddress() string { return cm.bc.ChainAddress() } // StateReader returns the state reader -func (cm *chainManager) StateReader() (protocol.StateReader, error) { - header, err := cm.bc.BlockHeaderByHeight(cm.bc.TipHeight()) +func (cm *forkChain) StateReader() protocol.StateReader { + return cm.sr +} + +// ValidateBlock validates a new block before adding it to the blockchain +func (cm *forkChain) ValidateBlock(blk *block.Block) error { + err := cm.bc.ValidateBlock(blk) + if err != nil { + return err + } + if err = cm.bbf.AddProposal(blk); err != nil { + blkHash := blk.HashBlock() + log.L().Error("failed to add proposal", zap.Error(err), zap.Uint64("height", blk.Height()), log.Hex("hash", blkHash[:])) + } + return nil +} + +// CommitBlock validates and appends a block to the chain +func (cm *forkChain) CommitBlock(blk *block.Block) error { + if err := cm.bc.CommitBlock(blk); err != nil { + return err + } + if err := cm.bbf.ReceiveBlock(blk); err != nil { + blkHash := blk.HashBlock() + log.L().Error("failed to receive block", zap.Error(err), zap.Uint64("height", blk.Height()), log.Hex("hash", blkHash[:])) + } + return nil +} + +func (cm *chainManager) Start(ctx context.Context) error { + head, err := cm.bc.BlockHeaderByHeight(cm.bc.TipHeight()) + if err != nil { + return errors.Wrap(err, "failed to get the head block") + } + sr, err := cm.srf.StateReaderAt(head) if err != nil { - return nil, err + return errors.Wrapf(err, "failed to create state reader at %d, hash %x", head.Height(), head.HashBlock()) } - return cm.srf.StateReaderAt(header) + cm.forkChain = newForkChain(cm.bc, head, sr, cm.bbf) + cm.bbf.Init(cm.forkChain.TipHash()) + return nil } // Fork creates a new chain manager with the given hash func (cm *chainManager) Fork(hash hash.Hash256) (ChainManager, error) { - fork, err := cm.bc.Fork(hash) + head := cm.head + if hash != cm.tipHash() { + blk := cm.bbf.Block(hash) + if blk == nil { + return nil, errors.Errorf("block %x not found when fork", hash) + } + head = &blk.Header + } + sr, err := cm.srf.StateReaderAt(head) if err != nil { - return nil, err + return nil, errors.Wrapf(err, "failed to create state reader at %d, hash %x", head.Height(), head.HashBlock()) + } + return &chainManager{ + srf: cm.srf, + forkChain: newForkChain(cm.bc, head, sr, cm.bbf), + }, nil +} + +func (fc *forkChain) draftBlockByHeight(height uint64) *block.Block { + for blk := fc.bbf.Block(fc.tipHash()); blk != nil && blk.Height() >= height; blk = fc.bbf.Block(blk.PrevHash()) { + if blk.Height() == height { + return blk + } } - return NewChainManager(fork, cm.srf), nil + return nil +} + +func (fc *forkChain) tipHash() hash.Hash256 { + if fc.head.Height() == 0 { + g := fc.bc.Genesis() + return g.Hash() + } + return fc.head.HashBlock() +} + +func (fc *forkChain) tipInfo() *protocol.TipInfo { + if fc.head.Height() == 0 { + g := fc.bc.Genesis() + return &protocol.TipInfo{ + Height: 0, + Hash: g.Hash(), + Timestamp: time.Unix(g.Timestamp, 0), + } + } + return &protocol.TipInfo{ + Height: fc.head.Height(), + GasUsed: fc.head.GasUsed(), + Hash: fc.head.HashBlock(), + Timestamp: fc.head.Timestamp(), + BaseFee: fc.head.BaseFee(), + BlobGasUsed: fc.head.BlobGasUsed(), + ExcessBlobGas: fc.head.ExcessBlobGas(), + } +} + +func (fc *forkChain) mintContext(ctx context.Context, + timestamp time.Time, producer address.Address) context.Context { + // blockchain context + tip := fc.tipInfo() + ctx = genesis.WithGenesisContext( + protocol.WithBlockchainCtx( + ctx, + protocol.BlockchainCtx{ + Tip: *tip, + ChainID: fc.bc.ChainID(), + EvmNetworkID: fc.bc.EvmNetworkID(), + }, + ), + fc.bc.Genesis(), + ) + ctx = protocol.WithFeatureWithHeightCtx(ctx) + // block context + g := fc.bc.Genesis() + height := tip.Height + 1 + ctx = protocol.WithBlockCtx( + ctx, + protocol.BlockCtx{ + BlockHeight: height, + BlockTimeStamp: timestamp, + Producer: producer, + GasLimit: g.BlockGasLimitByHeight(height), + BaseFee: protocol.CalcBaseFee(g.Blockchain, tip), + ExcessBlobGas: protocol.CalcExcessBlobGas(tip.ExcessBlobGas, tip.BlobGasUsed), + }) + ctx = protocol.WithFeatureCtx(ctx) + return ctx } diff --git a/consensus/scheme/rolldpos/rolldpos.go b/consensus/scheme/rolldpos/rolldpos.go index ed301a07cc..7e46333df0 100644 --- a/consensus/scheme/rolldpos/rolldpos.go +++ b/consensus/scheme/rolldpos/rolldpos.go @@ -199,7 +199,7 @@ func (r *RollDPoS) ValidateBlockFooter(blk *block.Block) error { // Metrics returns RollDPoS consensus metrics func (r *RollDPoS) Metrics() (scheme.ConsensusMetrics, error) { var metrics scheme.ConsensusMetrics - height, _ := r.ctx.Chain().Tip() + height := r.ctx.Chain().TipHeight() round, err := r.ctx.RoundCalculator().NewRound(height+1, r.ctx.BlockInterval(height), r.ctx.Clock().Now(), nil) if err != nil { return metrics, errors.Wrap(err, "error when calculating round") diff --git a/consensus/scheme/rolldpos/rolldpos_test.go b/consensus/scheme/rolldpos/rolldpos_test.go index cf53f63ca6..4650f8eee9 100644 --- a/consensus/scheme/rolldpos/rolldpos_test.go +++ b/consensus/scheme/rolldpos/rolldpos_test.go @@ -79,7 +79,7 @@ func TestNewRollDPoS(t *testing.T) { SetConfig(builderCfg). SetAddr(identityset.Address(0).String()). SetPriKey(sk). - SetChainManager(NewChainManager(mock_blockchain.NewMockBlockchain(ctrl), mock_factory.NewMockFactory(ctrl))). + SetChainManager(NewChainManager(mock_blockchain.NewMockBlockchain(ctrl), mock_factory.NewMockFactory(ctrl), &dummyBlockBuildFactory{})). SetBroadcast(func(_ proto.Message) error { return nil }). @@ -96,7 +96,7 @@ func TestNewRollDPoS(t *testing.T) { SetConfig(builderCfg). SetAddr(identityset.Address(0).String()). SetPriKey(sk). - SetChainManager(NewChainManager(mock_blockchain.NewMockBlockchain(ctrl), mock_factory.NewMockFactory(ctrl))). + SetChainManager(NewChainManager(mock_blockchain.NewMockBlockchain(ctrl), mock_factory.NewMockFactory(ctrl), &dummyBlockBuildFactory{})). SetBroadcast(func(_ proto.Message) error { return nil }). @@ -117,7 +117,7 @@ func TestNewRollDPoS(t *testing.T) { SetConfig(builderCfg). SetAddr(identityset.Address(0).String()). SetPriKey(sk). - SetChainManager(NewChainManager(mock_blockchain.NewMockBlockchain(ctrl), mock_factory.NewMockFactory(ctrl))). + SetChainManager(NewChainManager(mock_blockchain.NewMockBlockchain(ctrl), mock_factory.NewMockFactory(ctrl), &dummyBlockBuildFactory{})). SetBroadcast(func(_ proto.Message) error { return nil }). @@ -201,7 +201,10 @@ func TestValidateBlockFooter(t *testing.T) { blockHeight := uint64(8) footer := &block.Footer{} bc := mock_blockchain.NewMockBlockchain(ctrl) - bc.EXPECT().BlockFooterByHeight(blockHeight).Return(footer, nil).Times(5) + bc.EXPECT().BlockFooterByHeight(blockHeight).Return(footer, nil).AnyTimes() + bc.EXPECT().ChainID().Return(uint32(1)).AnyTimes() + bc.EXPECT().TipHeight().Return(blockHeight).AnyTimes() + bc.EXPECT().BlockHeaderByHeight(blockHeight).Return(&block.Header{}, nil).Times(1) sk1 := identityset.PrivateKey(1) g := genesis.TestDefault() @@ -217,8 +220,10 @@ func TestValidateBlockFooter(t *testing.T) { Genesis: g, SystemActive: true, } - bc.EXPECT().Genesis().Return(g).Times(5) + builderCfg.Consensus.ConsensusDBPath = "" + bc.EXPECT().Genesis().Return(g).AnyTimes() sf := mock_factory.NewMockFactory(ctrl) + sf.EXPECT().StateReaderAt(gomock.Any()).Return(nil, nil).AnyTimes() rp := rolldpos.NewProtocol( g.NumCandidateDelegates, g.NumDelegates, @@ -236,7 +241,7 @@ func TestValidateBlockFooter(t *testing.T) { SetConfig(builderCfg). SetAddr(identityset.Address(1).String()). SetPriKey(sk1). - SetChainManager(NewChainManager(bc, sf)). + SetChainManager(NewChainManager(bc, sf, &dummyBlockBuildFactory{})). SetBroadcast(func(_ proto.Message) error { return nil }). @@ -247,6 +252,7 @@ func TestValidateBlockFooter(t *testing.T) { Build() require.NoError(t, err) require.NotNil(t, r) + require.NoError(t, r.Start(context.Background())) // all right blk := makeBlock(t, 1, 4, false, 9) @@ -328,7 +334,7 @@ func TestRollDPoS_Metrics(t *testing.T) { SetConfig(builderCfg). SetAddr(identityset.Address(1).String()). SetPriKey(sk1). - SetChainManager(NewChainManager(bc, sf)). + SetChainManager(NewChainManager(bc, sf, &dummyBlockBuildFactory{})). SetBroadcast(func(_ proto.Message) error { return nil }). @@ -490,7 +496,7 @@ func TestRollDPoSConsensus(t *testing.T) { SetAddr(chainAddrs[i].encodedAddr). SetPriKey(chainAddrs[i].priKey). SetConfig(builderCfg). - SetChainManager(NewChainManager(chain, sf)). + SetChainManager(NewChainManager(chain, sf, &dummyBlockBuildFactory{})). SetBroadcast(p2p.Broadcast). SetDelegatesByEpochFunc(delegatesByEpochFunc). SetProposersByEpochFunc(delegatesByEpochFunc). @@ -739,3 +745,20 @@ func TestRollDPoSConsensus(t *testing.T) { } }) } + +type dummyBlockBuildFactory struct{} + +func (d *dummyBlockBuildFactory) Mint(ctx context.Context) (*block.Block, error) { + return &block.Block{}, nil +} + +func (d *dummyBlockBuildFactory) ReceiveBlock(*block.Block) error { + return nil +} +func (d *dummyBlockBuildFactory) Init(hash.Hash256) {} +func (d *dummyBlockBuildFactory) AddProposal(*block.Block) error { + return nil +} +func (d *dummyBlockBuildFactory) Block(hash.Hash256) *block.Block { + return &block.Block{} +} diff --git a/consensus/scheme/rolldpos/rolldposctx.go b/consensus/scheme/rolldpos/rolldposctx.go index e37f2b73cc..4882d3cade 100644 --- a/consensus/scheme/rolldpos/rolldposctx.go +++ b/consensus/scheme/rolldpos/rolldposctx.go @@ -17,6 +17,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "go.uber.org/zap" + "github.com/iotexproject/iotex-core/v2/action/protocol" "github.com/iotexproject/iotex-core/v2/action/protocol/rolldpos" "github.com/iotexproject/iotex-core/v2/blockchain" "github.com/iotexproject/iotex-core/v2/blockchain/block" @@ -174,6 +175,9 @@ func NewRollDPoSCtx( } func (ctx *rollDPoSCtx) Start(c context.Context) (err error) { + if err := ctx.chain.Start(c); err != nil { + return errors.Wrap(err, "Error when starting the chain") + } var eManager *endorsementManager if ctx.eManagerDB != nil { if err := ctx.eManagerDB.Start(c); err != nil { @@ -337,7 +341,7 @@ func (ctx *rollDPoSCtx) Logger() *zap.Logger { func (ctx *rollDPoSCtx) Prepare() error { ctx.mutex.Lock() defer ctx.mutex.Unlock() - tipHeight, _ := ctx.chain.Tip() + tipHeight := ctx.chain.TipHeight() height := tipHeight + 1 newRound, err := ctx.roundCalc.UpdateRound(ctx.round, height, ctx.BlockInterval(height), ctx.clock.Now(), ctx.toleratedOvertime) if err != nil { @@ -413,8 +417,15 @@ func (ctx *rollDPoSCtx) PrepareNextProposal(msg any) error { return nil } ctx.logger().Debug("prepare next proposal", log.Hex("prevHash", prevHash[:]), zap.Uint64("height", ctx.round.height+1), zap.Time("timestamp", startTime), zap.String("ioAddr", ctx.encodedAddr), zap.String("nextproposer", nextProposer)) + mintCtx := protocol.WithConsensusRoundCtx(context.Background(), protocol.ConsensusRoundCtx{ + Height: height, + Round: 0, + StartTime: startTime, + EncodedProposer: nextProposer, + PrevHash: prevHash, + }) go func() { - blk, err := fork.MintNewBlock(startTime) + blk, err := fork.MintNewBlock(mintCtx) if err != nil { ctx.logger().Error("failed to mint new block", zap.Error(err)) return @@ -681,7 +692,14 @@ func (ctx *rollDPoSCtx) mintNewBlock() (*EndorsedConsensusMessage, error) { blk := ctx.round.CachedMintedBlock() if blk == nil { // in case that there is no cached block in eManagerDB, it mints a new block. - blk, err = ctx.chain.MintNewBlock(ctx.round.StartTime()) + mintCtx := protocol.WithConsensusRoundCtx(context.Background(), protocol.ConsensusRoundCtx{ + Height: ctx.round.Height(), + Round: ctx.round.Number(), + StartTime: ctx.round.StartTime(), + EncodedProposer: ctx.encodedAddr, + PrevHash: ctx.round.prevHash, + }) + blk, err = ctx.chain.MintNewBlock(mintCtx) if err != nil { return nil, err } diff --git a/consensus/scheme/rolldpos/rolldposctx_test.go b/consensus/scheme/rolldpos/rolldposctx_test.go index e036f1441e..f6e2efa7ae 100644 --- a/consensus/scheme/rolldpos/rolldposctx_test.go +++ b/consensus/scheme/rolldpos/rolldposctx_test.go @@ -43,7 +43,7 @@ func TestRollDPoSCtx(t *testing.T) { }) t.Run("case 2:panic because of rp is nil", func(t *testing.T) { - _, err := NewRollDPoSCtx(consensusfsm.NewConsensusConfig(cfg.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, cfg.Delay), dbConfig, true, time.Second, true, NewChainManager(b, sf), block.NewDeserializer(0), nil, nil, dummyCandidatesByHeightFunc, dummyCandidatesByHeightFunc, "", nil, nil, 0) + _, err := NewRollDPoSCtx(consensusfsm.NewConsensusConfig(cfg.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, cfg.Delay), dbConfig, true, time.Second, true, NewChainManager(b, sf, &dummyBlockBuildFactory{}), block.NewDeserializer(0), nil, nil, dummyCandidatesByHeightFunc, dummyCandidatesByHeightFunc, "", nil, nil, 0) require.Error(err) }) @@ -53,7 +53,7 @@ func TestRollDPoSCtx(t *testing.T) { g.NumSubEpochs, ) t.Run("case 3:panic because of clock is nil", func(t *testing.T) { - _, err := NewRollDPoSCtx(consensusfsm.NewConsensusConfig(cfg.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, cfg.Delay), dbConfig, true, time.Second, true, NewChainManager(b, sf), block.NewDeserializer(0), rp, nil, dummyCandidatesByHeightFunc, dummyCandidatesByHeightFunc, "", nil, nil, 0) + _, err := NewRollDPoSCtx(consensusfsm.NewConsensusConfig(cfg.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, cfg.Delay), dbConfig, true, time.Second, true, NewChainManager(b, sf, &dummyBlockBuildFactory{}), block.NewDeserializer(0), rp, nil, dummyCandidatesByHeightFunc, dummyCandidatesByHeightFunc, "", nil, nil, 0) require.Error(err) }) @@ -63,19 +63,19 @@ func TestRollDPoSCtx(t *testing.T) { cfg.FSM.AcceptLockEndorsementTTL = time.Second cfg.FSM.CommitTTL = time.Second t.Run("case 4:panic because of fsm time bigger than block interval", func(t *testing.T) { - _, err := NewRollDPoSCtx(consensusfsm.NewConsensusConfig(cfg.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, cfg.Delay), dbConfig, true, time.Second, true, NewChainManager(b, sf), block.NewDeserializer(0), rp, nil, dummyCandidatesByHeightFunc, dummyCandidatesByHeightFunc, "", nil, c, 0) + _, err := NewRollDPoSCtx(consensusfsm.NewConsensusConfig(cfg.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, cfg.Delay), dbConfig, true, time.Second, true, NewChainManager(b, sf, &dummyBlockBuildFactory{}), block.NewDeserializer(0), rp, nil, dummyCandidatesByHeightFunc, dummyCandidatesByHeightFunc, "", nil, c, 0) require.Error(err) }) g.Blockchain.BlockInterval = time.Second * 20 t.Run("case 5:panic because of nil CandidatesByHeight function", func(t *testing.T) { - _, err := NewRollDPoSCtx(consensusfsm.NewConsensusConfig(cfg.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, cfg.Delay), dbConfig, true, time.Second, true, NewChainManager(b, sf), block.NewDeserializer(0), rp, nil, nil, nil, "", nil, c, 0) + _, err := NewRollDPoSCtx(consensusfsm.NewConsensusConfig(cfg.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, cfg.Delay), dbConfig, true, time.Second, true, NewChainManager(b, sf, &dummyBlockBuildFactory{}), block.NewDeserializer(0), rp, nil, nil, nil, "", nil, c, 0) require.Error(err) }) t.Run("case 6:normal", func(t *testing.T) { bh := g.BeringBlockHeight - rctx, err := NewRollDPoSCtx(consensusfsm.NewConsensusConfig(cfg.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, cfg.Delay), dbConfig, true, time.Second, true, NewChainManager(b, sf), block.NewDeserializer(0), rp, nil, dummyCandidatesByHeightFunc, dummyCandidatesByHeightFunc, "", nil, c, bh) + rctx, err := NewRollDPoSCtx(consensusfsm.NewConsensusConfig(cfg.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, cfg.Delay), dbConfig, true, time.Second, true, NewChainManager(b, sf, &dummyBlockBuildFactory{}), block.NewDeserializer(0), rp, nil, dummyCandidatesByHeightFunc, dummyCandidatesByHeightFunc, "", nil, c, bh) require.NoError(err) require.Equal(bh, rctx.RoundCalculator().beringHeight) require.NotNil(rctx) @@ -129,7 +129,7 @@ func TestCheckVoteEndorser(t *testing.T) { true, time.Second, true, - NewChainManager(b, sf), + NewChainManager(b, sf, &dummyBlockBuildFactory{}), block.NewDeserializer(0), rp, nil, @@ -203,7 +203,7 @@ func TestCheckBlockProposer(t *testing.T) { true, time.Second, true, - NewChainManager(b, sf), + NewChainManager(b, sf, &dummyBlockBuildFactory{}), block.NewDeserializer(0), rp, nil, @@ -315,7 +315,7 @@ func TestNotProducingMultipleBlocks(t *testing.T) { true, time.Second, true, - NewChainManager(b, sf), + NewChainManager(b, sf, &dummyBlockBuildFactory{}), block.NewDeserializer(0), rp, nil, diff --git a/consensus/scheme/rolldpos/roundcalculator.go b/consensus/scheme/rolldpos/roundcalculator.go index e4a43b5e7b..437a49af85 100644 --- a/consensus/scheme/rolldpos/roundcalculator.go +++ b/consensus/scheme/rolldpos/roundcalculator.go @@ -81,7 +81,7 @@ func (c *roundCalculator) UpdateRound(round *roundCtx, height uint64, blockInter return nil, err } } - _, prevHash := c.chain.Tip() + prevHash := c.chain.TipHash() return &roundCtx{ epochNum: epochNum, epochStartHeight: epochStartHeight, @@ -188,14 +188,14 @@ func (c *roundCalculator) roundInfo( // Delegates returns list of delegates at given height func (c *roundCalculator) Delegates(height uint64) ([]string, error) { epochNum := c.rp.GetEpochNum(height) - _, prevHash := c.chain.Tip() + prevHash := c.chain.TipHash() return c.delegatesByEpochFunc(epochNum, prevHash[:]) } // Proposers returns list of candidate proposers at given height func (c *roundCalculator) Proposers(height uint64) ([]string, error) { epochNum := c.rp.GetEpochNum(height) - _, prevHash := c.chain.Tip() + prevHash := c.chain.TipHash() return c.proposersByEpochFunc(epochNum, prevHash[:]) } @@ -254,7 +254,7 @@ func (c *roundCalculator) newRound( return nil, err } } - _, prevHash := c.chain.Tip() + prevHash := c.chain.TipHash() round = &roundCtx{ epochNum: epochNum, epochStartHeight: epochStartHeight, diff --git a/consensus/scheme/rolldpos/roundcalculator_test.go b/consensus/scheme/rolldpos/roundcalculator_test.go index 75d329edd6..bfeb150766 100644 --- a/consensus/scheme/rolldpos/roundcalculator_test.go +++ b/consensus/scheme/rolldpos/roundcalculator_test.go @@ -265,7 +265,7 @@ func makeRoundCalculator(t *testing.T) *roundCalculator { return addrs, nil } return &roundCalculator{ - NewChainManager(bc, sf), + NewChainManager(bc, sf, &dummyBlockBuildFactory{}), true, rp, delegatesByEpoch, From 5818141e1d8d2ea54ef0285ea25454de3fb298eb Mon Sep 17 00:00:00 2001 From: envestcc Date: Thu, 20 Mar 2025 21:58:32 +0800 Subject: [PATCH 10/10] blockchain ctx util func --- action/protocol/context.go | 4 ++ action/protocol/execution/evm/evm.go | 2 + action/protocol/execution/protocol.go | 14 ++-- action/protocol/execution/protocol_test.go | 3 + action/protocol/poll/consortium.go | 16 ++--- action/protocol/poll/governance_protocol.go | 9 +-- .../protocol/poll/governance_protocol_test.go | 6 ++ action/protocol/poll/protocol.go | 10 +-- .../protocol/poll/staking_committee_test.go | 24 +++++++ api/coreservice.go | 16 +++-- api/coreservice_test.go | 2 +- api/grpcserver_integrity_test.go | 10 ++- api/serverV2.go | 4 +- api/serverV2_integrity_test.go | 3 +- blockchain/blockchain.go | 60 ++++++++++++++++- blockchain/integrity/benchmark_test.go | 1 + blockchain/integrity/integrity_test.go | 21 ++++-- blocksync/blocksync_test.go | 6 ++ blocksync/buffer_test.go | 2 + chainservice/builder.go | 58 +++++------------ chainservice/chainservice.go | 28 ++++---- consensus/consensus.go | 18 ++++- consensus/scheme/rolldpos/chainmanager.go | 65 ++++++++++++++----- consensus/scheme/rolldpos/rolldpos_test.go | 13 ++-- consensus/scheme/rolldpos/rolldposctx_test.go | 17 ++--- .../scheme/rolldpos/roundcalculator_test.go | 2 +- e2etest/bigint_test.go | 1 + e2etest/contract_staking_test.go | 1 + e2etest/local_test.go | 3 + e2etest/local_transfer_test.go | 1 + gasstation/gasstattion_test.go | 2 + pkg/util/blockutil/block_time_calculator.go | 37 +++++++++++ state/factory/factory_test.go | 4 +- testutil/blockutil.go | 14 ++++ .../internal/client/client_test.go | 3 +- 35 files changed, 340 insertions(+), 140 deletions(-) create mode 100644 testutil/blockutil.go diff --git a/action/protocol/context.go b/action/protocol/context.go index 3849ccd3a0..ff9ddc0117 100644 --- a/action/protocol/context.go +++ b/action/protocol/context.go @@ -56,6 +56,10 @@ type ( ChainID uint32 // EvmNetworkID is the EVM network ID EvmNetworkID uint32 + // GetBlockHash is the function to get block hash by height + GetBlockHash func(uint64) (hash.Hash256, error) + // GetBlockTime is the function to get block time by height + GetBlockTime func(uint64) (time.Time, error) } // BlockCtx provides block auxiliary information. diff --git a/action/protocol/execution/evm/evm.go b/action/protocol/execution/evm/evm.go index 7b050fbe34..f7d25602f4 100644 --- a/action/protocol/execution/evm/evm.go +++ b/action/protocol/execution/evm/evm.go @@ -191,6 +191,8 @@ func newParams( if err != nil { return nil, err } + isCancun := chainConfig.IsCancun(big.NewInt(int64(blkCtx.BlockHeight)), uint64(blkCtx.BlockTimeStamp.Unix())) + log.L().Info("check cancun", zap.Bool("isCancun", isCancun), zap.Uint64("height", blkCtx.BlockHeight), zap.Uint64("timestamp", uint64(blkCtx.BlockTimeStamp.Unix())), zap.Uint64("cancuntime", *chainConfig.CancunTime)) vmTxCtx := vm.TxContext{ Origin: executorAddr, GasPrice: execution.GasPrice(), diff --git a/action/protocol/execution/protocol.go b/action/protocol/execution/protocol.go index ccd871c1f2..6d8f5bf825 100644 --- a/action/protocol/execution/protocol.go +++ b/action/protocol/execution/protocol.go @@ -29,20 +29,19 @@ const ( // Protocol defines the protocol of handling executions type Protocol struct { - getBlockHash evm.GetBlockHash - getBlockTime evm.GetBlockTime - depositGas protocol.DepositGas - addr address.Address + depositGas protocol.DepositGas + addr address.Address } // NewProtocol instantiates the protocol of exeuction +// TODO: remove unused getBlockHash and getBlockTime func NewProtocol(getBlockHash evm.GetBlockHash, depositGas protocol.DepositGas, getBlockTime evm.GetBlockTime) *Protocol { h := hash.Hash160b([]byte(_protocolID)) addr, err := address.FromBytes(h[:]) if err != nil { log.L().Panic("Error when constructing the address of vote protocol", zap.Error(err)) } - return &Protocol{getBlockHash: getBlockHash, depositGas: depositGas, addr: addr, getBlockTime: getBlockTime} + return &Protocol{depositGas: depositGas, addr: addr} } // FindProtocol finds the registered protocol from registry @@ -66,9 +65,10 @@ func (p *Protocol) Handle(ctx context.Context, elp action.Envelope, sm protocol. if _, ok := elp.Action().(*action.Execution); !ok { return nil, nil } + bcCtx := protocol.MustGetBlockchainCtx(ctx) ctx = evm.WithHelperCtx(ctx, evm.HelperContext{ - GetBlockHash: p.getBlockHash, - GetBlockTime: p.getBlockTime, + GetBlockHash: bcCtx.GetBlockHash, + GetBlockTime: bcCtx.GetBlockTime, DepositGasFunc: p.depositGas, }) _, receipt, err := evm.ExecuteContract(ctx, sm, elp) diff --git a/action/protocol/execution/protocol_test.go b/action/protocol/execution/protocol_test.go index 24478437a3..5bd51ba2f4 100644 --- a/action/protocol/execution/protocol_test.go +++ b/action/protocol/execution/protocol_test.go @@ -474,6 +474,7 @@ func (sct *SmartContractTest) prepareBlockchain( r.NoError(err) dao := blockdao.NewBlockDAOWithIndexersAndCache(store, []blockdao.BlockIndexer{sf, indexer}, cfg.DB.MaxCacheSize) r.NotNil(dao) + btcd := testutil.DummyBlockTimeBuilder() bc := blockchain.NewBlockchain( cfg.Chain, cfg.Genesis, @@ -483,6 +484,7 @@ func (sct *SmartContractTest) prepareBlockchain( sf, protocol.NewGenericValidator(sf, accountutil.AccountState), )), + blockchain.BlockTimeCalculatorBuilderOption(btcd), ) reward := rewarding.NewProtocol(cfg.Genesis.Rewarding) r.NoError(reward.Register(registry)) @@ -735,6 +737,7 @@ func TestProtocol_Handle(t *testing.T) { sf, protocol.NewGenericValidator(sf, accountutil.AccountState), )), + blockchain.BlockTimeCalculatorBuilderOption(testutil.DummyBlockTimeBuilder()), ) exeProtocol := execution.NewProtocol(dao.GetBlockHash, rewarding.DepositGas, getBlockTimeForTest) require.NoError(exeProtocol.Register(registry)) diff --git a/action/protocol/poll/consortium.go b/action/protocol/poll/consortium.go index 6596f6665a..95de763403 100644 --- a/action/protocol/poll/consortium.go +++ b/action/protocol/poll/consortium.go @@ -4,7 +4,6 @@ import ( "context" "math/big" "strings" - "time" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" @@ -50,11 +49,10 @@ type consortiumCommittee struct { bufferResult state.CandidateList indexer *CandidateIndexer addr address.Address - getBlockHash evm.GetBlockHash } // NewConsortiumCommittee creates a committee for consorium chain -func NewConsortiumCommittee(indexer *CandidateIndexer, readContract ReadContract, getBlockHash evm.GetBlockHash) (Protocol, error) { +func NewConsortiumCommittee(indexer *CandidateIndexer, readContract ReadContract) (Protocol, error) { abi, err := abi.JSON(strings.NewReader(ConsortiumManagementABI)) if err != nil { return nil, err @@ -73,7 +71,6 @@ func NewConsortiumCommittee(indexer *CandidateIndexer, readContract ReadContract abi: abi, addr: addr, indexer: indexer, - getBlockHash: getBlockHash, }, nil } @@ -116,17 +113,14 @@ func (cc *consortiumCommittee) CreateGenesisStates(ctx context.Context, sm proto if err != nil { return err } + bcCtx := protocol.MustGetBlockchainCtx(ctx) ctx = protocol.WithActionCtx(ctx, actionCtx) ctx = protocol.WithBlockCtx(ctx, blkCtx) - getBlockTime := func(u uint64) (time.Time, error) { - // make sure the returned timestamp is after the current block time so that evm upgrades based on timestamp (Shanghai and onwards) are disabled - return blkCtx.BlockTimeStamp.Add(5 * time.Second), nil - } ctx = evm.WithHelperCtx(ctx, evm.HelperContext{ GetBlockHash: func(height uint64) (hash.Hash256, error) { return hash.ZeroHash256, nil }, - GetBlockTime: getBlockTime, + GetBlockTime: bcCtx.GetBlockTime, DepositGasFunc: func(context.Context, protocol.StateManager, *big.Int, ...protocol.DepositOption) ([]*action.TransactionLog, error) { return nil, nil }, @@ -143,8 +137,8 @@ func (cc *consortiumCommittee) CreateGenesisStates(ctx context.Context, sm proto cc.contract = receipt.ContractAddress ctx = evm.WithHelperCtx(ctx, evm.HelperContext{ - GetBlockHash: cc.getBlockHash, - GetBlockTime: getBlockTime, + GetBlockHash: bcCtx.GetBlockHash, + GetBlockTime: bcCtx.GetBlockTime, }) r := getContractReaderForGenesisStates(ctx, sm) cands, err := cc.readDelegatesWithContractReader(ctx, r) diff --git a/action/protocol/poll/governance_protocol.go b/action/protocol/poll/governance_protocol.go index d627432149..a065991f0b 100644 --- a/action/protocol/poll/governance_protocol.go +++ b/action/protocol/poll/governance_protocol.go @@ -25,7 +25,6 @@ import ( ) type governanceChainCommitteeProtocol struct { - getBlockTime GetBlockTime electionCommittee committee.Committee initGravityChainHeight uint64 addr address.Address @@ -35,6 +34,7 @@ type governanceChainCommitteeProtocol struct { } // NewGovernanceChainCommitteeProtocol creates a Poll Protocol which fetch result from governance chain +// TODO: remove getBlockTime func NewGovernanceChainCommitteeProtocol( candidatesIndexer *CandidateIndexer, electionCommittee committee.Committee, @@ -46,9 +46,6 @@ func NewGovernanceChainCommitteeProtocol( if electionCommittee == nil { return nil, ErrNoElectionCommittee } - if getBlockTime == nil { - return nil, errors.New("getBlockTime api is not provided") - } h := hash.Hash160b([]byte(_protocolID)) addr, err := address.FromBytes(h[:]) @@ -59,7 +56,6 @@ func NewGovernanceChainCommitteeProtocol( return &governanceChainCommitteeProtocol{ electionCommittee: electionCommittee, initGravityChainHeight: initGravityChainHeight, - getBlockTime: getBlockTime, addr: addr, initialCandidatesInterval: initialCandidatesInterval, sh: sh, @@ -234,7 +230,8 @@ func (p *governanceChainCommitteeProtocol) getGravityHeight(ctx context.Context, rp := rolldpos.MustGetProtocol(protocol.MustGetRegistry(ctx)) epochNumber := rp.GetEpochNum(height) epochHeight := rp.GetEpochHeight(epochNumber) - blkTime, err := p.getBlockTime(epochHeight) + bcCtx := protocol.MustGetBlockchainCtx(ctx) + blkTime, err := bcCtx.GetBlockTime(epochHeight) if err != nil { return 0, err } diff --git a/action/protocol/poll/governance_protocol_test.go b/action/protocol/poll/governance_protocol_test.go index e93a5e5411..0e08786442 100644 --- a/action/protocol/poll/governance_protocol_test.go +++ b/action/protocol/poll/governance_protocol_test.go @@ -65,6 +65,12 @@ func initConstruct(ctrl *gomock.Controller) (Protocol, context.Context, protocol Tip: protocol.TipInfo{ Height: epochStartHeight - 1, }, + GetBlockHash: func(u uint64) (hash.Hash256, error) { + return hash.Hash256b([]byte{0}), nil + }, + GetBlockTime: func(h uint64) (time.Time, error) { + return time.Unix(1562382522, 0), nil + }, }, ), cfg.Genesis, diff --git a/action/protocol/poll/protocol.go b/action/protocol/poll/protocol.go index 257325288c..6fbbba448b 100644 --- a/action/protocol/poll/protocol.go +++ b/action/protocol/poll/protocol.go @@ -133,10 +133,10 @@ func NewProtocol( getUnproductiveDelegate GetUnproductiveDelegate, electionCommittee committee.Committee, stakingProto *staking.Protocol, - getBlockTimeFunc GetBlockTime, + _ GetBlockTime, productivity Productivity, - getBlockHash evm.GetBlockHash, - getBlockTime evm.GetBlockTime, + _ evm.GetBlockHash, + _ evm.GetBlockTime, ) (Protocol, error) { if scheme != _rollDPoSScheme { return nil, nil @@ -184,7 +184,7 @@ func NewProtocol( candidateIndexer, electionCommittee, genesisConfig.GravityChainStartHeight, - getBlockTimeFunc, + nil, chainConfig.PollInitialCandidatesInterval, slasher, ) @@ -220,7 +220,7 @@ func NewProtocol( } return NewStakingCommand(stakingV1, stakingV2) case _modeConsortium: - return NewConsortiumCommittee(candidateIndexer, readContract, getBlockHash) + return NewConsortiumCommittee(candidateIndexer, readContract) default: return nil, errors.Errorf("unsupported poll mode %s", genesisConfig.PollMode) } diff --git a/action/protocol/poll/staking_committee_test.go b/action/protocol/poll/staking_committee_test.go index f0d17f116e..0df2a0d0b5 100644 --- a/action/protocol/poll/staking_committee_test.go +++ b/action/protocol/poll/staking_committee_test.go @@ -180,6 +180,14 @@ func TestCreatePostSystemActions_StakingCommittee(t *testing.T) { psac, ok := p.(protocol.PostSystemActionsCreator) require.True(ok) ctx = protocol.WithFeatureWithHeightCtx(ctx) + ctx = protocol.WithBlockchainCtx(ctx, protocol.BlockchainCtx{ + GetBlockHash: func(uint64) (hash.Hash256, error) { + return hash.ZeroHash256, nil + }, + GetBlockTime: func(uint64) (time.Time, error) { + return time.Now(), nil + }, + }) elp, err := psac.CreatePostSystemActions(ctx, sr) require.NoError(err) require.Equal(1, len(elp)) @@ -331,6 +339,14 @@ func TestHandle_StakingCommittee(t *testing.T) { }, ) ctx4 = protocol.WithFeatureWithHeightCtx(ctx4) + ctx4 = protocol.WithBlockchainCtx(ctx4, protocol.BlockchainCtx{ + GetBlockHash: func(uint64) (hash.Hash256, error) { + return hash.ZeroHash256, nil + }, + GetBlockTime: func(uint64) (time.Time, error) { + return time.Now(), nil + }, + }) err = p4.Validate(ctx4, elp4, sm4) require.Contains(err.Error(), "the proposed delegate list length") }) @@ -361,6 +377,14 @@ func TestHandle_StakingCommittee(t *testing.T) { Caller: caller, }, ) + ctx5 = protocol.WithBlockchainCtx(ctx5, protocol.BlockchainCtx{ + GetBlockHash: func(uint64) (hash.Hash256, error) { + return hash.ZeroHash256, nil + }, + GetBlockTime: func(uint64) (time.Time, error) { + return time.Now(), nil + }, + }) err = p5.Validate(ctx5, elp5, sm5) require.Contains(err.Error(), "delegates are not as expected") }) diff --git a/api/coreservice.go b/api/coreservice.go index 1069f8822c..f276445c2f 100644 --- a/api/coreservice.go +++ b/api/coreservice.go @@ -211,7 +211,6 @@ type ( readCache *ReadCache actionRadio *ActionRadio apiStats *nodestats.APILocalStats - getBlockTime evm.GetBlockTime } // jobDesc provides a struct to get and store logs in core.LogsInRange @@ -275,7 +274,6 @@ func newCoreService( bfIndexer blockindex.BloomFilterIndexer, actPool actpool.ActPool, registry *protocol.Registry, - getBlockTime evm.GetBlockTime, opts ...Option, ) (CoreService, error) { if cfg == (Config{}) { @@ -300,7 +298,6 @@ func newCoreService( chainListener: NewChainListener(cfg.ListenerLimit), gs: gasstation.NewGasStation(chain, dao, cfg.GasStation), readCache: NewReadCache(), - getBlockTime: getBlockTime, } for _, opt := range opts { @@ -953,6 +950,10 @@ func (core *coreService) readState(ctx context.Context, p protocol.Protocol, hei } // TODO: need to complete the context + ctx, err := core.bc.Context(ctx) + if err != nil { + return nil, 0, err + } ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{ BlockHeight: tipHeight, }) @@ -2008,6 +2009,11 @@ func (core *coreService) simulateExecution( ctx = protocol.WithFeatureCtx(protocol.WithBlockCtx(ctx, protocol.BlockCtx{ BlockHeight: height, })) + ctx, err = core.bc.Context(ctx) + if err != nil { + return nil, nil, status.Error(codes.Internal, err.Error()) + } + bcCtx := protocol.MustGetBlockchainCtx(ctx) if protocol.MustGetFeatureCtx(ctx).UseZeroNonceForFreshAccount { pendingNonce = state.PendingNonceConsideringFreshAccount() } else { @@ -2015,8 +2021,8 @@ func (core *coreService) simulateExecution( } elp.SetNonce(pendingNonce) ctx = evm.WithHelperCtx(ctx, evm.HelperContext{ - GetBlockHash: core.dao.GetBlockHash, - GetBlockTime: core.getBlockTime, + GetBlockHash: bcCtx.GetBlockHash, + GetBlockTime: bcCtx.GetBlockTime, DepositGasFunc: rewarding.DepositGas, }) return evm.SimulateExecution(ctx, ws, addr, elp, opts...) diff --git a/api/coreservice_test.go b/api/coreservice_test.go index b695b3ef57..fa242edb12 100644 --- a/api/coreservice_test.go +++ b/api/coreservice_test.go @@ -200,7 +200,7 @@ func setupTestCoreService() (CoreService, blockchain.Blockchain, blockdao.BlockD opts := []Option{WithBroadcastOutbound(func(ctx context.Context, chainID uint32, msg proto.Message) error { return nil })} - svr, err := newCoreService(cfg.api, bc, nil, sf, dao, indexer, bfIndexer, ap, registry, func(u uint64) (time.Time, error) { return time.Time{}, nil }, opts...) + svr, err := newCoreService(cfg.api, bc, nil, sf, dao, indexer, bfIndexer, ap, registry, opts...) if err != nil { panic(err) } diff --git a/api/grpcserver_integrity_test.go b/api/grpcserver_integrity_test.go index e650f96286..e65ce3e041 100644 --- a/api/grpcserver_integrity_test.go +++ b/api/grpcserver_integrity_test.go @@ -2064,7 +2064,11 @@ func TestGrpcServer_GetEpochMetaIntegrity(t *testing.T) { } else if test.pollProtocolType == "governanceChainCommittee" { committee := mock_committee.NewMockCommittee(ctrl) mbc := mock_blockchain.NewMockBlockchain(ctrl) - mbc.EXPECT().Genesis().Return(cfg.genesis).Times(3) + mbc.EXPECT().Genesis().Return(cfg.genesis).AnyTimes() + mbc.EXPECT().Context(gomock.Any()).Return(protocol.WithBlockchainCtx(context.Background(), protocol.BlockchainCtx{ + GetBlockHash: func(uint64) (hash.Hash256, error) { return hash.ZeroHash256, nil }, + GetBlockTime: func(uint64) (time.Time, error) { return time.Now(), nil }, + }), nil).AnyTimes() indexer, err := poll.NewCandidateIndexer(db.NewMemKVStore()) require.NoError(err) slasher, _ := poll.NewSlasher( @@ -2123,9 +2127,9 @@ func TestGrpcServer_GetEpochMetaIntegrity(t *testing.T) { cfg.chain.PollInitialCandidatesInterval, slasher) require.NoError(pol.ForceRegister(registry)) - committee.EXPECT().HeightByTime(gomock.Any()).Return(test.epochData.GravityChainStartHeight, nil) + committee.EXPECT().HeightByTime(gomock.Any()).Return(test.epochData.GravityChainStartHeight, nil).AnyTimes() - mbc.EXPECT().TipHeight().Return(uint64(4)).Times(4) + mbc.EXPECT().TipHeight().Return(uint64(4)).AnyTimes() mbc.EXPECT().BlockHeaderByHeight(gomock.Any()).DoAndReturn(func(height uint64) (*block.Header, error) { if height > 0 && height <= 4 { pk := identityset.PrivateKey(int(height)) diff --git a/api/serverV2.go b/api/serverV2.go index 1916d666ff..4b9c414bc6 100644 --- a/api/serverV2.go +++ b/api/serverV2.go @@ -14,7 +14,6 @@ import ( "golang.org/x/time/rate" "github.com/iotexproject/iotex-core/v2/action/protocol" - "github.com/iotexproject/iotex-core/v2/action/protocol/execution/evm" "github.com/iotexproject/iotex-core/v2/actpool" "github.com/iotexproject/iotex-core/v2/blockchain" "github.com/iotexproject/iotex-core/v2/blockchain/block" @@ -45,10 +44,9 @@ func NewServerV2( bfIndexer blockindex.BloomFilterIndexer, actPool actpool.ActPool, registry *protocol.Registry, - getBlockTime evm.GetBlockTime, opts ...Option, ) (*ServerV2, error) { - coreAPI, err := newCoreService(cfg, chain, bs, sf, dao, indexer, bfIndexer, actPool, registry, getBlockTime, opts...) + coreAPI, err := newCoreService(cfg, chain, bs, sf, dao, indexer, bfIndexer, actPool, registry, opts...) if err != nil { return nil, err } diff --git a/api/serverV2_integrity_test.go b/api/serverV2_integrity_test.go index 88f59fdc1b..54eda2588b 100644 --- a/api/serverV2_integrity_test.go +++ b/api/serverV2_integrity_test.go @@ -330,6 +330,7 @@ func setupChain(cfg testConfig) (blockchain.Blockchain, blockdao.BlockDAO, block sf, protocol.NewGenericValidator(sf, accountutil.AccountState), )), + blockchain.BlockTimeCalculatorBuilderOption(testutil.DummyBlockTimeBuilder()), ) if bc == nil { return nil, nil, nil, nil, nil, nil, nil, "", errors.New("failed to create blockchain") @@ -455,7 +456,7 @@ func createServerV2(cfg testConfig, needActPool bool) (*ServerV2, blockchain.Blo opts := []Option{WithBroadcastOutbound(func(ctx context.Context, chainID uint32, msg proto.Message) error { return nil })} - svr, err := NewServerV2(cfg.api, bc, nil, sf, dao, indexer, bfIndexer, ap, registry, func(u uint64) (time.Time, error) { return time.Time{}, nil }, opts...) + svr, err := NewServerV2(cfg.api, bc, nil, sf, dao, indexer, bfIndexer, ap, registry, opts...) if err != nil { return nil, nil, nil, nil, nil, nil, "", err } diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index b249ee4c90..0c5e240e4e 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -29,6 +29,7 @@ import ( "github.com/iotexproject/iotex-core/v2/pkg/log" "github.com/iotexproject/iotex-core/v2/pkg/prometheustimer" "github.com/iotexproject/iotex-core/v2/pkg/unit" + "github.com/iotexproject/iotex-core/v2/pkg/util/blockutil" ) // const @@ -108,6 +109,10 @@ type ( // BlockBuilderFactory is the factory interface of block builder BlockBuilderFactory interface { Mint(ctx context.Context) (*block.Block, error) + ReceiveBlock(*block.Block) error + Init(hash.Hash256) + AddProposal(*block.Block) error + Block(hash.Hash256) *block.Block } // blockchain implements the Blockchain interface @@ -123,7 +128,8 @@ type ( timerFactory *prometheustimer.TimerFactory // used by account-based model - bbf BlockBuilderFactory + bbf BlockBuilderFactory + btcBuilder *blockutil.BlockTimeCalculatorBuilder } ) @@ -161,6 +167,13 @@ func ClockOption(clk clock.Clock) Option { } } +func BlockTimeCalculatorBuilderOption(btcBuilder *blockutil.BlockTimeCalculatorBuilder) Option { + return func(bc *blockchain) error { + bc.btcBuilder = btcBuilder + return nil + } +} + type ( BlockValidationCfg struct { skipSidecarValidation bool @@ -204,6 +217,9 @@ func NewBlockchain(cfg Config, g genesis.Genesis, dao blockdao.BlockDAO, bbf Blo if chain.dao == nil { log.L().Panic("blockdao is nil") } + if chain.btcBuilder == nil { + log.L().Panic("block time calculator builder is nil") + } chain.lifecycle.Add(chain.dao) chain.lifecycle.Add(chain.pubSubManager) return chain @@ -225,7 +241,19 @@ func (bc *blockchain) ChainAddress() string { func (bc *blockchain) Start(ctx context.Context) error { bc.mu.Lock() defer bc.mu.Unlock() - + btc, err := bc.btcBuilder.SetTipHeight(bc.TipHeight).SetHistoryBlockTime(func(height uint64) (time.Time, error) { + if height == 0 { + return time.Unix(bc.genesis.Timestamp, 0), nil + } + header, err := bc.dao.HeaderByHeight(height) + if err != nil { + return time.Time{}, err + } + return header.Timestamp(), nil + }).Build() + if err != nil { + return err + } // pass registry to be used by state factory's initialization ctx = protocol.WithFeatureWithHeightCtx(genesis.WithGenesisContext( protocol.WithBlockchainCtx( @@ -233,9 +261,20 @@ func (bc *blockchain) Start(ctx context.Context) error { protocol.BlockchainCtx{ ChainID: bc.ChainID(), EvmNetworkID: bc.EvmNetworkID(), + GetBlockHash: bc.dao.GetBlockHash, + GetBlockTime: btc.CalculateBlockTime, }, ), bc.genesis)) - return bc.lifecycle.OnStart(ctx) + err = bc.lifecycle.OnStart(ctx) + if err != nil { + return err + } + if tip, err := bc.tipInfo(bc.TipHeight()); err != nil { + return errors.Wrap(err, "failed to get tip info") + } else { + bc.bbf.Init(tip.Hash) + return nil + } } // Stop stops the blockchain. @@ -389,6 +428,19 @@ func (bc *blockchain) context(ctx context.Context, height uint64) (context.Conte if err != nil { return nil, err } + btc, err := bc.btcBuilder.SetTipHeight(bc.TipHeight).SetHistoryBlockTime(func(height uint64) (time.Time, error) { + if height == 0 { + return time.Unix(bc.genesis.Timestamp, 0), nil + } + header, err := bc.dao.HeaderByHeight(height) + if err != nil { + return time.Time{}, err + } + return header.Timestamp(), nil + }).Build() + if err != nil { + return nil, err + } ctx = genesis.WithGenesisContext( protocol.WithBlockchainCtx( ctx, @@ -396,6 +448,8 @@ func (bc *blockchain) context(ctx context.Context, height uint64) (context.Conte Tip: *tip, ChainID: bc.ChainID(), EvmNetworkID: bc.EvmNetworkID(), + GetBlockHash: bc.dao.GetBlockHash, + GetBlockTime: btc.CalculateBlockTime, }, ), bc.genesis, diff --git a/blockchain/integrity/benchmark_test.go b/blockchain/integrity/benchmark_test.go index 1ac1d73df8..b64e0c3d0e 100644 --- a/blockchain/integrity/benchmark_test.go +++ b/blockchain/integrity/benchmark_test.go @@ -278,6 +278,7 @@ func newChainInDB() (blockchain.Blockchain, actpool.ActPool, error) { sf, protocol.NewGenericValidator(sf, accountutil.AccountState), )), + blockchain.BlockTimeCalculatorBuilderOption(testutil.DummyBlockTimeBuilder()), ) if bc == nil { return nil, nil, errors.New("pointer is nil") diff --git a/blockchain/integrity/integrity_test.go b/blockchain/integrity/integrity_test.go index 66c12d48b3..5a1344b159 100644 --- a/blockchain/integrity/integrity_test.go +++ b/blockchain/integrity/integrity_test.go @@ -506,6 +506,7 @@ func TestCreateBlockchain(t *testing.T) { sf, protocol.NewGenericValidator(sf, accountutil.AccountState), )), + blockchain.BlockTimeCalculatorBuilderOption(testutil.DummyBlockTimeBuilder()), ) ep := execution.NewProtocol(dao.GetBlockHash, rewarding.DepositGas, fakeGetBlockTime) require.NoError(ep.Register(registry)) @@ -562,6 +563,7 @@ func TestGetBlockHash(t *testing.T) { sf, protocol.NewGenericValidator(sf, accountutil.AccountState), )), + blockchain.BlockTimeCalculatorBuilderOption(testutil.DummyBlockTimeBuilder()), ) ep := execution.NewProtocol(dao.GetBlockHash, rewarding.DepositGas, fakeGetBlockTime) require.NoError(ep.Register(registry)) @@ -727,6 +729,7 @@ func TestBlockchain_MintNewBlock(t *testing.T) { sf, protocol.NewGenericValidator(sf, accountutil.AccountState), )), + blockchain.BlockTimeCalculatorBuilderOption(testutil.DummyBlockTimeBuilder()), ) ep := execution.NewProtocol(dao.GetBlockHash, rewarding.DepositGas, fakeGetBlockTime) require.NoError(t, ep.Register(registry)) @@ -797,6 +800,7 @@ func TestBlockchain_MintNewBlock_PopAccount(t *testing.T) { sf, protocol.NewGenericValidator(sf, accountutil.AccountState), )), + blockchain.BlockTimeCalculatorBuilderOption(testutil.DummyBlockTimeBuilder()), ) rp := rolldpos.NewProtocol(cfg.Genesis.NumCandidateDelegates, cfg.Genesis.NumDelegates, cfg.Genesis.NumSubEpochs) require.NoError(t, rp.Register(registry)) @@ -940,6 +944,7 @@ func createChain(cfg config.Config, inMem bool) (blockchain.Blockchain, factory. sf, protocol.NewGenericValidator(sf, accountutil.AccountState), )), + blockchain.BlockTimeCalculatorBuilderOption(testutil.DummyBlockTimeBuilder().SetBlockInterval(func(height uint64) time.Duration { return time.Second })), ) btc, err := blockutil.NewBlockTimeCalculator(func(uint64) time.Duration { return time.Second }, bc.TipHeight, func(height uint64) (time.Time, error) { @@ -1485,6 +1490,7 @@ func TestConstantinople(t *testing.T) { sf, protocol.NewGenericValidator(sf, accountutil.AccountState), )), + blockchain.BlockTimeCalculatorBuilderOption(testutil.DummyBlockTimeBuilder()), ) ep := execution.NewProtocol(dao.GetBlockHash, rewarding.DepositGas, fakeGetBlockTime) require.NoError(ep.Register(registry)) @@ -1740,6 +1746,7 @@ func TestLoadBlockchainfromDB(t *testing.T) { sf, protocol.NewGenericValidator(sf, accountutil.AccountState), )), + blockchain.BlockTimeCalculatorBuilderOption(testutil.DummyBlockTimeBuilder()), ) ep := execution.NewProtocol(dao.GetBlockHash, rewarding.DepositGas, fakeGetBlockTime) require.NoError(ep.Register(registry)) @@ -1769,6 +1776,7 @@ func TestLoadBlockchainfromDB(t *testing.T) { sf, protocol.NewGenericValidator(sf, accountutil.AccountState), )), + blockchain.BlockTimeCalculatorBuilderOption(testutil.DummyBlockTimeBuilder()), ) require.NoError(bc.Start(ctx)) defer func() { @@ -2047,6 +2055,7 @@ func TestBlockchainInitialCandidate(t *testing.T) { dao, factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockValidatorOption(sf), + blockchain.BlockTimeCalculatorBuilderOption(testutil.DummyBlockTimeBuilder()), ) rolldposProtocol := rolldpos.NewProtocol( cfg.Genesis.NumCandidateDelegates, @@ -2088,7 +2097,7 @@ func TestBlockchain_AccountState(t *testing.T) { store, err := filedao.NewFileDAOInMemForTest() require.NoError(err) dao := blockdao.NewBlockDAOWithIndexersAndCache(store, []blockdao.BlockIndexer{sf}, cfg.DB.MaxCacheSize) - bc := blockchain.NewBlockchain(cfg.Chain, cfg.Genesis, dao, factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey()))) + bc := blockchain.NewBlockchain(cfg.Chain, cfg.Genesis, dao, factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockTimeCalculatorBuilderOption(testutil.DummyBlockTimeBuilder())) require.NoError(bc.Start(ctx)) require.NotNil(bc) defer func() { @@ -2120,7 +2129,7 @@ func TestNewAccountAction(t *testing.T) { store, err := filedao.NewFileDAOInMemForTest() require.NoError(err) dao := blockdao.NewBlockDAOWithIndexersAndCache(store, []blockdao.BlockIndexer{sf}, cfg.DB.MaxCacheSize) - bc := blockchain.NewBlockchain(cfg.Chain, cfg.Genesis, dao, factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey()))) + bc := blockchain.NewBlockchain(cfg.Chain, cfg.Genesis, dao, factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockTimeCalculatorBuilderOption(testutil.DummyBlockTimeBuilder())) require.NoError(bc.Start(ctx)) require.NotNil(bc) defer func() { @@ -2165,7 +2174,7 @@ func TestNewAccountAction(t *testing.T) { store, err := filedao.NewFileDAOInMemForTest() require.NoError(err) dao1 := blockdao.NewBlockDAOWithIndexersAndCache(store, []blockdao.BlockIndexer{sf1}, cfg.DB.MaxCacheSize) - bc1 := blockchain.NewBlockchain(cfg.Chain, cfg.Genesis, dao1, factory.NewMinter(sf1, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey()))) + bc1 := blockchain.NewBlockchain(cfg.Chain, cfg.Genesis, dao1, factory.NewMinter(sf1, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockTimeCalculatorBuilderOption(testutil.DummyBlockTimeBuilder())) require.NoError(bc1.Start(ctx)) require.NotNil(bc1) defer func() { @@ -2234,7 +2243,7 @@ func TestBlocks(t *testing.T) { dao := blockdao.NewBlockDAOWithIndexersAndCache(store, []blockdao.BlockIndexer{sf}, dbcfg.MaxCacheSize) // Create a blockchain from scratch - bc := blockchain.NewBlockchain(cfg.Chain, cfg.Genesis, dao, factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey()))) + bc := blockchain.NewBlockchain(cfg.Chain, cfg.Genesis, dao, factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockTimeCalculatorBuilderOption(testutil.DummyBlockTimeBuilder())) require.NoError(bc.Start(context.Background())) defer func() { require.NoError(bc.Stop(context.Background())) @@ -2317,6 +2326,7 @@ func TestActions(t *testing.T) { sf, protocol.NewGenericValidator(sf, accountutil.AccountState), )), + blockchain.BlockTimeCalculatorBuilderOption(testutil.DummyBlockTimeBuilder()), ) require.NoError(bc.Start(context.Background())) defer func() { @@ -2372,7 +2382,7 @@ func TestBlockchain_AddRemoveSubscriber(t *testing.T) { store, err := filedao.NewFileDAOInMemForTest() req.NoError(err) dao := blockdao.NewBlockDAOWithIndexersAndCache(store, []blockdao.BlockIndexer{sf}, cfg.DB.MaxCacheSize) - bc := blockchain.NewBlockchain(cfg.Chain, cfg.Genesis, dao, factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey()))) + bc := blockchain.NewBlockchain(cfg.Chain, cfg.Genesis, dao, factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockTimeCalculatorBuilderOption(testutil.DummyBlockTimeBuilder())) // mock ctrl := gomock.NewController(t) mb := mock_blockcreationsubscriber.NewMockBlockCreationSubscriber(ctrl) @@ -2612,6 +2622,7 @@ func newChain(t *testing.T, stateTX bool) (blockchain.Blockchain, factory.Factor sf, protocol.NewGenericValidator(sf, accountutil.AccountState), )), + blockchain.BlockTimeCalculatorBuilderOption(testutil.DummyBlockTimeBuilder()), ) require.NotNil(bc) ep := execution.NewProtocol(dao.GetBlockHash, rewarding.DepositGas, fakeGetBlockTime) diff --git a/blocksync/blocksync_test.go b/blocksync/blocksync_test.go index d5edebde28..449bbc98d8 100644 --- a/blocksync/blocksync_test.go +++ b/blocksync/blocksync_test.go @@ -210,6 +210,7 @@ func TestBlockSyncerProcessBlockTipHeight(t *testing.T) { dao, factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockValidatorOption(block.NewValidator(sf, ap)), + blockchain.BlockTimeCalculatorBuilderOption(testutil.DummyBlockTimeBuilder()), ) require.NoError(chain.Start(ctx)) require.NotNil(chain) @@ -276,6 +277,7 @@ func TestBlockSyncerProcessBlockOutOfOrder(t *testing.T) { dao, factory.NewMinter(sf, ap1, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockValidatorOption(block.NewValidator(sf, ap1)), + blockchain.BlockTimeCalculatorBuilderOption(testutil.DummyBlockTimeBuilder()), ) require.NotNil(chain1) require.NoError(chain1.Start(ctx)) @@ -303,6 +305,7 @@ func TestBlockSyncerProcessBlockOutOfOrder(t *testing.T) { dao2, factory.NewMinter(sf2, ap2, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockValidatorOption(block.NewValidator(sf2, ap2)), + blockchain.BlockTimeCalculatorBuilderOption(testutil.DummyBlockTimeBuilder()), ) require.NotNil(chain2) require.NoError(chain2.Start(ctx)) @@ -378,6 +381,7 @@ func TestBlockSyncerProcessBlock(t *testing.T) { dao, factory.NewMinter(sf, ap1, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockValidatorOption(block.NewValidator(sf, ap1)), + blockchain.BlockTimeCalculatorBuilderOption(testutil.DummyBlockTimeBuilder()), ) require.NoError(chain1.Start(ctx)) require.NotNil(chain1) @@ -404,6 +408,7 @@ func TestBlockSyncerProcessBlock(t *testing.T) { dao2, factory.NewMinter(sf2, ap2, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockValidatorOption(block.NewValidator(sf2, ap2)), + blockchain.BlockTimeCalculatorBuilderOption(testutil.DummyBlockTimeBuilder()), ) require.NoError(chain2.Start(ctx)) require.NotNil(chain2) @@ -472,6 +477,7 @@ func TestBlockSyncerSync(t *testing.T) { dao, factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockValidatorOption(block.NewValidator(sf, ap)), + blockchain.BlockTimeCalculatorBuilderOption(testutil.DummyBlockTimeBuilder()), ) require.NoError(chain.Start(ctx)) require.NotNil(chain) diff --git a/blocksync/buffer_test.go b/blocksync/buffer_test.go index 03674c9a13..cb13e49eb5 100644 --- a/blocksync/buffer_test.go +++ b/blocksync/buffer_test.go @@ -57,6 +57,7 @@ func TestBlockBufferFlush(t *testing.T) { dao, factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), blockchain.BlockValidatorOption(block.NewValidator(sf, ap)), + blockchain.BlockTimeCalculatorBuilderOption(testutil.DummyBlockTimeBuilder()), ) require.NoError(chain.Start(ctx)) require.NotNil(chain) @@ -150,6 +151,7 @@ func TestBlockBufferGetBlocksIntervalsToSync(t *testing.T) { cfg.Genesis, dao, factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), + blockchain.BlockTimeCalculatorBuilderOption(testutil.DummyBlockTimeBuilder()), ) require.NotNil(chain) require.NoError(chain.Start(ctx)) diff --git a/chainservice/builder.go b/chainservice/builder.go index d457fe29e3..a138588503 100644 --- a/chainservice/builder.go +++ b/chainservice/builder.go @@ -513,12 +513,16 @@ func (builder *Builder) createBlockchain(forSubChain, forTest bool) blockchain.B } else { chainOpts = append(chainOpts, blockchain.BlockValidatorOption(builder.cs.factory)) } + consensusCfg := consensusfsm.NewConsensusConfig(builder.cfg.Consensus.RollDPoS.FSM, builder.cfg.DardanellesUpgrade, builder.cfg.Genesis, builder.cfg.Consensus.RollDPoS.Delay) + chainOpts = append(chainOpts, blockchain.BlockTimeCalculatorBuilderOption(blockutil.NewBlockTimeCalculatorBuilder().SetBlockInterval(consensusCfg.BlockInterval))) mintOpts := []factory.MintOption{factory.WithPrivateKeyOption(builder.cfg.Chain.ProducerPrivateKey())} if builder.cfg.Consensus.Scheme == config.RollDPoSScheme { mintOpts = append(mintOpts, factory.WithTimeoutOption(builder.cfg.Chain.MintTimeout)) } - return blockchain.NewBlockchain(builder.cfg.Chain, builder.cfg.Genesis, builder.cs.blockdao, factory.NewMinter(builder.cs.factory, builder.cs.actpool, mintOpts...), chainOpts...) + minter := factory.NewMinter(builder.cs.factory, builder.cs.actpool, mintOpts...) + builder.cs.blockBuildFactory = minter + return blockchain.NewBlockchain(builder.cfg.Chain, builder.cfg.Genesis, builder.cs.blockdao, minter, chainOpts...) } func (builder *Builder) buildNodeInfoManager() error { @@ -706,7 +710,7 @@ func (builder *Builder) registerAccountProtocol() error { } func (builder *Builder) registerExecutionProtocol() error { - return execution.NewProtocol(builder.cs.blockdao.GetBlockHash, rewarding.DepositGas, builder.cs.blockTimeCalculator.CalculateBlockTime).Register(builder.cs.registry) + return execution.NewProtocol(nil, rewarding.DepositGas, nil).Register(builder.cs.registry) } func (builder *Builder) registerRollDPoSProtocol() error { @@ -722,9 +726,7 @@ func (builder *Builder) registerRollDPoSProtocol() error { return err } factory := builder.cs.factory - dao := builder.cs.blockdao chain := builder.cs.chain - getBlockTime := builder.cs.blockTimeCalculator.CalculateBlockTime pollProtocol, err := poll.NewProtocol( builder.cfg.Consensus.Scheme, builder.cfg.Chain, @@ -742,10 +744,10 @@ func (builder *Builder) registerRollDPoSProtocol() error { if err != nil { return nil, err } - + bcCtx := protocol.MustGetBlockchainCtx(ctx) ctx = evm.WithHelperCtx(ctx, evm.HelperContext{ - GetBlockHash: dao.GetBlockHash, - GetBlockTime: getBlockTime, + GetBlockHash: bcCtx.GetBlockHash, + GetBlockTime: bcCtx.GetBlockTime, DepositGasFunc: rewarding.DepositGas, }) ws, err := factory.WorkingSet(ctx) @@ -760,21 +762,13 @@ func (builder *Builder) registerRollDPoSProtocol() error { candidatesutil.UnproductiveDelegateFromDB, builder.cs.electionCommittee, staking.FindProtocol(builder.cs.registry), - func(height uint64) (time.Time, error) { - header, err := chain.BlockHeaderByHeight(height) - if err != nil { - return time.Now(), errors.Wrapf( - err, "error when getting the block at height: %d", - height, - ) - } - return header.Timestamp(), nil - }, + nil, + // Productivity function only used before greenland, so we can use the default chain func(start, end uint64) (map[string]uint64, error) { return blockchain.Productivity(chain, start, end) }, - dao.GetBlockHash, - getBlockTime, + nil, + nil, ) if err != nil { return errors.Wrap(err, "failed to generate poll protocol") @@ -782,32 +776,15 @@ func (builder *Builder) registerRollDPoSProtocol() error { return pollProtocol.Register(builder.cs.registry) } -func (builder *Builder) buildBlockTimeCalculator() (err error) { - consensusCfg := consensusfsm.NewConsensusConfig(builder.cfg.Consensus.RollDPoS.FSM, builder.cfg.DardanellesUpgrade, builder.cfg.Genesis, builder.cfg.Consensus.RollDPoS.Delay) - dao := builder.cs.BlockDAO() - builder.cs.blockTimeCalculator, err = blockutil.NewBlockTimeCalculator(consensusCfg.BlockInterval, func() uint64 { - tip, err := dao.Height() - if err != nil { - log.L().Error("failed to get tip height", zap.Error(err)) - return 0 - } - return tip - }, func(height uint64) (time.Time, error) { - blk, err := dao.GetBlockByHeight(height) - if err != nil { - return time.Time{}, err - } - return blk.Timestamp(), nil - }) - return err -} - func (builder *Builder) buildConsensusComponent() error { p2pAgent := builder.cs.p2pAgent + consensusCfg := consensusfsm.NewConsensusConfig(builder.cfg.Consensus.RollDPoS.FSM, builder.cfg.DardanellesUpgrade, builder.cfg.Genesis, builder.cfg.Consensus.RollDPoS.Delay) copts := []consensus.Option{ consensus.WithBroadcast(func(msg proto.Message) error { return p2pAgent.BroadcastOutbound(context.Background(), msg) }), + consensus.WithBlockBuilderFactory(builder.cs.blockBuildFactory), + consensus.WithBlockTimeCalculatorBuilder(blockutil.NewBlockTimeCalculatorBuilder().SetBlockInterval(consensusCfg.BlockInterval)), } if rDPoSProtocol := rolldpos.FindProtocol(builder.cs.registry); rDPoSProtocol != nil { copts = append(copts, consensus.WithRollDPoSProtocol(rDPoSProtocol)) @@ -862,9 +839,6 @@ func (builder *Builder) build(forSubChain, forTest bool) (*ChainService, error) if err := builder.buildBlockchain(forSubChain, forTest); err != nil { return nil, err } - if err := builder.buildBlockTimeCalculator(); err != nil { - return nil, err - } // staking protocol need to be put in registry before poll protocol when enabling if err := builder.registerStakingProtocol(); err != nil { return nil, errors.Wrap(err, "failed to register staking protocol") diff --git a/chainservice/chainservice.go b/chainservice/chainservice.go index 3cf53c9ead..e66eb5ed76 100644 --- a/chainservice/chainservice.go +++ b/chainservice/chainservice.go @@ -71,19 +71,20 @@ type ChainService struct { p2pAgent p2p.Agent electionCommittee committee.Committee // TODO: explorer dependency deleted at #1085, need to api related params - indexer blockindex.Indexer - bfIndexer blockindex.BloomFilterIndexer - candidateIndexer *poll.CandidateIndexer - candBucketsIndexer *staking.CandidatesBucketsIndexer - contractStakingIndexer *contractstaking.Indexer - contractStakingIndexerV2 stakingindex.StakingIndexer - registry *protocol.Registry - nodeInfoManager *nodeinfo.InfoManager - apiStats *nodestats.APILocalStats - blockTimeCalculator *blockutil.BlockTimeCalculator - actionsync *actsync.ActionSync - rateLimiters cache.LRUCache - accRateLimitCfg int + indexer blockindex.Indexer + bfIndexer blockindex.BloomFilterIndexer + candidateIndexer *poll.CandidateIndexer + candBucketsIndexer *staking.CandidatesBucketsIndexer + contractStakingIndexer *contractstaking.Indexer + contractStakingIndexerV2 stakingindex.StakingIndexer + registry *protocol.Registry + nodeInfoManager *nodeinfo.InfoManager + apiStats *nodestats.APILocalStats + blockTimeCalculatorBuilder *blockutil.BlockTimeCalculatorBuilder + blockBuildFactory blockchain.BlockBuilderFactory + actionsync *actsync.ActionSync + rateLimiters cache.LRUCache + accRateLimitCfg int } // Start starts the server @@ -258,7 +259,6 @@ func (cs *ChainService) NewAPIServer(cfg api.Config, archive bool) (*api.ServerV cs.bfIndexer, cs.actpool, cs.registry, - cs.blockTimeCalculator.CalculateBlockTime, apiServerOptions..., ) if err != nil { diff --git a/consensus/consensus.go b/consensus/consensus.go index cc3b102907..8a5cf52f6e 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -24,6 +24,7 @@ import ( "github.com/iotexproject/iotex-core/v2/consensus/scheme/rolldpos" "github.com/iotexproject/iotex-core/v2/pkg/lifecycle" "github.com/iotexproject/iotex-core/v2/pkg/log" + "github.com/iotexproject/iotex-core/v2/pkg/util/blockutil" "github.com/iotexproject/iotex-core/v2/state" ) @@ -50,6 +51,7 @@ type optionParams struct { pp poll.Protocol rp *rp.Protocol bbf rolldpos.BlockBuilderFactory + btcBuilder *blockutil.BlockTimeCalculatorBuilder } // Option sets Consensus construction parameter. @@ -87,6 +89,14 @@ func WithBlockBuilderFactory(bbf rolldpos.BlockBuilderFactory) Option { } } +// WithBlockTimeCalculatorBuilder is an option to set block time calculator builder +func WithBlockTimeCalculatorBuilder(btcBuilder *blockutil.BlockTimeCalculatorBuilder) Option { + return func(ops *optionParams) error { + ops.btcBuilder = btcBuilder + return nil + } +} + // NewConsensus creates a IotxConsensus struct. func NewConsensus( cfg rolldpos.BuilderConfig, @@ -109,7 +119,13 @@ func NewConsensus( var err error switch cfg.Scheme { case RollDPoSScheme: - chanMgr := rolldpos.NewChainManager(bc, sf, ops.bbf) + if ops.bbf == nil { + return nil, errors.New("block builder factory is not set") + } + if ops.btcBuilder == nil { + return nil, errors.New("block time calculator builder is not set") + } + chanMgr := rolldpos.NewChainManager(bc, sf, ops.bbf, ops.btcBuilder) delegatesByEpochFunc := func(epochNum uint64, prevHash []byte) ([]string, error) { fork, serr := chanMgr.Fork(hash.Hash256(prevHash)) if serr != nil { diff --git a/consensus/scheme/rolldpos/chainmanager.go b/consensus/scheme/rolldpos/chainmanager.go index 7cf4480bec..05f624f25d 100644 --- a/consensus/scheme/rolldpos/chainmanager.go +++ b/consensus/scheme/rolldpos/chainmanager.go @@ -18,6 +18,7 @@ import ( "github.com/iotexproject/iotex-core/v2/db" "github.com/iotexproject/iotex-core/v2/pkg/log" "github.com/iotexproject/iotex-core/v2/pkg/prometheustimer" + "github.com/iotexproject/iotex-core/v2/pkg/util/blockutil" ) var ( @@ -76,7 +77,8 @@ type ( chainManager struct { *forkChain - srf StateReaderFactory + srf StateReaderFactory + btcBuilder *blockutil.BlockTimeCalculatorBuilder } forkChain struct { @@ -85,6 +87,7 @@ type ( sr protocol.StateReader timerFactory *prometheustimer.TimerFactory bbf BlockBuilderFactory + btCalc *blockutil.BlockTimeCalculator } ) @@ -112,10 +115,11 @@ func newForkChain(bc blockchain.Blockchain, head *block.Header, sr protocol.Stat } // NewChainManager creates a chain manager -func NewChainManager(bc blockchain.Blockchain, srf StateReaderFactory, bbf BlockBuilderFactory) ChainManager { +func NewChainManager(bc blockchain.Blockchain, srf StateReaderFactory, bbf BlockBuilderFactory, btcBuilder *blockutil.BlockTimeCalculatorBuilder) ChainManager { return &chainManager{ - forkChain: newForkChain(bc, nil, nil, bbf), - srf: srf, + forkChain: newForkChain(bc, nil, nil, bbf), + srf: srf, + btcBuilder: btcBuilder, } } @@ -124,18 +128,11 @@ func (cm *forkChain) BlockProposeTime(height uint64) (time.Time, error) { if height == 0 { return time.Unix(cm.bc.Genesis().Timestamp, 0), nil } - header, err := cm.bc.BlockHeaderByHeight(height) - switch errors.Cause(err) { - case nil: - return header.Timestamp(), nil - case db.ErrNotExist: - if blk := cm.draftBlockByHeight(height); blk != nil { - return blk.Timestamp(), nil - } - return time.Time{}, err - default: + header, err := cm.header(height) + if err != nil { return time.Time{}, err } + return header.Timestamp(), nil } // BlockCommitTime return commit time by height @@ -264,9 +261,24 @@ func (cm *chainManager) Fork(hash hash.Hash256) (ChainManager, error) { if err != nil { return nil, errors.Wrapf(err, "failed to create state reader at %d, hash %x", head.Height(), head.HashBlock()) } + chain := newForkChain(cm.bc, head, sr, cm.bbf) + btc, err := cm.btcBuilder.SetTipHeight(chain.TipHeight).SetHistoryBlockTime(func(height uint64) (time.Time, error) { + if height == 0 { + return time.Unix(cm.bc.Genesis().Timestamp, 0), nil + } + header, err := chain.header(height) + if err != nil { + return time.Time{}, err + } + return header.Timestamp(), nil + }).Build() + if err != nil { + return nil, errors.Wrap(err, "failed to build block time calculator") + } + chain.btCalc = btc return &chainManager{ srf: cm.srf, - forkChain: newForkChain(cm.bc, head, sr, cm.bbf), + forkChain: chain, }, nil } @@ -307,6 +319,21 @@ func (fc *forkChain) tipInfo() *protocol.TipInfo { } } +func (fc *forkChain) header(height uint64) (*block.Header, error) { + header, err := fc.bc.BlockHeaderByHeight(height) + switch errors.Cause(err) { + case nil: + return header, nil + case db.ErrNotExist: + if blk := fc.draftBlockByHeight(height); blk != nil { + return &blk.Header, nil + } + return nil, err + default: + return nil, err + } +} + func (fc *forkChain) mintContext(ctx context.Context, timestamp time.Time, producer address.Address) context.Context { // blockchain context @@ -318,6 +345,14 @@ func (fc *forkChain) mintContext(ctx context.Context, Tip: *tip, ChainID: fc.bc.ChainID(), EvmNetworkID: fc.bc.EvmNetworkID(), + GetBlockHash: func(u uint64) (hash.Hash256, error) { + header, err := fc.header(u) + if err != nil { + return hash.ZeroHash256, err + } + return header.HashBlock(), nil + }, + GetBlockTime: fc.btCalc.CalculateBlockTime, }, ), fc.bc.Genesis(), diff --git a/consensus/scheme/rolldpos/rolldpos_test.go b/consensus/scheme/rolldpos/rolldpos_test.go index 4650f8eee9..1f5dee99c1 100644 --- a/consensus/scheme/rolldpos/rolldpos_test.go +++ b/consensus/scheme/rolldpos/rolldpos_test.go @@ -79,7 +79,7 @@ func TestNewRollDPoS(t *testing.T) { SetConfig(builderCfg). SetAddr(identityset.Address(0).String()). SetPriKey(sk). - SetChainManager(NewChainManager(mock_blockchain.NewMockBlockchain(ctrl), mock_factory.NewMockFactory(ctrl), &dummyBlockBuildFactory{})). + SetChainManager(NewChainManager(mock_blockchain.NewMockBlockchain(ctrl), mock_factory.NewMockFactory(ctrl), &dummyBlockBuildFactory{}, testutil.DummyBlockTimeBuilder())). SetBroadcast(func(_ proto.Message) error { return nil }). @@ -96,7 +96,7 @@ func TestNewRollDPoS(t *testing.T) { SetConfig(builderCfg). SetAddr(identityset.Address(0).String()). SetPriKey(sk). - SetChainManager(NewChainManager(mock_blockchain.NewMockBlockchain(ctrl), mock_factory.NewMockFactory(ctrl), &dummyBlockBuildFactory{})). + SetChainManager(NewChainManager(mock_blockchain.NewMockBlockchain(ctrl), mock_factory.NewMockFactory(ctrl), &dummyBlockBuildFactory{}, testutil.DummyBlockTimeBuilder())). SetBroadcast(func(_ proto.Message) error { return nil }). @@ -117,7 +117,7 @@ func TestNewRollDPoS(t *testing.T) { SetConfig(builderCfg). SetAddr(identityset.Address(0).String()). SetPriKey(sk). - SetChainManager(NewChainManager(mock_blockchain.NewMockBlockchain(ctrl), mock_factory.NewMockFactory(ctrl), &dummyBlockBuildFactory{})). + SetChainManager(NewChainManager(mock_blockchain.NewMockBlockchain(ctrl), mock_factory.NewMockFactory(ctrl), &dummyBlockBuildFactory{}, testutil.DummyBlockTimeBuilder())). SetBroadcast(func(_ proto.Message) error { return nil }). @@ -241,7 +241,7 @@ func TestValidateBlockFooter(t *testing.T) { SetConfig(builderCfg). SetAddr(identityset.Address(1).String()). SetPriKey(sk1). - SetChainManager(NewChainManager(bc, sf, &dummyBlockBuildFactory{})). + SetChainManager(NewChainManager(bc, sf, &dummyBlockBuildFactory{}, testutil.DummyBlockTimeBuilder())). SetBroadcast(func(_ proto.Message) error { return nil }). @@ -334,7 +334,7 @@ func TestRollDPoS_Metrics(t *testing.T) { SetConfig(builderCfg). SetAddr(identityset.Address(1).String()). SetPriKey(sk1). - SetChainManager(NewChainManager(bc, sf, &dummyBlockBuildFactory{})). + SetChainManager(NewChainManager(bc, sf, &dummyBlockBuildFactory{}, testutil.DummyBlockTimeBuilder())). SetBroadcast(func(_ proto.Message) error { return nil }). @@ -483,6 +483,7 @@ func TestRollDPoSConsensus(t *testing.T) { sf, protocol.NewGenericValidator(sf, accountutil.AccountState), )), + blockchain.BlockTimeCalculatorBuilderOption(testutil.DummyBlockTimeBuilder()), ) chains = append(chains, chain) @@ -496,7 +497,7 @@ func TestRollDPoSConsensus(t *testing.T) { SetAddr(chainAddrs[i].encodedAddr). SetPriKey(chainAddrs[i].priKey). SetConfig(builderCfg). - SetChainManager(NewChainManager(chain, sf, &dummyBlockBuildFactory{})). + SetChainManager(NewChainManager(chain, sf, &dummyBlockBuildFactory{}, testutil.DummyBlockTimeBuilder())). SetBroadcast(p2p.Broadcast). SetDelegatesByEpochFunc(delegatesByEpochFunc). SetProposersByEpochFunc(delegatesByEpochFunc). diff --git a/consensus/scheme/rolldpos/rolldposctx_test.go b/consensus/scheme/rolldpos/rolldposctx_test.go index f6e2efa7ae..b67c8d5dc9 100644 --- a/consensus/scheme/rolldpos/rolldposctx_test.go +++ b/consensus/scheme/rolldpos/rolldposctx_test.go @@ -25,6 +25,7 @@ import ( "github.com/iotexproject/iotex-core/v2/endorsement" "github.com/iotexproject/iotex-core/v2/state" "github.com/iotexproject/iotex-core/v2/test/identityset" + "github.com/iotexproject/iotex-core/v2/testutil" ) var dummyCandidatesByHeightFunc = func(uint64, []byte) ([]string, error) { return nil, nil } @@ -43,7 +44,7 @@ func TestRollDPoSCtx(t *testing.T) { }) t.Run("case 2:panic because of rp is nil", func(t *testing.T) { - _, err := NewRollDPoSCtx(consensusfsm.NewConsensusConfig(cfg.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, cfg.Delay), dbConfig, true, time.Second, true, NewChainManager(b, sf, &dummyBlockBuildFactory{}), block.NewDeserializer(0), nil, nil, dummyCandidatesByHeightFunc, dummyCandidatesByHeightFunc, "", nil, nil, 0) + _, err := NewRollDPoSCtx(consensusfsm.NewConsensusConfig(cfg.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, cfg.Delay), dbConfig, true, time.Second, true, NewChainManager(b, sf, &dummyBlockBuildFactory{}, testutil.DummyBlockTimeBuilder()), block.NewDeserializer(0), nil, nil, dummyCandidatesByHeightFunc, dummyCandidatesByHeightFunc, "", nil, nil, 0) require.Error(err) }) @@ -53,7 +54,7 @@ func TestRollDPoSCtx(t *testing.T) { g.NumSubEpochs, ) t.Run("case 3:panic because of clock is nil", func(t *testing.T) { - _, err := NewRollDPoSCtx(consensusfsm.NewConsensusConfig(cfg.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, cfg.Delay), dbConfig, true, time.Second, true, NewChainManager(b, sf, &dummyBlockBuildFactory{}), block.NewDeserializer(0), rp, nil, dummyCandidatesByHeightFunc, dummyCandidatesByHeightFunc, "", nil, nil, 0) + _, err := NewRollDPoSCtx(consensusfsm.NewConsensusConfig(cfg.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, cfg.Delay), dbConfig, true, time.Second, true, NewChainManager(b, sf, &dummyBlockBuildFactory{}, testutil.DummyBlockTimeBuilder()), block.NewDeserializer(0), rp, nil, dummyCandidatesByHeightFunc, dummyCandidatesByHeightFunc, "", nil, nil, 0) require.Error(err) }) @@ -63,19 +64,19 @@ func TestRollDPoSCtx(t *testing.T) { cfg.FSM.AcceptLockEndorsementTTL = time.Second cfg.FSM.CommitTTL = time.Second t.Run("case 4:panic because of fsm time bigger than block interval", func(t *testing.T) { - _, err := NewRollDPoSCtx(consensusfsm.NewConsensusConfig(cfg.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, cfg.Delay), dbConfig, true, time.Second, true, NewChainManager(b, sf, &dummyBlockBuildFactory{}), block.NewDeserializer(0), rp, nil, dummyCandidatesByHeightFunc, dummyCandidatesByHeightFunc, "", nil, c, 0) + _, err := NewRollDPoSCtx(consensusfsm.NewConsensusConfig(cfg.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, cfg.Delay), dbConfig, true, time.Second, true, NewChainManager(b, sf, &dummyBlockBuildFactory{}, testutil.DummyBlockTimeBuilder()), block.NewDeserializer(0), rp, nil, dummyCandidatesByHeightFunc, dummyCandidatesByHeightFunc, "", nil, c, 0) require.Error(err) }) g.Blockchain.BlockInterval = time.Second * 20 t.Run("case 5:panic because of nil CandidatesByHeight function", func(t *testing.T) { - _, err := NewRollDPoSCtx(consensusfsm.NewConsensusConfig(cfg.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, cfg.Delay), dbConfig, true, time.Second, true, NewChainManager(b, sf, &dummyBlockBuildFactory{}), block.NewDeserializer(0), rp, nil, nil, nil, "", nil, c, 0) + _, err := NewRollDPoSCtx(consensusfsm.NewConsensusConfig(cfg.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, cfg.Delay), dbConfig, true, time.Second, true, NewChainManager(b, sf, &dummyBlockBuildFactory{}, testutil.DummyBlockTimeBuilder()), block.NewDeserializer(0), rp, nil, nil, nil, "", nil, c, 0) require.Error(err) }) t.Run("case 6:normal", func(t *testing.T) { bh := g.BeringBlockHeight - rctx, err := NewRollDPoSCtx(consensusfsm.NewConsensusConfig(cfg.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, cfg.Delay), dbConfig, true, time.Second, true, NewChainManager(b, sf, &dummyBlockBuildFactory{}), block.NewDeserializer(0), rp, nil, dummyCandidatesByHeightFunc, dummyCandidatesByHeightFunc, "", nil, c, bh) + rctx, err := NewRollDPoSCtx(consensusfsm.NewConsensusConfig(cfg.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, cfg.Delay), dbConfig, true, time.Second, true, NewChainManager(b, sf, &dummyBlockBuildFactory{}, testutil.DummyBlockTimeBuilder()), block.NewDeserializer(0), rp, nil, dummyCandidatesByHeightFunc, dummyCandidatesByHeightFunc, "", nil, c, bh) require.NoError(err) require.Equal(bh, rctx.RoundCalculator().beringHeight) require.NotNil(rctx) @@ -129,7 +130,7 @@ func TestCheckVoteEndorser(t *testing.T) { true, time.Second, true, - NewChainManager(b, sf, &dummyBlockBuildFactory{}), + NewChainManager(b, sf, &dummyBlockBuildFactory{}, testutil.DummyBlockTimeBuilder()), block.NewDeserializer(0), rp, nil, @@ -203,7 +204,7 @@ func TestCheckBlockProposer(t *testing.T) { true, time.Second, true, - NewChainManager(b, sf, &dummyBlockBuildFactory{}), + NewChainManager(b, sf, &dummyBlockBuildFactory{}, testutil.DummyBlockTimeBuilder()), block.NewDeserializer(0), rp, nil, @@ -315,7 +316,7 @@ func TestNotProducingMultipleBlocks(t *testing.T) { true, time.Second, true, - NewChainManager(b, sf, &dummyBlockBuildFactory{}), + NewChainManager(b, sf, &dummyBlockBuildFactory{}, testutil.DummyBlockTimeBuilder()), block.NewDeserializer(0), rp, nil, diff --git a/consensus/scheme/rolldpos/roundcalculator_test.go b/consensus/scheme/rolldpos/roundcalculator_test.go index bfeb150766..6be48c7e4e 100644 --- a/consensus/scheme/rolldpos/roundcalculator_test.go +++ b/consensus/scheme/rolldpos/roundcalculator_test.go @@ -265,7 +265,7 @@ func makeRoundCalculator(t *testing.T) *roundCalculator { return addrs, nil } return &roundCalculator{ - NewChainManager(bc, sf, &dummyBlockBuildFactory{}), + NewChainManager(bc, sf, &dummyBlockBuildFactory{}, testutil.DummyBlockTimeBuilder()), true, rp, delegatesByEpoch, diff --git a/e2etest/bigint_test.go b/e2etest/bigint_test.go index b9c99e1fa2..3a8ee566c0 100644 --- a/e2etest/bigint_test.go +++ b/e2etest/bigint_test.go @@ -111,6 +111,7 @@ func prepareBlockchain(ctx context.Context, _executor string, r *require.Asserti sf, genericValidator, )), + blockchain.BlockTimeCalculatorBuilderOption(testutil.DummyBlockTimeBuilder()), ) r.NotNil(bc) reward := rewarding.NewProtocol(cfg.Genesis.Rewarding) diff --git a/e2etest/contract_staking_test.go b/e2etest/contract_staking_test.go index ec8fa92df0..6754ba13b2 100644 --- a/e2etest/contract_staking_test.go +++ b/e2etest/contract_staking_test.go @@ -1989,6 +1989,7 @@ func prepareContractStakingBlockchain(ctx context.Context, cfg config.Config, r sf, protocol.NewGenericValidator(sf, accountutil.AccountState), )), + blockchain.BlockTimeCalculatorBuilderOption(testutil.DummyBlockTimeBuilder()), ) // reward := rewarding.NewProtocol(cfg.Genesis.Rewarding) // r.NoError(reward.Register(registry)) diff --git a/e2etest/local_test.go b/e2etest/local_test.go index 93f8cbfb83..b13dcc2a84 100644 --- a/e2etest/local_test.go +++ b/e2etest/local_test.go @@ -64,6 +64,7 @@ func TestLocalCommit(t *testing.T) { testTriePath := cfg.Chain.TrieDBPath testDBPath := cfg.Chain.ChainDBPath indexDBPath := cfg.Chain.IndexDBPath + cfg.ActPool.Store = nil // create server ctx := genesis.WithGenesisContext(context.Background(), cfg.Genesis) @@ -172,6 +173,7 @@ func TestLocalCommit(t *testing.T) { sf2, protocol.NewGenericValidator(sf2, accountutil.AccountState), )), + blockchain.BlockTimeCalculatorBuilderOption(testutil.DummyBlockTimeBuilder()), ) rolldposProtocol := rolldpos.NewProtocol( cfg.Genesis.NumCandidateDelegates, @@ -448,6 +450,7 @@ func TestStartExistingBlockchain(t *testing.T) { cfg.Chain.BlobStoreDBPath = testBlobIndexPath cfg.Chain.ContractStakingIndexDBPath = testContractStakeIndexPath cfg.Chain.EnableAsyncIndexWrite = false + cfg.ActPool.Store = nil cfg.Genesis = genesis.TestDefault() cfg.ActPool.MinGasPriceStr = "0" cfg.Consensus.Scheme = config.NOOPScheme diff --git a/e2etest/local_transfer_test.go b/e2etest/local_transfer_test.go index 762b6ec75b..08dbfad7f7 100644 --- a/e2etest/local_transfer_test.go +++ b/e2etest/local_transfer_test.go @@ -680,6 +680,7 @@ func TestEnforceChainID(t *testing.T) { cfg.Genesis, blkMemDao, factory.NewMinter(sf, ap, factory.WithPrivateKeyOption(cfg.Chain.ProducerPrivateKey())), + blockchain.BlockTimeCalculatorBuilderOption(testutil.DummyBlockTimeBuilder()), ) require.NoError(bc.Start(ctx)) diff --git a/gasstation/gasstattion_test.go b/gasstation/gasstattion_test.go index f840ec3782..414b4895b4 100644 --- a/gasstation/gasstattion_test.go +++ b/gasstation/gasstattion_test.go @@ -98,6 +98,7 @@ func TestSuggestGasPriceForUserAction(t *testing.T) { sf, protocol.NewGenericValidator(sf, accountutil.AccountState), )), + blockchain.BlockTimeCalculatorBuilderOption(testutil.DummyBlockTimeBuilder()), ) ep := execution.NewProtocol(blkMemDao.GetBlockHash, rewarding.DepositGas, func(u uint64) (time.Time, error) { return time.Time{}, nil }) require.NoError(t, ep.Register(registry)) @@ -169,6 +170,7 @@ func TestSuggestGasPriceForSystemAction(t *testing.T) { sf, protocol.NewGenericValidator(sf, accountutil.AccountState), )), + blockchain.BlockTimeCalculatorBuilderOption(testutil.DummyBlockTimeBuilder()), ) ep := execution.NewProtocol(blkMemDao.GetBlockHash, rewarding.DepositGas, func(u uint64) (time.Time, error) { return time.Time{}, nil }) require.NoError(t, ep.Register(registry)) diff --git a/pkg/util/blockutil/block_time_calculator.go b/pkg/util/blockutil/block_time_calculator.go index 71726fcb14..47115f022e 100644 --- a/pkg/util/blockutil/block_time_calculator.go +++ b/pkg/util/blockutil/block_time_calculator.go @@ -14,11 +14,48 @@ type ( getHistoryBlockTime getHistoryblockTimeFn } + BlockTimeCalculatorBuilder struct { + getBlockInterval getBlockIntervalFn + getTipHeight getTipHeightFn + getHistoryBlockTime getHistoryblockTimeFn + } + getBlockIntervalFn func(uint64) time.Duration getTipHeightFn func() uint64 getHistoryblockTimeFn func(uint64) (time.Time, error) ) +func NewBlockTimeCalculatorBuilder() *BlockTimeCalculatorBuilder { + return &BlockTimeCalculatorBuilder{} +} + +func (btc *BlockTimeCalculatorBuilder) Clone() *BlockTimeCalculatorBuilder { + return &BlockTimeCalculatorBuilder{ + getBlockInterval: btc.getBlockInterval, + getTipHeight: btc.getTipHeight, + getHistoryBlockTime: btc.getHistoryBlockTime, + } +} + +func (btc *BlockTimeCalculatorBuilder) SetBlockInterval(getBlockInterval getBlockIntervalFn) *BlockTimeCalculatorBuilder { + btc.getBlockInterval = getBlockInterval + return btc +} + +func (btc *BlockTimeCalculatorBuilder) SetTipHeight(getTipHeight getTipHeightFn) *BlockTimeCalculatorBuilder { + btc.getTipHeight = getTipHeight + return btc +} + +func (btc *BlockTimeCalculatorBuilder) SetHistoryBlockTime(getHistoryBlockTime getHistoryblockTimeFn) *BlockTimeCalculatorBuilder { + btc.getHistoryBlockTime = getHistoryBlockTime + return btc +} + +func (btc *BlockTimeCalculatorBuilder) Build() (*BlockTimeCalculator, error) { + return NewBlockTimeCalculator(btc.getBlockInterval, btc.getTipHeight, btc.getHistoryBlockTime) +} + // NewBlockTimeCalculator creates a new BlockTimeCalculator. func NewBlockTimeCalculator(getBlockInterval getBlockIntervalFn, getTipHeight getTipHeightFn, getHistoryBlockTime getHistoryblockTimeFn) (*BlockTimeCalculator, error) { if getBlockInterval == nil { diff --git a/state/factory/factory_test.go b/state/factory/factory_test.go index 4d8c08defb..458eb81d6d 100644 --- a/state/factory/factory_test.go +++ b/state/factory/factory_test.go @@ -330,7 +330,9 @@ func testCandidates(sf Factory, t *testing.T) { }, ), protocol.BlockchainCtx{ - ChainID: 1, + ChainID: 1, + GetBlockHash: func(uint64) (hash.Hash256, error) { return hash.ZeroHash256, nil }, + GetBlockTime: func(uint64) (time.Time, error) { return time.Now(), nil }, }, ), ), diff --git a/testutil/blockutil.go b/testutil/blockutil.go new file mode 100644 index 0000000000..90347729c0 --- /dev/null +++ b/testutil/blockutil.go @@ -0,0 +1,14 @@ +package testutil + +import ( + "time" + + "github.com/iotexproject/iotex-core/v2/pkg/util/blockutil" +) + +func DummyBlockTimeBuilder() *blockutil.BlockTimeCalculatorBuilder { + return blockutil.NewBlockTimeCalculatorBuilder(). + SetBlockInterval(func(uint64) time.Duration { return 5 * time.Second }). + SetTipHeight(func() uint64 { return 1 }). + SetHistoryBlockTime(func(uint64) (time.Time, error) { return time.Now(), nil }) +} diff --git a/tools/actioninjector.v2/internal/client/client_test.go b/tools/actioninjector.v2/internal/client/client_test.go index 29a4be0a80..8c557fb92b 100644 --- a/tools/actioninjector.v2/internal/client/client_test.go +++ b/tools/actioninjector.v2/internal/client/client_test.go @@ -5,7 +5,6 @@ import ( "fmt" "math/big" "testing" - "time" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" @@ -86,7 +85,7 @@ func TestClient(t *testing.T) { require.NoError(err) bfIndexer, err := blockindex.NewBloomfilterIndexer(db.NewMemKVStore(), cfg.Indexer) require.NoError(err) - apiServer, err := api.NewServerV2(cfg.API, bc, nil, sf, nil, indexer, bfIndexer, ap, nil, func(u uint64) (time.Time, error) { return time.Time{}, nil }, newOption) + apiServer, err := api.NewServerV2(cfg.API, bc, nil, sf, nil, indexer, bfIndexer, ap, nil, newOption) require.NoError(err) require.NoError(apiServer.Start(ctx)) // test New()