From 961369e3b9e9ca0f2fd297124b9a147be4fdb46e Mon Sep 17 00:00:00 2001 From: envestcc Date: Wed, 26 Mar 2025 11:49:48 +0800 Subject: [PATCH 1/3] prepare next proposal --- consensus/consensusfsm/context.go | 1 + consensus/consensusfsm/fsm.go | 4 +- consensus/consensusfsm/fsm_test.go | 1 + consensus/consensusfsm/mock_context_test.go | 14 +++++++ consensus/scheme/rolldpos/rolldposctx.go | 46 +++++++++++++++++++++ 5 files changed, 65 insertions(+), 1 deletion(-) diff --git a/consensus/consensusfsm/context.go b/consensus/consensusfsm/context.go index dbee63dd83..4781f099f4 100644 --- a/consensus/consensusfsm/context.go +++ b/consensus/consensusfsm/context.go @@ -34,6 +34,7 @@ type Context interface { Prepare() error HasDelegate() 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 694c763f4d..8414e41dbf 100644 --- a/consensus/consensusfsm/fsm.go +++ b/consensus/consensusfsm/fsm.go @@ -466,7 +466,9 @@ func (m *ConsensusFSM) onReceiveBlock(evt fsm.Event) (fsm.State, error) { m.ctx.Logger().Debug("Failed to generate proposal endorsement", zap.Error(err)) return sAcceptBlockProposal, nil } - + if err := m.ctx.PrepareNextProposal(cEvt.Data()); err != nil { + m.ctx.Logger().Warn("Failed to prepare next proposal", zap.Error(err)) + } return sAcceptProposalEndorsement, nil } diff --git a/consensus/consensusfsm/fsm_test.go b/consensus/consensusfsm/fsm_test.go index d37827bf72..bfede015a3 100644 --- a/consensus/consensusfsm/fsm_test.go +++ b/consensus/consensusfsm/fsm_test.go @@ -240,6 +240,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 4257115da2..73bfb54863 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/rolldposctx.go b/consensus/scheme/rolldpos/rolldposctx.go index 8cf0eb60c7..b762b1ca67 100644 --- a/consensus/scheme/rolldpos/rolldposctx.go +++ b/consensus/scheme/rolldpos/rolldposctx.go @@ -405,6 +405,52 @@ func (ctx *rollDPoSCtx) Proposal() (interface{}, error) { return ctx.mintNewBlock(privateKey) } +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[:]) + } + 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 := roundCalc.Proposer(height, interval, startTime) + var privateKey crypto.PrivateKey = nil + if idx := slices.Index(ctx.encodedAddrs, nextProposer); idx < 0 { + return nil + } else { + privateKey = ctx.priKeys[idx] + } + ctx.logger().Debug("prepare next proposal", log.Hex("prevHash", prevHash[:]), zap.Uint64("height", ctx.round.height+1), zap.Time("timestamp", startTime), zap.String("nextproposer", nextProposer)) + go func() { + blk, err := fork.MintNewBlock(startTime, privateKey, prevHash) + 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 ef2909cb81b777d8ea196572fbd68073793e3758 Mon Sep 17 00:00:00 2001 From: envestcc Date: Thu, 10 Apr 2025 15:13:35 +0800 Subject: [PATCH 2/3] address comment --- consensus/consensusfsm/context.go | 1 - consensus/consensusfsm/fsm.go | 3 -- consensus/consensusfsm/fsm_test.go | 1 - consensus/consensusfsm/mock_context_test.go | 14 --------- consensus/scheme/rolldpos/rolldposctx.go | 32 +++++++++------------ 5 files changed, 13 insertions(+), 38 deletions(-) diff --git a/consensus/consensusfsm/context.go b/consensus/consensusfsm/context.go index 4781f099f4..dbee63dd83 100644 --- a/consensus/consensusfsm/context.go +++ b/consensus/consensusfsm/context.go @@ -34,7 +34,6 @@ type Context interface { Prepare() error HasDelegate() 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 8414e41dbf..c31db07675 100644 --- a/consensus/consensusfsm/fsm.go +++ b/consensus/consensusfsm/fsm.go @@ -466,9 +466,6 @@ func (m *ConsensusFSM) onReceiveBlock(evt fsm.Event) (fsm.State, error) { m.ctx.Logger().Debug("Failed to generate proposal endorsement", zap.Error(err)) return sAcceptBlockProposal, nil } - if err := m.ctx.PrepareNextProposal(cEvt.Data()); err != nil { - m.ctx.Logger().Warn("Failed to prepare next proposal", zap.Error(err)) - } return sAcceptProposalEndorsement, nil } diff --git a/consensus/consensusfsm/fsm_test.go b/consensus/consensusfsm/fsm_test.go index bfede015a3..d37827bf72 100644 --- a/consensus/consensusfsm/fsm_test.go +++ b/consensus/consensusfsm/fsm_test.go @@ -240,7 +240,6 @@ 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 73bfb54863..4257115da2 100644 --- a/consensus/consensusfsm/mock_context_test.go +++ b/consensus/consensusfsm/mock_context_test.go @@ -373,20 +373,6 @@ 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/rolldposctx.go b/consensus/scheme/rolldpos/rolldposctx.go index b762b1ca67..a1305aa894 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" @@ -405,32 +406,18 @@ func (ctx *rollDPoSCtx) Proposal() (interface{}, error) { return ctx.mintNewBlock(privateKey) } -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") - } +func (ctx *rollDPoSCtx) prepareNextProposal(prevHeight uint64, prevHash hash.Hash256) error { var ( - blk = proposal.block - height = blk.Height() + 1 + height = prevHeight + 1 interval = ctx.BlockInterval(height) - startTime = blk.Timestamp().Add(interval) - prevHash = blk.HashBlock() + startTime = ctx.round.StartTime().Add(interval) 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[:]) - } - roundCalc, err := ctx.roundCalc.Fork(prevHash) - if err != nil { - return errors.Wrapf(err, "failed to fork at block %d, hash %x", blk.Height(), prevHash[:]) + return errors.Wrapf(err, "failed to check fork at block %d, hash %x", prevHeight, prevHash[:]) } + roundCalc := ctx.roundCalc.Fork(fork) // check if the current node is the next proposer nextProposer := roundCalc.Proposer(height, interval, startTime) var privateKey crypto.PrivateKey = nil @@ -500,9 +487,16 @@ func (ctx *rollDPoSCtx) NewProposalEndorsement(msg interface{}) (interface{}, er if err := ctx.round.AddBlock(proposal.block); err != nil { return nil, err } + if err := ctx.prepareNextProposal(proposal.block.Height(), blkHash); err != nil { + ctx.loggerWithStats().Warn("failed to prepare next proposal", zap.Error(err), zap.Uint64("prevHeight", proposal.block.Height())) + } ctx.loggerWithStats().Debug("accept block proposal", log.Hex("block", blockHash)) } else if ctx.round.IsLocked() { blockHash = ctx.round.HashOfBlockInLock() + } else { + if err := ctx.prepareNextProposal(ctx.round.Height()-1, ctx.round.PrevHash()); err != nil { + ctx.loggerWithStats().Warn("failed to prepare next proposal", zap.Error(err), zap.Uint64("prevHeight", ctx.round.Height()-1)) + } } // TODO: prepare next block if the current node will be a proposer From efc580c613dbffdba63c841f7a5d4e9b077683d1 Mon Sep 17 00:00:00 2001 From: envestcc Date: Mon, 21 Apr 2025 14:07:24 +0800 Subject: [PATCH 3/3] address comment --- consensus/scheme/rolldpos/rolldposctx.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/consensus/scheme/rolldpos/rolldposctx.go b/consensus/scheme/rolldpos/rolldposctx.go index a1305aa894..0902648540 100644 --- a/consensus/scheme/rolldpos/rolldposctx.go +++ b/consensus/scheme/rolldpos/rolldposctx.go @@ -420,12 +420,11 @@ func (ctx *rollDPoSCtx) prepareNextProposal(prevHeight uint64, prevHash hash.Has roundCalc := ctx.roundCalc.Fork(fork) // check if the current node is the next proposer nextProposer := roundCalc.Proposer(height, interval, startTime) - var privateKey crypto.PrivateKey = nil - if idx := slices.Index(ctx.encodedAddrs, nextProposer); idx < 0 { + idx := slices.Index(ctx.encodedAddrs, nextProposer) + if idx < 0 { return nil - } else { - privateKey = ctx.priKeys[idx] } + privateKey := ctx.priKeys[idx] ctx.logger().Debug("prepare next proposal", log.Hex("prevHash", prevHash[:]), zap.Uint64("height", ctx.round.height+1), zap.Time("timestamp", startTime), zap.String("nextproposer", nextProposer)) go func() { blk, err := fork.MintNewBlock(startTime, privateKey, prevHash)