diff --git a/app/app.go b/app/app.go index d9a815c30..0d9940a23 100644 --- a/app/app.go +++ b/app/app.go @@ -15,7 +15,6 @@ import ( "github.com/Fantom-foundation/go-lachesis/evmcore" "github.com/Fantom-foundation/go-lachesis/inter" "github.com/Fantom-foundation/go-lachesis/inter/idx" - "github.com/Fantom-foundation/go-lachesis/inter/sfctype" "github.com/Fantom-foundation/go-lachesis/logger" ) @@ -59,25 +58,22 @@ func New(cfg Config, s *Store) *App { // beginBlock signals the beginning of a block. func (a *App) beginBlock( evmHeader evmcore.EvmHeader, - stateRoot common.Hash, cheaters inter.Cheaters, - blockParticipated map[idx.StakerID]bool, -) { + participated map[idx.StakerID]bool, +) (sealEpoch bool) { block := blockInfo(&evmHeader) epoch := a.GetEpoch() + sealEpoch = a.shouldSealEpoch(block, cheaters) prev := a.store.GetBlock(block.Index - 1) - if prev.Root != stateRoot { - panic("inconsistent state db") - } a.ctx = &blockContext{ block: block, header: &evmHeader, - statedb: a.store.StateDB(stateRoot), + statedb: a.store.StateDB(prev.Root), evmProcessor: evmcore.NewStateProcessor(a.config.Net.EvmChainConfig(), a.BlockChain()), cheaters: cheaters, - sealEpoch: a.shouldSealEpoch(block, cheaters), + sealEpoch: sealEpoch, gp: new(evmcore.GasPool), totalFee: big.NewInt(0), txCount: 0, @@ -85,7 +81,9 @@ func (a *App) beginBlock( a.ctx.header.GasUsed = 0 a.ctx.gp.AddGas(evmHeader.GasLimit) - a.updateValidationScores(epoch, block.Index, blockParticipated) + a.updateValidationScores(epoch, block.Index, participated) + + return } // deliverTx for full processing. @@ -110,13 +108,11 @@ func (a *App) deliverTx(tx *eth.Transaction, originator idx.StakerID) (*eth.Rece } // endBlock signals the end of a block, returns changes to the validator set. -func (a *App) endBlock(n idx.Block) (sealEpoch bool) { +func (a *App) endBlock(n idx.Block) { if a.ctx.block.Index != n { a.Log.Crit("missed block", "current", a.ctx.block.Index, "got", n) } - sealEpoch = a.ctx.sealEpoch || sfctype.EpochIsForceSealed(a.ctx.receipts) - for _, r := range a.ctx.receipts { a.store.IndexLogs(r.Logs...) } @@ -126,22 +122,20 @@ func (a *App) endBlock(n idx.Block) (sealEpoch bool) { } // Process PoI/score changes - a.updateOriginationScores(sealEpoch) + a.updateOriginationScores(a.ctx.sealEpoch) a.updateUsersPOI(a.ctx.block, a.ctx.txs, a.ctx.receipts) a.updateStakersPOI(a.ctx.block) // Process SFC contract transactions epoch := a.GetEpoch() - stats := a.updateEpochStats(epoch, a.ctx.block.Time, a.ctx.totalFee, sealEpoch) + stats := a.updateEpochStats(epoch, a.ctx.block.Time, a.ctx.totalFee, a.ctx.sealEpoch) a.processSfc(epoch, a.ctx.block, a.ctx.receipts, a.ctx.cheaters, stats) a.incLastBlock() - if sealEpoch { + if a.ctx.sealEpoch { a.SetLastVoting(a.ctx.block.Index, a.ctx.block.Time) a.incEpoch() } - - return sealEpoch } // commit the state and return the application Merkle root hash. diff --git a/app/common_test.go b/app/common_test.go index 21f23d6e9..2be8bb0bd 100644 --- a/app/common_test.go +++ b/app/common_test.go @@ -168,7 +168,7 @@ func (env *testEnv) ApplyBlock(spent time.Duration, txs ...*eth.Transaction) eth blockParticipated[p] = true } - env.App.beginBlock(evmHeader, env.lastState, inter.Cheaters{}, blockParticipated) + env.App.beginBlock(evmHeader, inter.Cheaters{}, blockParticipated) receipts := make(eth.Receipts, len(txs)) for i, tx := range txs { diff --git a/app/tendermint.go b/app/tendermint.go index e4aa4ffff..9b512511b 100644 --- a/app/tendermint.go +++ b/app/tendermint.go @@ -39,13 +39,19 @@ func (a *App) CheckTx(req types.RequestCheckTx) types.ResponseCheckTx { // Wraps beginBlock() to implement ABCIApplication.BeginBlock. func (a *App) BeginBlock(req types.RequestBeginBlock) types.ResponseBeginBlock { evmHeader := extractEvmHeader(req) - stateRoot := extractStateRoot(req) cheaters := extractCheaters(req) blockParticipated := extractParticipated(req) - a.beginBlock(evmHeader, stateRoot, cheaters, blockParticipated) + sealEpoch := a.beginBlock(evmHeader, cheaters, blockParticipated) - return types.ResponseBeginBlock{} + res := types.ResponseBeginBlock{} + if sealEpoch { + res.Events = []types.Event{ + {Type: "epoch sealed"}, + } + } + + return res } // DeliverTx for full processing. @@ -75,15 +81,8 @@ func (a *App) DeliverTx(req types.RequestDeliverTx) types.ResponseDeliverTx { // Wraps endBlock() to implement ABCIApplication.EndBlock. func (a *App) EndBlock(req types.RequestEndBlock) types.ResponseEndBlock { n := idx.Block(req.Height) - - sealEpoch := a.endBlock(n) - + a.endBlock(n) res := types.ResponseEndBlock{} - if sealEpoch { - res.Events = []types.Event{ - {Type: "epoch sealed"}, - } - } return res } @@ -115,11 +114,6 @@ func extractCheaters(req types.RequestBeginBlock) inter.Cheaters { return cheaters } -func extractStateRoot(req types.RequestBeginBlock) common.Hash { - return common.BytesToHash( - req.Header.LastCommitHash) -} - func extractParticipated(req types.RequestBeginBlock) map[idx.StakerID]bool { res := make(map[idx.StakerID]bool, len(req.LastCommitInfo.Votes)) for _, v := range req.LastCommitInfo.Votes { diff --git a/gossip/consensus_callbacks.go b/gossip/consensus_callbacks.go index c95bff664..bf4ab73bb 100644 --- a/gossip/consensus_callbacks.go +++ b/gossip/consensus_callbacks.go @@ -6,17 +6,13 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" - tendermint "github.com/tendermint/tendermint/abci/types" - "github.com/Fantom-foundation/go-lachesis/app" "github.com/Fantom-foundation/go-lachesis/eventcheck" "github.com/Fantom-foundation/go-lachesis/eventcheck/epochcheck" - "github.com/Fantom-foundation/go-lachesis/evmcore" "github.com/Fantom-foundation/go-lachesis/inter" "github.com/Fantom-foundation/go-lachesis/inter/idx" "github.com/Fantom-foundation/go-lachesis/inter/pos" "github.com/Fantom-foundation/go-lachesis/inter/sfctype" - "github.com/Fantom-foundation/go-lachesis/tracing" "github.com/Fantom-foundation/go-lachesis/utils" ) @@ -90,100 +86,6 @@ func (s *Service) processEvent(realEngine Consensus, e *inter.Event) error { return s.store.Commit(e.Hash().Bytes(), immediately) } -// applyNewState moves the state according to new block (txs execution, SFC logic, epoch sealing) -func (s *Service) applyNewState( - abci tendermint.Application, - block *inter.Block, - cheaters inter.Cheaters, -) ( - *inter.Block, - map[common.Hash]*app.TxPosition, - types.Transactions, - bool, -) { - // s.engineMu is locked here - - start := time.Now() - - events, allTxs := s.usedEvents(block) - unusedCount := len(block.Events) - len(events) - block.Events = block.Events[unusedCount:] - - // memorize position of each tx, for indexing and origination scores - txsPositions := make(map[common.Hash]*app.TxPosition) - for _, e := range events { - for i, tx := range e.Transactions { - // if tx was met in multiple events, then assign to first ordered event - if _, ok := txsPositions[tx.Hash()]; ok { - continue - } - txsPositions[tx.Hash()] = &app.TxPosition{ - Event: e.Hash(), - Creator: e.Creator, - EventOffset: uint32(i), - } - } - } - - epoch := s.engine.GetEpoch() - stateRoot := s.store.GetBlock(block.Index - 1).Root - - abci.BeginBlock( - beginBlockRequest(cheaters, stateRoot, block, s.blockParticipated)) - - okTxs := make(types.Transactions, 0, len(allTxs)) - block.SkippedTxs = make([]uint, 0, len(allTxs)) - for i, tx := range allTxs { - originator := txsPositions[tx.Hash()].Creator - req := deliverTxRequest(tx, originator) - resp := abci.DeliverTx(req) - block.GasUsed += uint64(resp.GasUsed) - - if resp.Code != txIsFullyValid { - block.SkippedTxs = append(block.SkippedTxs, uint(i)) - continue - } - okTxs = append(okTxs, tx) - txsPositions[tx.Hash()].Block = block.Index - notUsedGas := resp.GasWanted - resp.GasUsed - s.store.IncGasPowerRefund(epoch, originator, notUsedGas) - - if resp.Log != "" { - s.Log.Info("tx processed", "log", resp.Log) - } - } - - var sealEpoch bool - resp := abci.EndBlock(endBlockRequest(block.Index)) - for _, appEvent := range resp.Events { - switch appEvent.Type { - case "epoch sealed": - sealEpoch = true - } - } - - commit := abci.Commit() - block.Root = common.BytesToHash(commit.Data) - block.TxHash = types.DeriveSha(okTxs) - - // process new epoch - if sealEpoch { - // prune not needed gas power records - s.store.DelGasPowerRefunds(epoch - 1) - s.onEpochSealed(block, cheaters) - } - - log.Info("New block", - "index", block.Index, - "atropos", block.Atropos, - "gasUsed", block.GasUsed, - "skipped_txs", len(block.SkippedTxs), - "txs", len(okTxs), - "t", time.Since(start)) - - return block, txsPositions, okTxs, sealEpoch -} - // spillBlockEvents excludes first events which exceed BlockGasHardLimit func (s *Service) spillBlockEvents(block *inter.Block) inter.Events { events := make(inter.Events, len(block.Events)) @@ -252,53 +154,16 @@ func (s *Service) legacyShouldSealEpoch(block *inter.Block, decidedFrame idx.Fra } // applyBlock execs ordered txns of new block on state, and fills the block DB indexes. -func (s *Service) applyBlock(block *inter.Block, decidedFrame idx.Frame, cheaters inter.Cheaters) (newAppHash common.Hash, sealEpoch bool) { +func (s *Service) applyBlock(block *inter.Block, decidedFrame idx.Frame, cheaters inter.Cheaters) (sealEpoch bool, newAppHashes []common.Hash) { // s.engineMu is locked here s.updateMetrics(block) - block, txsPositions, okTxs, sealEpoch := s.applyNewState(s.abciApp, block, cheaters) - newAppHash = block.TxHash - - s.store.SetBlock(block) - s.store.SetBlockIndex(block.Atropos, block.Index) - - // Build index for txs - if s.config.TxIndex { - var i uint32 - for txHash, txPos := range txsPositions { - if txPos.Block <= 0 { - continue - } - // not skipped txs only - txPos.BlockOffset = i - i++ - s.store.SetTxPosition(txHash, txPos) - } - } - - // Trace by which event this block was confirmed (only for API) - if s.config.DecisiveEventsIndex { - s.store.SetBlockDecidedBy(block.Index, s.currentEvent) - } - - evmHeader := evmcore.ToEvmHeader(block) - s.feed.newBlock.Send(evmcore.ChainHeadNotify{ - Block: &evmcore.EvmBlock{ - EvmHeader: *evmHeader, - Transactions: okTxs, - }}) - - // trace confirmed transactions - confirmTxnsMeter.Inc(int64(len(txsPositions))) - for tx := range txsPositions { - tracing.FinishTx(tx, "Service.onNewBlock()") - if latency, err := txLatency.Finish(tx); err == nil { - txTtfMeter.Update(latency.Milliseconds()) - } - } + // reset map of participated validators + var participated map[idx.StakerID]bool + participated, s.blockParticipated = s.blockParticipated, make(map[idx.StakerID]bool) - s.blockParticipated = make(map[idx.StakerID]bool) // reset map of participated validators + sealEpoch, newAppHashes = s.applyNewStateAsync(block, cheaters, participated, decidedFrame) return } @@ -315,7 +180,7 @@ func (s *Service) updateMetrics(block *inter.Block) { epochGauge.Update(int64(epoch)) // lachesis_epoch:time - epochStat := s.abciApp.GetEpochStats(epoch-1) + epochStat := s.abciApp.GetEpochStats(epoch - 1) if epochStat != nil { epochTimeGauge.Update(int64(time.Since(epochStat.End.Time()).Seconds())) if epochStat.TotalFee != nil { diff --git a/gossip/evm_async.go b/gossip/evm_async.go new file mode 100644 index 000000000..48731a3d8 --- /dev/null +++ b/gossip/evm_async.go @@ -0,0 +1,221 @@ +package gossip + +import ( + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + tendermint "github.com/tendermint/tendermint/abci/types" + + "github.com/Fantom-foundation/go-lachesis/app" + "github.com/Fantom-foundation/go-lachesis/evmcore" + "github.com/Fantom-foundation/go-lachesis/inter" + "github.com/Fantom-foundation/go-lachesis/inter/idx" + "github.com/Fantom-foundation/go-lachesis/tracing" +) + +type ( + // evmProcessor organizes evm async processing + evmProcessor struct { + sync.WaitGroup + blocks chan *processBlockArgs + blockTxHashes []common.Hash // buffer of block.TxHash to return to the consensus + } + + processBlockArgs struct { + Start time.Time + Epoch idx.Epoch + Block *inter.Block + Cheaters inter.Cheaters + BlockParticipated map[idx.StakerID]bool + ShouldSealEpoch bool + } +) + +func (ep *evmProcessor) Init(maxEpochBlocks int) { + ep.blocks = make(chan *processBlockArgs, maxEpochBlocks) +} + +func (s *Service) startEvmProcessing() { + go func() { + for { + select { + case <-s.done: + return + case args := <-s.evmProcessor.blocks: + s.processBlockAsync( + args.Start, + args.Epoch, + args.Block, + args.Cheaters, + args.BlockParticipated, + args.ShouldSealEpoch) + s.evmProcessor.Done() + } + } + }() +} + +// applyNewStateAsync is an async option of Service.applyNewStateSync() +func (s *Service) applyNewStateAsync( + block *inter.Block, + cheaters inter.Cheaters, + blockParticipated map[idx.StakerID]bool, + frame idx.Frame, +) ( + sealEpoch bool, + blockTxHashes []common.Hash, +) { + // s.engineMu is locked here + + start := time.Now() + epoch := s.engine.GetEpoch() + + sealEpoch = s.legacyShouldSealEpoch(block, frame, cheaters) + + s.evmProcessor.Add(1) + s.evmProcessor.blocks <- &processBlockArgs{ + Start: start, + Epoch: epoch, + Block: block, + Cheaters: cheaters, + BlockParticipated: blockParticipated, + ShouldSealEpoch: sealEpoch, + } + + // process new epoch + if sealEpoch { + s.evmProcessor.Wait() + // prune not needed gas power records + s.store.DelGasPowerRefunds(epoch - 1) + s.onEpochSealed(block, cheaters) + blockTxHashes, s.evmProcessor.blockTxHashes = s.evmProcessor.blockTxHashes, nil + } + + return +} + +// processBlockAsync is an async part of Service.applyNewStateAsync() +func (s *Service) processBlockAsync( + start time.Time, + epoch idx.Epoch, + block *inter.Block, + cheaters inter.Cheaters, + participated map[idx.StakerID]bool, + shouldSealEpoch bool, +) { + events, allTxs := s.usedEvents(block) + unusedCount := len(block.Events) - len(events) + block.Events = block.Events[unusedCount:] + // memorize position of each tx, for indexing and origination scores + txsPositions := make(map[common.Hash]*app.TxPosition) + for _, e := range events { + for i, tx := range e.Transactions { + // if tx was met in multiple events, then assign to first ordered event + if _, ok := txsPositions[tx.Hash()]; ok { + continue + } + txsPositions[tx.Hash()] = &app.TxPosition{ + Event: e.Hash(), + Creator: e.Creator, + EventOffset: uint32(i), + } + } + } + + var ( + abci tendermint.Application = s.abciApp + sealEpoch bool + ) + + resp := abci.BeginBlock( + beginBlockRequest(block, cheaters, participated)) + for _, appEvent := range resp.Events { + switch appEvent.Type { + case "epoch sealed": + sealEpoch = true + } + } + if sealEpoch != shouldSealEpoch { + s.Log.Crit("sealEpoch missing", "got", sealEpoch, "want", shouldSealEpoch) + } + + okTxs := make(types.Transactions, 0, len(allTxs)) + block.SkippedTxs = make([]uint, 0, len(allTxs)) + for i, tx := range allTxs { + originator := txsPositions[tx.Hash()].Creator + req := deliverTxRequest(tx, originator) + resp := abci.DeliverTx(req) + block.GasUsed += uint64(resp.GasUsed) + + if resp.Code != txIsFullyValid { + block.SkippedTxs = append(block.SkippedTxs, uint(i)) + continue + } + okTxs = append(okTxs, tx) + txsPositions[tx.Hash()].Block = block.Index + notUsedGas := resp.GasWanted - resp.GasUsed + s.store.IncGasPowerRefund(epoch, originator, notUsedGas) + + if resp.Log != "" { + s.Log.Info("tx processed", "log", resp.Log) + } + } + + abci.EndBlock(endBlockRequest(block.Index)) + + commit := abci.Commit() + block.Root = common.BytesToHash(commit.Data) + block.TxHash = types.DeriveSha(okTxs) + s.blockTxHashes = append(s.blockTxHashes, block.TxHash) + + log.Info("New block", + "index", block.Index, + "atropos", block.Atropos, + "gasUsed", block.GasUsed, + "skipped_txs", len(block.SkippedTxs), + "txs", len(okTxs), + "t", time.Since(start)) + + s.store.SetBlock(block) + s.store.SetBlockIndex(block.Atropos, block.Index) + + // Build index for txs + if s.config.TxIndex { + var i uint32 + for txHash, txPos := range txsPositions { + if txPos.Block <= 0 { + continue + } + // not skipped txs only + txPos.BlockOffset = i + i++ + s.store.SetTxPosition(txHash, txPos) + } + } + + // Trace by which event this block was confirmed (only for API) + if s.config.DecisiveEventsIndex { + s.store.SetBlockDecidedBy(block.Index, s.currentEvent) + } + + evmHeader := evmcore.ToEvmHeader(block) + s.feed.newBlock.Send(evmcore.ChainHeadNotify{ + Block: &evmcore.EvmBlock{ + EvmHeader: *evmHeader, + Transactions: okTxs, + }}) + + // trace confirmed transactions + confirmTxnsMeter.Inc(int64(len(txsPositions))) + for tx := range txsPositions { + tracing.FinishTx(tx, "Service.onNewBlock()") + if latency, err := txLatency.Finish(tx); err == nil { + txTtfMeter.Update(latency.Milliseconds()) + } + } + + return +} diff --git a/gossip/service.go b/gossip/service.go index c0a773a3e..2c4f174ca 100644 --- a/gossip/service.go +++ b/gossip/service.go @@ -99,6 +99,7 @@ type Service struct { // global variables. TODO refactor to pass them as arguments if possible blockParticipated map[idx.StakerID]bool // validators who participated in last block currentEvent hash.Event // current event which is being processed + evmProcessor feed ServiceFeed @@ -300,6 +301,8 @@ func (s *Service) Start(srv *p2p.Server) error { } s.abciApp.Start() + s.evmProcessor.Init(int(s.config.Net.Dag.MaxEpochBlocks)) + s.startEvmProcessing() s.pm.Start(srv.MaxPeers) diff --git a/gossip/tendermint.go b/gossip/tendermint.go index 8925061d1..5c191f5a2 100644 --- a/gossip/tendermint.go +++ b/gossip/tendermint.go @@ -1,7 +1,6 @@ package gossip import ( - "github.com/ethereum/go-ethereum/common" eth "github.com/ethereum/go-ethereum/core/types" "github.com/tendermint/tendermint/abci/types" @@ -21,9 +20,8 @@ func (s *Service) initApp() { } func beginBlockRequest( - cheaters inter.Cheaters, - stateHash common.Hash, block *inter.Block, + cheaters inter.Cheaters, participated map[idx.StakerID]bool, ) types.RequestBeginBlock { @@ -55,7 +53,6 @@ func beginBlockRequest( LastBlockId: types.BlockID{ Hash: block.PrevHash.Bytes(), }, - LastCommitHash: stateHash.Bytes(), }, LastCommitInfo: types.LastCommitInfo{ Votes: votes, diff --git a/inter/block.go b/inter/block.go index 92b8130f8..05dbaa54a 100644 --- a/inter/block.go +++ b/inter/block.go @@ -11,7 +11,7 @@ import ( // ConsensusCallbacks contains callbacks called during block processing by consensus engine type ConsensusCallbacks struct { // ApplyBlock is callback type to apply the new block to the state - ApplyBlock func(block *Block, decidedFrame idx.Frame, cheaters Cheaters) (newAppHash common.Hash, sealEpoch bool) + ApplyBlock func(block *Block, decidedFrame idx.Frame, cheaters Cheaters) (sealEpoch bool, newAppHashes []common.Hash) // SelectValidatorsGroup is a callback type to select new validators group. SelectValidatorsGroup func(oldEpoch, newEpoch idx.Epoch) (newValidators *pos.Validators) // OnEventConfirmed is callback type to notify about event confirmation. diff --git a/inter/sfctype/sfc_type.go b/inter/sfctype/sfc_type.go index 01ec7d894..be289657b 100644 --- a/inter/sfctype/sfc_type.go +++ b/inter/sfctype/sfc_type.go @@ -4,9 +4,7 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/Fantom-foundation/go-lachesis/hash" "github.com/Fantom-foundation/go-lachesis/inter" "github.com/Fantom-foundation/go-lachesis/inter/idx" ) @@ -103,22 +101,3 @@ type EpochStats struct { func (s *EpochStats) Duration() inter.Timestamp { return s.End - s.Start } - -// EpochIsForceSealed -func EpochIsForceSealed(receipts types.Receipts) bool { - // temporary magic, will be placed to a constant - asSigHash := hash.Of([]byte("AdvanceEpoch()")) - - for _, receipt := range receipts { - for _, log := range receipt.Logs { - if len(log.Topics) == 0 { - continue - } - - if log.Topics[0] == asSigHash { - return true - } - } - } - return false -} diff --git a/poset/common_test.go b/poset/common_test.go index 3a4765791..133a838de 100644 --- a/poset/common_test.go +++ b/poset/common_test.go @@ -83,14 +83,14 @@ func FakePoset(namespace string, nodes []idx.StakerID, mods ...memorydb.Mod) (*E } extended.Bootstrap(inter.ConsensusCallbacks{ - ApplyBlock: func(block *inter.Block, decidedFrame idx.Frame, cheaters inter.Cheaters) (newAppHash common.Hash, sealEpoch bool) { + ApplyBlock: func(block *inter.Block, decidedFrame idx.Frame, cheaters inter.Cheaters) (sealEpoch bool, newAppHash []common.Hash) { // track block events if extended.blocks[block.Index] != nil { extended.Log.Crit("Created block twice") } extended.blocks[block.Index] = block - return common.Hash{}, false + return false, nil }, }) diff --git a/poset/frame_decide.go b/poset/frame_decide.go index 5ffca3d29..f55187e5b 100644 --- a/poset/frame_decide.go +++ b/poset/frame_decide.go @@ -101,12 +101,16 @@ func (p *Poset) onFrameDecided(frame idx.Frame, atropos hash.Event) bool { block, cheaters := p.confirmBlock(frame, atropos) // new checkpoint - var sealEpoch bool - var appHash common.Hash + var ( + sealEpoch bool + appHashes []common.Hash + ) p.Checkpoint.LastBlockN++ if p.callback.ApplyBlock != nil { - appHash, sealEpoch = p.callback.ApplyBlock(block, frame, cheaters) - p.Checkpoint.AppHash = hash.Of(p.Checkpoint.AppHash.Bytes(), appHash.Bytes()) + sealEpoch, appHashes = p.callback.ApplyBlock(block, frame, cheaters) + for _, appHash := range appHashes { + p.Checkpoint.AppHash = hash.Of(p.Checkpoint.AppHash.Bytes(), appHash.Bytes()) + } } p.Checkpoint.LastAtropos = atropos p.saveCheckpoint() diff --git a/poset/frame_decide_test.go b/poset/frame_decide_test.go index b50183c93..37ec19dd0 100644 --- a/poset/frame_decide_test.go +++ b/poset/frame_decide_test.go @@ -26,7 +26,7 @@ func TestConfirmBlockEvents(t *testing.T) { blocks []*inter.Block ) applyBlock := poset.callback.ApplyBlock - poset.callback.ApplyBlock = func(block *inter.Block, decidedFrame idx.Frame, cheaters inter.Cheaters) (common.Hash, bool) { + poset.callback.ApplyBlock = func(block *inter.Block, decidedFrame idx.Frame, cheaters inter.Cheaters) (bool, []common.Hash) { frames = append(frames, poset.LastDecidedFrame) blocks = append(blocks, block) diff --git a/poset/restore_test.go b/poset/restore_test.go index 94657124b..8f914ff80 100644 --- a/poset/restore_test.go +++ b/poset/restore_test.go @@ -54,9 +54,9 @@ func TestRestore(t *testing.T) { // seal epoch on decided frame == maxEpochBlocks for _, poset := range posets { applyBlock := poset.callback.ApplyBlock - poset.callback.ApplyBlock = func(block *inter.Block, decidedFrame idx.Frame, cheaters inter.Cheaters) (common.Hash, bool) { - h, _ := applyBlock(block, decidedFrame, cheaters) - return h, decidedFrame == idx.Frame(maxEpochBlocks) + poset.callback.ApplyBlock = func(block *inter.Block, decidedFrame idx.Frame, cheaters inter.Cheaters) (bool, []common.Hash) { + _, hh := applyBlock(block, decidedFrame, cheaters) + return decidedFrame == idx.Frame(maxEpochBlocks), hh } }