From 8c935a6999cdc1fa2b621903a8bdacefe7cc6839 Mon Sep 17 00:00:00 2001 From: galaio Date: Tue, 3 Sep 2024 20:32:19 +0800 Subject: [PATCH 01/19] txdag: add metrics when mining; --- miner/worker.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/miner/worker.go b/miner/worker.go index 051445f9b1..3a11d488ff 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1041,6 +1041,9 @@ func (w *worker) generateDAGTx(statedb *state.StateDB, signer types.Signer, txIn } // txIndex is the index of this txDAG transaction txDAG.SetTxDep(txIndex, types.TxDep{Flags: &types.NonDependentRelFlag}) + if metrics.EnabledExpensive { + go types.EvaluateTxDAGPerformance(txDAG, statedb.ResolveStats()) + } publicKey := sender.Public() publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) From 320d565bda377762dd5cfaba3d06396f69547b7d Mon Sep 17 00:00:00 2001 From: galaio Date: Wed, 4 Sep 2024 10:57:48 +0800 Subject: [PATCH 02/19] txdag: fix mvstates copy issue; --- core/state/statedb.go | 8 +++++--- core/state_processor.go | 2 +- core/types/mvstates.go | 45 +++++++++++++++++++++++++++++++++++++++++ miner/worker.go | 3 +++ 4 files changed, 54 insertions(+), 4 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 2c6b25e668..4e05212cc6 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -788,7 +788,7 @@ func (s *StateDB) CreateAccount(addr common.Address) { func (s *StateDB) CopyWithMvStates() *StateDB { state := s.Copy() if s.mvStates != nil { - state.mvStates = s.mvStates + state.mvStates = s.mvStates.Copy() } return state } @@ -1758,18 +1758,20 @@ func (s *StateDB) RecordStorageWrite(addr common.Address, slot common.Hash, val s.rwSet.RecordStorageWrite(addr, slot, val) } -func (s *StateDB) ResetMVStates(txCount int) { +func (s *StateDB) ResetMVStates(txCount int) *types.MVStates { if s.mvStates != nil { s.mvStates.Stop() } - s.mvStates = types.NewMVStates(txCount).EnableAsyncGen() + s.mvStates = types.NewMVStates(txCount) s.rwSet = nil + return s.mvStates } func (s *StateDB) FinaliseRWSet() error { if s.rwSet == nil { return nil } + log.Debug("FinaliseRWSet", "index", s.txIndex) rwSet := s.rwSet stat := s.stat if metrics.EnabledExpensive { diff --git a/core/state_processor.go b/core/state_processor.go index 0b4aeed745..a586c282da 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -91,7 +91,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg } statedb.MarkFullProcessed() if p.bc.enableTxDAG { - statedb.ResetMVStates(len(block.Transactions())) + statedb.ResetMVStates(len(block.Transactions())).EnableAsyncGen() } // Iterate over and process the individual transactions for i, tx := range block.Transactions() { diff --git a/core/types/mvstates.go b/core/types/mvstates.go index c6601c45b3..8cab6a4a11 100644 --- a/core/types/mvstates.go +++ b/core/types/mvstates.go @@ -321,6 +321,14 @@ func (w *PendingWrites) FindPrevWrites(txIndex int) []*RWItem { return nil } +func (w *PendingWrites) Copy() *PendingWrites { + np := &PendingWrites{} + for i, item := range w.list { + np.list[i] = item + } + return np +} + type MVStates struct { rwSets map[int]*RWSet pendingAccWriteSet map[common.Address]map[AccountState]*PendingWrites @@ -365,6 +373,40 @@ func (s *MVStates) EnableAsyncGen() *MVStates { return s } +func (s *MVStates) Copy() *MVStates { + s.lock.Lock() + defer s.lock.Unlock() + if len(s.asyncGenChan) > 0 { + log.Error("It's dangerous to copy a async MVStates") + } + ns := NewMVStates(len(s.rwSets)) + ns.nextFinaliseIndex = s.nextFinaliseIndex + ns.txDepCache = append(ns.txDepCache, s.txDepCache...) + for k, v := range s.rwSets { + ns.rwSets[k] = v + } + for k, v := range s.stats { + ns.stats[k] = v + } + for addr, sub := range s.pendingAccWriteSet { + for state, writes := range sub { + if _, ok := ns.pendingAccWriteSet[addr]; !ok { + ns.pendingAccWriteSet[addr] = make(map[AccountState]*PendingWrites) + } + ns.pendingAccWriteSet[addr][state] = writes.Copy() + } + } + for addr, sub := range s.pendingSlotWriteSet { + for slot, writes := range sub { + if _, ok := ns.pendingSlotWriteSet[addr]; !ok { + ns.pendingSlotWriteSet[addr] = make(map[common.Hash]*PendingWrites) + } + ns.pendingSlotWriteSet[addr][slot] = writes.Copy() + } + } + return ns +} + func (s *MVStates) Stop() error { s.lock.Lock() defer s.lock.Unlock() @@ -489,6 +531,9 @@ func (s *MVStates) Finalise(index int) error { return err } s.resolveDepsMapCacheByWrites(i, s.rwSets[i]) + log.Debug("Finalise the reads/writes", "index", i, + "readCnt", len(s.rwSets[i].accReadSet)+len(s.rwSets[i].slotReadSet), + "writeCnt", len(s.rwSets[i].accWriteSet)+len(s.rwSets[i].slotWriteSet)) } return nil diff --git a/miner/worker.go b/miner/worker.go index 3a11d488ff..4db17e49ed 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1344,6 +1344,7 @@ func (w *worker) generateWork(genParams *generateParams) *newPayloadResult { wg.Add(1) go func() { defer wg.Done() + newWork.state.MVStates().EnableAsyncGen() err := w.fillTransactions(interrupt, newWork) if errors.Is(err, errBlockInterruptedByTimeout) { log.Warn("Block building is interrupted", "allowance", common.PrettyDuration(w.newpayloadTimeout), "parentHash", genParams.parentHash) @@ -1353,6 +1354,7 @@ func (w *worker) generateWork(genParams *generateParams) *newPayloadResult { isBuildBlockInterruptCounter.Inc(1) } }() + work.state.MVStates().EnableAsyncGen() err := w.fillTransactionsAndBundles(interrupt, work) wg.Wait() timer.Stop() // don't need timeout interruption any more @@ -1361,6 +1363,7 @@ func (w *worker) generateWork(genParams *generateParams) *newPayloadResult { work = newWork } } else { + work.state.MVStates().EnableAsyncGen() err := w.fillTransactions(interrupt, work) timer.Stop() // don't need timeout interruption any more if errors.Is(err, errBlockInterruptedByTimeout) { From d738fd0983cdcaa2d2fa0c93fd56a6032a7e153c Mon Sep 17 00:00:00 2001 From: galaio Date: Wed, 4 Sep 2024 11:34:06 +0800 Subject: [PATCH 03/19] txdag: add timeout for async generation; --- core/types/mvstates.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/types/mvstates.go b/core/types/mvstates.go index 8cab6a4a11..ffd5761833 100644 --- a/core/types/mvstates.go +++ b/core/types/mvstates.go @@ -5,6 +5,7 @@ import ( "fmt" "strings" "sync" + "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" @@ -425,6 +426,7 @@ func (s *MVStates) stopAsyncGen() { } func (s *MVStates) asyncGenLoop() { + timeout := time.After(3 * time.Second) for { select { case tx := <-s.asyncGenChan: @@ -433,6 +435,9 @@ func (s *MVStates) asyncGenLoop() { } case <-s.asyncStopChan: return + case <-timeout: + log.Warn("asyncDepGenLoop exit by timeout") + return } } } From 724996dc54479b53beb8d9e089b5db884b1e86d7 Mon Sep 17 00:00:00 2001 From: galaio Date: Wed, 4 Sep 2024 12:04:44 +0800 Subject: [PATCH 04/19] txdag: fix nil pointer issue; --- miner/worker.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/miner/worker.go b/miner/worker.go index 4db17e49ed..429e4fef56 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1344,7 +1344,9 @@ func (w *worker) generateWork(genParams *generateParams) *newPayloadResult { wg.Add(1) go func() { defer wg.Done() - newWork.state.MVStates().EnableAsyncGen() + if newWork.state.MVStates() != nil { + newWork.state.MVStates().EnableAsyncGen() + } err := w.fillTransactions(interrupt, newWork) if errors.Is(err, errBlockInterruptedByTimeout) { log.Warn("Block building is interrupted", "allowance", common.PrettyDuration(w.newpayloadTimeout), "parentHash", genParams.parentHash) @@ -1354,7 +1356,9 @@ func (w *worker) generateWork(genParams *generateParams) *newPayloadResult { isBuildBlockInterruptCounter.Inc(1) } }() - work.state.MVStates().EnableAsyncGen() + if work.state.MVStates() != nil { + work.state.MVStates().EnableAsyncGen() + } err := w.fillTransactionsAndBundles(interrupt, work) wg.Wait() timer.Stop() // don't need timeout interruption any more @@ -1363,7 +1367,9 @@ func (w *worker) generateWork(genParams *generateParams) *newPayloadResult { work = newWork } } else { - work.state.MVStates().EnableAsyncGen() + if work.state.MVStates() != nil { + work.state.MVStates().EnableAsyncGen() + } err := w.fillTransactions(interrupt, work) timer.Stop() // don't need timeout interruption any more if errors.Is(err, errBlockInterruptedByTimeout) { From 0c304127a7125f424339545b2a58d435ab27d770 Mon Sep 17 00:00:00 2001 From: galaio Date: Thu, 5 Sep 2024 15:19:23 +0800 Subject: [PATCH 05/19] txdag: fix mining reset tx issue; --- core/types/mvstates.go | 11 ++++++----- core/types/mvstates_test.go | 1 - miner/worker.go | 2 ++ 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/core/types/mvstates.go b/core/types/mvstates.go index ffd5761833..49503a1a9e 100644 --- a/core/types/mvstates.go +++ b/core/types/mvstates.go @@ -501,9 +501,6 @@ func (s *MVStates) FulfillRWSet(rwSet *RWSet, stat *ExeStat) error { s.lock.Lock() defer s.lock.Unlock() index := rwSet.ver.TxIndex - if index < s.nextFinaliseIndex { - return errors.New("fulfill a finalized RWSet") - } if stat != nil { if stat.txIndex != index { return errors.New("wrong execution stat") @@ -531,7 +528,11 @@ func (s *MVStates) Finalise(index int) error { defer s.lock.Unlock() // just finalise all previous txs - for i := s.nextFinaliseIndex; i <= index; i++ { + start := s.nextFinaliseIndex + if start > index { + start = index + } + for i := start; i <= index; i++ { if err := s.innerFinalise(i); err != nil { return err } @@ -550,7 +551,7 @@ func (s *MVStates) innerFinalise(index int) error { return fmt.Errorf("finalise a non-exist RWSet, index: %d", index) } - if index != s.nextFinaliseIndex { + if index > s.nextFinaliseIndex { return fmt.Errorf("finalise in wrong order, next: %d, input: %d", s.nextFinaliseIndex, index) } diff --git a/core/types/mvstates_test.go b/core/types/mvstates_test.go index 792f8cd031..70e716a852 100644 --- a/core/types/mvstates_test.go +++ b/core/types/mvstates_test.go @@ -27,7 +27,6 @@ func TestMVStates_BasicUsage(t *testing.T) { require.Nil(t, ms.ReadSlotState(0, common.Address{}, str2Slot("0x00"))) require.NoError(t, ms.Finalise(0)) - require.Error(t, ms.FulfillRWSet(mockRWSetWithVal(0, nil, nil), nil)) require.Nil(t, ms.ReadAccState(0, common.Address{}, AccountBalance)) require.Nil(t, ms.ReadSlotState(0, common.Address{}, str2Slot("0x00"))) require.Equal(t, NewRWItem(StateVersion{TxIndex: 0}, 0), ms.ReadAccState(1, common.Address{}, AccountBalance)) diff --git a/miner/worker.go b/miner/worker.go index 429e4fef56..9881a8a9c6 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -890,6 +890,7 @@ func (w *worker) applyTransaction(env *environment, tx *types.Transaction) (*typ ) receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &env.coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, *w.chain.GetVMConfig()) if err != nil { + log.Debug("ApplyTransaction err", "block", env.header.Number.Uint64(), "tx", env.tcount, "err", err) env.state.RevertToSnapshot(snap) env.gasPool.SetGas(gp) } @@ -1310,6 +1311,7 @@ func (w *worker) generateWork(genParams *generateParams) *newPayloadResult { start := time.Now() if w.chain.TxDAGEnabledWhenMine() { work.state.ResetMVStates(0) + log.Debug("ResetMVStates", "block", work.header.Number.Uint64()) } for _, tx := range genParams.txs { from, _ := types.Sender(work.signer, tx) From 05c94b476243be85ce589833c6176af62ec13d46 Mon Sep 17 00:00:00 2001 From: galaio Date: Thu, 5 Sep 2024 16:45:33 +0800 Subject: [PATCH 06/19] txdag: add debug log; --- miner/worker.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/miner/worker.go b/miner/worker.go index 9881a8a9c6..0b7e3c44b2 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1061,6 +1061,8 @@ func (w *worker) generateDAGTx(statedb *state.StateDB, signer types.Signer, txIn return nil, fmt.Errorf("failed to encode txDAG, err: %v", err) } + enc, _ := types.EncodeTxDAG(txDAG) + log.Debug("EncodeTxDAGCalldata", "tx", txDAG.TxCount(), "enc", len(enc), "data", data, "dag", txDAG) // Create the transaction tx := types.NewTx(&types.LegacyTx{ Nonce: nonce, From 2241db1136ac76f71caa57257e1015915fda5f10 Mon Sep 17 00:00:00 2001 From: galaio Date: Thu, 5 Sep 2024 19:58:19 +0800 Subject: [PATCH 07/19] txdag: fix txdep cache logic to replace failed tx; --- core/types/mvstates.go | 25 +++++++++++++++---------- core/types/mvstates_test.go | 8 ++++++-- miner/worker.go | 2 +- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/core/types/mvstates.go b/core/types/mvstates.go index 49503a1a9e..2f19c88515 100644 --- a/core/types/mvstates.go +++ b/core/types/mvstates.go @@ -338,7 +338,7 @@ type MVStates struct { // dependency map cache for generating TxDAG // depMapCache[i].exist(j) means j->i, and i > j - txDepCache []TxDepMaker + txDepCache map[int]TxDepMaker // async dep analysis asyncGenChan chan int @@ -355,7 +355,7 @@ func NewMVStates(txCount int) *MVStates { rwSets: make(map[int]*RWSet, txCount), pendingAccWriteSet: make(map[common.Address]map[AccountState]*PendingWrites, txCount*8), pendingSlotWriteSet: make(map[common.Address]map[common.Hash]*PendingWrites, txCount*8), - txDepCache: make([]TxDepMaker, 0, txCount), + txDepCache: make(map[int]TxDepMaker, txCount), stats: make(map[int]*ExeStat, txCount), } } @@ -382,7 +382,9 @@ func (s *MVStates) Copy() *MVStates { } ns := NewMVStates(len(s.rwSets)) ns.nextFinaliseIndex = s.nextFinaliseIndex - ns.txDepCache = append(ns.txDepCache, s.txDepCache...) + for k, v := range s.txDepCache { + ns.txDepCache[k] = v + } for k, v := range s.rwSets { ns.rwSets[k] = v } @@ -578,7 +580,8 @@ func (s *MVStates) innerFinalise(index int) error { s.pendingSlotWriteSet[k][slot].Append(&item) } } - s.nextFinaliseIndex++ + // reset nextFinaliseIndex to index+1, it may revert to previous txs + s.nextFinaliseIndex = index + 1 return nil } @@ -601,7 +604,7 @@ func (s *MVStates) resolveDepsMapCacheByWrites(index int, rwSet *RWSet) { // analysis dep, if the previous transaction is not executed/validated, re-analysis is required depMap := NewTxDepMap(0) if rwSet.excludedTx { - s.txDepCache = append(s.txDepCache, depMap) + s.txDepCache[index] = depMap return } // check tx dependency, only check key, skip version @@ -633,14 +636,16 @@ func (s *MVStates) resolveDepsMapCacheByWrites(index int, rwSet *RWSet) { } } } + log.Debug("resolveDepsMapCacheByWrites", "tx", index, "deps", depMap.deps()) // clear redundancy deps compared with prev preDeps := depMap.deps() for _, prev := range preDeps { - for _, tx := range s.txDepCache[prev].deps() { + for _, tx := range s.txDepCache[int(prev)].deps() { depMap.remove(tx) } } - s.txDepCache = append(s.txDepCache, depMap) + log.Debug("resolveDepsMapCacheByWrites after clean", "tx", index, "deps", depMap.deps()) + s.txDepCache[index] = depMap } // resolveDepsCache must be executed in order @@ -648,7 +653,7 @@ func (s *MVStates) resolveDepsCache(index int, rwSet *RWSet) { // analysis dep, if the previous transaction is not executed/validated, re-analysis is required depMap := NewTxDepMap(0) if rwSet.excludedTx { - s.txDepCache = append(s.txDepCache, depMap) + s.txDepCache[index] = depMap return } for prev := 0; prev < index; prev++ { @@ -682,7 +687,7 @@ func (s *MVStates) resolveDepsCache(index int, rwSet *RWSet) { } } } - s.txDepCache = append(s.txDepCache, depMap) + s.txDepCache[index] = depMap } // ResolveTxDAG generate TxDAG from RWSets @@ -712,7 +717,7 @@ func (s *MVStates) ResolveTxDAG(txCnt int, gasFeeReceivers []common.Address) (Tx } } txDAG.TxDeps[i].TxIndexes = []uint64{} - if len(s.txDepCache) <= i { + if s.txDepCache[i] == nil { s.resolveDepsMapCacheByWrites(i, s.rwSets[i]) } if s.rwSets[i].excludedTx { diff --git a/core/types/mvstates_test.go b/core/types/mvstates_test.go index 70e716a852..9737c7f9e0 100644 --- a/core/types/mvstates_test.go +++ b/core/types/mvstates_test.go @@ -89,7 +89,11 @@ func TestMVStates_AsyncDepGen_SimpleResolveTxDAG(t *testing.T) { mockRWSet(0, []interface{}{"0x00"}, []interface{}{"0x00"}), mockRWSet(1, []interface{}{"0x01"}, []interface{}{"0x01"}), mockRWSet(2, []interface{}{"0x02"}, []interface{}{"0x02"}), + mockRWSet(3, []interface{}{"0x03"}, []interface{}{"0x03"}), + mockRWSet(3, []interface{}{"0x03"}, []interface{}{"0x03"}), mockRWSet(3, []interface{}{"0x00", "0x03"}, []interface{}{"0x03"}), + }) + finaliseRWSets(t, ms, []*RWSet{ mockRWSet(4, []interface{}{"0x00", "0x04"}, []interface{}{"0x04"}), mockRWSet(5, []interface{}{"0x01", "0x02", "0x05"}, []interface{}{"0x05"}), mockRWSet(6, []interface{}{"0x02", "0x05", "0x06"}, []interface{}{"0x06"}), @@ -509,9 +513,9 @@ func mockRandomRWSet(count int) []*RWSet { } func finaliseRWSets(t *testing.T, mv *MVStates, rwSets []*RWSet) { - for i, rwSet := range rwSets { + for _, rwSet := range rwSets { require.NoError(t, mv.FulfillRWSet(rwSet, nil)) - require.NoError(t, mv.Finalise(i)) + require.NoError(t, mv.Finalise(rwSet.ver.TxIndex)) } } diff --git a/miner/worker.go b/miner/worker.go index 0b7e3c44b2..5078544770 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1062,7 +1062,7 @@ func (w *worker) generateDAGTx(statedb *state.StateDB, signer types.Signer, txIn } enc, _ := types.EncodeTxDAG(txDAG) - log.Debug("EncodeTxDAGCalldata", "tx", txDAG.TxCount(), "enc", len(enc), "data", data, "dag", txDAG) + log.Debug("EncodeTxDAGCalldata", "tx", txDAG.TxCount(), "enc", len(enc), "data", len(data), "dag", txDAG) // Create the transaction tx := types.NewTx(&types.LegacyTx{ Nonce: nonce, From 1615c8ef8e6e5b5d91842eb7dc1d09b03c41c54f Mon Sep 17 00:00:00 2001 From: galaio Date: Thu, 29 Aug 2024 20:52:50 +0800 Subject: [PATCH 08/19] txdag: clean code, abandon useless codes, add more async logic; txdag: refactor async logic, reduce concurrent logic; txdag: using slice cache rather than sending chan directly; txdag: opt gc issue; txdag: support init cache pool; --- cmd/evm/blockrunner.go | 2 +- core/blockchain.go | 4 +- core/state/state_object.go | 12 +- core/state/statedb.go | 229 ++++-------- core/state_processor.go | 14 +- core/state_transition.go | 24 +- core/types/dag.go | 182 +--------- core/types/dag_test.go | 12 +- core/types/mvstates.go | 698 ++++++++++++++++++++---------------- core/types/mvstates_test.go | 275 ++++---------- core/vm/interface.go | 5 +- go.mod | 1 + go.sum | 2 + miner/worker.go | 15 +- tests/block_test.go | 60 +++- tests/block_test_util.go | 5 +- 16 files changed, 637 insertions(+), 903 deletions(-) diff --git a/cmd/evm/blockrunner.go b/cmd/evm/blockrunner.go index c5d836e0ea..1eef34dd04 100644 --- a/cmd/evm/blockrunner.go +++ b/cmd/evm/blockrunner.go @@ -86,7 +86,7 @@ func blockTestCmd(ctx *cli.Context) error { continue } test := tests[name] - if err := test.Run(false, rawdb.HashScheme, tracer, func(res error, chain *core.BlockChain) { + if err := test.Run(false, rawdb.HashScheme, tracer, false, func(res error, chain *core.BlockChain) { if ctx.Bool(DumpFlag.Name) { if state, _ := chain.State(); state != nil { fmt.Println(string(state.Dump(nil))) diff --git a/core/blockchain.go b/core/blockchain.go index 74d91cedd6..e80522b4cf 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1965,12 +1965,12 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) if bc.enableTxDAG { // compare input TxDAG when it enable in consensus - dag, err := statedb.ResolveTxDAG(len(block.Transactions()), []common.Address{block.Coinbase(), params.OptimismBaseFeeRecipient, params.OptimismL1FeeRecipient}) + dag, err := statedb.ResolveTxDAG(len(block.Transactions())) if err == nil { // TODO(galaio): check TxDAG correctness? log.Debug("Process TxDAG result", "block", block.NumberU64(), "txDAG", dag) if metrics.EnabledExpensive { - go types.EvaluateTxDAGPerformance(dag, statedb.ResolveStats()) + go types.EvaluateTxDAGPerformance(dag) } } else { log.Error("ResolveTxDAG err", "block", block.NumberU64(), "tx", len(block.Transactions()), "err", err) diff --git a/core/state/state_object.go b/core/state/state_object.go index 6ed0c3cf27..6fa6e5e8a7 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -300,23 +300,27 @@ func (s *stateObject) finalise(prefetch bool) { } func (s *stateObject) finaliseRWSet() { + if s.db.mvStates == nil { + return + } + ms := s.db.mvStates for key, value := range s.dirtyStorage { // three are some unclean dirtyStorage from previous reverted txs, it will skip finalise // so add a new rule, if val has no change, then skip it if value == s.GetCommittedState(key) { continue } - s.db.RecordStorageWrite(s.address, key, value) + ms.RecordStorageWrite(s.address, key) } if s.dirtyNonce != nil && *s.dirtyNonce != s.data.Nonce { - s.db.RecordAccountWrite(s.address, types.AccountNonce, *s.dirtyNonce) + ms.RecordAccountWrite(s.address, types.AccountNonce) } if s.dirtyBalance != nil && s.dirtyBalance.Cmp(s.data.Balance) != 0 { - s.db.RecordAccountWrite(s.address, types.AccountBalance, new(big.Int).Set(s.dirtyBalance)) + ms.RecordAccountWrite(s.address, types.AccountBalance) } if s.dirtyCodeHash != nil && !slices.Equal(s.dirtyCodeHash, s.data.CodeHash) { - s.db.RecordAccountWrite(s.address, types.AccountCodeHash, s.dirtyCodeHash) + ms.RecordAccountWrite(s.address, types.AccountCodeHash) } } diff --git a/core/state/statedb.go b/core/state/statedb.go index 4e05212cc6..290222e070 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -18,7 +18,6 @@ package state import ( - "errors" "fmt" "math/big" "runtime" @@ -26,6 +25,8 @@ import ( "sync" "time" + "golang.org/x/exp/slices" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/gopool" "github.com/ethereum/go-ethereum/core/rawdb" @@ -116,9 +117,7 @@ type StateDB struct { logSize uint // parallel EVM related - rwSet *types.RWSet mvStates *types.MVStates - stat *types.ExeStat // Preimages occurred seen by VM in the scope of block. preimages map[common.Hash][]byte @@ -346,10 +345,10 @@ func (s *StateDB) Empty(addr common.Address) bool { } // GetBalance retrieves the balance from the given address or 0 if object not found -func (s *StateDB) GetBalance(addr common.Address) (ret *big.Int) { - defer func() { - s.RecordAccountRead(addr, types.AccountBalance, ret) - }() +func (s *StateDB) GetBalance(addr common.Address) *big.Int { + if s.mvStates != nil { + s.mvStates.RecordAccountRead(addr, types.AccountBalance) + } stateObject := s.getStateObject(addr) if stateObject != nil { @@ -359,10 +358,10 @@ func (s *StateDB) GetBalance(addr common.Address) (ret *big.Int) { } // GetNonce retrieves the nonce from the given address or 0 if object not found -func (s *StateDB) GetNonce(addr common.Address) (ret uint64) { - defer func() { - s.RecordAccountRead(addr, types.AccountNonce, ret) - }() +func (s *StateDB) GetNonce(addr common.Address) uint64 { + if s.mvStates != nil { + s.mvStates.RecordAccountRead(addr, types.AccountNonce) + } stateObject := s.getStateObject(addr) if stateObject != nil { return stateObject.Nonce() @@ -387,9 +386,9 @@ func (s *StateDB) TxIndex() int { } func (s *StateDB) GetCode(addr common.Address) []byte { - defer func() { - s.RecordAccountRead(addr, types.AccountCodeHash, s.GetCodeHash(addr)) - }() + if s.mvStates != nil { + s.mvStates.RecordAccountRead(addr, types.AccountCodeHash) + } stateObject := s.getStateObject(addr) if stateObject != nil { return stateObject.Code() @@ -398,9 +397,9 @@ func (s *StateDB) GetCode(addr common.Address) []byte { } func (s *StateDB) GetCodeSize(addr common.Address) int { - defer func() { - s.RecordAccountRead(addr, types.AccountCodeHash, s.GetCodeHash(addr)) - }() + if s.mvStates != nil { + s.mvStates.RecordAccountRead(addr, types.AccountCodeHash) + } stateObject := s.getStateObject(addr) if stateObject != nil { return stateObject.CodeSize() @@ -408,10 +407,10 @@ func (s *StateDB) GetCodeSize(addr common.Address) int { return 0 } -func (s *StateDB) GetCodeHash(addr common.Address) (ret common.Hash) { - defer func() { - s.RecordAccountRead(addr, types.AccountCodeHash, ret.Bytes()) - }() +func (s *StateDB) GetCodeHash(addr common.Address) common.Hash { + if s.mvStates != nil { + s.mvStates.RecordAccountRead(addr, types.AccountCodeHash) + } stateObject := s.getStateObject(addr) if stateObject == nil { return common.Hash{} @@ -420,10 +419,10 @@ func (s *StateDB) GetCodeHash(addr common.Address) (ret common.Hash) { } // GetState retrieves a value from the given account's storage trie. -func (s *StateDB) GetState(addr common.Address, hash common.Hash) (ret common.Hash) { - defer func() { - s.RecordStorageRead(addr, hash, ret) - }() +func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash { + if s.mvStates != nil { + s.mvStates.RecordStorageRead(addr, hash) + } stateObject := s.getStateObject(addr) if stateObject != nil { return stateObject.GetState(hash) @@ -432,10 +431,10 @@ func (s *StateDB) GetState(addr common.Address, hash common.Hash) (ret common.Ha } // GetCommittedState retrieves a value from the given account's committed storage trie. -func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) (ret common.Hash) { - defer func() { - s.RecordStorageRead(addr, hash, ret) - }() +func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash { + if s.mvStates != nil { + s.mvStates.RecordStorageRead(addr, hash) + } stateObject := s.getStateObject(addr) if stateObject != nil { return stateObject.GetCommittedState(hash) @@ -462,24 +461,26 @@ func (s *StateDB) HasSelfDestructed(addr common.Address) bool { // AddBalance adds amount to the account associated with addr. func (s *StateDB) AddBalance(addr common.Address, amount *big.Int) { + if s.mvStates != nil { + s.mvStates.RecordAccountRead(addr, types.AccountBalance) + } stateObject := s.GetOrNewStateObject(addr) if stateObject != nil { - s.RecordAccountRead(addr, types.AccountBalance, stateObject.Balance()) stateObject.AddBalance(amount) return } - s.RecordAccountRead(addr, types.AccountBalance, common.Big0) } // SubBalance subtracts amount from the account associated with addr. func (s *StateDB) SubBalance(addr common.Address, amount *big.Int) { + if s.mvStates != nil { + s.mvStates.RecordAccountRead(addr, types.AccountBalance) + } stateObject := s.GetOrNewStateObject(addr) if stateObject != nil { - s.RecordAccountRead(addr, types.AccountBalance, stateObject.Balance()) stateObject.SubBalance(amount) return } - s.RecordAccountRead(addr, types.AccountBalance, common.Big0) } func (s *StateDB) SetBalance(addr common.Address, amount *big.Int) { @@ -659,7 +660,6 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject { // flag set. This is needed by the state journal to revert to the correct s- // destructed object instead of wiping all knowledge about the state object. func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { - s.RecordAccountRead(addr, types.AccountSelf, struct{}{}) // Prefer live objects if any is available if obj := s.stateObjects[addr]; obj != nil { return obj @@ -934,13 +934,19 @@ func (s *StateDB) GetRefund() uint64 { // the journal as well as the refunds. Finalise, however, will not push any updates // into the tries just yet. Only IntermediateRoot or Commit will do that. func (s *StateDB) Finalise(deleteEmptyObjects bool) { + var feeReceivers []common.Address + if s.mvStates != nil { + feeReceivers = s.mvStates.FeeReceivers() + } addressesToPrefetch := make([][]byte, 0, len(s.journal.dirties)) // finalise stateObjectsDestruct for addr, acc := range s.stateObjectsDestructDirty { s.stateObjectsDestruct[addr] = acc + if s.mvStates != nil && !slices.Contains(feeReceivers, addr) { + s.mvStates.RecordAccountWrite(addr, types.AccountSuicide) + } } - s.stateObjectsDestructDirty = make(map[common.Address]*types.StateAccount) for addr := range s.journal.dirties { obj, exist := s.stateObjects[addr] if !exist { @@ -960,6 +966,9 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { // event is tracked. if _, ok := s.stateObjectsDestruct[obj.address]; !ok { s.stateObjectsDestruct[obj.address] = obj.origin + if s.mvStates != nil && !slices.Contains(feeReceivers, addr) { + s.mvStates.RecordAccountWrite(addr, types.AccountSuicide) + } } // Note, we can't do this only at the end of a block because multiple // transactions within the same block might self destruct and then @@ -969,6 +978,9 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { delete(s.accountsOrigin, obj.address) // Clear out any previously updated account data (may be recreated via a resurrect) delete(s.storagesOrigin, obj.address) // Clear out any previously updated storage data (may be recreated via a resurrect) } else { + if s.mvStates != nil && !slices.Contains(feeReceivers, addr) { + obj.finaliseRWSet() + } obj.finalise(true) // Prefetch slots in the background } obj.created = false @@ -980,6 +992,7 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { // the commit-phase will be a lot faster addressesToPrefetch = append(addressesToPrefetch, common.CopyBytes(addr[:])) // Copy needed for closure } + if s.prefetcher != nil && len(addressesToPrefetch) > 0 { s.prefetcher.prefetch(common.Hash{}, s.originalRoot, common.Address{}, addressesToPrefetch) } @@ -1692,129 +1705,51 @@ func (s *StateDB) GetSnap() snapshot.Snapshot { return s.snap } -func (s *StateDB) BeforeTxTransition() { - log.Debug("BeforeTxTransition", "mvStates", s.mvStates == nil, "rwSet", s.rwSet == nil) - if s.mvStates == nil { - return - } - s.rwSet = types.NewRWSet(types.StateVersion{ - TxIndex: s.txIndex, - }) -} - -func (s *StateDB) BeginTxStat(index int) { - if s.mvStates == nil { - return - } - if metrics.EnabledExpensive { - s.stat = types.NewExeStat(index).Begin() - } -} - -func (s *StateDB) StopTxStat(usedGas uint64) { +func (s *StateDB) BeginTxRecorder(index int, isExcludeTx bool) { if s.mvStates == nil { return } - // record stat first - if metrics.EnabledExpensive && s.stat != nil { - s.stat.Done().WithGas(usedGas) - rwSet := s.mvStates.RWSet(s.txIndex) - if rwSet != nil { - ar, sr := rwSet.ReadSet() - s.stat.WithRead(len(ar) + len(sr)) + if isExcludeTx { + rwSet := types.NewRWSet(index).WithExcludedTxFlag() + if err := s.mvStates.FinaliseWithRWSet(rwSet); err != nil { + log.Error("MVStates SystemTx Finalise err", "err", err) } - } -} - -func (s *StateDB) RecordAccountRead(addr common.Address, state types.AccountState, val interface{}) { - if s.rwSet == nil { return } - s.rwSet.RecordAccountRead(addr, state, types.StateVersion{ - TxIndex: -1, - }, val) + s.mvStates.RecordNewTx(index) } -func (s *StateDB) RecordStorageRead(addr common.Address, slot common.Hash, val interface{}) { - if s.rwSet == nil { - return - } - s.rwSet.RecordStorageRead(addr, slot, types.StateVersion{ - TxIndex: -1, - }, val) -} - -func (s *StateDB) RecordAccountWrite(addr common.Address, state types.AccountState, val interface{}) { - if s.rwSet == nil { - return - } - s.rwSet.RecordAccountWrite(addr, state, val) -} - -func (s *StateDB) RecordStorageWrite(addr common.Address, slot common.Hash, val interface{}) { - if s.rwSet == nil { - return - } - s.rwSet.RecordStorageWrite(addr, slot, val) -} - -func (s *StateDB) ResetMVStates(txCount int) *types.MVStates { - if s.mvStates != nil { - s.mvStates.Stop() - } - s.mvStates = types.NewMVStates(txCount) - s.rwSet = nil +func (s *StateDB) ResetMVStates(txCount int, feeReceivers []common.Address) *types.MVStates { + s.mvStates = types.NewMVStates(txCount, feeReceivers) return s.mvStates } -func (s *StateDB) FinaliseRWSet() error { - if s.rwSet == nil { +func (s *StateDB) CheckFeeReceiversRWSet() error { + if s.mvStates == nil { return nil } - log.Debug("FinaliseRWSet", "index", s.txIndex) - rwSet := s.rwSet - stat := s.stat if metrics.EnabledExpensive { defer func(start time.Time) { s.TxDAGGenerate += time.Since(start) }(time.Now()) } - ver := types.StateVersion{ - TxIndex: s.txIndex, - } - if ver != rwSet.Version() { - return errors.New("you finalize a wrong ver of RWSet") - } - - // finalise stateObjectsDestruct - for addr := range s.stateObjectsDestructDirty { - s.RecordAccountWrite(addr, types.AccountSuicide, struct{}{}) - } - for addr := range s.journal.dirties { - obj, exist := s.stateObjects[addr] - if !exist { + s.mvStates.RecordReadDone() + feeReceivers := s.mvStates.FeeReceivers() + for _, addr := range feeReceivers { + if _, ok := s.stateObjectsDestructDirty[addr]; !ok { continue } - if obj.selfDestructed || obj.empty() { - // We need to maintain account deletions explicitly (will remain - // set indefinitely). Note only the first occurred self-destruct - // event is tracked. - if _, ok := s.stateObjectsDestruct[obj.address]; !ok { - s.RecordAccountWrite(addr, types.AccountSuicide, struct{}{}) - } - } else { - // finalise account & storages - obj.finaliseRWSet() - } + s.mvStates.RecordCannotDelayGasFee() + return nil } - // reset stateDB - s.rwSet = nil - if err := s.mvStates.FulfillRWSet(rwSet, stat); err != nil { - return err + for _, addr := range feeReceivers { + if _, ok := s.journal.dirties[addr]; !ok { + continue + } + s.mvStates.RecordCannotDelayGasFee() + return nil } - // just Finalise rwSet in serial execution - s.mvStates.AsyncFinalise(s.txIndex) return nil } @@ -1834,7 +1769,7 @@ func (s *StateDB) removeStateObjectsDestruct(addr common.Address) { delete(s.stateObjectsDestructDirty, addr) } -func (s *StateDB) ResolveTxDAG(txCnt int, gasFeeReceivers []common.Address) (types.TxDAG, error) { +func (s *StateDB) ResolveTxDAG(txCnt int) (types.TxDAG, error) { if s.mvStates == nil { return types.NewEmptyTxDAG(), nil } @@ -1844,33 +1779,13 @@ func (s *StateDB) ResolveTxDAG(txCnt int, gasFeeReceivers []common.Address) (typ }(time.Now()) } - return s.mvStates.ResolveTxDAG(txCnt, gasFeeReceivers) -} - -func (s *StateDB) ResolveStats() map[int]*types.ExeStat { - if s.mvStates == nil { - return nil - } - - return s.mvStates.Stats() + return s.mvStates.ResolveTxDAG(txCnt) } func (s *StateDB) MVStates() *types.MVStates { return s.mvStates } -func (s *StateDB) RecordSystemTxRWSet(index int) { - if s.mvStates == nil { - return - } - s.mvStates.FulfillRWSet(types.NewRWSet(types.StateVersion{ - TxIndex: index, - }).WithExcludedTxFlag(), types.NewExeStat(index).WithExcludedTxFlag()) - if err := s.mvStates.Finalise(index); err != nil { - log.Error("MVStates SystemTx Finalise err", "err", err) - } -} - // copySet returns a deep-copied set. func copySet[k comparable](set map[k][]byte) map[k][]byte { copied := make(map[k][]byte, len(set)) diff --git a/core/state_processor.go b/core/state_processor.go index a586c282da..86a700a89a 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -91,11 +91,12 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg } statedb.MarkFullProcessed() if p.bc.enableTxDAG { - statedb.ResetMVStates(len(block.Transactions())).EnableAsyncGen() + feeReceivers := []common.Address{context.Coinbase, params.OptimismBaseFeeRecipient, params.OptimismL1FeeRecipient} + statedb.ResetMVStates(len(block.Transactions()), feeReceivers).EnableAsyncGen() } // Iterate over and process the individual transactions for i, tx := range block.Transactions() { - statedb.BeginTxStat(i) + statedb.BeginTxRecorder(i, tx.IsSystemTx() || tx.IsDepositTx()) start := time.Now() msg, err := TransactionToMessage(tx, signer, header.BaseFee) if err != nil { @@ -106,17 +107,14 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg if err != nil { return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } - - // if systemTx or depositTx, tag it - if tx.IsSystemTx() || tx.IsDepositTx() { - statedb.RecordSystemTxRWSet(i) - } receipts = append(receipts, receipt) allLogs = append(allLogs, receipt.Logs...) if metrics.EnabledExpensive { processTxTimer.UpdateSince(start) } - statedb.StopTxStat(receipt.GasUsed) + } + if statedb.MVStates() != nil { + statedb.MVStates().BatchRecordHandle() } // Fail if Shanghai not enabled and len(withdrawals) is non-zero. withdrawals := block.Withdrawals() diff --git a/core/state_transition.go b/core/state_transition.go index 7c25cc93f8..650263c494 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -398,10 +398,6 @@ func (st *StateTransition) preCheck() error { // However if any consensus issue encountered, return the error directly with // nil evm execution result. func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { - // start record rw set in here - if !st.msg.IsSystemTx && !st.msg.IsDepositTx { - st.state.BeforeTxTransition() - } if mint := st.msg.Mint; mint != nil { st.state.AddBalance(st.msg.From, mint) } @@ -422,10 +418,6 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { if st.msg.IsSystemTx && !st.evm.ChainConfig().IsRegolith(st.evm.Context.Time) { gasUsed = 0 } - // just record error tx here - if ferr := st.state.FinaliseRWSet(); ferr != nil { - log.Error("finalise error deposit tx rwSet fail", "block", st.evm.Context.BlockNumber, "tx", st.evm.StateDB.TxIndex(), "err", ferr) - } result = &ExecutionResult{ UsedGas: gasUsed, Err: fmt.Errorf("failed deposit: %w", err), @@ -433,12 +425,6 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { } err = nil } - if err != nil { - // just record error tx here - if ferr := st.state.FinaliseRWSet(); ferr != nil { - log.Error("finalise error tx rwSet fail", "block", st.evm.Context.BlockNumber, "tx", st.evm.StateDB.TxIndex(), "err", ferr) - } - } return result, err } @@ -511,11 +497,6 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) { } DebugInnerExecutionDuration += time.Since(start) - // stop record rw set in here, skip gas fee distribution - if ferr := st.state.FinaliseRWSet(); ferr != nil { - log.Error("finalise tx rwSet fail", "block", st.evm.Context.BlockNumber, "tx", st.evm.StateDB.TxIndex(), "err", ferr) - } - // if deposit: skip refunds, skip tipping coinbase // Regolith changes this behaviour to report the actual gasUsed instead of always reporting all gas used. if st.msg.IsDepositTx && !rules.IsOptimismRegolith { @@ -552,6 +533,11 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) { ReturnData: ret, }, nil } + + // check fee receiver rwSet here + if ferr := st.state.CheckFeeReceiversRWSet(); ferr != nil { + log.Error("CheckFeeReceiversRWSet err", "block", st.evm.Context.BlockNumber, "tx", st.evm.StateDB.TxIndex(), "err", ferr) + } effectiveTip := msg.GasPrice if rules.IsLondon { effectiveTip = cmath.BigMin(msg.GasTipCap, new(big.Int).Sub(msg.GasFeeCap, st.evm.Context.BaseFee)) diff --git a/core/types/dag.go b/core/types/dag.go index e46f7bb897..2bdcb46e97 100644 --- a/core/types/dag.go +++ b/core/types/dag.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "strings" - "time" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/metrics" @@ -394,16 +393,6 @@ func findTxPathIndex(path []uint64, cur uint64, txMap map[uint64]uint64) (uint64 return 0, false } -// travelTxDAGExecutionPaths will print all tx execution path -func travelTxDAGExecutionPaths(d TxDAG) [][]uint64 { - exePaths := make([][]uint64, 0) - // travel tx deps with BFS - for i := uint64(0); i < uint64(d.TxCount()); i++ { - exePaths = append(exePaths, travelTxDAGTargetPath(d, i)) - } - return exePaths -} - // TxDep store the current tx dependency relation with other txs type TxDep struct { TxIndexes []uint64 @@ -473,175 +462,18 @@ func (d *TxDep) ClearFlag(flag uint8) { } var ( - longestTimeTimer = metrics.NewRegisteredTimer("dag/longesttime", nil) - longestGasTimer = metrics.NewRegisteredTimer("dag/longestgas", nil) - serialTimeTimer = metrics.NewRegisteredTimer("dag/serialtime", nil) - totalTxMeter = metrics.NewRegisteredMeter("dag/txcnt", nil) - totalNoDepMeter = metrics.NewRegisteredMeter("dag/nodepcnt", nil) - total2DepMeter = metrics.NewRegisteredMeter("dag/2depcnt", nil) - total4DepMeter = metrics.NewRegisteredMeter("dag/4depcnt", nil) - total8DepMeter = metrics.NewRegisteredMeter("dag/8depcnt", nil) - total16DepMeter = metrics.NewRegisteredMeter("dag/16depcnt", nil) - total32DepMeter = metrics.NewRegisteredMeter("dag/32depcnt", nil) + totalTxMeter = metrics.NewRegisteredMeter("dag/txcnt", nil) + totalNoDepMeter = metrics.NewRegisteredMeter("dag/nodepcnt", nil) ) -func EvaluateTxDAGPerformance(dag TxDAG, stats map[int]*ExeStat) { - if len(stats) != dag.TxCount() || dag.TxCount() == 0 { +func EvaluateTxDAGPerformance(dag TxDAG) { + if dag.TxCount() == 0 { return } - paths := travelTxDAGExecutionPaths(dag) - // Attention: this is based on best schedule, it will reduce a lot by executing previous txs in parallel - // It assumes that there is no parallel thread limit - txCount := dag.TxCount() - var ( - maxGasIndex int - maxGas uint64 - maxTimeIndex int - maxTime time.Duration - txTimes = make([]time.Duration, txCount) - txGases = make([]uint64, txCount) - txReads = make([]int, txCount) - noDepCnt int - ) - - totalTxMeter.Mark(int64(txCount)) - for i, path := range paths { - if stats[i].excludedTx { - continue - } - if len(path) <= 1 { - noDepCnt++ + totalTxMeter.Mark(int64(dag.TxCount())) + for i := 0; i < dag.TxCount(); i++ { + if len(TxDependency(dag, i)) == 0 { totalNoDepMeter.Mark(1) } - if len(path) <= 3 { - total2DepMeter.Mark(1) - } - if len(path) <= 5 { - total4DepMeter.Mark(1) - } - if len(path) <= 9 { - total8DepMeter.Mark(1) - } - if len(path) <= 17 { - total16DepMeter.Mark(1) - } - if len(path) <= 33 { - total32DepMeter.Mark(1) - } - - // find the biggest cost time from dependency txs - for j := 0; j < len(path)-1; j++ { - prev := path[j] - if txTimes[prev] > txTimes[i] { - txTimes[i] = txTimes[prev] - } - if txGases[prev] > txGases[i] { - txGases[i] = txGases[prev] - } - if txReads[prev] > txReads[i] { - txReads[i] = txReads[prev] - } - } - txTimes[i] += stats[i].costTime - txGases[i] += stats[i].usedGas - txReads[i] += stats[i].readCount - - // try to find max gas - if txGases[i] > maxGas { - maxGas = txGases[i] - maxGasIndex = i - } - if txTimes[i] > maxTime { - maxTime = txTimes[i] - maxTimeIndex = i - } } - - longestTimeTimer.Update(txTimes[maxTimeIndex]) - longestGasTimer.Update(txTimes[maxGasIndex]) - // serial path - var ( - sTime time.Duration - sGas uint64 - sRead int - sPath []int - ) - for i, stat := range stats { - if stat.excludedTx { - continue - } - sPath = append(sPath, i) - sTime += stat.costTime - sGas += stat.usedGas - sRead += stat.readCount - } - serialTimeTimer.Update(sTime) -} - -// travelTxDAGTargetPath will print target execution path -func travelTxDAGTargetPath(d TxDAG, from uint64) []uint64 { - var ( - queue []uint64 - path []uint64 - ) - - queue = append(queue, from) - path = append(path, from) - for len(queue) > 0 { - var next []uint64 - for _, i := range queue { - for _, dep := range TxDependency(d, int(i)) { - if !slices.Contains(path, dep) { - path = append(path, dep) - next = append(next, dep) - } - } - } - queue = next - } - slices.Sort(path) - return path -} - -// ExeStat records tx execution info -type ExeStat struct { - txIndex int - usedGas uint64 - readCount int - startTime time.Time - costTime time.Duration - - // some flags - excludedTx bool -} - -func NewExeStat(txIndex int) *ExeStat { - return &ExeStat{ - txIndex: txIndex, - } -} - -func (s *ExeStat) Begin() *ExeStat { - s.startTime = time.Now() - return s -} - -func (s *ExeStat) Done() *ExeStat { - s.costTime = time.Since(s.startTime) - return s -} - -func (s *ExeStat) WithExcludedTxFlag() *ExeStat { - s.excludedTx = true - return s -} - -func (s *ExeStat) WithGas(gas uint64) *ExeStat { - s.usedGas = gas - return s -} - -func (s *ExeStat) WithRead(rc int) *ExeStat { - s.readCount = rc - return s } diff --git a/core/types/dag_test.go b/core/types/dag_test.go index cc50f2e5db..7da1a183b3 100644 --- a/core/types/dag_test.go +++ b/core/types/dag_test.go @@ -3,7 +3,6 @@ package types import ( "encoding/hex" "testing" - "time" "github.com/golang/snappy" @@ -50,16 +49,7 @@ func TestTxDAG(t *testing.T) { func TestEvaluateTxDAG(t *testing.T) { dag := mockSystemTxDAG() - stats := make(map[int]*ExeStat, dag.TxCount()) - for i := 0; i < dag.TxCount(); i++ { - stats[i] = NewExeStat(i).WithGas(uint64(i)).WithRead(i) - stats[i].costTime = time.Duration(i) - txDep := dag.TxDep(i) - if txDep.CheckFlag(NonDependentRelFlag) { - stats[i].WithExcludedTxFlag() - } - } - EvaluateTxDAGPerformance(dag, stats) + EvaluateTxDAGPerformance(dag) } func TestMergeTxDAGExecutionPaths_Simple(t *testing.T) { diff --git a/core/types/mvstates.go b/core/types/mvstates.go index 2f19c88515..aa318ac95f 100644 --- a/core/types/mvstates.go +++ b/core/types/mvstates.go @@ -1,7 +1,6 @@ package types import ( - "errors" "fmt" "strings" "sync" @@ -24,138 +23,113 @@ var ( ) const ( - asyncDepGenChanSize = 1000 + initRWEventCacheSize = 2 ) -// StateVersion record specific TxIndex & TxIncarnation -// if TxIndex equals to -1, it means the state read from DB. -type StateVersion struct { - TxIndex int - // Tx incarnation used for multi ver state - TxIncarnation int +func init() { + for i := 0; i < initRWEventCacheSize; i++ { + cache := make([]RWEventItem, 200) + rwEventCachePool.Put(&cache) + } } // RWSet record all read & write set in txs // Attention: this is not a concurrent safety structure type RWSet struct { - ver StateVersion - accReadSet map[common.Address]map[AccountState]RWItem - accWriteSet map[common.Address]map[AccountState]RWItem - slotReadSet map[common.Address]map[common.Hash]RWItem - slotWriteSet map[common.Address]map[common.Hash]RWItem + index int + accReadSet map[common.Address]map[AccountState]struct{} + accWriteSet map[common.Address]map[AccountState]struct{} + slotReadSet map[common.Address]map[common.Hash]struct{} + slotWriteSet map[common.Address]map[common.Hash]struct{} // some flags - rwRecordDone bool - excludedTx bool + excludedTx bool } -func NewRWSet(ver StateVersion) *RWSet { +func NewRWSet(index int) *RWSet { return &RWSet{ - ver: ver, - accReadSet: make(map[common.Address]map[AccountState]RWItem), - accWriteSet: make(map[common.Address]map[AccountState]RWItem), - slotReadSet: make(map[common.Address]map[common.Hash]RWItem), - slotWriteSet: make(map[common.Address]map[common.Hash]RWItem), + index: index, + accReadSet: make(map[common.Address]map[AccountState]struct{}), + accWriteSet: make(map[common.Address]map[AccountState]struct{}), + slotReadSet: make(map[common.Address]map[common.Hash]struct{}), + slotWriteSet: make(map[common.Address]map[common.Hash]struct{}), } } -func (s *RWSet) RecordAccountRead(addr common.Address, state AccountState, ver StateVersion, val interface{}) { +func (s *RWSet) Index() int { + return s.index +} + +func (s *RWSet) RecordAccountRead(addr common.Address, state AccountState) { // only record the first read version sub, ok := s.accReadSet[addr] if !ok { - s.accReadSet[addr] = make(map[AccountState]RWItem) - s.accReadSet[addr][state] = RWItem{ - Ver: ver, - Val: val, - } + s.accReadSet[addr] = make(map[AccountState]struct{}) + s.accReadSet[addr][AccountSelf] = struct{}{} + s.accReadSet[addr][state] = struct{}{} return } if _, ok = sub[state]; ok { return } - s.accReadSet[addr][state] = RWItem{ - Ver: ver, - Val: val, - } + s.accReadSet[addr][state] = struct{}{} } -func (s *RWSet) RecordStorageRead(addr common.Address, slot common.Hash, ver StateVersion, val interface{}) { +func (s *RWSet) RecordStorageRead(addr common.Address, slot common.Hash) { // only record the first read version sub, ok := s.slotReadSet[addr] if !ok { - s.slotReadSet[addr] = make(map[common.Hash]RWItem) - s.slotReadSet[addr][slot] = RWItem{ - Ver: ver, - Val: val, - } + s.slotReadSet[addr] = make(map[common.Hash]struct{}) + s.slotReadSet[addr][slot] = struct{}{} return } if _, ok = sub[slot]; ok { return } - s.slotReadSet[addr][slot] = RWItem{ - Ver: ver, - Val: val, - } + s.slotReadSet[addr][slot] = struct{}{} } -func (s *RWSet) RecordAccountWrite(addr common.Address, state AccountState, val interface{}) { +func (s *RWSet) RecordAccountWrite(addr common.Address, state AccountState) { _, ok := s.accWriteSet[addr] if !ok { - s.accWriteSet[addr] = make(map[AccountState]RWItem) - } - s.accWriteSet[addr][state] = RWItem{ - Ver: s.ver, - Val: val, + s.accWriteSet[addr] = make(map[AccountState]struct{}) } + s.accWriteSet[addr][state] = struct{}{} } -func (s *RWSet) RecordStorageWrite(addr common.Address, slot common.Hash, val interface{}) { +func (s *RWSet) RecordStorageWrite(addr common.Address, slot common.Hash) { _, ok := s.slotWriteSet[addr] if !ok { - s.slotWriteSet[addr] = make(map[common.Hash]RWItem) - } - s.slotWriteSet[addr][slot] = RWItem{ - Ver: s.ver, - Val: val, + s.slotWriteSet[addr] = make(map[common.Hash]struct{}) } + s.slotWriteSet[addr][slot] = struct{}{} } -func (s *RWSet) queryAccReadItem(addr common.Address, state AccountState) *RWItem { +func (s *RWSet) queryAccReadItem(addr common.Address, state AccountState) bool { sub, ok := s.accReadSet[addr] if !ok { - return nil + return false } - ret, ok := sub[state] - if !ok { - return nil - } - return &ret + _, ok = sub[state] + return ok } -func (s *RWSet) querySlotReadItem(addr common.Address, slot common.Hash) *RWItem { +func (s *RWSet) querySlotReadItem(addr common.Address, slot common.Hash) bool { sub, ok := s.slotReadSet[addr] if !ok { - return nil - } - - ret, ok := sub[slot] - if !ok { - return nil + return false } - return &ret -} -func (s *RWSet) Version() StateVersion { - return s.ver + _, ok = sub[slot] + return ok } -func (s *RWSet) ReadSet() (map[common.Address]map[AccountState]RWItem, map[common.Address]map[common.Hash]RWItem) { +func (s *RWSet) ReadSet() (map[common.Address]map[AccountState]struct{}, map[common.Address]map[common.Hash]struct{}) { return s.accReadSet, s.slotReadSet } -func (s *RWSet) WriteSet() (map[common.Address]map[AccountState]RWItem, map[common.Address]map[common.Hash]RWItem) { +func (s *RWSet) WriteSet() (map[common.Address]map[AccountState]struct{}, map[common.Address]map[common.Hash]struct{}) { return s.accWriteSet, s.slotWriteSet } @@ -166,7 +140,7 @@ func (s *RWSet) WithExcludedTxFlag() *RWSet { func (s *RWSet) String() string { builder := strings.Builder{} - builder.WriteString(fmt.Sprintf("tx: %v, inc: %v\nreadSet: [", s.ver.TxIndex, s.ver.TxIncarnation)) + builder.WriteString(fmt.Sprintf("tx: %v\nreadSet: [", s.index)) i := 0 for key, _ := range s.accReadSet { if i > 0 { @@ -206,79 +180,42 @@ func (s *RWSet) String() string { return builder.String() } -// isEqualRWVal compare state -func isEqualRWVal(accState *AccountState, src interface{}, compared interface{}) bool { - if accState != nil { - switch *accState { - case AccountBalance: - if src != nil && compared != nil { - return equalUint256(src.(*uint256.Int), compared.(*uint256.Int)) - } - return src == compared - case AccountNonce: - return src.(uint64) == compared.(uint64) - case AccountCodeHash: - if src != nil && compared != nil { - return slices.Equal(src.([]byte), compared.([]byte)) - } - return src == compared - } - return false - } - - if src != nil && compared != nil { - return src.(common.Hash) == compared.(common.Hash) - } - return src == compared -} - -func equalUint256(s, c *uint256.Int) bool { - if s != nil && c != nil { - return s.Eq(c) - } - - return s == c -} - -type RWItem struct { - Ver StateVersion - Val interface{} -} - -func NewRWItem(ver StateVersion, val interface{}) *RWItem { - return &RWItem{ - Ver: ver, - Val: val, - } -} - -func (w *RWItem) TxIndex() int { - return w.Ver.TxIndex -} +const ( + NewTxRWEvent byte = iota + ReadAccRWEvent + WriteAccRWEvent + ReadSlotRWEvent + WriteSlotRWEvent + CannotGasFeeDelayRWEvent +) -func (w *RWItem) TxIncarnation() int { - return w.Ver.TxIncarnation +type RWEventItem struct { + Event byte + Index int + Addr common.Address + State AccountState + Slot common.Hash } type PendingWrites struct { - list []*RWItem + list []int } func NewPendingWrites() *PendingWrites { return &PendingWrites{ - list: make([]*RWItem, 0), + list: make([]int, 0), } } -func (w *PendingWrites) Append(pw *RWItem) { - if i, found := w.SearchTxIndex(pw.TxIndex()); found { +func (w *PendingWrites) Append(pw int) { + if i, found := w.SearchTxIndex(pw); found { w.list[i] = pw return } w.list = append(w.list, pw) for i := len(w.list) - 1; i > 0; i-- { - if w.list[i].TxIndex() > w.list[i-1].TxIndex() { + if w.list[i] > w.list[i-1] { break } w.list[i-1], w.list[i] = w.list[i], w.list[i-1] @@ -291,30 +228,19 @@ func (w *PendingWrites) SearchTxIndex(txIndex int) (int, bool) { for i < j { h := int(uint(i+j) >> 1) // i ≤ h < j - if w.list[h].TxIndex() < txIndex { + if w.list[h] < txIndex { i = h + 1 } else { j = h } } - return i, i < n && w.list[i].TxIndex() == txIndex -} - -func (w *PendingWrites) FindLastWrite(txIndex int) *RWItem { - var i, _ = w.SearchTxIndex(txIndex) - for j := i - 1; j >= 0; j-- { - if w.list[j].TxIndex() < txIndex { - return w.list[j] - } - } - - return nil + return i, i < n && w.list[i] == txIndex } -func (w *PendingWrites) FindPrevWrites(txIndex int) []*RWItem { +func (w *PendingWrites) FindPrevWrites(txIndex int) []int { var i, _ = w.SearchTxIndex(txIndex) for j := i - 1; j >= 0; j-- { - if w.list[j].TxIndex() < txIndex { + if w.list[j] < txIndex { return w.list[:j+1] } } @@ -330,57 +256,64 @@ func (w *PendingWrites) Copy() *PendingWrites { return np } +var ( + rwEventCachePool = sync.Pool{New: func() any { + buf := make([]RWEventItem, 0) + return &buf + }} +) + type MVStates struct { rwSets map[int]*RWSet pendingAccWriteSet map[common.Address]map[AccountState]*PendingWrites pendingSlotWriteSet map[common.Address]map[common.Hash]*PendingWrites nextFinaliseIndex int + gasFeeReceivers []common.Address // dependency map cache for generating TxDAG // depMapCache[i].exist(j) means j->i, and i > j - txDepCache map[int]TxDepMaker + txDepCache map[int]TxDep - // async dep analysis - asyncGenChan chan int - asyncStopChan chan struct{} - asyncRunning bool + // async rw event recorder + asyncRWSet *RWSet + rwEventCh chan []RWEventItem + rwEventCache []RWEventItem + rwEventCacheIndex int + recordeReadDone bool // execution stat infos - stats map[int]*ExeStat - lock sync.RWMutex + lock sync.RWMutex + asyncWG sync.WaitGroup + cannotGasFeeDelay bool } -func NewMVStates(txCount int) *MVStates { - return &MVStates{ +func NewMVStates(txCount int, gasFeeReceivers []common.Address) *MVStates { + m := &MVStates{ rwSets: make(map[int]*RWSet, txCount), - pendingAccWriteSet: make(map[common.Address]map[AccountState]*PendingWrites, txCount*8), - pendingSlotWriteSet: make(map[common.Address]map[common.Hash]*PendingWrites, txCount*8), - txDepCache: make(map[int]TxDepMaker, txCount), - stats: make(map[int]*ExeStat, txCount), + pendingAccWriteSet: make(map[common.Address]map[AccountState]*PendingWrites, txCount), + pendingSlotWriteSet: make(map[common.Address]map[common.Hash]*PendingWrites, txCount), + txDepCache: make(map[int]TxDep, txCount), + rwEventCh: make(chan []RWEventItem, 100), + gasFeeReceivers: gasFeeReceivers, } + m.rwEventCache = *rwEventCachePool.Get().(*[]RWEventItem) + m.rwEventCache = m.rwEventCache[:cap(m.rwEventCache)] + m.rwEventCacheIndex = 0 + return m } func (s *MVStates) EnableAsyncGen() *MVStates { s.lock.Lock() defer s.lock.Unlock() - chanSize := asyncDepGenChanSize - if len(s.rwSets) > 0 && len(s.rwSets) < asyncDepGenChanSize { - chanSize = len(s.rwSets) - } - s.asyncGenChan = make(chan int, chanSize) - s.asyncStopChan = make(chan struct{}) - s.asyncRunning = true - go s.asyncGenLoop() + s.asyncWG.Add(1) + go s.asyncRWEventLoop() return s } func (s *MVStates) Copy() *MVStates { s.lock.Lock() defer s.lock.Unlock() - if len(s.asyncGenChan) > 0 { - log.Error("It's dangerous to copy a async MVStates") - } - ns := NewMVStates(len(s.rwSets)) + ns := NewMVStates(len(s.rwSets), s.gasFeeReceivers) ns.nextFinaliseIndex = s.nextFinaliseIndex for k, v := range s.txDepCache { ns.txDepCache[k] = v @@ -388,9 +321,6 @@ func (s *MVStates) Copy() *MVStates { for k, v := range s.rwSets { ns.rwSets[k] = v } - for k, v := range s.stats { - ns.stats[k] = v - } for addr, sub := range s.pendingAccWriteSet { for state, writes := range sub { if _, ok := ns.pendingAccWriteSet[addr]; !ok { @@ -410,32 +340,21 @@ func (s *MVStates) Copy() *MVStates { return ns } -func (s *MVStates) Stop() error { - s.lock.Lock() - defer s.lock.Unlock() - s.stopAsyncGen() - return nil -} - -func (s *MVStates) stopAsyncGen() { - if !s.asyncRunning { - return - } - s.asyncRunning = false - if s.asyncStopChan != nil { - close(s.asyncStopChan) - } -} - -func (s *MVStates) asyncGenLoop() { +func (s *MVStates) asyncRWEventLoop() { + defer s.asyncWG.Done() timeout := time.After(3 * time.Second) for { select { - case tx := <-s.asyncGenChan: - if err := s.Finalise(tx); err != nil { - log.Error("async MVStates Finalise err", "err", err) + case items, ok := <-s.rwEventCh: + if !ok { + return } - case <-s.asyncStopChan: + for _, item := range items { + s.handleRWEvent(item) + } + rwEventCachePool.Put(&items) + case <-timeout: + log.Warn("asyncRWEventLoop timeout") return case <-timeout: log.Warn("asyncDepGenLoop exit by timeout") @@ -444,91 +363,198 @@ func (s *MVStates) asyncGenLoop() { } } -func (s *MVStates) RWSets() map[int]*RWSet { - s.lock.RLock() - defer s.lock.RUnlock() - return s.rwSets +func (s *MVStates) handleRWEvent(item RWEventItem) { + s.lock.Lock() + defer s.lock.Unlock() + // init next RWSet, and finalise previous RWSet + if item.Event == NewTxRWEvent { + if item.Index > 0 { + s.finalisePreviousRWSet() + } + s.asyncRWSet = NewRWSet(item.Index) + return + } + // recorde current as cannot gas fee delay + if item.Event == CannotGasFeeDelayRWEvent { + s.cannotGasFeeDelay = true + return + } + if s.asyncRWSet == nil { + return + } + switch item.Event { + // recorde current read/write event + case ReadAccRWEvent: + s.asyncRWSet.RecordAccountRead(item.Addr, item.State) + case ReadSlotRWEvent: + s.asyncRWSet.RecordStorageRead(item.Addr, item.Slot) + case WriteAccRWEvent: + s.finaliseAccWrite(s.asyncRWSet.index, item.Addr, item.State) + case WriteSlotRWEvent: + s.finaliseSlotWrite(s.asyncRWSet.index, item.Addr, item.Slot) + } } -func (s *MVStates) Stats() map[int]*ExeStat { - s.lock.RLock() - defer s.lock.RUnlock() - return s.stats +func (s *MVStates) finalisePreviousRWSet() { + if s.asyncRWSet == nil { + return + } + index := s.asyncRWSet.index + if err := s.quickFinaliseWithRWSet(s.asyncRWSet); err != nil { + log.Error("Finalise err when handle NewTxRWEvent", "tx", index, "err", err) + return + } + // check if there are RW with gas fee receiver for gas delay calculation + for _, addr := range s.gasFeeReceivers { + if _, exist := s.asyncRWSet.accReadSet[addr]; !exist { + continue + } + if _, exist := s.asyncRWSet.accReadSet[addr][AccountSelf]; exist { + s.cannotGasFeeDelay = true + } + } + s.resolveDepsMapCacheByWrites(index, s.asyncRWSet) } -func (s *MVStates) RWSet(index int) *RWSet { - s.lock.RLock() - defer s.lock.RUnlock() - if index >= len(s.rwSets) { - return nil +func (s *MVStates) RecordNewTx(index int) { + if s.rwEventCacheIndex < len(s.rwEventCache) { + s.rwEventCache[s.rwEventCacheIndex].Event = NewTxRWEvent + s.rwEventCache[s.rwEventCacheIndex].Index = index + } else { + s.rwEventCache = append(s.rwEventCache, RWEventItem{ + Event: NewTxRWEvent, + Index: index, + }) } - return s.rwSets[index] + s.rwEventCacheIndex++ + s.recordeReadDone = false + s.BatchRecordHandle() } -// ReadAccState read state from MVStates -func (s *MVStates) ReadAccState(txIndex int, addr common.Address, state AccountState) *RWItem { - s.lock.RLock() - defer s.lock.RUnlock() +func (s *MVStates) RecordAccountRead(addr common.Address, state AccountState) { + if s.recordeReadDone { + return + } + if s.rwEventCacheIndex < len(s.rwEventCache) { + s.rwEventCache[s.rwEventCacheIndex].Event = ReadAccRWEvent + s.rwEventCache[s.rwEventCacheIndex].Addr = addr + s.rwEventCache[s.rwEventCacheIndex].State = state + s.rwEventCacheIndex++ + return + } + s.rwEventCache = append(s.rwEventCache, RWEventItem{ + Event: ReadAccRWEvent, + Addr: addr, + State: state, + }) + s.rwEventCacheIndex++ +} - sub, ok := s.pendingAccWriteSet[addr] - if !ok { - return nil +func (s *MVStates) RecordStorageRead(addr common.Address, slot common.Hash) { + if s.recordeReadDone { + return } - wset, ok := sub[state] - if !ok { - return nil + if s.rwEventCacheIndex < len(s.rwEventCache) { + s.rwEventCache[s.rwEventCacheIndex].Event = ReadSlotRWEvent + s.rwEventCache[s.rwEventCacheIndex].Addr = addr + s.rwEventCache[s.rwEventCacheIndex].Slot = slot + s.rwEventCacheIndex++ + return } - return wset.FindLastWrite(txIndex) + s.rwEventCache = append(s.rwEventCache, RWEventItem{ + Event: ReadSlotRWEvent, + Addr: addr, + Slot: slot, + }) + s.rwEventCacheIndex++ } -// ReadSlotState read state from MVStates -func (s *MVStates) ReadSlotState(txIndex int, addr common.Address, slot common.Hash) *RWItem { - s.lock.RLock() - defer s.lock.RUnlock() +func (s *MVStates) RecordReadDone() { + s.recordeReadDone = true +} - sub, ok := s.pendingSlotWriteSet[addr] - if !ok { - return nil +func (s *MVStates) RecordAccountWrite(addr common.Address, state AccountState) { + if s.rwEventCacheIndex < len(s.rwEventCache) { + s.rwEventCache[s.rwEventCacheIndex].Event = WriteAccRWEvent + s.rwEventCache[s.rwEventCacheIndex].Addr = addr + s.rwEventCache[s.rwEventCacheIndex].State = state + s.rwEventCacheIndex++ + return } - wset, ok := sub[slot] - if !ok { - return nil + s.rwEventCache = append(s.rwEventCache, RWEventItem{ + Event: WriteAccRWEvent, + Addr: addr, + State: state, + }) + s.rwEventCacheIndex++ +} + +func (s *MVStates) RecordStorageWrite(addr common.Address, slot common.Hash) { + if s.rwEventCacheIndex < len(s.rwEventCache) { + s.rwEventCache[s.rwEventCacheIndex].Event = WriteSlotRWEvent + s.rwEventCache[s.rwEventCacheIndex].Addr = addr + s.rwEventCache[s.rwEventCacheIndex].Slot = slot + s.rwEventCacheIndex++ + return } - return wset.FindLastWrite(txIndex) + s.rwEventCache = append(s.rwEventCache, RWEventItem{ + Event: WriteSlotRWEvent, + Addr: addr, + Slot: slot, + }) + s.rwEventCacheIndex++ } -// FulfillRWSet it can execute as async, and rwSet & stat must guarantee read-only -// try to generate TxDAG, when fulfill RWSet -func (s *MVStates) FulfillRWSet(rwSet *RWSet, stat *ExeStat) error { - s.lock.Lock() - defer s.lock.Unlock() - index := rwSet.ver.TxIndex - if stat != nil { - if stat.txIndex != index { - return errors.New("wrong execution stat") - } - s.stats[index] = stat +func (s *MVStates) RecordCannotDelayGasFee() { + if s.rwEventCacheIndex < len(s.rwEventCache) { + s.rwEventCache[s.rwEventCacheIndex].Event = CannotGasFeeDelayRWEvent + s.rwEventCacheIndex++ + return } - s.rwSets[index] = rwSet - return nil + s.rwEventCache = append(s.rwEventCache, RWEventItem{ + Event: CannotGasFeeDelayRWEvent, + }) + s.rwEventCacheIndex++ } -// AsyncFinalise it will put target write set into pending writes. -func (s *MVStates) AsyncFinalise(index int) { - // async resolve dependency, but non-block action - if s.asyncRunning && s.asyncGenChan != nil { - select { - case s.asyncGenChan <- index: - default: - } +func (s *MVStates) BatchRecordHandle() { + if len(s.rwEventCache) == 0 { + return } + s.rwEventCh <- s.rwEventCache[:s.rwEventCacheIndex] + s.rwEventCache = *rwEventCachePool.Get().(*[]RWEventItem) + s.rwEventCache = s.rwEventCache[:cap(s.rwEventCache)] + s.rwEventCacheIndex = 0 } -// Finalise it will put target write set into pending writes. -func (s *MVStates) Finalise(index int) error { +func (s *MVStates) stopAsyncRecorder() { + close(s.rwEventCh) + s.asyncWG.Wait() +} + +// quickFinaliseWithRWSet it just store RWSet and inc pendingIndex +func (s *MVStates) quickFinaliseWithRWSet(rwSet *RWSet) error { + index := rwSet.index + if s.nextFinaliseIndex != index { + return fmt.Errorf("finalise in wrong order, next: %d, input: %d", s.nextFinaliseIndex, index) + } + s.rwSets[index] = rwSet + s.nextFinaliseIndex++ + return nil +} + +// FinaliseWithRWSet it will put target write set into pending writes. +func (s *MVStates) FinaliseWithRWSet(rwSet *RWSet) error { s.lock.Lock() defer s.lock.Unlock() - + if s.asyncRWSet == nil { + s.asyncRWSet = nil + } + index := rwSet.index + if s.nextFinaliseIndex > index { + return fmt.Errorf("finalise in wrong order, next: %d, input: %d", s.nextFinaliseIndex, index) + } + s.rwSets[index] = rwSet // just finalise all previous txs start := s.nextFinaliseIndex if start > index { @@ -543,6 +569,7 @@ func (s *MVStates) Finalise(index int) error { "readCnt", len(s.rwSets[i].accReadSet)+len(s.rwSets[i].slotReadSet), "writeCnt", len(s.rwSets[i].accWriteSet)+len(s.rwSets[i].slotWriteSet)) } + s.rwSets[index] = rwSet return nil } @@ -562,22 +589,22 @@ func (s *MVStates) innerFinalise(index int) error { if _, exist := s.pendingAccWriteSet[addr]; !exist { s.pendingAccWriteSet[addr] = make(map[AccountState]*PendingWrites) } - for state, item := range sub { + for state := range sub { if _, exist := s.pendingAccWriteSet[addr][state]; !exist { s.pendingAccWriteSet[addr][state] = NewPendingWrites() } - s.pendingAccWriteSet[addr][state].Append(&item) + s.pendingAccWriteSet[addr][state].Append(index) } } - for k, sub := range rwSet.slotWriteSet { - if _, exist := s.pendingSlotWriteSet[k]; !exist { - s.pendingSlotWriteSet[k] = make(map[common.Hash]*PendingWrites) + for addr, sub := range rwSet.slotWriteSet { + if _, exist := s.pendingSlotWriteSet[addr]; !exist { + s.pendingSlotWriteSet[addr] = make(map[common.Hash]*PendingWrites) } - for slot, item := range sub { - if _, exist := s.pendingSlotWriteSet[k][slot]; !exist { - s.pendingSlotWriteSet[k][slot] = NewPendingWrites() + for slot := range sub { + if _, exist := s.pendingSlotWriteSet[addr][slot]; !exist { + s.pendingSlotWriteSet[addr][slot] = NewPendingWrites() } - s.pendingSlotWriteSet[k][slot].Append(&item) + s.pendingSlotWriteSet[addr][slot].Append(index) } } // reset nextFinaliseIndex to index+1, it may revert to previous txs @@ -585,6 +612,28 @@ func (s *MVStates) innerFinalise(index int) error { return nil } +func (s *MVStates) finaliseSlotWrite(index int, addr common.Address, slot common.Hash) { + // append to pending write set + if _, exist := s.pendingSlotWriteSet[addr]; !exist { + s.pendingSlotWriteSet[addr] = make(map[common.Hash]*PendingWrites) + } + if _, exist := s.pendingSlotWriteSet[addr][slot]; !exist { + s.pendingSlotWriteSet[addr][slot] = NewPendingWrites() + } + s.pendingSlotWriteSet[addr][slot].Append(index) +} + +func (s *MVStates) finaliseAccWrite(index int, addr common.Address, state AccountState) { + // append to pending write set + if _, exist := s.pendingAccWriteSet[addr]; !exist { + s.pendingAccWriteSet[addr] = make(map[AccountState]*PendingWrites) + } + if _, exist := s.pendingAccWriteSet[addr][state]; !exist { + s.pendingAccWriteSet[addr][state] = NewPendingWrites() + } + s.pendingAccWriteSet[addr][state].Append(index) +} + func (s *MVStates) queryAccWrites(addr common.Address, state AccountState) *PendingWrites { if _, exist := s.pendingAccWriteSet[addr]; !exist { return nil @@ -602,11 +651,11 @@ func (s *MVStates) querySlotWrites(addr common.Address, slot common.Hash) *Pendi // resolveDepsMapCacheByWrites must be executed in order func (s *MVStates) resolveDepsMapCacheByWrites(index int, rwSet *RWSet) { // analysis dep, if the previous transaction is not executed/validated, re-analysis is required - depMap := NewTxDepMap(0) if rwSet.excludedTx { - s.txDepCache[index] = depMap + s.txDepCache[index] = NewTxDep([]uint64{}, ExcludedTxFlag) return } + depMap := NewTxDepMap(0) // check tx dependency, only check key, skip version for addr, sub := range rwSet.accReadSet { for state := range sub { @@ -620,7 +669,11 @@ func (s *MVStates) resolveDepsMapCacheByWrites(index int, rwSet *RWSet) { } items := writes.FindPrevWrites(index) for _, item := range items { - depMap.add(uint64(item.TxIndex())) + tx := uint64(item) + if depMap.exist(tx) { + continue + } + depMap.add(tx) } } } @@ -632,7 +685,11 @@ func (s *MVStates) resolveDepsMapCacheByWrites(index int, rwSet *RWSet) { } items := writes.FindPrevWrites(index) for _, item := range items { - depMap.add(uint64(item.TxIndex())) + tx := uint64(item) + if depMap.exist(tx) { + continue + } + depMap.add(tx) } } } @@ -640,27 +697,27 @@ func (s *MVStates) resolveDepsMapCacheByWrites(index int, rwSet *RWSet) { // clear redundancy deps compared with prev preDeps := depMap.deps() for _, prev := range preDeps { - for _, tx := range s.txDepCache[int(prev)].deps() { + for _, tx := range s.txDepCache[int(prev)].TxIndexes { depMap.remove(tx) } } log.Debug("resolveDepsMapCacheByWrites after clean", "tx", index, "deps", depMap.deps()) - s.txDepCache[index] = depMap + s.txDepCache[index] = NewTxDep(depMap.deps()) } // resolveDepsCache must be executed in order func (s *MVStates) resolveDepsCache(index int, rwSet *RWSet) { // analysis dep, if the previous transaction is not executed/validated, re-analysis is required - depMap := NewTxDepMap(0) if rwSet.excludedTx { - s.txDepCache[index] = depMap + s.txDepCache[index] = NewTxDep([]uint64{}, ExcludedTxFlag) return } + depMap := NewTxDepMap(0) for prev := 0; prev < index; prev++ { // if there are some parallel execution or system txs, it will fulfill in advance // it's ok, and try re-generate later - prevSet, ok := s.rwSets[prev] - if !ok { + prevSet := s.rwSets[prev] + if prevSet == nil { continue } // if prev tx is tagged ExcludedTxFlag, just skip the check @@ -672,7 +729,7 @@ func (s *MVStates) resolveDepsCache(index int, rwSet *RWSet) { depMap.add(uint64(prev)) // clear redundancy deps compared with prev for _, dep := range depMap.deps() { - if s.txDepCache[prev].exist(dep) { + if slices.Contains(s.txDepCache[prev].TxIndexes, dep) { depMap.remove(dep) } } @@ -681,52 +738,35 @@ func (s *MVStates) resolveDepsCache(index int, rwSet *RWSet) { depMap.add(uint64(prev)) // clear redundancy deps compared with prev for _, dep := range depMap.deps() { - if s.txDepCache[prev].exist(dep) { + if slices.Contains(s.txDepCache[prev].TxIndexes, dep) { depMap.remove(dep) } } } } - s.txDepCache[index] = depMap + s.txDepCache[index] = NewTxDep(depMap.deps()) } // ResolveTxDAG generate TxDAG from RWSets -func (s *MVStates) ResolveTxDAG(txCnt int, gasFeeReceivers []common.Address) (TxDAG, error) { +func (s *MVStates) ResolveTxDAG(txCnt int) (TxDAG, error) { + s.BatchRecordHandle() + s.stopAsyncRecorder() + s.lock.Lock() defer s.lock.Unlock() - if len(s.rwSets) != txCnt { - return nil, fmt.Errorf("wrong rwSet count, expect: %v, actual: %v", txCnt, len(s.rwSets)) + if s.cannotGasFeeDelay { + return NewEmptyTxDAG(), nil } - s.stopAsyncGen() - // collect all rw sets, try to finalise them - for i := s.nextFinaliseIndex; i < txCnt; i++ { - if err := s.innerFinalise(i); err != nil { - return nil, err - } + s.finalisePreviousRWSet() + if s.nextFinaliseIndex != txCnt { + return nil, fmt.Errorf("cannot resolve with wrong FinaliseIndex, expect: %v, now: %v", txCnt, s.nextFinaliseIndex) } txDAG := NewPlainTxDAG(txCnt) for i := 0; i < txCnt; i++ { - // check if there are RW with gas fee receiver for gas delay calculation - for _, addr := range gasFeeReceivers { - if _, ok := s.rwSets[i].accReadSet[addr]; !ok { - continue - } - if _, ok := s.rwSets[i].accReadSet[addr][AccountSelf]; ok { - return NewEmptyTxDAG(), nil - } - } - txDAG.TxDeps[i].TxIndexes = []uint64{} - if s.txDepCache[i] == nil { - s.resolveDepsMapCacheByWrites(i, s.rwSets[i]) - } - if s.rwSets[i].excludedTx { - txDAG.TxDeps[i].SetFlag(ExcludedTxFlag) - continue - } - deps := s.txDepCache[i].deps() + deps := s.txDepCache[i].TxIndexes if len(deps) <= (txCnt-1)/2 { - txDAG.TxDeps[i].TxIndexes = deps + txDAG.TxDeps[i] = s.txDepCache[i] continue } // if tx deps larger than half of txs, then convert with NonDependentRelFlag @@ -741,7 +781,11 @@ func (s *MVStates) ResolveTxDAG(txCnt int, gasFeeReceivers []common.Address) (Tx return txDAG, nil } -func checkAccDependency(writeSet map[common.Address]map[AccountState]RWItem, readSet map[common.Address]map[AccountState]RWItem) bool { +func (s *MVStates) FeeReceivers() []common.Address { + return s.gasFeeReceivers +} + +func checkAccDependency(writeSet map[common.Address]map[AccountState]struct{}, readSet map[common.Address]map[AccountState]struct{}) bool { // check tx dependency, only check key, skip version for addr, sub := range writeSet { if _, ok := readSet[addr]; !ok { @@ -764,7 +808,7 @@ func checkAccDependency(writeSet map[common.Address]map[AccountState]RWItem, rea return false } -func checkSlotDependency(writeSet map[common.Address]map[common.Hash]RWItem, readSet map[common.Address]map[common.Hash]RWItem) bool { +func checkSlotDependency(writeSet map[common.Address]map[common.Hash]struct{}, readSet map[common.Address]map[common.Hash]struct{}) bool { // check tx dependency, only check key, skip version for addr, sub := range writeSet { if _, ok := readSet[addr]; !ok { @@ -786,6 +830,7 @@ type TxDepMaker interface { deps() []uint64 remove(index uint64) len() int + reset() } type TxDepMap struct { @@ -830,3 +875,42 @@ func (m *TxDepMap) remove(index uint64) { func (m *TxDepMap) len() int { return len(m.tm) } + +func (m *TxDepMap) reset() { + m.cache = nil + m.tm = make(map[uint64]struct{}) +} + +// isEqualRWVal compare state +func isEqualRWVal(accState *AccountState, src interface{}, compared interface{}) bool { + if accState != nil { + switch *accState { + case AccountBalance: + if src != nil && compared != nil { + return equalUint256(src.(*uint256.Int), compared.(*uint256.Int)) + } + return src == compared + case AccountNonce: + return src.(uint64) == compared.(uint64) + case AccountCodeHash: + if src != nil && compared != nil { + return slices.Equal(src.([]byte), compared.([]byte)) + } + return src == compared + } + return false + } + + if src != nil && compared != nil { + return src.(common.Hash) == compared.(common.Hash) + } + return src == compared +} + +func equalUint256(s, c *uint256.Int) bool { + if s != nil && c != nil { + return s.Eq(c) + } + + return s == c +} diff --git a/core/types/mvstates_test.go b/core/types/mvstates_test.go index 9737c7f9e0..f22ba40fe4 100644 --- a/core/types/mvstates_test.go +++ b/core/types/mvstates_test.go @@ -17,83 +17,16 @@ import ( ) const ( - mockRWSetSize = 5000 + mockRWSetSize = 10000 ) -func TestMVStates_BasicUsage(t *testing.T) { - ms := NewMVStates(0) - require.NoError(t, ms.FulfillRWSet(mockRWSetWithVal(0, []interface{}{AccountSelf, nil, AccountBalance, 0, "0x00", 0}, []interface{}{AccountBalance, 0, "0x00", 0}), nil)) - require.Nil(t, ms.ReadAccState(0, common.Address{}, AccountBalance)) - require.Nil(t, ms.ReadSlotState(0, common.Address{}, str2Slot("0x00"))) - require.NoError(t, ms.Finalise(0)) - - require.Nil(t, ms.ReadAccState(0, common.Address{}, AccountBalance)) - require.Nil(t, ms.ReadSlotState(0, common.Address{}, str2Slot("0x00"))) - require.Equal(t, NewRWItem(StateVersion{TxIndex: 0}, 0), ms.ReadAccState(1, common.Address{}, AccountBalance)) - require.Equal(t, NewRWItem(StateVersion{TxIndex: 0}, 0), ms.ReadSlotState(1, common.Address{}, str2Slot("0x00"))) - - require.NoError(t, ms.FulfillRWSet(mockRWSetWithVal(1, []interface{}{AccountSelf, nil, AccountBalance, 0, "0x01", 1}, []interface{}{AccountBalance, 1, "0x01", 1}), nil)) - require.Nil(t, ms.ReadSlotState(1, common.Address{}, str2Slot("0x01"))) - require.NoError(t, ms.Finalise(1)) - require.Nil(t, ms.ReadSlotState(0, common.Address{}, str2Slot("0x01"))) - require.Equal(t, NewRWItem(StateVersion{TxIndex: 1}, 1), ms.ReadSlotState(2, common.Address{}, str2Slot("0x01"))) - require.Equal(t, NewRWItem(StateVersion{TxIndex: 1}, 1), ms.ReadAccState(2, common.Address{}, AccountBalance)) - - require.NoError(t, ms.FulfillRWSet(mockRWSetWithVal(2, []interface{}{AccountSelf, nil, AccountBalance, 1, "0x02", 2, "0x01", 1}, []interface{}{AccountBalance, 2, "0x01", 2, "0x02", 2}), nil)) - require.NoError(t, ms.Finalise(2)) - require.Equal(t, NewRWItem(StateVersion{TxIndex: 1}, 1), ms.ReadAccState(2, common.Address{}, AccountBalance)) - require.Equal(t, NewRWItem(StateVersion{TxIndex: 1}, 1), ms.ReadSlotState(2, common.Address{}, str2Slot("0x01"))) - require.Equal(t, NewRWItem(StateVersion{TxIndex: 2}, 2), ms.ReadAccState(3, common.Address{}, AccountBalance)) - require.Equal(t, NewRWItem(StateVersion{TxIndex: 2}, 2), ms.ReadSlotState(3, common.Address{}, str2Slot("0x01"))) - - require.NoError(t, ms.FulfillRWSet(mockRWSetWithVal(3, []interface{}{AccountSelf, nil, AccountBalance, 2, "0x03", 3, "0x00", 0, "0x01", 2}, []interface{}{AccountBalance, 3, "0x00", 3, "0x01", 3, "0x03", 3}), nil)) - require.Nil(t, ms.ReadSlotState(3, common.Address{}, str2Slot("0x03"))) - require.NoError(t, ms.Finalise(3)) - require.Nil(t, ms.ReadSlotState(0, common.Address{}, str2Slot("0x01"))) - require.Equal(t, NewRWItem(StateVersion{TxIndex: 1}, 1), ms.ReadSlotState(2, common.Address{}, str2Slot("0x01"))) - require.Equal(t, NewRWItem(StateVersion{TxIndex: 1}, 1), ms.ReadAccState(2, common.Address{}, AccountBalance)) - require.Equal(t, NewRWItem(StateVersion{TxIndex: 2}, 2), ms.ReadSlotState(3, common.Address{}, str2Slot("0x01"))) - require.Equal(t, NewRWItem(StateVersion{TxIndex: 2}, 2), ms.ReadAccState(3, common.Address{}, AccountBalance)) - require.Equal(t, NewRWItem(StateVersion{TxIndex: 3}, 3), ms.ReadSlotState(4, common.Address{}, str2Slot("0x01"))) - require.Equal(t, NewRWItem(StateVersion{TxIndex: 3}, 3), ms.ReadAccState(4, common.Address{}, AccountBalance)) - require.Nil(t, ms.ReadSlotState(0, common.Address{}, str2Slot("0x00"))) - require.Nil(t, ms.ReadAccState(0, common.Address{}, AccountBalance)) - require.Equal(t, NewRWItem(StateVersion{TxIndex: 3}, 3), ms.ReadSlotState(5, common.Address{}, str2Slot("0x00"))) - require.Equal(t, NewRWItem(StateVersion{TxIndex: 3}, 3), ms.ReadAccState(5, common.Address{}, AccountBalance)) -} - func TestMVStates_SimpleResolveTxDAG(t *testing.T) { - ms := NewMVStates(10) - finaliseRWSets(t, ms, []*RWSet{ - mockRWSet(0, []interface{}{"0x00"}, []interface{}{"0x00"}), - mockRWSet(1, []interface{}{"0x01"}, []interface{}{"0x01"}), - mockRWSet(2, []interface{}{"0x02"}, []interface{}{"0x02"}), - mockRWSet(3, []interface{}{"0x00", "0x03"}, []interface{}{"0x03"}), - mockRWSet(4, []interface{}{"0x00", "0x04"}, []interface{}{"0x04"}), - mockRWSet(5, []interface{}{"0x01", "0x02", "0x05"}, []interface{}{"0x05"}), - mockRWSet(6, []interface{}{"0x02", "0x05", "0x06"}, []interface{}{"0x06"}), - mockRWSet(7, []interface{}{"0x06", "0x07"}, []interface{}{"0x07"}), - mockRWSet(8, []interface{}{"0x08"}, []interface{}{"0x08"}), - mockRWSet(9, []interface{}{"0x08", "0x09"}, []interface{}{"0x09"}), - }) - - dag, err := ms.ResolveTxDAG(10, nil) - require.NoError(t, err) - require.Equal(t, mockSimpleDAG(), dag) - t.Log(dag) -} - -func TestMVStates_AsyncDepGen_SimpleResolveTxDAG(t *testing.T) { - ms := NewMVStates(10).EnableAsyncGen() + ms := NewMVStates(10, nil).EnableAsyncGen() finaliseRWSets(t, ms, []*RWSet{ mockRWSet(0, []interface{}{"0x00"}, []interface{}{"0x00"}), mockRWSet(1, []interface{}{"0x01"}, []interface{}{"0x01"}), mockRWSet(2, []interface{}{"0x02"}, []interface{}{"0x02"}), - mockRWSet(3, []interface{}{"0x03"}, []interface{}{"0x03"}), - mockRWSet(3, []interface{}{"0x03"}, []interface{}{"0x03"}), mockRWSet(3, []interface{}{"0x00", "0x03"}, []interface{}{"0x03"}), - }) - finaliseRWSets(t, ms, []*RWSet{ mockRWSet(4, []interface{}{"0x00", "0x04"}, []interface{}{"0x04"}), mockRWSet(5, []interface{}{"0x01", "0x02", "0x05"}, []interface{}{"0x05"}), mockRWSet(6, []interface{}{"0x02", "0x05", "0x06"}, []interface{}{"0x06"}), @@ -101,41 +34,24 @@ func TestMVStates_AsyncDepGen_SimpleResolveTxDAG(t *testing.T) { mockRWSet(8, []interface{}{"0x08"}, []interface{}{"0x08"}), mockRWSet(9, []interface{}{"0x08", "0x09"}, []interface{}{"0x09"}), }) - time.Sleep(10 * time.Millisecond) - dag, err := ms.ResolveTxDAG(10, nil) + dag, err := ms.ResolveTxDAG(10) require.NoError(t, err) - time.Sleep(100 * time.Millisecond) - require.NoError(t, ms.Stop()) require.Equal(t, mockSimpleDAG(), dag) t.Log(dag) } -func TestMVStates_ResolveTxDAG_Async(t *testing.T) { - txCnt := 10000 - rwSets := mockRandomRWSet(txCnt) - ms1 := NewMVStates(txCnt).EnableAsyncGen() - for i := 0; i < txCnt; i++ { - require.NoError(t, ms1.FulfillRWSet(rwSets[i], nil)) - require.NoError(t, ms1.Finalise(i)) - } - time.Sleep(100 * time.Millisecond) - _, err := ms1.ResolveTxDAG(txCnt, nil) - require.NoError(t, err) -} - func TestMVStates_ResolveTxDAG_Compare(t *testing.T) { txCnt := 3000 rwSets := mockRandomRWSet(txCnt) - ms1 := NewMVStates(txCnt) - ms2 := NewMVStates(txCnt) + ms1 := NewMVStates(txCnt, nil).EnableAsyncGen() + ms2 := NewMVStates(txCnt, nil).EnableAsyncGen() for i, rwSet := range rwSets { ms1.rwSets[i] = rwSet - ms2.rwSets[i] = rwSet - require.NoError(t, ms2.Finalise(i)) + require.NoError(t, ms2.FinaliseWithRWSet(rwSet)) } - d1 := resolveTxDAGInMVStates(ms1) + d1 := resolveTxDAGInMVStates(ms1, txCnt) d2 := resolveDepsMapCacheByWritesInMVStates(ms2) require.Equal(t, d1.(*PlainTxDAG).String(), d2.(*PlainTxDAG).String()) } @@ -143,10 +59,9 @@ func TestMVStates_ResolveTxDAG_Compare(t *testing.T) { func TestMVStates_TxDAG_Compression(t *testing.T) { txCnt := 10000 rwSets := mockRandomRWSet(txCnt) - ms1 := NewMVStates(txCnt) - for i, rwSet := range rwSets { - ms1.rwSets[i] = rwSet - ms1.Finalise(i) + ms1 := NewMVStates(txCnt, nil).EnableAsyncGen() + for _, rwSet := range rwSets { + ms1.FinaliseWithRWSet(rwSet) } dag := resolveDepsMapCacheByWritesInMVStates(ms1) enc, err := EncodeTxDAG(dag) @@ -175,22 +90,21 @@ func TestMVStates_TxDAG_Compression(t *testing.T) { func BenchmarkResolveTxDAGInMVStates(b *testing.B) { rwSets := mockRandomRWSet(mockRWSetSize) - ms1 := NewMVStates(mockRWSetSize) + ms1 := NewMVStates(mockRWSetSize, nil).EnableAsyncGen() for i, rwSet := range rwSets { ms1.rwSets[i] = rwSet } b.ResetTimer() for i := 0; i < b.N; i++ { - resolveTxDAGInMVStates(ms1) + resolveTxDAGInMVStates(ms1, mockRWSetSize) } } func BenchmarkResolveTxDAGByWritesInMVStates(b *testing.B) { rwSets := mockRandomRWSet(mockRWSetSize) - ms1 := NewMVStates(mockRWSetSize) - for i, rwSet := range rwSets { - ms1.rwSets[i] = rwSet - ms1.innerFinalise(i) + ms1 := NewMVStates(mockRWSetSize, nil).EnableAsyncGen() + for _, rwSet := range rwSets { + ms1.FinaliseWithRWSet(rwSet) } b.ResetTimer() for i := 0; i < b.N; i++ { @@ -200,36 +114,36 @@ func BenchmarkResolveTxDAGByWritesInMVStates(b *testing.B) { func BenchmarkMVStates_Finalise(b *testing.B) { rwSets := mockRandomRWSet(mockRWSetSize) - ms1 := NewMVStates(mockRWSetSize) + ms1 := NewMVStates(mockRWSetSize, nil).EnableAsyncGen() b.ResetTimer() for i := 0; i < b.N; i++ { - for k, rwSet := range rwSets { - ms1.rwSets[k] = rwSet - ms1.innerFinalise(k) + for _, rwSet := range rwSets { + ms1.FinaliseWithRWSet(rwSet) } } } -func resolveTxDAGInMVStates(s *MVStates) TxDAG { - txDAG := NewPlainTxDAG(len(s.rwSets)) - for i := 0; i < len(s.rwSets); i++ { +func resolveTxDAGInMVStates(s *MVStates, txCnt int) TxDAG { + txDAG := NewPlainTxDAG(txCnt) + for i := 0; i < txCnt; i++ { s.resolveDepsCache(i, s.rwSets[i]) - txDAG.TxDeps[i].TxIndexes = s.txDepCache[i].deps() + txDAG.TxDeps[i] = s.txDepCache[i] } return txDAG } func resolveDepsMapCacheByWritesInMVStates(s *MVStates) TxDAG { - txDAG := NewPlainTxDAG(len(s.rwSets)) - for i := 0; i < len(s.rwSets); i++ { + txCnt := s.nextFinaliseIndex + txDAG := NewPlainTxDAG(txCnt) + for i := 0; i < txCnt; i++ { s.resolveDepsMapCacheByWrites(i, s.rwSets[i]) - txDAG.TxDeps[i].TxIndexes = s.txDepCache[i].deps() + txDAG.TxDeps[i] = s.txDepCache[i] } return txDAG } func TestMVStates_SystemTxResolveTxDAG(t *testing.T) { - ms := NewMVStates(12) + ms := NewMVStates(12, nil).EnableAsyncGen() finaliseRWSets(t, ms, []*RWSet{ mockRWSet(0, []interface{}{"0x00"}, []interface{}{"0x00"}), mockRWSet(1, []interface{}{"0x01"}, []interface{}{"0x01"}), @@ -245,14 +159,14 @@ func TestMVStates_SystemTxResolveTxDAG(t *testing.T) { mockRWSet(11, []interface{}{"0x11"}, []interface{}{"0x11"}).WithExcludedTxFlag(), }) - dag, err := ms.ResolveTxDAG(12, nil) + dag, err := ms.ResolveTxDAG(12) require.NoError(t, err) require.Equal(t, mockSystemTxDAG(), dag) t.Log(dag) } func TestMVStates_SystemTxWithLargeDepsResolveTxDAG(t *testing.T) { - ms := NewMVStates(12) + ms := NewMVStates(12, nil).EnableAsyncGen() finaliseRWSets(t, ms, []*RWSet{ mockRWSet(0, []interface{}{"0x00"}, []interface{}{"0x00"}), mockRWSet(1, []interface{}{"0x01"}, []interface{}{"0x01"}), @@ -267,7 +181,7 @@ func TestMVStates_SystemTxWithLargeDepsResolveTxDAG(t *testing.T) { mockRWSet(10, []interface{}{"0x10"}, []interface{}{"0x10"}).WithExcludedTxFlag(), mockRWSet(11, []interface{}{"0x11"}, []interface{}{"0x11"}).WithExcludedTxFlag(), }) - dag, err := ms.ResolveTxDAG(12, nil) + dag, err := ms.ResolveTxDAG(12) require.NoError(t, err) require.Equal(t, mockSystemTxDAGWithLargeDeps(), dag) t.Log(dag) @@ -371,107 +285,65 @@ func TestIsEqualRWVal(t *testing.T) { } } -func mockRWSet(index int, read []interface{}, write []interface{}) *RWSet { - ver := StateVersion{ - TxIndex: index, +func TestTxRecorder_Basic(t *testing.T) { + sets := []*RWSet{ + mockRWSet(0, []interface{}{AccountSelf, AccountBalance, "0x00"}, + []interface{}{AccountBalance, AccountCodeHash, "0x00"}), + mockRWSet(1, []interface{}{AccountSelf, AccountBalance, "0x01"}, + []interface{}{AccountBalance, AccountCodeHash, "0x01"}), + mockRWSet(2, []interface{}{AccountSelf, AccountBalance, "0x01", "0x01"}, + []interface{}{AccountBalance, AccountCodeHash, "0x01"}), } - set := NewRWSet(ver) - set.accReadSet[common.Address{}] = map[AccountState]RWItem{} - set.accWriteSet[common.Address{}] = map[AccountState]RWItem{} - set.slotReadSet[common.Address{}] = map[common.Hash]RWItem{} - set.slotWriteSet[common.Address{}] = map[common.Hash]RWItem{} - for _, k := range read { - state, ok := k.(AccountState) - if ok { - set.accReadSet[common.Address{}][state] = RWItem{ - Ver: ver, - Val: struct{}{}, + ms := NewMVStates(0, nil).EnableAsyncGen() + for _, item := range sets { + ms.RecordNewTx(item.index) + for addr, sub := range item.accReadSet { + for state := range sub { + ms.RecordAccountRead(addr, state) } - } else { - set.slotReadSet[common.Address{}][str2Slot(k.(string))] = RWItem{ - Ver: ver, - Val: struct{}{}, + } + for addr, sub := range item.slotReadSet { + for slot := range sub { + ms.RecordStorageRead(addr, slot) } } - } - for _, k := range write { - state, ok := k.(AccountState) - if ok { - set.accWriteSet[common.Address{}][state] = RWItem{ - Ver: ver, - Val: struct{}{}, + for addr, sub := range item.accWriteSet { + for state := range sub { + ms.RecordAccountWrite(addr, state) } - } else { - set.slotWriteSet[common.Address{}][str2Slot(k.(string))] = RWItem{ - Ver: ver, - Val: struct{}{}, + } + for addr, sub := range item.slotWriteSet { + for slot := range sub { + ms.RecordStorageWrite(addr, slot) } } } - - return set -} - -func mockUintSlice(cnt int) []uint64 { - ret := make([]uint64, cnt) - for i := 0; i < cnt; i++ { - ret[i] = rand.Uint64() % uint64(cnt) - } - return ret + dag, err := ms.ResolveTxDAG(3) + require.NoError(t, err) + t.Log(dag) } -func mockRWSetWithVal(index int, read []interface{}, write []interface{}) *RWSet { - ver := StateVersion{ - TxIndex: index, - } - set := NewRWSet(ver) - - if len(read)%2 != 0 { - panic("wrong read size") - } - if len(write)%2 != 0 { - panic("wrong write size") - } - - set.accReadSet[common.Address{}] = map[AccountState]RWItem{} - set.slotReadSet[common.Address{}] = map[common.Hash]RWItem{} - set.accWriteSet[common.Address{}] = map[AccountState]RWItem{} - set.slotWriteSet[common.Address{}] = map[common.Hash]RWItem{} - for i := 0; i < len(read); { - state, ok := read[i].(AccountState) +func mockRWSet(index int, read []interface{}, write []interface{}) *RWSet { + set := NewRWSet(index) + set.accReadSet[common.Address{}] = map[AccountState]struct{}{} + set.accWriteSet[common.Address{}] = map[AccountState]struct{}{} + set.slotReadSet[common.Address{}] = map[common.Hash]struct{}{} + set.slotWriteSet[common.Address{}] = map[common.Hash]struct{}{} + for _, k := range read { + state, ok := k.(AccountState) if ok { - set.accReadSet[common.Address{}][state] = RWItem{ - Ver: StateVersion{ - TxIndex: index - 1, - }, - Val: read[i+1], - } + set.accReadSet[common.Address{}][state] = struct{}{} } else { - slot := str2Slot(read[i].(string)) - set.slotReadSet[common.Address{}][slot] = RWItem{ - Ver: StateVersion{ - TxIndex: index - 1, - }, - Val: read[i+1], - } + set.slotReadSet[common.Address{}][str2Slot(k.(string))] = struct{}{} } - i += 2 } - for i := 0; i < len(write); { - state, ok := write[i].(AccountState) + for _, k := range write { + state, ok := k.(AccountState) if ok { - set.accWriteSet[common.Address{}][state] = RWItem{ - Ver: ver, - Val: write[i+1], - } + set.accWriteSet[common.Address{}][state] = struct{}{} } else { - slot := str2Slot(write[i].(string)) - set.slotWriteSet[common.Address{}][slot] = RWItem{ - Ver: ver, - Val: write[i+1], - } + set.slotWriteSet[common.Address{}][str2Slot(k.(string))] = struct{}{} } - i += 2 } return set @@ -514,8 +386,7 @@ func mockRandomRWSet(count int) []*RWSet { func finaliseRWSets(t *testing.T, mv *MVStates, rwSets []*RWSet) { for _, rwSet := range rwSets { - require.NoError(t, mv.FulfillRWSet(rwSet, nil)) - require.NoError(t, mv.Finalise(rwSet.ver.TxIndex)) + require.NoError(t, mv.FinaliseWithRWSet(rwSet)) } } diff --git a/core/vm/interface.go b/core/vm/interface.go index 8c82851349..4ad4ecc311 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -80,10 +80,7 @@ type StateDB interface { AddPreimage(common.Hash, []byte) TxIndex() int - - // parallel DAG related - BeforeTxTransition() - FinaliseRWSet() error + CheckFeeReceiversRWSet() error } // CallContext provides a basic interface for the EVM calling conventions. The EVM diff --git a/go.mod b/go.mod index 3415346fc3..9581d53c54 100644 --- a/go.mod +++ b/go.mod @@ -109,6 +109,7 @@ require ( github.com/dgraph-io/ristretto v0.0.4-0.20210318174700-74754f61e018 // indirect github.com/dlclark/regexp2 v1.7.0 // indirect github.com/dustin/go-humanize v1.0.0 // indirect + github.com/emirpasic/gods v1.18.1 // indirect github.com/ferranbt/fastssz v0.0.0-20210905181407-59cf6761a7d5 // indirect github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61 // indirect github.com/getsentry/sentry-go v0.27.0 // indirect diff --git a/go.sum b/go.sum index d7ed4975c9..c5a0593b14 100644 --- a/go.sum +++ b/go.sum @@ -348,6 +348,8 @@ github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZi github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/dot v0.11.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= diff --git a/miner/worker.go b/miner/worker.go index 5078544770..100b65943a 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1006,7 +1006,7 @@ func (w *worker) appendTxDAG(env *environment) { return } // TODO this is a placeholder for the tx DAG data that will be generated by the stateDB - txForDAG, err := w.generateDAGTx(env.state, env.signer, env.tcount, env.coinbase, env.gasForTxDAG) + txForDAG, err := w.generateDAGTx(env.state, env.signer, env.tcount, env.gasForTxDAG) if err != nil { log.Warn("failed to generate DAG tx", "err", err) return @@ -1020,7 +1020,7 @@ func (w *worker) appendTxDAG(env *environment) { } // generateDAGTx generates a DAG transaction for the block -func (w *worker) generateDAGTx(statedb *state.StateDB, signer types.Signer, txIndex int, coinbase common.Address, gasLimitForDag uint64) (*types.Transaction, error) { +func (w *worker) generateDAGTx(statedb *state.StateDB, signer types.Signer, txIndex int, gasLimitForDag uint64) (*types.Transaction, error) { if statedb == nil { return nil, fmt.Errorf("failed to get state db, env.state=nil") } @@ -1036,14 +1036,14 @@ func (w *worker) generateDAGTx(statedb *state.StateDB, signer types.Signer, txIn } // get txDAG data from the stateDB - txDAG, err := statedb.ResolveTxDAG(txIndex, []common.Address{coinbase, params.OptimismBaseFeeRecipient, params.OptimismL1FeeRecipient}) + txDAG, err := statedb.ResolveTxDAG(txIndex) if txDAG == nil { return nil, err } // txIndex is the index of this txDAG transaction txDAG.SetTxDep(txIndex, types.TxDep{Flags: &types.NonDependentRelFlag}) if metrics.EnabledExpensive { - go types.EvaluateTxDAGPerformance(txDAG, statedb.ResolveStats()) + go types.EvaluateTxDAGPerformance(txDAG) } publicKey := sender.Public() @@ -1312,19 +1312,18 @@ func (w *worker) generateWork(genParams *generateParams) *newPayloadResult { start := time.Now() if w.chain.TxDAGEnabledWhenMine() { - work.state.ResetMVStates(0) + feeReceivers := []common.Address{work.coinbase, params.OptimismBaseFeeRecipient, params.OptimismL1FeeRecipient} + work.state.ResetMVStates(0, feeReceivers) log.Debug("ResetMVStates", "block", work.header.Number.Uint64()) } for _, tx := range genParams.txs { from, _ := types.Sender(work.signer, tx) work.state.SetTxContext(tx.Hash(), work.tcount) + work.state.BeginTxRecorder(work.tcount, tx.IsSystemTx() || tx.IsDepositTx()) _, err := w.commitTransaction(work, tx) if err != nil { return &newPayloadResult{err: fmt.Errorf("failed to force-include tx: %s type: %d sender: %s nonce: %d, err: %w", tx.Hash(), tx.Type(), from, tx.Nonce(), err)} } - if tx.IsSystemTx() || tx.IsDepositTx() { - work.state.RecordSystemTxRWSet(work.tcount) - } work.tcount++ } commitDepositTxsTimer.UpdateSince(start) diff --git a/tests/block_test.go b/tests/block_test.go index aa6f27b8f3..20bb285eac 100644 --- a/tests/block_test.go +++ b/tests/block_test.go @@ -73,20 +73,72 @@ func TestExecutionSpec(t *testing.T) { }) } +func TestBlockchainWithTxDAG(t *testing.T) { + //log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelDebug, true))) + bt := new(testMatcher) + // General state tests are 'exported' as blockchain tests, but we can run them natively. + // For speedier CI-runs, the line below can be uncommented, so those are skipped. + // For now, in hardfork-times (Berlin), we run the tests both as StateTests and + // as blockchain tests, since the latter also covers things like receipt root + bt.skipLoad(`^GeneralStateTests/`) + + // Skip random failures due to selfish mining test + bt.skipLoad(`.*bcForgedTest/bcForkUncle\.json`) + + // Slow tests + bt.slow(`.*bcExploitTest/DelegateCallSpam.json`) + bt.slow(`.*bcExploitTest/ShanghaiLove.json`) + bt.slow(`.*bcExploitTest/SuicideIssue.json`) + bt.slow(`.*/bcForkStressTest/`) + bt.slow(`.*/bcGasPricerTest/RPC_API_Test.json`) + bt.slow(`.*/bcWalletTest/`) + + // Very slow test + bt.skipLoad(`.*/stTimeConsuming/.*`) + // test takes a lot for time and goes easily OOM because of sha3 calculation on a huge range, + // using 4.6 TGas + bt.skipLoad(`.*randomStatetest94.json.*`) + + bt.walk(t, blockTestDir, func(t *testing.T, name string, test *BlockTest) { + if runtime.GOARCH == "386" && runtime.GOOS == "windows" && rand.Int63()%2 == 0 { + t.Skip("test (randomly) skipped on 32-bit windows") + } + if err := bt.checkFailure(t, test.Run(true, rawdb.PathScheme, nil, true, nil)); err != nil { + t.Errorf("test in path mode with snapshotter failed: %v", err) + return + } + }) + + //bt := new(testMatcher) + //path := filepath.Join(blockTestDir, "ValidBlocks", "bcStatetests", "refundReset.json") + //_, name := filepath.Split(path) + //t.Run(name, func(t *testing.T) { + // bt.runTestFile(t, path, name, func(t *testing.T, name string, test *BlockTest) { + // if runtime.GOARCH == "386" && runtime.GOOS == "windows" && rand.Int63()%2 == 0 { + // t.Skip("test (randomly) skipped on 32-bit windows") + // } + // if err := bt.checkFailure(t, test.Run(true, rawdb.PathScheme, nil, true, nil)); err != nil { + // t.Errorf("test in path mode with snapshotter failed: %v", err) + // return + // } + // }) + //}) +} + func execBlockTest(t *testing.T, bt *testMatcher, test *BlockTest) { - if err := bt.checkFailure(t, test.Run(false, rawdb.HashScheme, nil, nil)); err != nil { + if err := bt.checkFailure(t, test.Run(false, rawdb.HashScheme, nil, false, nil)); err != nil { t.Errorf("test in hash mode without snapshotter failed: %v", err) return } - if err := bt.checkFailure(t, test.Run(true, rawdb.HashScheme, nil, nil)); err != nil { + if err := bt.checkFailure(t, test.Run(true, rawdb.HashScheme, nil, false, nil)); err != nil { t.Errorf("test in hash mode with snapshotter failed: %v", err) return } - if err := bt.checkFailure(t, test.Run(false, rawdb.PathScheme, nil, nil)); err != nil { + if err := bt.checkFailure(t, test.Run(false, rawdb.PathScheme, nil, false, nil)); err != nil { t.Errorf("test in path mode without snapshotter failed: %v", err) return } - if err := bt.checkFailure(t, test.Run(true, rawdb.PathScheme, nil, nil)); err != nil { + if err := bt.checkFailure(t, test.Run(true, rawdb.PathScheme, nil, false, nil)); err != nil { t.Errorf("test in path mode with snapshotter failed: %v", err) return } diff --git a/tests/block_test_util.go b/tests/block_test_util.go index 06b6715181..d3b0d5b3d3 100644 --- a/tests/block_test_util.go +++ b/tests/block_test_util.go @@ -108,7 +108,7 @@ type btHeaderMarshaling struct { ExcessBlobGas *math.HexOrDecimal64 } -func (t *BlockTest) Run(snapshotter bool, scheme string, tracer vm.EVMLogger, postCheck func(error, *core.BlockChain)) (result error) { +func (t *BlockTest) Run(snapshotter bool, scheme string, tracer vm.EVMLogger, enableTxDAG bool, postCheck func(error, *core.BlockChain)) (result error) { config, ok := Forks[t.json.Network] if !ok { return UnsupportedForkError{t.json.Network} @@ -156,6 +156,9 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, tracer vm.EVMLogger, po return err } defer chain.Stop() + if enableTxDAG { + chain.SetupTxDAGGeneration() + } validBlocks, err := t.insertBlocks(chain) if err != nil { From 23b4aa0233f57888c36366b0b5d6b2bdd570afc7 Mon Sep 17 00:00:00 2001 From: galaio Date: Wed, 4 Sep 2024 20:52:16 +0800 Subject: [PATCH 09/19] txdag: adaptor txdag for mining; --- core/blockchain.go | 2 +- core/state/statedb.go | 6 +++--- core/state_processor.go | 2 +- core/types/mvstates.go | 6 ++---- miner/worker.go | 1 - 5 files changed, 7 insertions(+), 10 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index e80522b4cf..de01ceadff 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1968,7 +1968,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) dag, err := statedb.ResolveTxDAG(len(block.Transactions())) if err == nil { // TODO(galaio): check TxDAG correctness? - log.Debug("Process TxDAG result", "block", block.NumberU64(), "txDAG", dag) + log.Debug("Process TxDAG result", "block", block.NumberU64(), "tx", len(block.Transactions()), "txDAG", dag) if metrics.EnabledExpensive { go types.EvaluateTxDAGPerformance(dag) } diff --git a/core/state/statedb.go b/core/state/statedb.go index 290222e070..1316aa1b04 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -1705,18 +1705,18 @@ func (s *StateDB) GetSnap() snapshot.Snapshot { return s.snap } -func (s *StateDB) BeginTxRecorder(index int, isExcludeTx bool) { +func (s *StateDB) BeginTxRecorder(isExcludeTx bool) { if s.mvStates == nil { return } if isExcludeTx { - rwSet := types.NewRWSet(index).WithExcludedTxFlag() + rwSet := types.NewRWSet(s.txIndex).WithExcludedTxFlag() if err := s.mvStates.FinaliseWithRWSet(rwSet); err != nil { log.Error("MVStates SystemTx Finalise err", "err", err) } return } - s.mvStates.RecordNewTx(index) + s.mvStates.RecordNewTx(s.txIndex) } func (s *StateDB) ResetMVStates(txCount int, feeReceivers []common.Address) *types.MVStates { diff --git a/core/state_processor.go b/core/state_processor.go index 86a700a89a..2d32cba647 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -96,7 +96,6 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg } // Iterate over and process the individual transactions for i, tx := range block.Transactions() { - statedb.BeginTxRecorder(i, tx.IsSystemTx() || tx.IsDepositTx()) start := time.Now() msg, err := TransactionToMessage(tx, signer, header.BaseFee) if err != nil { @@ -130,6 +129,7 @@ func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, sta // Create a new context to be used in the EVM environment. txContext := NewEVMTxContext(msg) evm.Reset(txContext, statedb) + statedb.BeginTxRecorder(tx.IsSystemTx() || tx.IsDepositTx()) nonce := tx.Nonce() if msg.IsDepositTx && config.IsOptimismRegolith(evm.Context.Time) { diff --git a/core/types/mvstates.go b/core/types/mvstates.go index aa318ac95f..fff029d169 100644 --- a/core/types/mvstates.go +++ b/core/types/mvstates.go @@ -23,7 +23,7 @@ var ( ) const ( - initRWEventCacheSize = 2 + initRWEventCacheSize = 4 ) func init() { @@ -314,6 +314,7 @@ func (s *MVStates) Copy() *MVStates { s.lock.Lock() defer s.lock.Unlock() ns := NewMVStates(len(s.rwSets), s.gasFeeReceivers) + ns.cannotGasFeeDelay = s.cannotGasFeeDelay ns.nextFinaliseIndex = s.nextFinaliseIndex for k, v := range s.txDepCache { ns.txDepCache[k] = v @@ -356,9 +357,6 @@ func (s *MVStates) asyncRWEventLoop() { case <-timeout: log.Warn("asyncRWEventLoop timeout") return - case <-timeout: - log.Warn("asyncDepGenLoop exit by timeout") - return } } } diff --git a/miner/worker.go b/miner/worker.go index 100b65943a..1d3449a9cb 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1319,7 +1319,6 @@ func (w *worker) generateWork(genParams *generateParams) *newPayloadResult { for _, tx := range genParams.txs { from, _ := types.Sender(work.signer, tx) work.state.SetTxContext(tx.Hash(), work.tcount) - work.state.BeginTxRecorder(work.tcount, tx.IsSystemTx() || tx.IsDepositTx()) _, err := w.commitTransaction(work, tx) if err != nil { return &newPayloadResult{err: fmt.Errorf("failed to force-include tx: %s type: %d sender: %s nonce: %d, err: %w", tx.Hash(), tx.Type(), from, tx.Nonce(), err)} From 1362f2485106607828e615f2cc723a17904425a5 Mon Sep 17 00:00:00 2001 From: galaio Date: Wed, 4 Sep 2024 22:06:31 +0800 Subject: [PATCH 10/19] txdag: clean codes; --- core/state/statedb.go | 15 +++-- core/state_transition.go | 6 +- core/types/mvstates.go | 112 +++++++++--------------------------- core/types/mvstates_test.go | 99 ------------------------------- core/vm/interface.go | 4 +- 5 files changed, 40 insertions(+), 196 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 1316aa1b04..fbe6fb90c7 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -947,6 +947,7 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { s.mvStates.RecordAccountWrite(addr, types.AccountSuicide) } } + s.stateObjectsDestructDirty = make(map[common.Address]*types.StateAccount) for addr := range s.journal.dirties { obj, exist := s.stateObjects[addr] if !exist { @@ -992,7 +993,9 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { // the commit-phase will be a lot faster addressesToPrefetch = append(addressesToPrefetch, common.CopyBytes(addr[:])) // Copy needed for closure } - + if s.mvStates != nil { + s.mvStates.RecordWriteDone() + } if s.prefetcher != nil && len(addressesToPrefetch) > 0 { s.prefetcher.prefetch(common.Hash{}, s.originalRoot, common.Address{}, addressesToPrefetch) } @@ -1709,6 +1712,7 @@ func (s *StateDB) BeginTxRecorder(isExcludeTx bool) { if s.mvStates == nil { return } + log.Debug("BeginTxRecorder", "tx", s.txIndex) if isExcludeTx { rwSet := types.NewRWSet(s.txIndex).WithExcludedTxFlag() if err := s.mvStates.FinaliseWithRWSet(rwSet); err != nil { @@ -1724,9 +1728,9 @@ func (s *StateDB) ResetMVStates(txCount int, feeReceivers []common.Address) *typ return s.mvStates } -func (s *StateDB) CheckFeeReceiversRWSet() error { +func (s *StateDB) CheckFeeReceiversRWSet() { if s.mvStates == nil { - return nil + return } if metrics.EnabledExpensive { defer func(start time.Time) { @@ -1740,7 +1744,7 @@ func (s *StateDB) CheckFeeReceiversRWSet() error { continue } s.mvStates.RecordCannotDelayGasFee() - return nil + return } for _, addr := range feeReceivers { @@ -1748,9 +1752,8 @@ func (s *StateDB) CheckFeeReceiversRWSet() error { continue } s.mvStates.RecordCannotDelayGasFee() - return nil + return } - return nil } func (s *StateDB) getStateObjectsDestruct(addr common.Address) (*types.StateAccount, bool) { diff --git a/core/state_transition.go b/core/state_transition.go index 650263c494..e0ace277f7 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -27,7 +27,6 @@ import ( cmath "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" ) @@ -535,9 +534,8 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) { } // check fee receiver rwSet here - if ferr := st.state.CheckFeeReceiversRWSet(); ferr != nil { - log.Error("CheckFeeReceiversRWSet err", "block", st.evm.Context.BlockNumber, "tx", st.evm.StateDB.TxIndex(), "err", ferr) - } + st.state.CheckFeeReceiversRWSet() + effectiveTip := msg.GasPrice if rules.IsLondon { effectiveTip = cmath.BigMin(msg.GasTipCap, new(big.Int).Sub(msg.GasFeeCap, st.evm.Context.BaseFee)) diff --git a/core/types/mvstates.go b/core/types/mvstates.go index fff029d169..e7d13cc072 100644 --- a/core/types/mvstates.go +++ b/core/types/mvstates.go @@ -8,7 +8,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" - "github.com/holiman/uint256" "golang.org/x/exp/slices" ) @@ -56,10 +55,6 @@ func NewRWSet(index int) *RWSet { } } -func (s *RWSet) Index() int { - return s.index -} - func (s *RWSet) RecordAccountRead(addr common.Address, state AccountState) { // only record the first read version sub, ok := s.accReadSet[addr] @@ -105,26 +100,6 @@ func (s *RWSet) RecordStorageWrite(addr common.Address, slot common.Hash) { s.slotWriteSet[addr][slot] = struct{}{} } -func (s *RWSet) queryAccReadItem(addr common.Address, state AccountState) bool { - sub, ok := s.accReadSet[addr] - if !ok { - return false - } - - _, ok = sub[state] - return ok -} - -func (s *RWSet) querySlotReadItem(addr common.Address, slot common.Hash) bool { - sub, ok := s.slotReadSet[addr] - if !ok { - return false - } - - _, ok = sub[slot] - return ok -} - func (s *RWSet) ReadSet() (map[common.Address]map[AccountState]struct{}, map[common.Address]map[common.Hash]struct{}) { return s.accReadSet, s.slotReadSet } @@ -275,11 +250,13 @@ type MVStates struct { txDepCache map[int]TxDep // async rw event recorder + // these fields are only used in one routine asyncRWSet *RWSet rwEventCh chan []RWEventItem rwEventCache []RWEventItem rwEventCacheIndex int - recordeReadDone bool + recordingRead bool + recordingWrite bool // execution stat infos lock sync.RWMutex @@ -409,6 +386,7 @@ func (s *MVStates) finalisePreviousRWSet() { } if _, exist := s.asyncRWSet.accReadSet[addr][AccountSelf]; exist { s.cannotGasFeeDelay = true + break } } s.resolveDepsMapCacheByWrites(index, s.asyncRWSet) @@ -425,12 +403,21 @@ func (s *MVStates) RecordNewTx(index int) { }) } s.rwEventCacheIndex++ - s.recordeReadDone = false + s.recordingRead = true + s.recordingWrite = true s.BatchRecordHandle() } +func (s *MVStates) RecordReadDone() { + s.recordingRead = false +} + +func (s *MVStates) RecordWriteDone() { + s.recordingWrite = false +} + func (s *MVStates) RecordAccountRead(addr common.Address, state AccountState) { - if s.recordeReadDone { + if !s.recordingRead { return } if s.rwEventCacheIndex < len(s.rwEventCache) { @@ -449,7 +436,7 @@ func (s *MVStates) RecordAccountRead(addr common.Address, state AccountState) { } func (s *MVStates) RecordStorageRead(addr common.Address, slot common.Hash) { - if s.recordeReadDone { + if !s.recordingRead { return } if s.rwEventCacheIndex < len(s.rwEventCache) { @@ -467,11 +454,10 @@ func (s *MVStates) RecordStorageRead(addr common.Address, slot common.Hash) { s.rwEventCacheIndex++ } -func (s *MVStates) RecordReadDone() { - s.recordeReadDone = true -} - func (s *MVStates) RecordAccountWrite(addr common.Address, state AccountState) { + if !s.recordingWrite { + return + } if s.rwEventCacheIndex < len(s.rwEventCache) { s.rwEventCache[s.rwEventCacheIndex].Event = WriteAccRWEvent s.rwEventCache[s.rwEventCacheIndex].Addr = addr @@ -488,6 +474,9 @@ func (s *MVStates) RecordAccountWrite(addr common.Address, state AccountState) { } func (s *MVStates) RecordStorageWrite(addr common.Address, slot common.Hash) { + if !s.recordingWrite { + return + } if s.rwEventCacheIndex < len(s.rwEventCache) { s.rwEventCache[s.rwEventCacheIndex].Event = WriteSlotRWEvent s.rwEventCache[s.rwEventCacheIndex].Addr = addr @@ -504,6 +493,9 @@ func (s *MVStates) RecordStorageWrite(addr common.Address, slot common.Hash) { } func (s *MVStates) RecordCannotDelayGasFee() { + if !s.recordingWrite { + return + } if s.rwEventCacheIndex < len(s.rwEventCache) { s.rwEventCache[s.rwEventCacheIndex].Event = CannotGasFeeDelayRWEvent s.rwEventCacheIndex++ @@ -516,7 +508,7 @@ func (s *MVStates) RecordCannotDelayGasFee() { } func (s *MVStates) BatchRecordHandle() { - if len(s.rwEventCache) == 0 { + if s.rwEventCacheIndex == 0 { return } s.rwEventCh <- s.rwEventCache[:s.rwEventCacheIndex] @@ -545,9 +537,6 @@ func (s *MVStates) quickFinaliseWithRWSet(rwSet *RWSet) error { func (s *MVStates) FinaliseWithRWSet(rwSet *RWSet) error { s.lock.Lock() defer s.lock.Unlock() - if s.asyncRWSet == nil { - s.asyncRWSet = nil - } index := rwSet.index if s.nextFinaliseIndex > index { return fmt.Errorf("finalise in wrong order, next: %d, input: %d", s.nextFinaliseIndex, index) @@ -567,7 +556,6 @@ func (s *MVStates) FinaliseWithRWSet(rwSet *RWSet) error { "readCnt", len(s.rwSets[i].accReadSet)+len(s.rwSets[i].slotReadSet), "writeCnt", len(s.rwSets[i].accWriteSet)+len(s.rwSets[i].slotWriteSet)) } - s.rwSets[index] = rwSet return nil } @@ -822,15 +810,6 @@ func checkSlotDependency(writeSet map[common.Address]map[common.Hash]struct{}, r return false } -type TxDepMaker interface { - add(index uint64) - exist(index uint64) bool - deps() []uint64 - remove(index uint64) - len() int - reset() -} - type TxDepMap struct { tm map[uint64]struct{} cache []uint64 @@ -873,42 +852,3 @@ func (m *TxDepMap) remove(index uint64) { func (m *TxDepMap) len() int { return len(m.tm) } - -func (m *TxDepMap) reset() { - m.cache = nil - m.tm = make(map[uint64]struct{}) -} - -// isEqualRWVal compare state -func isEqualRWVal(accState *AccountState, src interface{}, compared interface{}) bool { - if accState != nil { - switch *accState { - case AccountBalance: - if src != nil && compared != nil { - return equalUint256(src.(*uint256.Int), compared.(*uint256.Int)) - } - return src == compared - case AccountNonce: - return src.(uint64) == compared.(uint64) - case AccountCodeHash: - if src != nil && compared != nil { - return slices.Equal(src.([]byte), compared.([]byte)) - } - return src == compared - } - return false - } - - if src != nil && compared != nil { - return src.(common.Hash) == compared.(common.Hash) - } - return src == compared -} - -func equalUint256(s, c *uint256.Int) bool { - if s != nil && c != nil { - return s.Eq(c) - } - - return s == c -} diff --git a/core/types/mvstates_test.go b/core/types/mvstates_test.go index f22ba40fe4..2f3eba3828 100644 --- a/core/types/mvstates_test.go +++ b/core/types/mvstates_test.go @@ -12,7 +12,6 @@ import ( "github.com/cometbft/cometbft/libs/rand" "github.com/golang/snappy" - "github.com/holiman/uint256" "github.com/stretchr/testify/require" ) @@ -187,104 +186,6 @@ func TestMVStates_SystemTxWithLargeDepsResolveTxDAG(t *testing.T) { t.Log(dag) } -func TestIsEqualRWVal(t *testing.T) { - tests := []struct { - key *AccountState - src interface{} - compared interface{} - isEqual bool - }{ - { - key: &AccountNonce, - src: uint64(0), - compared: uint64(0), - isEqual: true, - }, - { - key: &AccountNonce, - src: uint64(0), - compared: uint64(1), - isEqual: false, - }, - { - key: &AccountBalance, - src: new(uint256.Int).SetUint64(1), - compared: new(uint256.Int).SetUint64(1), - isEqual: true, - }, - { - key: &AccountBalance, - src: nil, - compared: new(uint256.Int).SetUint64(1), - isEqual: false, - }, - { - key: &AccountBalance, - src: (*uint256.Int)(nil), - compared: new(uint256.Int).SetUint64(1), - isEqual: false, - }, - { - key: &AccountBalance, - src: (*uint256.Int)(nil), - compared: (*uint256.Int)(nil), - isEqual: true, - }, - { - key: &AccountCodeHash, - src: []byte{1}, - compared: []byte{1}, - isEqual: true, - }, - { - key: &AccountCodeHash, - src: nil, - compared: []byte{1}, - isEqual: false, - }, - { - key: &AccountCodeHash, - src: ([]byte)(nil), - compared: []byte{1}, - isEqual: false, - }, - { - key: &AccountCodeHash, - src: ([]byte)(nil), - compared: ([]byte)(nil), - isEqual: true, - }, - { - key: &AccountSuicide, - src: struct{}{}, - compared: struct{}{}, - isEqual: false, - }, - { - key: &AccountSuicide, - src: nil, - compared: struct{}{}, - isEqual: false, - }, - { - key: nil, - src: mockHash, - compared: mockHash, - isEqual: true, - }, - { - key: nil, - src: nil, - compared: mockHash, - isEqual: false, - }, - } - - for i, item := range tests { - require.Equal(t, item.isEqual, isEqualRWVal(item.key, item.src, item.compared), i) - } -} - func TestTxRecorder_Basic(t *testing.T) { sets := []*RWSet{ mockRWSet(0, []interface{}{AccountSelf, AccountBalance, "0x00"}, diff --git a/core/vm/interface.go b/core/vm/interface.go index 4ad4ecc311..b6db1fd461 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -80,7 +80,9 @@ type StateDB interface { AddPreimage(common.Hash, []byte) TxIndex() int - CheckFeeReceiversRWSet() error + + // parallel DAG related + CheckFeeReceiversRWSet() } // CallContext provides a basic interface for the EVM calling conventions. The EVM From d1442bdf147956ee8c85f36d536f10c682a32b63 Mon Sep 17 00:00:00 2001 From: galaio Date: Thu, 5 Sep 2024 23:12:51 +0800 Subject: [PATCH 11/19] txdag: opt generation logic, support stop mvstates; --- core/blockchain.go | 14 ------ core/state/statedb.go | 15 ++++--- core/state_processor.go | 18 +++++++- core/types/mvstates.go | 86 ++++++++++++++++--------------------- core/types/mvstates_test.go | 31 ++++++++++++- miner/worker.go | 11 +++-- 6 files changed, 100 insertions(+), 75 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index de01ceadff..c4397eed54 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1963,20 +1963,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) vtime := time.Since(vstart) proctime := time.Since(start) // processing + validation - if bc.enableTxDAG { - // compare input TxDAG when it enable in consensus - dag, err := statedb.ResolveTxDAG(len(block.Transactions())) - if err == nil { - // TODO(galaio): check TxDAG correctness? - log.Debug("Process TxDAG result", "block", block.NumberU64(), "tx", len(block.Transactions()), "txDAG", dag) - if metrics.EnabledExpensive { - go types.EvaluateTxDAGPerformance(dag) - } - } else { - log.Error("ResolveTxDAG err", "block", block.NumberU64(), "tx", len(block.Transactions()), "err", err) - } - } - // Update the metrics touched during block processing and validation accountReadTimer.Update(statedb.AccountReads) // Account reads are complete(in processing) storageReadTimer.Update(statedb.StorageReads) // Storage reads are complete(in processing) diff --git a/core/state/statedb.go b/core/state/statedb.go index fbe6fb90c7..cf088c5340 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -993,9 +993,6 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { // the commit-phase will be a lot faster addressesToPrefetch = append(addressesToPrefetch, common.CopyBytes(addr[:])) // Copy needed for closure } - if s.mvStates != nil { - s.mvStates.RecordWriteDone() - } if s.prefetcher != nil && len(addressesToPrefetch) > 0 { s.prefetcher.prefetch(common.Hash{}, s.originalRoot, common.Address{}, addressesToPrefetch) } @@ -1708,11 +1705,11 @@ func (s *StateDB) GetSnap() snapshot.Snapshot { return s.snap } -func (s *StateDB) BeginTxRecorder(isExcludeTx bool) { +func (s *StateDB) StartTxRecorder(isExcludeTx bool) { if s.mvStates == nil { return } - log.Debug("BeginTxRecorder", "tx", s.txIndex) + log.Debug("StartTxRecorder", "tx", s.txIndex) if isExcludeTx { rwSet := types.NewRWSet(s.txIndex).WithExcludedTxFlag() if err := s.mvStates.FinaliseWithRWSet(rwSet); err != nil { @@ -1723,6 +1720,14 @@ func (s *StateDB) BeginTxRecorder(isExcludeTx bool) { s.mvStates.RecordNewTx(s.txIndex) } +func (s *StateDB) StopTxRecorder() { + if s.mvStates == nil { + return + } + s.mvStates.RecordReadDone() + s.mvStates.RecordWriteDone() +} + func (s *StateDB) ResetMVStates(txCount int, feeReceivers []common.Address) *types.MVStates { s.mvStates = types.NewMVStates(txCount, feeReceivers) return s.mvStates diff --git a/core/state_processor.go b/core/state_processor.go index 2d32cba647..0e95538b39 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -22,6 +22,8 @@ import ( "math/big" "time" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/misc" @@ -122,6 +124,19 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg } // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) p.engine.Finalize(p.bc, header, statedb, block.Transactions(), block.Uncles(), withdrawals) + if p.bc.enableTxDAG { + // compare input TxDAG when it enable in consensus + dag, err := statedb.ResolveTxDAG(len(block.Transactions())) + if err == nil { + // TODO(galaio): check TxDAG correctness? + log.Debug("Process TxDAG result", "block", block.NumberU64(), "tx", len(block.Transactions()), "txDAG", dag) + if metrics.EnabledExpensive { + go types.EvaluateTxDAGPerformance(dag) + } + } else { + log.Error("ResolveTxDAG err", "block", block.NumberU64(), "tx", len(block.Transactions()), "err", err) + } + } return receipts, allLogs, *usedGas, nil } @@ -129,7 +144,8 @@ func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, sta // Create a new context to be used in the EVM environment. txContext := NewEVMTxContext(msg) evm.Reset(txContext, statedb) - statedb.BeginTxRecorder(tx.IsSystemTx() || tx.IsDepositTx()) + statedb.StartTxRecorder(tx.IsSystemTx() || tx.IsDepositTx()) + defer statedb.StopTxRecorder() nonce := tx.Nonce() if msg.IsDepositTx && config.IsOptimismRegolith(evm.Context.Time) { diff --git a/core/types/mvstates.go b/core/types/mvstates.go index e7d13cc072..300e0adf48 100644 --- a/core/types/mvstates.go +++ b/core/types/mvstates.go @@ -4,7 +4,6 @@ import ( "fmt" "strings" "sync" - "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" @@ -42,7 +41,8 @@ type RWSet struct { slotWriteSet map[common.Address]map[common.Hash]struct{} // some flags - excludedTx bool + excludedTx bool + cannotGasFeeDelay bool } func NewRWSet(index int) *RWSet { @@ -244,10 +244,10 @@ type MVStates struct { pendingSlotWriteSet map[common.Address]map[common.Hash]*PendingWrites nextFinaliseIndex int gasFeeReceivers []common.Address - // dependency map cache for generating TxDAG // depMapCache[i].exist(j) means j->i, and i > j txDepCache map[int]TxDep + lock sync.RWMutex // async rw event recorder // these fields are only used in one routine @@ -257,11 +257,8 @@ type MVStates struct { rwEventCacheIndex int recordingRead bool recordingWrite bool - - // execution stat infos - lock sync.RWMutex + asyncRunning bool asyncWG sync.WaitGroup - cannotGasFeeDelay bool } func NewMVStates(txCount int, gasFeeReceivers []common.Address) *MVStates { @@ -280,18 +277,20 @@ func NewMVStates(txCount int, gasFeeReceivers []common.Address) *MVStates { } func (s *MVStates) EnableAsyncGen() *MVStates { - s.lock.Lock() - defer s.lock.Unlock() s.asyncWG.Add(1) + s.asyncRunning = true go s.asyncRWEventLoop() return s } +func (s *MVStates) Stop() { + s.stopAsyncRecorder() +} + func (s *MVStates) Copy() *MVStates { s.lock.Lock() defer s.lock.Unlock() ns := NewMVStates(len(s.rwSets), s.gasFeeReceivers) - ns.cannotGasFeeDelay = s.cannotGasFeeDelay ns.nextFinaliseIndex = s.nextFinaliseIndex for k, v := range s.txDepCache { ns.txDepCache[k] = v @@ -320,7 +319,6 @@ func (s *MVStates) Copy() *MVStates { func (s *MVStates) asyncRWEventLoop() { defer s.asyncWG.Done() - timeout := time.After(3 * time.Second) for { select { case items, ok := <-s.rwEventCh: @@ -331,9 +329,6 @@ func (s *MVStates) asyncRWEventLoop() { s.handleRWEvent(item) } rwEventCachePool.Put(&items) - case <-timeout: - log.Warn("asyncRWEventLoop timeout") - return } } } @@ -343,17 +338,10 @@ func (s *MVStates) handleRWEvent(item RWEventItem) { defer s.lock.Unlock() // init next RWSet, and finalise previous RWSet if item.Event == NewTxRWEvent { - if item.Index > 0 { - s.finalisePreviousRWSet() - } + s.finalisePreviousRWSet() s.asyncRWSet = NewRWSet(item.Index) return } - // recorde current as cannot gas fee delay - if item.Event == CannotGasFeeDelayRWEvent { - s.cannotGasFeeDelay = true - return - } if s.asyncRWSet == nil { return } @@ -367,6 +355,9 @@ func (s *MVStates) handleRWEvent(item RWEventItem) { s.finaliseAccWrite(s.asyncRWSet.index, item.Addr, item.State) case WriteSlotRWEvent: s.finaliseSlotWrite(s.asyncRWSet.index, item.Addr, item.Slot) + // recorde current as cannot gas fee delay + case CannotGasFeeDelayRWEvent: + s.asyncRWSet.cannotGasFeeDelay = true } } @@ -375,20 +366,22 @@ func (s *MVStates) finalisePreviousRWSet() { return } index := s.asyncRWSet.index - if err := s.quickFinaliseWithRWSet(s.asyncRWSet); err != nil { - log.Error("Finalise err when handle NewTxRWEvent", "tx", index, "err", err) - return - } + s.rwSets[index] = s.asyncRWSet + // check if there are RW with gas fee receiver for gas delay calculation for _, addr := range s.gasFeeReceivers { if _, exist := s.asyncRWSet.accReadSet[addr]; !exist { continue } if _, exist := s.asyncRWSet.accReadSet[addr][AccountSelf]; exist { - s.cannotGasFeeDelay = true + s.rwSets[index].cannotGasFeeDelay = true break } } + if err := s.innerFinalise(index, false); err != nil { + log.Error("Finalise err when handle NewTxRWEvent", "tx", index, "err", err) + return + } s.resolveDepsMapCacheByWrites(index, s.asyncRWSet) } @@ -518,19 +511,12 @@ func (s *MVStates) BatchRecordHandle() { } func (s *MVStates) stopAsyncRecorder() { - close(s.rwEventCh) - s.asyncWG.Wait() -} - -// quickFinaliseWithRWSet it just store RWSet and inc pendingIndex -func (s *MVStates) quickFinaliseWithRWSet(rwSet *RWSet) error { - index := rwSet.index - if s.nextFinaliseIndex != index { - return fmt.Errorf("finalise in wrong order, next: %d, input: %d", s.nextFinaliseIndex, index) + if s.asyncRunning { + s.asyncRunning = false + s.BatchRecordHandle() + close(s.rwEventCh) + s.asyncWG.Wait() } - s.rwSets[index] = rwSet - s.nextFinaliseIndex++ - return nil } // FinaliseWithRWSet it will put target write set into pending writes. @@ -538,9 +524,6 @@ func (s *MVStates) FinaliseWithRWSet(rwSet *RWSet) error { s.lock.Lock() defer s.lock.Unlock() index := rwSet.index - if s.nextFinaliseIndex > index { - return fmt.Errorf("finalise in wrong order, next: %d, input: %d", s.nextFinaliseIndex, index) - } s.rwSets[index] = rwSet // just finalise all previous txs start := s.nextFinaliseIndex @@ -548,7 +531,7 @@ func (s *MVStates) FinaliseWithRWSet(rwSet *RWSet) error { start = index } for i := start; i <= index; i++ { - if err := s.innerFinalise(i); err != nil { + if err := s.innerFinalise(i, true); err != nil { return err } s.resolveDepsMapCacheByWrites(i, s.rwSets[i]) @@ -560,7 +543,7 @@ func (s *MVStates) FinaliseWithRWSet(rwSet *RWSet) error { return nil } -func (s *MVStates) innerFinalise(index int) error { +func (s *MVStates) innerFinalise(index int, applyWriteSet bool) error { rwSet := s.rwSets[index] if rwSet == nil { return fmt.Errorf("finalise a non-exist RWSet, index: %d", index) @@ -570,6 +553,12 @@ func (s *MVStates) innerFinalise(index int) error { return fmt.Errorf("finalise in wrong order, next: %d, input: %d", s.nextFinaliseIndex, index) } + // reset nextFinaliseIndex to index+1, it may revert to previous txs + s.nextFinaliseIndex = index + 1 + if !applyWriteSet { + return nil + } + // append to pending write set for addr, sub := range rwSet.accWriteSet { if _, exist := s.pendingAccWriteSet[addr]; !exist { @@ -593,8 +582,6 @@ func (s *MVStates) innerFinalise(index int) error { s.pendingSlotWriteSet[addr][slot].Append(index) } } - // reset nextFinaliseIndex to index+1, it may revert to previous txs - s.nextFinaliseIndex = index + 1 return nil } @@ -735,14 +722,10 @@ func (s *MVStates) resolveDepsCache(index int, rwSet *RWSet) { // ResolveTxDAG generate TxDAG from RWSets func (s *MVStates) ResolveTxDAG(txCnt int) (TxDAG, error) { - s.BatchRecordHandle() s.stopAsyncRecorder() s.lock.Lock() defer s.lock.Unlock() - if s.cannotGasFeeDelay { - return NewEmptyTxDAG(), nil - } s.finalisePreviousRWSet() if s.nextFinaliseIndex != txCnt { return nil, fmt.Errorf("cannot resolve with wrong FinaliseIndex, expect: %v, now: %v", txCnt, s.nextFinaliseIndex) @@ -750,6 +733,9 @@ func (s *MVStates) ResolveTxDAG(txCnt int) (TxDAG, error) { txDAG := NewPlainTxDAG(txCnt) for i := 0; i < txCnt; i++ { + if s.rwSets[i].cannotGasFeeDelay { + return NewEmptyTxDAG(), nil + } deps := s.txDepCache[i].TxIndexes if len(deps) <= (txCnt-1)/2 { txDAG.TxDeps[i] = s.txDepCache[i] diff --git a/core/types/mvstates_test.go b/core/types/mvstates_test.go index 2f3eba3828..5275c7a8dc 100644 --- a/core/types/mvstates_test.go +++ b/core/types/mvstates_test.go @@ -25,7 +25,11 @@ func TestMVStates_SimpleResolveTxDAG(t *testing.T) { mockRWSet(0, []interface{}{"0x00"}, []interface{}{"0x00"}), mockRWSet(1, []interface{}{"0x01"}, []interface{}{"0x01"}), mockRWSet(2, []interface{}{"0x02"}, []interface{}{"0x02"}), + mockRWSet(3, []interface{}{"0x03"}, []interface{}{"0x03"}), + mockRWSet(3, []interface{}{"0x03"}, []interface{}{"0x03"}), mockRWSet(3, []interface{}{"0x00", "0x03"}, []interface{}{"0x03"}), + }) + finaliseRWSets(t, ms, []*RWSet{ mockRWSet(4, []interface{}{"0x00", "0x04"}, []interface{}{"0x04"}), mockRWSet(5, []interface{}{"0x01", "0x02", "0x05"}, []interface{}{"0x05"}), mockRWSet(6, []interface{}{"0x02", "0x05", "0x06"}, []interface{}{"0x06"}), @@ -36,10 +40,24 @@ func TestMVStates_SimpleResolveTxDAG(t *testing.T) { dag, err := ms.ResolveTxDAG(10) require.NoError(t, err) + time.Sleep(10 * time.Millisecond) + ms.Stop() require.Equal(t, mockSimpleDAG(), dag) t.Log(dag) } +func TestMVStates_ResolveTxDAG_Async(t *testing.T) { + txCnt := 10000 + rwSets := mockRandomRWSet(txCnt) + ms1 := NewMVStates(txCnt, nil).EnableAsyncGen() + for i := 0; i < txCnt; i++ { + require.NoError(t, ms1.FinaliseWithRWSet(rwSets[i])) + } + time.Sleep(100 * time.Millisecond) + _, err := ms1.ResolveTxDAG(txCnt) + require.NoError(t, err) +} + func TestMVStates_ResolveTxDAG_Compare(t *testing.T) { txCnt := 3000 rwSets := mockRandomRWSet(txCnt) @@ -221,7 +239,18 @@ func TestTxRecorder_Basic(t *testing.T) { } dag, err := ms.ResolveTxDAG(3) require.NoError(t, err) - t.Log(dag) + require.Equal(t, "[]\n[0]\n[1]\n", dag.(*PlainTxDAG).String()) +} + +func TestTxRecorder_CannotDelayGasFee(t *testing.T) { + ms := NewMVStates(0, nil).EnableAsyncGen() + ms.RecordNewTx(0) + ms.RecordNewTx(1) + ms.RecordCannotDelayGasFee() + ms.RecordNewTx(2) + dag, err := ms.ResolveTxDAG(3) + require.NoError(t, err) + require.Equal(t, NewEmptyTxDAG(), dag) } func mockRWSet(index int, read []interface{}, write []interface{}) *RWSet { diff --git a/miner/worker.go b/miner/worker.go index 1d3449a9cb..47cf69d817 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1346,7 +1346,7 @@ func (w *worker) generateWork(genParams *generateParams) *newPayloadResult { wg.Add(1) go func() { defer wg.Done() - if newWork.state.MVStates() != nil { + if w.chain.TxDAGEnabledWhenMine() { newWork.state.MVStates().EnableAsyncGen() } err := w.fillTransactions(interrupt, newWork) @@ -1358,7 +1358,7 @@ func (w *worker) generateWork(genParams *generateParams) *newPayloadResult { isBuildBlockInterruptCounter.Inc(1) } }() - if work.state.MVStates() != nil { + if w.chain.TxDAGEnabledWhenMine() { work.state.MVStates().EnableAsyncGen() } err := w.fillTransactionsAndBundles(interrupt, work) @@ -1366,10 +1366,13 @@ func (w *worker) generateWork(genParams *generateParams) *newPayloadResult { timer.Stop() // don't need timeout interruption any more if errors.Is(err, errFillBundleInterrupted) { log.Warn("fill bundles is interrupted, discard", "err", err) - work = newWork + work, newWork = newWork, work + } + if w.chain.TxDAGEnabledWhenMine() { + newWork.state.MVStates().Stop() } } else { - if work.state.MVStates() != nil { + if w.chain.TxDAGEnabledWhenMine() { work.state.MVStates().EnableAsyncGen() } err := w.fillTransactions(interrupt, work) From 74277b351bc54d49e730c8610dd7240b174e8fee Mon Sep 17 00:00:00 2001 From: galaio Date: Fri, 6 Sep 2024 11:42:13 +0800 Subject: [PATCH 12/19] txdag: fix record panic after stop the async gen; worker: add UT to test txdag gasless block generation; --- core/state/statedb.go | 2 + core/types/mvstates.go | 15 +++--- miner/payload_building_test.go | 2 +- miner/worker.go | 4 +- miner/worker_test.go | 99 ++++++++++++++++++++++++++++++---- 5 files changed, 104 insertions(+), 18 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index cf088c5340..2afc093b83 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -1715,6 +1715,8 @@ func (s *StateDB) StartTxRecorder(isExcludeTx bool) { if err := s.mvStates.FinaliseWithRWSet(rwSet); err != nil { log.Error("MVStates SystemTx Finalise err", "err", err) } + s.mvStates.RecordReadDone() + s.mvStates.RecordWriteDone() return } s.mvStates.RecordNewTx(s.txIndex) diff --git a/core/types/mvstates.go b/core/types/mvstates.go index 300e0adf48..c7e88a0283 100644 --- a/core/types/mvstates.go +++ b/core/types/mvstates.go @@ -386,6 +386,9 @@ func (s *MVStates) finalisePreviousRWSet() { } func (s *MVStates) RecordNewTx(index int) { + if !s.asyncRunning { + return + } if s.rwEventCacheIndex < len(s.rwEventCache) { s.rwEventCache[s.rwEventCacheIndex].Event = NewTxRWEvent s.rwEventCache[s.rwEventCacheIndex].Index = index @@ -410,7 +413,7 @@ func (s *MVStates) RecordWriteDone() { } func (s *MVStates) RecordAccountRead(addr common.Address, state AccountState) { - if !s.recordingRead { + if !s.asyncRunning || !s.recordingRead { return } if s.rwEventCacheIndex < len(s.rwEventCache) { @@ -429,7 +432,7 @@ func (s *MVStates) RecordAccountRead(addr common.Address, state AccountState) { } func (s *MVStates) RecordStorageRead(addr common.Address, slot common.Hash) { - if !s.recordingRead { + if !s.asyncRunning || !s.recordingRead { return } if s.rwEventCacheIndex < len(s.rwEventCache) { @@ -448,7 +451,7 @@ func (s *MVStates) RecordStorageRead(addr common.Address, slot common.Hash) { } func (s *MVStates) RecordAccountWrite(addr common.Address, state AccountState) { - if !s.recordingWrite { + if !s.asyncRunning || !s.recordingWrite { return } if s.rwEventCacheIndex < len(s.rwEventCache) { @@ -467,7 +470,7 @@ func (s *MVStates) RecordAccountWrite(addr common.Address, state AccountState) { } func (s *MVStates) RecordStorageWrite(addr common.Address, slot common.Hash) { - if !s.recordingWrite { + if !s.asyncRunning || !s.recordingWrite { return } if s.rwEventCacheIndex < len(s.rwEventCache) { @@ -486,7 +489,7 @@ func (s *MVStates) RecordStorageWrite(addr common.Address, slot common.Hash) { } func (s *MVStates) RecordCannotDelayGasFee() { - if !s.recordingWrite { + if !s.asyncRunning || !s.recordingWrite { return } if s.rwEventCacheIndex < len(s.rwEventCache) { @@ -501,7 +504,7 @@ func (s *MVStates) RecordCannotDelayGasFee() { } func (s *MVStates) BatchRecordHandle() { - if s.rwEventCacheIndex == 0 { + if !s.asyncRunning || s.rwEventCacheIndex == 0 { return } s.rwEventCh <- s.rwEventCache[:s.rwEventCacheIndex] diff --git a/miner/payload_building_test.go b/miner/payload_building_test.go index 7eea54a25d..c8f193baab 100644 --- a/miner/payload_building_test.go +++ b/miner/payload_building_test.go @@ -44,7 +44,7 @@ func testBuildPayload(t *testing.T, noTxPool, interrupt bool) { db = rawdb.NewMemoryDatabase() recipient = common.HexToAddress("0xdeadbeef") ) - w, b := newTestWorker(t, params.TestChainConfig, ethash.NewFaker(), db, 0) + w, b := newTestWorker(t, params.TestChainConfig, ethash.NewFaker(), db, 0, nil, nil) defer w.close() const numInterruptTxs = 256 diff --git a/miner/worker.go b/miner/worker.go index 47cf69d817..9eb47a7297 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1011,6 +1011,7 @@ func (w *worker) appendTxDAG(env *environment) { log.Warn("failed to generate DAG tx", "err", err) return } + env.state.SetTxContext(txForDAG.Hash(), env.tcount) _, err = w.commitTransaction(env, txForDAG) if err != nil { log.Warn("failed to commit DAG tx", "err", err) @@ -1061,8 +1062,7 @@ func (w *worker) generateDAGTx(statedb *state.StateDB, signer types.Signer, txIn return nil, fmt.Errorf("failed to encode txDAG, err: %v", err) } - enc, _ := types.EncodeTxDAG(txDAG) - log.Debug("EncodeTxDAGCalldata", "tx", txDAG.TxCount(), "enc", len(enc), "data", len(data), "dag", txDAG) + log.Debug("EncodeTxDAGCalldata", "tx", txDAG.TxCount(), "data", len(data), "dag", txDAG) // Create the transaction tx := types.NewTx(&types.LegacyTx{ Nonce: nonce, diff --git a/miner/worker_test.go b/miner/worker_test.go index aa05565301..ccb2ed76e1 100644 --- a/miner/worker_test.go +++ b/miner/worker_test.go @@ -113,7 +113,8 @@ type testWorkerBackend struct { genesis *core.Genesis } -func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, n int) *testWorkerBackend { +func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, + db ethdb.Database, n int, overrideVMConfig *vm.Config) *testWorkerBackend { var gspec = &core.Genesis{ Config: chainConfig, Alloc: core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}}, @@ -129,7 +130,11 @@ func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine default: t.Fatalf("unexpected consensus engine type: %T", engine) } - chain, err := core.NewBlockChain(db, &core.CacheConfig{TrieDirtyDisabled: true}, gspec, nil, engine, vm.Config{}, nil, nil) + vmConfig := vm.Config{} + if overrideVMConfig != nil { + vmConfig = *overrideVMConfig + } + chain, err := core.NewBlockChain(db, &core.CacheConfig{TrieDirtyDisabled: true}, gspec, nil, engine, vmConfig, nil, nil) if err != nil { t.Fatalf("core.NewBlockChain failed: %v", err) } @@ -158,11 +163,16 @@ func (b *testWorkerBackend) newRandomTx(creation bool) *types.Transaction { return tx } -func newTestWorker(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, blocks int) (*worker, *testWorkerBackend) { - backend := newTestWorkerBackend(t, chainConfig, engine, db, blocks) +func newTestWorker(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, + blocks int, overrideConfig *Config, overrideVMConfig *vm.Config) (*worker, *testWorkerBackend) { + backend := newTestWorkerBackend(t, chainConfig, engine, db, blocks, overrideVMConfig) backend.txPool.Add(pendingTxs, true, false) time.Sleep(500 * time.Millisecond) // Wait for txs to be promoted - w := newWorker(testConfig, chainConfig, engine, backend, new(event.TypeMux), nil, false) + cfg := testConfig + if overrideConfig != nil { + cfg = overrideConfig + } + w := newWorker(cfg, chainConfig, engine, backend, new(event.TypeMux), nil, false) w.setEtherbase(testBankAddress) return w, backend } @@ -176,7 +186,7 @@ func TestGenerateAndImportBlock(t *testing.T) { config.Clique = ¶ms.CliqueConfig{Period: 1, Epoch: 30000} engine := clique.New(config.Clique, db) - w, b := newTestWorker(t, &config, engine, db, 0) + w, b := newTestWorker(t, &config, engine, db, 0, nil, nil) defer w.close() // This test chain imports the mined blocks. @@ -212,6 +222,77 @@ func TestGenerateAndImportBlock(t *testing.T) { } } +func TestGenerateTxDAGGaslessBlock(t *testing.T) { + generateTxDAGGaslessBlock(t, true, true) + generateTxDAGGaslessBlock(t, true, false) + generateTxDAGGaslessBlock(t, false, true) + generateTxDAGGaslessBlock(t, false, false) +} + +func generateTxDAGGaslessBlock(t *testing.T, enableMev, enableTxDAG bool) { + t.Log("generateTxDAGGaslessBlock", enableMev, enableTxDAG) + var ( + db = rawdb.NewMemoryDatabase() + config = *params.AllCliqueProtocolChanges + ) + config.Optimism = ¶ms.OptimismConfig{ + EIP1559Elasticity: 2, + EIP1559Denominator: 8, + EIP1559DenominatorCanyon: 8, + } + cfg := Config{} + cfg = *testConfig + if enableMev { + cfg.Mev.MevEnabled = true + } + cfg.NewPayloadTimeout = 3 * time.Second + cfg.ParallelTxDAGSenderPriv, _ = crypto.ToECDSA(crypto.Keccak256([]byte{1})) + vmConfig := vm.Config{NoBaseFee: true} + engine := clique.New(config.Clique, db) + + w, b := newTestWorker(t, &config, engine, db, 0, &cfg, &vmConfig) + defer w.close() + if enableTxDAG { + w.chain.SetupTxDAGGeneration() + } + + // Ignore empty commit here for less noise. + w.skipSealHook = func(task *task) bool { + return len(task.receipts) == 0 + } + + // Start mining! + w.start() + + for i := 0; i < 5; i++ { + b.txPool.Add([]*types.Transaction{b.newRandomTx(true)}, true, false) + b.txPool.Add([]*types.Transaction{b.newRandomTx(false)}, true, false) + time.Sleep(1 * time.Second) // Wait for txs to be promoted + + block := w.getSealingBlock(&generateParams{ + timestamp: uint64(time.Now().Unix()), + forceTime: false, + parentHash: common.Hash{}, + coinbase: common.Address{}, + random: common.Hash{}, + withdrawals: nil, + beaconRoot: nil, + noTxs: false, + txs: types.Transactions{ + types.NewTx(&types.DepositTx{ + To: nil, // contract creation + Value: big.NewInt(6), + Gas: 50, + })}, + gasLimit: nil, + interrupt: nil, + isUpdate: false, + }) + txDAG, _ := types.GetTxDAG(block.block) + t.Log("block", block.block.NumberU64(), "txs", len(block.block.Transactions()), "txdag", txDAG) + } +} + func TestEmptyWorkEthash(t *testing.T) { t.Parallel() testEmptyWork(t, ethashChainConfig, ethash.NewFaker()) @@ -224,7 +305,7 @@ func TestEmptyWorkClique(t *testing.T) { func testEmptyWork(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) { defer engine.Close() - w, _ := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0) + w, _ := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0, nil, nil) defer w.close() taskCh := make(chan struct{}, 2) @@ -269,7 +350,7 @@ func TestAdjustIntervalClique(t *testing.T) { func testAdjustInterval(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) { defer engine.Close() - w, _ := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0) + w, _ := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0, nil, nil) defer w.close() w.skipSealHook = func(task *task) bool { @@ -374,7 +455,7 @@ func TestGetSealingWorkPostMerge(t *testing.T) { func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) { defer engine.Close() - w, b := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0) + w, b := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0, nil, nil) defer w.close() w.setExtra([]byte{0x01, 0x02}) From d3823937c367b70a9be63aac680c5dc5b7bbac41 Mon Sep 17 00:00:00 2001 From: galaio Date: Fri, 6 Sep 2024 14:26:37 +0800 Subject: [PATCH 13/19] txdag: fix the last tx dep wrong when mining; --- core/state/statedb.go | 2 +- core/state_processor.go | 2 +- core/types/mvstates.go | 12 ++++++------ miner/worker.go | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 2afc093b83..ef47bd6ba1 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -1709,7 +1709,7 @@ func (s *StateDB) StartTxRecorder(isExcludeTx bool) { if s.mvStates == nil { return } - log.Debug("StartTxRecorder", "tx", s.txIndex) + //log.Debug("StartTxRecorder", "tx", s.txIndex) if isExcludeTx { rwSet := types.NewRWSet(s.txIndex).WithExcludedTxFlag() if err := s.mvStates.FinaliseWithRWSet(rwSet); err != nil { diff --git a/core/state_processor.go b/core/state_processor.go index 0e95538b39..afe060d432 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -129,7 +129,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg dag, err := statedb.ResolveTxDAG(len(block.Transactions())) if err == nil { // TODO(galaio): check TxDAG correctness? - log.Debug("Process TxDAG result", "block", block.NumberU64(), "tx", len(block.Transactions()), "txDAG", dag) + log.Debug("Process TxDAG result", "block", block.NumberU64(), "tx", len(block.Transactions()), "txDAG", dag.TxCount()) if metrics.EnabledExpensive { go types.EvaluateTxDAGPerformance(dag) } diff --git a/core/types/mvstates.go b/core/types/mvstates.go index c7e88a0283..1a47a9c3d1 100644 --- a/core/types/mvstates.go +++ b/core/types/mvstates.go @@ -515,8 +515,8 @@ func (s *MVStates) BatchRecordHandle() { func (s *MVStates) stopAsyncRecorder() { if s.asyncRunning { - s.asyncRunning = false s.BatchRecordHandle() + s.asyncRunning = false close(s.rwEventCh) s.asyncWG.Wait() } @@ -538,9 +538,9 @@ func (s *MVStates) FinaliseWithRWSet(rwSet *RWSet) error { return err } s.resolveDepsMapCacheByWrites(i, s.rwSets[i]) - log.Debug("Finalise the reads/writes", "index", i, - "readCnt", len(s.rwSets[i].accReadSet)+len(s.rwSets[i].slotReadSet), - "writeCnt", len(s.rwSets[i].accWriteSet)+len(s.rwSets[i].slotWriteSet)) + //log.Debug("Finalise the reads/writes", "index", i, + // "readCnt", len(s.rwSets[i].accReadSet)+len(s.rwSets[i].slotReadSet), + // "writeCnt", len(s.rwSets[i].accWriteSet)+len(s.rwSets[i].slotWriteSet)) } return nil @@ -669,7 +669,7 @@ func (s *MVStates) resolveDepsMapCacheByWrites(index int, rwSet *RWSet) { } } } - log.Debug("resolveDepsMapCacheByWrites", "tx", index, "deps", depMap.deps()) + //log.Debug("resolveDepsMapCacheByWrites", "tx", index, "deps", depMap.deps()) // clear redundancy deps compared with prev preDeps := depMap.deps() for _, prev := range preDeps { @@ -677,7 +677,7 @@ func (s *MVStates) resolveDepsMapCacheByWrites(index int, rwSet *RWSet) { depMap.remove(tx) } } - log.Debug("resolveDepsMapCacheByWrites after clean", "tx", index, "deps", depMap.deps()) + //log.Debug("resolveDepsMapCacheByWrites after clean", "tx", index, "deps", depMap.deps()) s.txDepCache[index] = NewTxDep(depMap.deps()) } diff --git a/miner/worker.go b/miner/worker.go index 9eb47a7297..157907c709 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -890,7 +890,7 @@ func (w *worker) applyTransaction(env *environment, tx *types.Transaction) (*typ ) receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &env.coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, *w.chain.GetVMConfig()) if err != nil { - log.Debug("ApplyTransaction err", "block", env.header.Number.Uint64(), "tx", env.tcount, "err", err) + //log.Debug("ApplyTransaction err", "block", env.header.Number.Uint64(), "tx", env.tcount, "err", err) env.state.RevertToSnapshot(snap) env.gasPool.SetGas(gp) } @@ -1062,7 +1062,7 @@ func (w *worker) generateDAGTx(statedb *state.StateDB, signer types.Signer, txIn return nil, fmt.Errorf("failed to encode txDAG, err: %v", err) } - log.Debug("EncodeTxDAGCalldata", "tx", txDAG.TxCount(), "data", len(data), "dag", txDAG) + //log.Debug("EncodeTxDAGCalldata", "tx", txDAG.TxCount(), "data", len(data), "dag", txDAG) // Create the transaction tx := types.NewTx(&types.LegacyTx{ Nonce: nonce, From 913a2eee3e3b76a673ca1afb08400d81290f6cc4 Mon Sep 17 00:00:00 2001 From: galaio Date: Fri, 6 Sep 2024 20:27:00 +0800 Subject: [PATCH 14/19] txdag: opt 100% conflict scenario perf, reduce send chan frequency; --- core/types/mvstates.go | 88 +++++++++++++++++++------------------ core/types/mvstates_test.go | 48 ++++++++++++++++++++ 2 files changed, 94 insertions(+), 42 deletions(-) diff --git a/core/types/mvstates.go b/core/types/mvstates.go index 1a47a9c3d1..ce78f1ae87 100644 --- a/core/types/mvstates.go +++ b/core/types/mvstates.go @@ -26,7 +26,7 @@ const ( func init() { for i := 0; i < initRWEventCacheSize; i++ { - cache := make([]RWEventItem, 200) + cache := make([]RWEventItem, 10000) rwEventCachePool.Put(&cache) } } @@ -172,17 +172,17 @@ type RWEventItem struct { Slot common.Hash } -type PendingWrites struct { +type StateWrites struct { list []int } -func NewPendingWrites() *PendingWrites { - return &PendingWrites{ +func NewStateWrites() *StateWrites { + return &StateWrites{ list: make([]int, 0), } } -func (w *PendingWrites) Append(pw int) { +func (w *StateWrites) Append(pw int) { if i, found := w.SearchTxIndex(pw); found { w.list[i] = pw return @@ -197,7 +197,7 @@ func (w *PendingWrites) Append(pw int) { } } -func (w *PendingWrites) SearchTxIndex(txIndex int) (int, bool) { +func (w *StateWrites) SearchTxIndex(txIndex int) (int, bool) { n := len(w.list) i, j := 0, n for i < j { @@ -212,19 +212,19 @@ func (w *PendingWrites) SearchTxIndex(txIndex int) (int, bool) { return i, i < n && w.list[i] == txIndex } -func (w *PendingWrites) FindPrevWrites(txIndex int) []int { +func (w *StateWrites) FindLastWrite(txIndex int) int { var i, _ = w.SearchTxIndex(txIndex) for j := i - 1; j >= 0; j-- { if w.list[j] < txIndex { - return w.list[:j+1] + return w.list[j] } } - return nil + return -1 } -func (w *PendingWrites) Copy() *PendingWrites { - np := &PendingWrites{} +func (w *StateWrites) Copy() *StateWrites { + np := &StateWrites{} for i, item := range w.list { np.list[i] = item } @@ -240,8 +240,8 @@ var ( type MVStates struct { rwSets map[int]*RWSet - pendingAccWriteSet map[common.Address]map[AccountState]*PendingWrites - pendingSlotWriteSet map[common.Address]map[common.Hash]*PendingWrites + pendingAccWriteSet map[common.Address]map[AccountState]*StateWrites + pendingSlotWriteSet map[common.Address]map[common.Hash]*StateWrites nextFinaliseIndex int gasFeeReceivers []common.Address // dependency map cache for generating TxDAG @@ -264,8 +264,8 @@ type MVStates struct { func NewMVStates(txCount int, gasFeeReceivers []common.Address) *MVStates { m := &MVStates{ rwSets: make(map[int]*RWSet, txCount), - pendingAccWriteSet: make(map[common.Address]map[AccountState]*PendingWrites, txCount), - pendingSlotWriteSet: make(map[common.Address]map[common.Hash]*PendingWrites, txCount), + pendingAccWriteSet: make(map[common.Address]map[AccountState]*StateWrites, txCount), + pendingSlotWriteSet: make(map[common.Address]map[common.Hash]*StateWrites, txCount), txDepCache: make(map[int]TxDep, txCount), rwEventCh: make(chan []RWEventItem, 100), gasFeeReceivers: gasFeeReceivers, @@ -301,7 +301,7 @@ func (s *MVStates) Copy() *MVStates { for addr, sub := range s.pendingAccWriteSet { for state, writes := range sub { if _, ok := ns.pendingAccWriteSet[addr]; !ok { - ns.pendingAccWriteSet[addr] = make(map[AccountState]*PendingWrites) + ns.pendingAccWriteSet[addr] = make(map[AccountState]*StateWrites) } ns.pendingAccWriteSet[addr][state] = writes.Copy() } @@ -309,7 +309,7 @@ func (s *MVStates) Copy() *MVStates { for addr, sub := range s.pendingSlotWriteSet { for slot, writes := range sub { if _, ok := ns.pendingSlotWriteSet[addr]; !ok { - ns.pendingSlotWriteSet[addr] = make(map[common.Hash]*PendingWrites) + ns.pendingSlotWriteSet[addr] = make(map[common.Hash]*StateWrites) } ns.pendingSlotWriteSet[addr][slot] = writes.Copy() } @@ -401,7 +401,9 @@ func (s *MVStates) RecordNewTx(index int) { s.rwEventCacheIndex++ s.recordingRead = true s.recordingWrite = true - s.BatchRecordHandle() + if index%10 == 0 { + s.BatchRecordHandle() + } } func (s *MVStates) RecordReadDone() { @@ -565,22 +567,22 @@ func (s *MVStates) innerFinalise(index int, applyWriteSet bool) error { // append to pending write set for addr, sub := range rwSet.accWriteSet { if _, exist := s.pendingAccWriteSet[addr]; !exist { - s.pendingAccWriteSet[addr] = make(map[AccountState]*PendingWrites) + s.pendingAccWriteSet[addr] = make(map[AccountState]*StateWrites) } for state := range sub { if _, exist := s.pendingAccWriteSet[addr][state]; !exist { - s.pendingAccWriteSet[addr][state] = NewPendingWrites() + s.pendingAccWriteSet[addr][state] = NewStateWrites() } s.pendingAccWriteSet[addr][state].Append(index) } } for addr, sub := range rwSet.slotWriteSet { if _, exist := s.pendingSlotWriteSet[addr]; !exist { - s.pendingSlotWriteSet[addr] = make(map[common.Hash]*PendingWrites) + s.pendingSlotWriteSet[addr] = make(map[common.Hash]*StateWrites) } for slot := range sub { if _, exist := s.pendingSlotWriteSet[addr][slot]; !exist { - s.pendingSlotWriteSet[addr][slot] = NewPendingWrites() + s.pendingSlotWriteSet[addr][slot] = NewStateWrites() } s.pendingSlotWriteSet[addr][slot].Append(index) } @@ -591,10 +593,10 @@ func (s *MVStates) innerFinalise(index int, applyWriteSet bool) error { func (s *MVStates) finaliseSlotWrite(index int, addr common.Address, slot common.Hash) { // append to pending write set if _, exist := s.pendingSlotWriteSet[addr]; !exist { - s.pendingSlotWriteSet[addr] = make(map[common.Hash]*PendingWrites) + s.pendingSlotWriteSet[addr] = make(map[common.Hash]*StateWrites) } if _, exist := s.pendingSlotWriteSet[addr][slot]; !exist { - s.pendingSlotWriteSet[addr][slot] = NewPendingWrites() + s.pendingSlotWriteSet[addr][slot] = NewStateWrites() } s.pendingSlotWriteSet[addr][slot].Append(index) } @@ -602,22 +604,22 @@ func (s *MVStates) finaliseSlotWrite(index int, addr common.Address, slot common func (s *MVStates) finaliseAccWrite(index int, addr common.Address, state AccountState) { // append to pending write set if _, exist := s.pendingAccWriteSet[addr]; !exist { - s.pendingAccWriteSet[addr] = make(map[AccountState]*PendingWrites) + s.pendingAccWriteSet[addr] = make(map[AccountState]*StateWrites) } if _, exist := s.pendingAccWriteSet[addr][state]; !exist { - s.pendingAccWriteSet[addr][state] = NewPendingWrites() + s.pendingAccWriteSet[addr][state] = NewStateWrites() } s.pendingAccWriteSet[addr][state].Append(index) } -func (s *MVStates) queryAccWrites(addr common.Address, state AccountState) *PendingWrites { +func (s *MVStates) queryAccWrites(addr common.Address, state AccountState) *StateWrites { if _, exist := s.pendingAccWriteSet[addr]; !exist { return nil } return s.pendingAccWriteSet[addr][state] } -func (s *MVStates) querySlotWrites(addr common.Address, slot common.Hash) *PendingWrites { +func (s *MVStates) querySlotWrites(addr common.Address, slot common.Hash) *StateWrites { if _, exist := s.pendingSlotWriteSet[addr]; !exist { return nil } @@ -643,14 +645,15 @@ func (s *MVStates) resolveDepsMapCacheByWrites(index int, rwSet *RWSet) { if writes == nil { continue } - items := writes.FindPrevWrites(index) - for _, item := range items { - tx := uint64(item) - if depMap.exist(tx) { - continue - } - depMap.add(tx) + find := writes.FindLastWrite(index) + if find < 0 { + continue } + tx := uint64(find) + if depMap.exist(tx) { + continue + } + depMap.add(tx) } } for addr, sub := range rwSet.slotReadSet { @@ -659,14 +662,15 @@ func (s *MVStates) resolveDepsMapCacheByWrites(index int, rwSet *RWSet) { if writes == nil { continue } - items := writes.FindPrevWrites(index) - for _, item := range items { - tx := uint64(item) - if depMap.exist(tx) { - continue - } - depMap.add(tx) + find := writes.FindLastWrite(index) + if find < 0 { + continue + } + tx := uint64(find) + if depMap.exist(tx) { + continue } + depMap.add(tx) } } //log.Debug("resolveDepsMapCacheByWrites", "tx", index, "deps", depMap.deps()) diff --git a/core/types/mvstates_test.go b/core/types/mvstates_test.go index 5275c7a8dc..6c9103b215 100644 --- a/core/types/mvstates_test.go +++ b/core/types/mvstates_test.go @@ -129,6 +129,30 @@ func BenchmarkResolveTxDAGByWritesInMVStates(b *testing.B) { } } +func BenchmarkResolveTxDAGByWritesInMVStates_100PercentConflict(b *testing.B) { + rwSets := mockSameRWSet(mockRWSetSize) + ms1 := NewMVStates(mockRWSetSize, nil).EnableAsyncGen() + for _, rwSet := range rwSets { + ms1.FinaliseWithRWSet(rwSet) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + resolveDepsMapCacheByWritesInMVStates(ms1) + } +} + +func BenchmarkResolveTxDAGByWritesInMVStates_0PercentConflict(b *testing.B) { + rwSets := mockDifferentRWSet(mockRWSetSize) + ms1 := NewMVStates(mockRWSetSize, nil).EnableAsyncGen() + for _, rwSet := range rwSets { + ms1.FinaliseWithRWSet(rwSet) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + resolveDepsMapCacheByWritesInMVStates(ms1) + } +} + func BenchmarkMVStates_Finalise(b *testing.B) { rwSets := mockRandomRWSet(mockRWSetSize) ms1 := NewMVStates(mockRWSetSize, nil).EnableAsyncGen() @@ -314,6 +338,30 @@ func mockRandomRWSet(count int) []*RWSet { return ret } +func mockSameRWSet(count int) []*RWSet { + var ret []*RWSet + for i := 0; i < count; i++ { + read := []interface{}{"0xa0", "0xa1", fmt.Sprintf("0x%d", i), fmt.Sprintf("0x%d", i)} + write := []interface{}{"0xa0", fmt.Sprintf("0x%d", i)} + // random write + s := mockRWSet(i, read, write) + ret = append(ret, s) + } + return ret +} + +func mockDifferentRWSet(count int) []*RWSet { + var ret []*RWSet + for i := 0; i < count; i++ { + read := []interface{}{fmt.Sprintf("0x%d", i), fmt.Sprintf("0x%d", i)} + write := []interface{}{fmt.Sprintf("0x%d", i)} + // random write + s := mockRWSet(i, read, write) + ret = append(ret, s) + } + return ret +} + func finaliseRWSets(t *testing.T, mv *MVStates, rwSets []*RWSet) { for _, rwSet := range rwSets { require.NoError(t, mv.FinaliseWithRWSet(rwSet)) From 9810ccc2b592c93e9a979677842ff3c89674a863 Mon Sep 17 00:00:00 2001 From: galaio Date: Fri, 6 Sep 2024 21:44:47 +0800 Subject: [PATCH 15/19] txdag: opt rwset string format; --- core/types/mvstates.go | 79 ++++++++++++++++++++++++++----------- core/types/mvstates_test.go | 1 + 2 files changed, 57 insertions(+), 23 deletions(-) diff --git a/core/types/mvstates.go b/core/types/mvstates.go index ce78f1ae87..23814be38c 100644 --- a/core/types/mvstates.go +++ b/core/types/mvstates.go @@ -115,43 +115,76 @@ func (s *RWSet) WithExcludedTxFlag() *RWSet { func (s *RWSet) String() string { builder := strings.Builder{} - builder.WriteString(fmt.Sprintf("tx: %v\nreadSet: [", s.index)) + builder.WriteString(fmt.Sprintf("{tx: %v", s.index)) + builder.WriteString(", accReadSet: [") i := 0 - for key, _ := range s.accReadSet { + for addr, sub := range s.accReadSet { if i > 0 { - builder.WriteString(fmt.Sprintf(", %v", key.String())) - continue + builder.WriteString(", ") + } + builder.WriteString(fmt.Sprintf("{addr: \"%v\", states: [", addr)) + j := 0 + for key := range sub { + if j > 0 { + builder.WriteString(", ") + } + builder.WriteString(fmt.Sprintf("%v", key)) + j++ } - builder.WriteString(fmt.Sprintf("%v", key.String())) - i++ + builder.WriteString("]}") } - for key, _ := range s.slotReadSet { + builder.WriteString("], slotReadSet: [") + i = 0 + for addr, sub := range s.slotReadSet { if i > 0 { - builder.WriteString(fmt.Sprintf(", %v", key.String())) - continue + builder.WriteString(", ") + } + builder.WriteString(fmt.Sprintf("{addr: \"%v\", slots: [", addr)) + j := 0 + for key := range sub { + if j > 0 { + builder.WriteString(", ") + } + builder.WriteString(fmt.Sprintf("\"%v\"", key.String())) + j++ } - builder.WriteString(fmt.Sprintf("%v", key.String())) - i++ + builder.WriteString("]}") } - builder.WriteString("]\nwriteSet: [") + builder.WriteString("], accWriteSet: [") i = 0 - for key, _ := range s.accWriteSet { + for addr, sub := range s.accWriteSet { if i > 0 { - builder.WriteString(fmt.Sprintf(", %v", key.String())) - continue + builder.WriteString(", ") + } + builder.WriteString(fmt.Sprintf("{addr: \"%v\", states: [", addr)) + j := 0 + for key := range sub { + if j > 0 { + builder.WriteString(", ") + } + builder.WriteString(fmt.Sprintf("%v", key)) + j++ } - builder.WriteString(fmt.Sprintf("%v", key.String())) - i++ + builder.WriteString("]}") } - for key, _ := range s.slotWriteSet { + builder.WriteString("], slotWriteSet: [") + i = 0 + for addr, sub := range s.slotWriteSet { if i > 0 { - builder.WriteString(fmt.Sprintf(", %v", key.String())) - continue + builder.WriteString(", ") + } + builder.WriteString(fmt.Sprintf("{addr: \"%v\", slots: [", addr)) + j := 0 + for key := range sub { + if j > 0 { + builder.WriteString(", ") + } + builder.WriteString(fmt.Sprintf("\"%v\"", key.String())) + j++ } - builder.WriteString(fmt.Sprintf("%v", key.String())) - i++ + builder.WriteString("]}") } - builder.WriteString("]\n") + builder.WriteString("]}") return builder.String() } diff --git a/core/types/mvstates_test.go b/core/types/mvstates_test.go index 6c9103b215..58278fa4fa 100644 --- a/core/types/mvstates_test.go +++ b/core/types/mvstates_test.go @@ -239,6 +239,7 @@ func TestTxRecorder_Basic(t *testing.T) { } ms := NewMVStates(0, nil).EnableAsyncGen() for _, item := range sets { + t.Log(item) ms.RecordNewTx(item.index) for addr, sub := range item.accReadSet { for state := range sub { From a4c6d75eb244d777d10c0624e07fd970f8279636 Mon Sep 17 00:00:00 2001 From: galaio Date: Sat, 7 Sep 2024 11:15:58 +0800 Subject: [PATCH 16/19] txdag: opt rwset string format; --- core/types/mvstates.go | 4 ++++ core/types/mvstates_test.go | 37 ++++++++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/core/types/mvstates.go b/core/types/mvstates.go index 23814be38c..596fec5c3c 100644 --- a/core/types/mvstates.go +++ b/core/types/mvstates.go @@ -131,6 +131,7 @@ func (s *RWSet) String() string { builder.WriteString(fmt.Sprintf("%v", key)) j++ } + i++ builder.WriteString("]}") } builder.WriteString("], slotReadSet: [") @@ -148,6 +149,7 @@ func (s *RWSet) String() string { builder.WriteString(fmt.Sprintf("\"%v\"", key.String())) j++ } + i++ builder.WriteString("]}") } builder.WriteString("], accWriteSet: [") @@ -165,6 +167,7 @@ func (s *RWSet) String() string { builder.WriteString(fmt.Sprintf("%v", key)) j++ } + i++ builder.WriteString("]}") } builder.WriteString("], slotWriteSet: [") @@ -182,6 +185,7 @@ func (s *RWSet) String() string { builder.WriteString(fmt.Sprintf("\"%v\"", key.String())) j++ } + i++ builder.WriteString("]}") } builder.WriteString("]}") diff --git a/core/types/mvstates_test.go b/core/types/mvstates_test.go index 58278fa4fa..7801a1bf79 100644 --- a/core/types/mvstates_test.go +++ b/core/types/mvstates_test.go @@ -239,7 +239,6 @@ func TestTxRecorder_Basic(t *testing.T) { } ms := NewMVStates(0, nil).EnableAsyncGen() for _, item := range sets { - t.Log(item) ms.RecordNewTx(item.index) for addr, sub := range item.accReadSet { for state := range sub { @@ -267,6 +266,17 @@ func TestTxRecorder_Basic(t *testing.T) { require.Equal(t, "[]\n[0]\n[1]\n", dag.(*PlainTxDAG).String()) } +func TestRWSet(t *testing.T) { + set := NewRWSet(0) + mockRWSetWithAddr(set, common.Address{1}, []interface{}{AccountSelf, AccountBalance, "0x00"}, + []interface{}{AccountBalance, AccountCodeHash, "0x00"}) + mockRWSetWithAddr(set, common.Address{2}, []interface{}{AccountSelf, AccountBalance, "0x01"}, + []interface{}{AccountBalance, AccountCodeHash, "0x01"}) + mockRWSetWithAddr(set, common.Address{3}, []interface{}{AccountSelf, AccountBalance, "0x01", "0x01"}, + []interface{}{AccountBalance, AccountCodeHash, "0x01"}) + t.Log(set) +} + func TestTxRecorder_CannotDelayGasFee(t *testing.T) { ms := NewMVStates(0, nil).EnableAsyncGen() ms.RecordNewTx(0) @@ -304,6 +314,31 @@ func mockRWSet(index int, read []interface{}, write []interface{}) *RWSet { return set } +func mockRWSetWithAddr(set *RWSet, addr common.Address, read []interface{}, write []interface{}) *RWSet { + set.accReadSet[addr] = map[AccountState]struct{}{} + set.accWriteSet[addr] = map[AccountState]struct{}{} + set.slotReadSet[addr] = map[common.Hash]struct{}{} + set.slotWriteSet[addr] = map[common.Hash]struct{}{} + for _, k := range read { + state, ok := k.(AccountState) + if ok { + set.accReadSet[addr][state] = struct{}{} + } else { + set.slotReadSet[addr][str2Slot(k.(string))] = struct{}{} + } + } + for _, k := range write { + state, ok := k.(AccountState) + if ok { + set.accWriteSet[addr][state] = struct{}{} + } else { + set.slotWriteSet[addr][str2Slot(k.(string))] = struct{}{} + } + } + + return set +} + func str2Slot(str string) common.Hash { return common.BytesToHash([]byte(str)) } From ddf28bc0a669f334aba70fb61d888de5bf95a916 Mon Sep 17 00:00:00 2001 From: galaio Date: Tue, 10 Sep 2024 14:28:27 +0800 Subject: [PATCH 17/19] txdag: reduce more mem usage; --- core/state/statedb.go | 4 +- core/types/dag.go | 2 + core/types/gen_plaintxdag_rlp.go | 32 ++++ core/types/mvstates.go | 259 +++++++++++++++++++++++-------- core/types/mvstates_test.go | 138 ++++++++++++++++ miner/worker.go | 5 +- 6 files changed, 373 insertions(+), 67 deletions(-) create mode 100644 core/types/gen_plaintxdag_rlp.go diff --git a/core/state/statedb.go b/core/state/statedb.go index ef47bd6ba1..4e4b8719c6 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -1779,7 +1779,7 @@ func (s *StateDB) removeStateObjectsDestruct(addr common.Address) { delete(s.stateObjectsDestructDirty, addr) } -func (s *StateDB) ResolveTxDAG(txCnt int) (types.TxDAG, error) { +func (s *StateDB) ResolveTxDAG(txCnt int, extraTxDeps ...types.TxDep) (types.TxDAG, error) { if s.mvStates == nil { return types.NewEmptyTxDAG(), nil } @@ -1789,7 +1789,7 @@ func (s *StateDB) ResolveTxDAG(txCnt int) (types.TxDAG, error) { }(time.Now()) } - return s.mvStates.ResolveTxDAG(txCnt) + return s.mvStates.ResolveTxDAG(txCnt, extraTxDeps...) } func (s *StateDB) MVStates() *types.MVStates { diff --git a/core/types/dag.go b/core/types/dag.go index 2bdcb46e97..f53e233b52 100644 --- a/core/types/dag.go +++ b/core/types/dag.go @@ -253,6 +253,8 @@ func (d *EmptyTxDAG) String() string { } // PlainTxDAG indicate how to use the dependency of txs, and delay the distribution of GasFee +// +//go:generate go run ../../rlp/rlpgen -type PlainTxDAG -out gen_plaintxdag_rlp.go type PlainTxDAG struct { // Tx Dependency List, the list index is equal to TxIndex TxDeps []TxDep diff --git a/core/types/gen_plaintxdag_rlp.go b/core/types/gen_plaintxdag_rlp.go new file mode 100644 index 0000000000..9e3ea46683 --- /dev/null +++ b/core/types/gen_plaintxdag_rlp.go @@ -0,0 +1,32 @@ +// Code generated by rlpgen. DO NOT EDIT. + +package types + +import "github.com/ethereum/go-ethereum/rlp" +import "io" + +func (obj *PlainTxDAG) EncodeRLP(_w io.Writer) error { + w := rlp.NewEncoderBuffer(_w) + _tmp0 := w.List() + _tmp1 := w.List() + for _, _tmp2 := range obj.TxDeps { + _tmp3 := w.List() + _tmp4 := w.List() + for _, _tmp5 := range _tmp2.TxIndexes { + w.WriteUint64(_tmp5) + } + w.ListEnd(_tmp4) + _tmp6 := _tmp2.Flags != nil + if _tmp6 { + if _tmp2.Flags == nil { + w.Write([]byte{0x80}) + } else { + w.WriteUint64(uint64((*_tmp2.Flags))) + } + } + w.ListEnd(_tmp3) + } + w.ListEnd(_tmp1) + w.ListEnd(_tmp0) + return w.Flush() +} diff --git a/core/types/mvstates.go b/core/types/mvstates.go index 596fec5c3c..ddd306bb07 100644 --- a/core/types/mvstates.go +++ b/core/types/mvstates.go @@ -21,12 +21,12 @@ var ( ) const ( - initRWEventCacheSize = 4 + initRWEventCacheSize = 40 ) func init() { for i := 0; i < initRWEventCacheSize; i++ { - cache := make([]RWEventItem, 10000) + cache := make([]RWEventItem, 200) rwEventCachePool.Put(&cache) } } @@ -55,6 +55,12 @@ func NewRWSet(index int) *RWSet { } } +func NewEmptyRWSet(index int) *RWSet { + return &RWSet{ + index: index, + } +} + func (s *RWSet) RecordAccountRead(addr common.Address, state AccountState) { // only record the first read version sub, ok := s.accReadSet[addr] @@ -362,70 +368,83 @@ func (s *MVStates) asyncRWEventLoop() { if !ok { return } - for _, item := range items { - s.handleRWEvent(item) - } + s.handleRWEvents(items) rwEventCachePool.Put(&items) } } } -func (s *MVStates) handleRWEvent(item RWEventItem) { - s.lock.Lock() - defer s.lock.Unlock() - // init next RWSet, and finalise previous RWSet - if item.Event == NewTxRWEvent { - s.finalisePreviousRWSet() - s.asyncRWSet = NewRWSet(item.Index) - return - } - if s.asyncRWSet == nil { - return +func (s *MVStates) handleRWEvents(items []RWEventItem) { + readFrom, readTo := -1, -1 + recordNewTx := false + for i, item := range items { + // init next RWSet, and finalise previous RWSet + if item.Event == NewTxRWEvent { + // handle previous rw set + if recordNewTx { + var prevItems []RWEventItem + if readFrom >= 0 && readTo > readFrom { + prevItems = items[readFrom:readTo] + } + s.finalisePreviousRWSet(prevItems) + readFrom, readTo = -1, -1 + } + recordNewTx = true + s.asyncRWSet = NewEmptyRWSet(item.Index) + continue + } + if s.asyncRWSet == nil { + continue + } + switch item.Event { + // recorde current read/write event + case ReadAccRWEvent, ReadSlotRWEvent: + if readFrom < 0 { + readFrom = i + } + readTo = i + 1 + case WriteAccRWEvent: + s.finaliseAccWrite(s.asyncRWSet.index, item.Addr, item.State) + case WriteSlotRWEvent: + s.finaliseSlotWrite(s.asyncRWSet.index, item.Addr, item.Slot) + // recorde current as cannot gas fee delay + case CannotGasFeeDelayRWEvent: + s.asyncRWSet.cannotGasFeeDelay = true + } } - switch item.Event { - // recorde current read/write event - case ReadAccRWEvent: - s.asyncRWSet.RecordAccountRead(item.Addr, item.State) - case ReadSlotRWEvent: - s.asyncRWSet.RecordStorageRead(item.Addr, item.Slot) - case WriteAccRWEvent: - s.finaliseAccWrite(s.asyncRWSet.index, item.Addr, item.State) - case WriteSlotRWEvent: - s.finaliseSlotWrite(s.asyncRWSet.index, item.Addr, item.Slot) - // recorde current as cannot gas fee delay - case CannotGasFeeDelayRWEvent: - s.asyncRWSet.cannotGasFeeDelay = true + // handle last tx rw set + if recordNewTx { + var prevItems []RWEventItem + if readFrom >= 0 && readTo > readFrom { + prevItems = items[readFrom:readTo] + } + s.finalisePreviousRWSet(prevItems) } } -func (s *MVStates) finalisePreviousRWSet() { +func (s *MVStates) finalisePreviousRWSet(reads []RWEventItem) { if s.asyncRWSet == nil { return } index := s.asyncRWSet.index s.rwSets[index] = s.asyncRWSet - // check if there are RW with gas fee receiver for gas delay calculation - for _, addr := range s.gasFeeReceivers { - if _, exist := s.asyncRWSet.accReadSet[addr]; !exist { - continue - } - if _, exist := s.asyncRWSet.accReadSet[addr][AccountSelf]; exist { - s.rwSets[index].cannotGasFeeDelay = true - break - } - } - if err := s.innerFinalise(index, false); err != nil { - log.Error("Finalise err when handle NewTxRWEvent", "tx", index, "err", err) + if index > s.nextFinaliseIndex { + log.Error("finalise in wrong order", "next", s.nextFinaliseIndex, "input", index) return } - s.resolveDepsMapCacheByWrites(index, s.asyncRWSet) + // reset nextFinaliseIndex to index+1, it may revert to previous txs + s.nextFinaliseIndex = index + 1 + s.resolveDepsMapCacheByWrites2(index, reads) } func (s *MVStates) RecordNewTx(index int) { if !s.asyncRunning { return } + if index%10 == 0 { + s.BatchRecordHandle() + } if s.rwEventCacheIndex < len(s.rwEventCache) { s.rwEventCache[s.rwEventCacheIndex].Event = NewTxRWEvent s.rwEventCache[s.rwEventCacheIndex].Index = index @@ -438,9 +457,6 @@ func (s *MVStates) RecordNewTx(index int) { s.rwEventCacheIndex++ s.recordingRead = true s.recordingWrite = true - if index%10 == 0 { - s.BatchRecordHandle() - } } func (s *MVStates) RecordReadDone() { @@ -557,6 +573,7 @@ func (s *MVStates) stopAsyncRecorder() { s.BatchRecordHandle() s.asyncRunning = false close(s.rwEventCh) + rwEventCachePool.Put(&s.rwEventCache) s.asyncWG.Wait() } } @@ -670,7 +687,7 @@ func (s *MVStates) resolveDepsMapCacheByWrites(index int, rwSet *RWSet) { s.txDepCache[index] = NewTxDep([]uint64{}, ExcludedTxFlag) return } - depMap := NewTxDepMap(0) + depSlice := NewTxDepSlice(0) // check tx dependency, only check key, skip version for addr, sub := range rwSet.accReadSet { for state := range sub { @@ -687,10 +704,10 @@ func (s *MVStates) resolveDepsMapCacheByWrites(index int, rwSet *RWSet) { continue } tx := uint64(find) - if depMap.exist(tx) { + if depSlice.exist(tx) { continue } - depMap.add(tx) + depSlice.add(tx) } } for addr, sub := range rwSet.slotReadSet { @@ -704,22 +721,91 @@ func (s *MVStates) resolveDepsMapCacheByWrites(index int, rwSet *RWSet) { continue } tx := uint64(find) - if depMap.exist(tx) { + if depSlice.exist(tx) { continue } - depMap.add(tx) + depSlice.add(tx) } } + // clear redundancy deps compared with prev + preDeps := depSlice.deps() + var removed []uint64 + for _, prev := range preDeps { + for _, tx := range s.txDepCache[int(prev)].TxIndexes { + if depSlice.exist(tx) { + removed = append(removed, tx) + } + } + } + for _, tx := range removed { + depSlice.remove(tx) + } //log.Debug("resolveDepsMapCacheByWrites", "tx", index, "deps", depMap.deps()) + s.txDepCache[index] = NewTxDep(depSlice.deps()) +} + +// resolveDepsMapCacheByWrites2 must be executed in order +func (s *MVStates) resolveDepsMapCacheByWrites2(index int, reads []RWEventItem) { + rwSet := s.rwSets[index] + // analysis dep, if the previous transaction is not executed/validated, re-analysis is required + if rwSet.excludedTx { + s.txDepCache[index] = NewTxDep([]uint64{}, ExcludedTxFlag) + return + } + depSlice := NewTxDepSlice(0) + addrMap := make(map[common.Address]struct{}) + // check tx dependency, only check key + for _, item := range reads { + // check account states & slots + var writes *StateWrites + if item.Event == ReadAccRWEvent { + writes = s.queryAccWrites(item.Addr, item.State) + } else { + writes = s.querySlotWrites(item.Addr, item.Slot) + } + if writes != nil { + if find := writes.FindLastWrite(index); find >= 0 { + if tx := uint64(find); !depSlice.exist(tx) { + depSlice.add(tx) + } + } + } + + // check again account self with Suicide + if _, ok := addrMap[item.Addr]; ok { + continue + } + addrMap[item.Addr] = struct{}{} + writes = s.queryAccWrites(item.Addr, AccountSuicide) + if writes != nil { + if find := writes.FindLastWrite(index); find >= 0 { + if tx := uint64(find); !depSlice.exist(tx) { + depSlice.add(tx) + } + } + } + } + for _, addr := range s.gasFeeReceivers { + if _, ok := addrMap[addr]; ok { + rwSet.cannotGasFeeDelay = true + break + } + } // clear redundancy deps compared with prev - preDeps := depMap.deps() + preDeps := depSlice.deps() + var removed []uint64 for _, prev := range preDeps { for _, tx := range s.txDepCache[int(prev)].TxIndexes { - depMap.remove(tx) + if depSlice.exist(tx) { + removed = append(removed, tx) + } } } - //log.Debug("resolveDepsMapCacheByWrites after clean", "tx", index, "deps", depMap.deps()) - s.txDepCache[index] = NewTxDep(depMap.deps()) + for _, tx := range removed { + depSlice.remove(tx) + } + //log.Debug("resolveDepsMapCacheByWrites", "tx", index, "deps", depSlice.deps()) + s.txDepCache[index] = NewTxDep(depSlice.deps()) } // resolveDepsCache must be executed in order @@ -765,34 +851,37 @@ func (s *MVStates) resolveDepsCache(index int, rwSet *RWSet) { } // ResolveTxDAG generate TxDAG from RWSets -func (s *MVStates) ResolveTxDAG(txCnt int) (TxDAG, error) { +func (s *MVStates) ResolveTxDAG(txCnt int, extraTxDeps ...TxDep) (TxDAG, error) { s.stopAsyncRecorder() s.lock.Lock() defer s.lock.Unlock() - s.finalisePreviousRWSet() if s.nextFinaliseIndex != txCnt { return nil, fmt.Errorf("cannot resolve with wrong FinaliseIndex, expect: %v, now: %v", txCnt, s.nextFinaliseIndex) } - txDAG := NewPlainTxDAG(txCnt) + totalCnt := txCnt + len(extraTxDeps) + txDAG := NewPlainTxDAG(totalCnt) for i := 0; i < txCnt; i++ { if s.rwSets[i].cannotGasFeeDelay { return NewEmptyTxDAG(), nil } - deps := s.txDepCache[i].TxIndexes - if len(deps) <= (txCnt-1)/2 { - txDAG.TxDeps[i] = s.txDepCache[i] + cache := s.txDepCache[i] + if len(cache.TxIndexes) <= (txCnt-1)/2 { + txDAG.TxDeps[i] = cache continue } // if tx deps larger than half of txs, then convert with NonDependentRelFlag txDAG.TxDeps[i].SetFlag(NonDependentRelFlag) for j := uint64(0); j < uint64(txCnt); j++ { - if !slices.Contains(deps, j) && j != uint64(i) { + if !slices.Contains(cache.TxIndexes, j) && j != uint64(i) { txDAG.TxDeps[i].TxIndexes = append(txDAG.TxDeps[i].TxIndexes, j) } } } + for i, j := txCnt, 0; i < totalCnt && j < len(extraTxDeps); i, j = i+1, j+1 { + txDAG.TxDeps[i] = extraTxDeps[j] + } return txDAG, nil } @@ -882,3 +971,49 @@ func (m *TxDepMap) remove(index uint64) { func (m *TxDepMap) len() int { return len(m.tm) } + +type TxDepSlice struct { + indexes []uint64 +} + +func NewTxDepSlice(cap int) *TxDepSlice { + return &TxDepSlice{ + indexes: make([]uint64, 0, cap), + } +} + +func (m *TxDepSlice) add(index uint64) { + if m.exist(index) { + return + } + m.indexes = append(m.indexes, index) + for i := len(m.indexes) - 1; i > 0; i-- { + if m.indexes[i] < m.indexes[i-1] { + m.indexes[i-1], m.indexes[i] = m.indexes[i], m.indexes[i-1] + } + } +} + +func (m *TxDepSlice) exist(index uint64) bool { + _, ok := slices.BinarySearch(m.indexes, index) + return ok +} + +func (m *TxDepSlice) deps() []uint64 { + return m.indexes +} + +func (m *TxDepSlice) remove(index uint64) { + pos, ok := slices.BinarySearch(m.indexes, index) + if !ok { + return + } + for i := pos; i < len(m.indexes)-1; i++ { + m.indexes[i] = m.indexes[i+1] + } + m.indexes = m.indexes[:len(m.indexes)-1] +} + +func (m *TxDepSlice) len() int { + return len(m.indexes) +} diff --git a/core/types/mvstates_test.go b/core/types/mvstates_test.go index 7801a1bf79..76678216b3 100644 --- a/core/types/mvstates_test.go +++ b/core/types/mvstates_test.go @@ -4,6 +4,7 @@ import ( "bytes" "compress/gzip" "fmt" + "sync" "testing" "time" @@ -63,14 +64,18 @@ func TestMVStates_ResolveTxDAG_Compare(t *testing.T) { rwSets := mockRandomRWSet(txCnt) ms1 := NewMVStates(txCnt, nil).EnableAsyncGen() ms2 := NewMVStates(txCnt, nil).EnableAsyncGen() + ms3 := NewMVStates(txCnt, nil).EnableAsyncGen() for i, rwSet := range rwSets { ms1.rwSets[i] = rwSet require.NoError(t, ms2.FinaliseWithRWSet(rwSet)) + ms3.handleRWEvents(mockRWEventItemsFromRWSet(i, rwSet)) } d1 := resolveTxDAGInMVStates(ms1, txCnt) d2 := resolveDepsMapCacheByWritesInMVStates(ms2) + d3 := resolveDepsMapCacheByWrites2InMVStates(ms3) require.Equal(t, d1.(*PlainTxDAG).String(), d2.(*PlainTxDAG).String()) + require.Equal(t, d1.(*PlainTxDAG).String(), d3.(*PlainTxDAG).String()) } func TestMVStates_TxDAG_Compression(t *testing.T) { @@ -129,6 +134,22 @@ func BenchmarkResolveTxDAGByWritesInMVStates(b *testing.B) { } } +func BenchmarkResolveTxDAGByWrites2InMVStates(b *testing.B) { + rwSets := mockRandomRWSet(mockRWSetSize) + items := make([][]RWEventItem, mockRWSetSize) + ms1 := NewMVStates(mockRWSetSize, nil).EnableAsyncGen() + for i, rwSet := range rwSets { + items[i] = mockRWEventItemsFromRWSet(i, rwSet) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + for _, item := range items { + ms1.handleRWEvents(item) + } + resolveDepsMapCacheByWrites2InMVStates(ms1) + } +} + func BenchmarkResolveTxDAGByWritesInMVStates_100PercentConflict(b *testing.B) { rwSets := mockSameRWSet(mockRWSetSize) ms1 := NewMVStates(mockRWSetSize, nil).EnableAsyncGen() @@ -164,6 +185,69 @@ func BenchmarkMVStates_Finalise(b *testing.B) { } } +func checkMap(m map[int][10]byte) { + for i, j := range m { + m[i] = j + } +} + +func BenchmarkEmptyMap(b *testing.B) { + for i := 0; i < b.N; i++ { + m := make(map[int][10]byte) + for j := 0; j < 10000; j++ { + m[i] = [10]byte{byte(j)} + } + checkMap(m) + } +} + +func BenchmarkInitMapWithSize(b *testing.B) { + for i := 0; i < b.N; i++ { + m := make(map[int][10]byte, 10) + for j := 0; j < 1000; j++ { + m[i] = [10]byte{byte(j)} + } + } +} + +func BenchmarkReuseMap(b *testing.B) { + sp := sync.Pool{New: func() interface{} { + return make(map[int]struct{}, 10) + }} + for i := 0; i < b.N; i++ { + m := sp.Get().(map[int]struct{}) + for j := 0; j < 1000; j++ { + m[i] = struct{}{} + } + for k := range m { + delete(m, k) + } + sp.Put(m) + } +} + +func BenchmarkExistArray(b *testing.B) { + for i := 0; i < b.N; i++ { + m := make(map[[20]byte]struct{}) + m[common.Address{1}] = struct{}{} + addr := common.Address{1} + if _, ok := m[addr]; ok { + continue + } + } +} + +func BenchmarkDonotExistArray(b *testing.B) { + for i := 0; i < b.N; i++ { + m := make(map[[20]byte]struct{}) + addr := common.Address{1} + if _, ok := m[addr]; !ok { + m[addr] = struct{}{} + delete(m, addr) + } + } +} + func resolveTxDAGInMVStates(s *MVStates, txCnt int) TxDAG { txDAG := NewPlainTxDAG(txCnt) for i := 0; i < txCnt; i++ { @@ -183,6 +267,15 @@ func resolveDepsMapCacheByWritesInMVStates(s *MVStates) TxDAG { return txDAG } +func resolveDepsMapCacheByWrites2InMVStates(s *MVStates) TxDAG { + txCnt := s.nextFinaliseIndex + txDAG := NewPlainTxDAG(txCnt) + for i := 0; i < txCnt; i++ { + txDAG.TxDeps[i] = s.txDepCache[i] + } + return txDAG +} + func TestMVStates_SystemTxResolveTxDAG(t *testing.T) { ms := NewMVStates(12, nil).EnableAsyncGen() finaliseRWSets(t, ms, []*RWSet{ @@ -410,3 +503,48 @@ func randInRange(i, j int) (int, bool) { } return rand.Int()%(j-i) + i, true } + +func mockRWEventItemsFromRWSet(index int, rwSet *RWSet) []RWEventItem { + items := make([]RWEventItem, 0) + items = append(items, RWEventItem{ + Event: NewTxRWEvent, + Index: index, + }) + for addr, sub := range rwSet.accReadSet { + for state := range sub { + items = append(items, RWEventItem{ + Event: ReadAccRWEvent, + Addr: addr, + State: state, + }) + } + } + for addr, sub := range rwSet.slotReadSet { + for slot := range sub { + items = append(items, RWEventItem{ + Event: ReadSlotRWEvent, + Addr: addr, + Slot: slot, + }) + } + } + for addr, sub := range rwSet.accWriteSet { + for state := range sub { + items = append(items, RWEventItem{ + Event: WriteAccRWEvent, + Addr: addr, + State: state, + }) + } + } + for addr, sub := range rwSet.slotWriteSet { + for slot := range sub { + items = append(items, RWEventItem{ + Event: WriteSlotRWEvent, + Addr: addr, + Slot: slot, + }) + } + } + return items +} diff --git a/miner/worker.go b/miner/worker.go index 157907c709..7a977a9934 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1037,12 +1037,11 @@ func (w *worker) generateDAGTx(statedb *state.StateDB, signer types.Signer, txIn } // get txDAG data from the stateDB - txDAG, err := statedb.ResolveTxDAG(txIndex) + // txIndex is the index of this txDAG transaction + txDAG, err := statedb.ResolveTxDAG(txIndex, types.TxDep{Flags: &types.NonDependentRelFlag}) if txDAG == nil { return nil, err } - // txIndex is the index of this txDAG transaction - txDAG.SetTxDep(txIndex, types.TxDep{Flags: &types.NonDependentRelFlag}) if metrics.EnabledExpensive { go types.EvaluateTxDAGPerformance(txDAG) } From 0e1348fd70f64b99488f7286406fe69a9e2c435c Mon Sep 17 00:00:00 2001 From: galaio Date: Thu, 12 Sep 2024 10:39:44 +0800 Subject: [PATCH 18/19] txdag: using a new mem pool; --- core/state/statedb.go | 2 +- core/state_processor.go | 3 + core/types/mvstates.go | 260 ++++++++++++++++++++++++------------ core/types/mvstates_test.go | 154 ++++++++++----------- miner/worker.go | 6 +- 5 files changed, 253 insertions(+), 172 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 4e4b8719c6..c4d4a43329 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -1711,7 +1711,7 @@ func (s *StateDB) StartTxRecorder(isExcludeTx bool) { } //log.Debug("StartTxRecorder", "tx", s.txIndex) if isExcludeTx { - rwSet := types.NewRWSet(s.txIndex).WithExcludedTxFlag() + rwSet := types.NewEmptyRWSet(s.txIndex).WithExcludedTxFlag() if err := s.mvStates.FinaliseWithRWSet(rwSet); err != nil { log.Error("MVStates SystemTx Finalise err", "err", err) } diff --git a/core/state_processor.go b/core/state_processor.go index afe060d432..88cffe6b7e 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -125,6 +125,9 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) p.engine.Finalize(p.bc, header, statedb, block.Transactions(), block.Uncles(), withdrawals) if p.bc.enableTxDAG { + defer func() { + statedb.MVStates().Stop() + }() // compare input TxDAG when it enable in consensus dag, err := statedb.ResolveTxDAG(len(block.Transactions())) if err == nil { diff --git a/core/types/mvstates.go b/core/types/mvstates.go index ddd306bb07..27b204dc22 100644 --- a/core/types/mvstates.go +++ b/core/types/mvstates.go @@ -7,6 +7,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" "golang.org/x/exp/slices" ) @@ -21,14 +22,49 @@ var ( ) const ( - initRWEventCacheSize = 40 + initSyncPoolSize = 4 + asyncSendInterval = 20 ) func init() { - for i := 0; i < initRWEventCacheSize; i++ { - cache := make([]RWEventItem, 200) + for i := 0; i < initSyncPoolSize*4; i++ { + cache := make([]RWEventItem, 400) rwEventCachePool.Put(&cache) } + for i := 0; i < initSyncPoolSize; i++ { + rwSets := make([]RWSet, 4000) + rwSetsPool.Put(&rwSets) + txDeps := make([]TxDep, 4000) + txDepsPool.Put(&txDeps) + } +} + +type ChanPool struct { + ch chan any + new func() any +} + +func NewChanPool(size int, f func() any) *ChanPool { + return &ChanPool{ + ch: make(chan any, size), + new: f, + } +} + +func (p ChanPool) Get() any { + select { + case item := <-p.ch: + return item + default: + } + return p.new() +} + +func (p ChanPool) Put(item any) { + select { + case p.ch <- item: + default: + } } // RWSet record all read & write set in txs @@ -275,26 +311,46 @@ func (w *StateWrites) Copy() *StateWrites { } var ( - rwEventCachePool = sync.Pool{New: func() any { + rwEventsAllocMeter = metrics.GetOrRegisterMeter("mvstate/alloc/rwevents/cnt", nil) + rwEventsAllocGauge = metrics.GetOrRegisterGauge("mvstate/alloc/rwevents/gauge", nil) + rwSetsAllocMeter = metrics.GetOrRegisterMeter("mvstate/alloc/rwsets/cnt", nil) + rwSetsAllocGauge = metrics.GetOrRegisterGauge("mvstate/alloc/rwsets/gauge", nil) + txDepsAllocMeter = metrics.GetOrRegisterMeter("mvstate/alloc/txdeps/cnt", nil) + txDepsAllocGauge = metrics.GetOrRegisterGauge("mvstate/alloc/txdeps/gauge", nil) +) + +var ( + rwEventCachePool = NewChanPool(initSyncPoolSize*4, func() any { + rwEventsAllocMeter.Mark(1) buf := make([]RWEventItem, 0) return &buf - }} + }) + rwSetsPool = NewChanPool(initSyncPoolSize, func() any { + rwSetsAllocMeter.Mark(1) + buf := make([]RWSet, 0) + return &buf + }) + txDepsPool = NewChanPool(initSyncPoolSize, func() any { + txDepsAllocMeter.Mark(1) + buf := make([]TxDep, 0) + return &buf + }) ) type MVStates struct { - rwSets map[int]*RWSet - pendingAccWriteSet map[common.Address]map[AccountState]*StateWrites - pendingSlotWriteSet map[common.Address]map[common.Hash]*StateWrites - nextFinaliseIndex int - gasFeeReceivers []common.Address + rwSets []RWSet + accWriteSet map[common.Address]map[AccountState]*StateWrites + slotWriteSet map[common.Address]map[common.Hash]*StateWrites + nextFinaliseIndex int + gasFeeReceivers []common.Address // dependency map cache for generating TxDAG // depMapCache[i].exist(j) means j->i, and i > j - txDepCache map[int]TxDep + txDepCache []TxDep lock sync.RWMutex // async rw event recorder // these fields are only used in one routine - asyncRWSet *RWSet + asyncRWSet RWSet rwEventCh chan []RWEventItem rwEventCache []RWEventItem rwEventCacheIndex int @@ -305,29 +361,34 @@ type MVStates struct { } func NewMVStates(txCount int, gasFeeReceivers []common.Address) *MVStates { - m := &MVStates{ - rwSets: make(map[int]*RWSet, txCount), - pendingAccWriteSet: make(map[common.Address]map[AccountState]*StateWrites, txCount), - pendingSlotWriteSet: make(map[common.Address]map[common.Hash]*StateWrites, txCount), - txDepCache: make(map[int]TxDep, txCount), - rwEventCh: make(chan []RWEventItem, 100), - gasFeeReceivers: gasFeeReceivers, + s := &MVStates{ + accWriteSet: make(map[common.Address]map[AccountState]*StateWrites, txCount), + slotWriteSet: make(map[common.Address]map[common.Hash]*StateWrites, txCount), + rwEventCh: make(chan []RWEventItem, 100), + gasFeeReceivers: gasFeeReceivers, } - m.rwEventCache = *rwEventCachePool.Get().(*[]RWEventItem) - m.rwEventCache = m.rwEventCache[:cap(m.rwEventCache)] - m.rwEventCacheIndex = 0 - return m + + s.rwSets = *rwSetsPool.Get().(*[]RWSet) + s.rwSets = s.rwSets[:0] + s.txDepCache = *txDepsPool.Get().(*[]TxDep) + s.txDepCache = s.txDepCache[:0] + return s } func (s *MVStates) EnableAsyncGen() *MVStates { s.asyncWG.Add(1) s.asyncRunning = true + s.rwEventCache = *rwEventCachePool.Get().(*[]RWEventItem) + s.rwEventCache = s.rwEventCache[:cap(s.rwEventCache)] + s.rwEventCacheIndex = 0 + s.asyncRWSet.index = -1 go s.asyncRWEventLoop() return s } func (s *MVStates) Stop() { s.stopAsyncRecorder() + s.ReuseMem() } func (s *MVStates) Copy() *MVStates { @@ -335,26 +396,22 @@ func (s *MVStates) Copy() *MVStates { defer s.lock.Unlock() ns := NewMVStates(len(s.rwSets), s.gasFeeReceivers) ns.nextFinaliseIndex = s.nextFinaliseIndex - for k, v := range s.txDepCache { - ns.txDepCache[k] = v - } - for k, v := range s.rwSets { - ns.rwSets[k] = v - } - for addr, sub := range s.pendingAccWriteSet { + ns.txDepCache = append(ns.txDepCache, s.txDepCache...) + ns.rwSets = append(ns.rwSets, s.rwSets...) + for addr, sub := range s.accWriteSet { for state, writes := range sub { - if _, ok := ns.pendingAccWriteSet[addr]; !ok { - ns.pendingAccWriteSet[addr] = make(map[AccountState]*StateWrites) + if _, ok := ns.accWriteSet[addr]; !ok { + ns.accWriteSet[addr] = make(map[AccountState]*StateWrites) } - ns.pendingAccWriteSet[addr][state] = writes.Copy() + ns.accWriteSet[addr][state] = writes.Copy() } } - for addr, sub := range s.pendingSlotWriteSet { + for addr, sub := range s.slotWriteSet { for slot, writes := range sub { - if _, ok := ns.pendingSlotWriteSet[addr]; !ok { - ns.pendingSlotWriteSet[addr] = make(map[common.Hash]*StateWrites) + if _, ok := ns.slotWriteSet[addr]; !ok { + ns.slotWriteSet[addr] = make(map[common.Hash]*StateWrites) } - ns.pendingSlotWriteSet[addr][slot] = writes.Copy() + ns.slotWriteSet[addr][slot] = writes.Copy() } } return ns @@ -364,12 +421,12 @@ func (s *MVStates) asyncRWEventLoop() { defer s.asyncWG.Done() for { select { - case items, ok := <-s.rwEventCh: + case item, ok := <-s.rwEventCh: if !ok { return } - s.handleRWEvents(items) - rwEventCachePool.Put(&items) + s.handleRWEvents(item) + rwEventCachePool.Put(&item) } } } @@ -390,10 +447,12 @@ func (s *MVStates) handleRWEvents(items []RWEventItem) { readFrom, readTo = -1, -1 } recordNewTx = true - s.asyncRWSet = NewEmptyRWSet(item.Index) + s.asyncRWSet = RWSet{ + index: item.Index, + } continue } - if s.asyncRWSet == nil { + if s.asyncRWSet.index < 0 { continue } switch item.Event { @@ -423,10 +482,13 @@ func (s *MVStates) handleRWEvents(items []RWEventItem) { } func (s *MVStates) finalisePreviousRWSet(reads []RWEventItem) { - if s.asyncRWSet == nil { + if s.asyncRWSet.index < 0 { return } index := s.asyncRWSet.index + for index >= len(s.rwSets) { + s.rwSets = append(s.rwSets, RWSet{index: -1}) + } s.rwSets[index] = s.asyncRWSet if index > s.nextFinaliseIndex { @@ -442,7 +504,12 @@ func (s *MVStates) RecordNewTx(index int) { if !s.asyncRunning { return } - if index%10 == 0 { + if index%2000 == 0 { + rwEventsAllocGauge.Update(int64(len(rwEventCachePool.ch))) + rwSetsAllocGauge.Update(int64(len(rwSetsPool.ch))) + txDepsAllocGauge.Update(int64(len(txDepsPool.ch))) + } + if index%asyncSendInterval == 0 { s.BatchRecordHandle() } if s.rwEventCacheIndex < len(s.rwEventCache) { @@ -583,7 +650,10 @@ func (s *MVStates) FinaliseWithRWSet(rwSet *RWSet) error { s.lock.Lock() defer s.lock.Unlock() index := rwSet.index - s.rwSets[index] = rwSet + for index >= len(s.rwSets) { + s.rwSets = append(s.rwSets, RWSet{index: -1}) + } + s.rwSets[index] = *rwSet // just finalise all previous txs start := s.nextFinaliseIndex if start > index { @@ -593,7 +663,7 @@ func (s *MVStates) FinaliseWithRWSet(rwSet *RWSet) error { if err := s.innerFinalise(i, true); err != nil { return err } - s.resolveDepsMapCacheByWrites(i, s.rwSets[i]) + s.resolveDepsMapCacheByWrites(i, &(s.rwSets[i])) //log.Debug("Finalise the reads/writes", "index", i, // "readCnt", len(s.rwSets[i].accReadSet)+len(s.rwSets[i].slotReadSet), // "writeCnt", len(s.rwSets[i].accWriteSet)+len(s.rwSets[i].slotWriteSet)) @@ -603,11 +673,11 @@ func (s *MVStates) FinaliseWithRWSet(rwSet *RWSet) error { } func (s *MVStates) innerFinalise(index int, applyWriteSet bool) error { - rwSet := s.rwSets[index] - if rwSet == nil { + if index >= len(s.rwSets) { return fmt.Errorf("finalise a non-exist RWSet, index: %d", index) } + rwSet := s.rwSets[index] if index > s.nextFinaliseIndex { return fmt.Errorf("finalise in wrong order, next: %d, input: %d", s.nextFinaliseIndex, index) } @@ -620,25 +690,25 @@ func (s *MVStates) innerFinalise(index int, applyWriteSet bool) error { // append to pending write set for addr, sub := range rwSet.accWriteSet { - if _, exist := s.pendingAccWriteSet[addr]; !exist { - s.pendingAccWriteSet[addr] = make(map[AccountState]*StateWrites) + if _, exist := s.accWriteSet[addr]; !exist { + s.accWriteSet[addr] = make(map[AccountState]*StateWrites) } for state := range sub { - if _, exist := s.pendingAccWriteSet[addr][state]; !exist { - s.pendingAccWriteSet[addr][state] = NewStateWrites() + if _, exist := s.accWriteSet[addr][state]; !exist { + s.accWriteSet[addr][state] = NewStateWrites() } - s.pendingAccWriteSet[addr][state].Append(index) + s.accWriteSet[addr][state].Append(index) } } for addr, sub := range rwSet.slotWriteSet { - if _, exist := s.pendingSlotWriteSet[addr]; !exist { - s.pendingSlotWriteSet[addr] = make(map[common.Hash]*StateWrites) + if _, exist := s.slotWriteSet[addr]; !exist { + s.slotWriteSet[addr] = make(map[common.Hash]*StateWrites) } for slot := range sub { - if _, exist := s.pendingSlotWriteSet[addr][slot]; !exist { - s.pendingSlotWriteSet[addr][slot] = NewStateWrites() + if _, exist := s.slotWriteSet[addr][slot]; !exist { + s.slotWriteSet[addr][slot] = NewStateWrites() } - s.pendingSlotWriteSet[addr][slot].Append(index) + s.slotWriteSet[addr][slot].Append(index) } } return nil @@ -646,42 +716,45 @@ func (s *MVStates) innerFinalise(index int, applyWriteSet bool) error { func (s *MVStates) finaliseSlotWrite(index int, addr common.Address, slot common.Hash) { // append to pending write set - if _, exist := s.pendingSlotWriteSet[addr]; !exist { - s.pendingSlotWriteSet[addr] = make(map[common.Hash]*StateWrites) + if _, exist := s.slotWriteSet[addr]; !exist { + s.slotWriteSet[addr] = make(map[common.Hash]*StateWrites) } - if _, exist := s.pendingSlotWriteSet[addr][slot]; !exist { - s.pendingSlotWriteSet[addr][slot] = NewStateWrites() + if _, exist := s.slotWriteSet[addr][slot]; !exist { + s.slotWriteSet[addr][slot] = NewStateWrites() } - s.pendingSlotWriteSet[addr][slot].Append(index) + s.slotWriteSet[addr][slot].Append(index) } func (s *MVStates) finaliseAccWrite(index int, addr common.Address, state AccountState) { // append to pending write set - if _, exist := s.pendingAccWriteSet[addr]; !exist { - s.pendingAccWriteSet[addr] = make(map[AccountState]*StateWrites) + if _, exist := s.accWriteSet[addr]; !exist { + s.accWriteSet[addr] = make(map[AccountState]*StateWrites) } - if _, exist := s.pendingAccWriteSet[addr][state]; !exist { - s.pendingAccWriteSet[addr][state] = NewStateWrites() + if _, exist := s.accWriteSet[addr][state]; !exist { + s.accWriteSet[addr][state] = NewStateWrites() } - s.pendingAccWriteSet[addr][state].Append(index) + s.accWriteSet[addr][state].Append(index) } func (s *MVStates) queryAccWrites(addr common.Address, state AccountState) *StateWrites { - if _, exist := s.pendingAccWriteSet[addr]; !exist { + if _, exist := s.accWriteSet[addr]; !exist { return nil } - return s.pendingAccWriteSet[addr][state] + return s.accWriteSet[addr][state] } func (s *MVStates) querySlotWrites(addr common.Address, slot common.Hash) *StateWrites { - if _, exist := s.pendingSlotWriteSet[addr]; !exist { + if _, exist := s.slotWriteSet[addr]; !exist { return nil } - return s.pendingSlotWriteSet[addr][slot] + return s.slotWriteSet[addr][slot] } // resolveDepsMapCacheByWrites must be executed in order func (s *MVStates) resolveDepsMapCacheByWrites(index int, rwSet *RWSet) { + for index >= len(s.txDepCache) { + s.txDepCache = append(s.txDepCache, TxDep{}) + } // analysis dep, if the previous transaction is not executed/validated, re-analysis is required if rwSet.excludedTx { s.txDepCache[index] = NewTxDep([]uint64{}, ExcludedTxFlag) @@ -746,13 +819,16 @@ func (s *MVStates) resolveDepsMapCacheByWrites(index int, rwSet *RWSet) { // resolveDepsMapCacheByWrites2 must be executed in order func (s *MVStates) resolveDepsMapCacheByWrites2(index int, reads []RWEventItem) { + for index >= len(s.txDepCache) { + s.txDepCache = append(s.txDepCache, TxDep{}) + } rwSet := s.rwSets[index] // analysis dep, if the previous transaction is not executed/validated, re-analysis is required if rwSet.excludedTx { s.txDepCache[index] = NewTxDep([]uint64{}, ExcludedTxFlag) return } - depSlice := NewTxDepSlice(0) + depSlice := NewTxDepSlice(1) addrMap := make(map[common.Address]struct{}) // check tx dependency, only check key for _, item := range reads { @@ -810,6 +886,9 @@ func (s *MVStates) resolveDepsMapCacheByWrites2(index int, reads []RWEventItem) // resolveDepsCache must be executed in order func (s *MVStates) resolveDepsCache(index int, rwSet *RWSet) { + for index >= len(s.txDepCache) { + s.txDepCache = append(s.txDepCache, TxDep{}) + } // analysis dep, if the previous transaction is not executed/validated, re-analysis is required if rwSet.excludedTx { s.txDepCache[index] = NewTxDep([]uint64{}, ExcludedTxFlag) @@ -819,10 +898,10 @@ func (s *MVStates) resolveDepsCache(index int, rwSet *RWSet) { for prev := 0; prev < index; prev++ { // if there are some parallel execution or system txs, it will fulfill in advance // it's ok, and try re-generate later - prevSet := s.rwSets[prev] - if prevSet == nil { + if prev >= len(s.rwSets) { continue } + prevSet := s.rwSets[prev] // if prev tx is tagged ExcludedTxFlag, just skip the check if prevSet.excludedTx { continue @@ -861,28 +940,32 @@ func (s *MVStates) ResolveTxDAG(txCnt int, extraTxDeps ...TxDep) (TxDAG, error) } totalCnt := txCnt + len(extraTxDeps) - txDAG := NewPlainTxDAG(totalCnt) for i := 0; i < txCnt; i++ { if s.rwSets[i].cannotGasFeeDelay { return NewEmptyTxDAG(), nil } - cache := s.txDepCache[i] - if len(cache.TxIndexes) <= (txCnt-1)/2 { - txDAG.TxDeps[i] = cache + } + txDAG := &PlainTxDAG{ + TxDeps: s.txDepCache, + } + if len(extraTxDeps) > 0 { + txDAG.TxDeps = append(txDAG.TxDeps, extraTxDeps...) + } + for i := 0; i < len(txDAG.TxDeps); i++ { + if len(txDAG.TxDeps[i].TxIndexes) <= (totalCnt-1)/2 { continue } // if tx deps larger than half of txs, then convert with NonDependentRelFlag txDAG.TxDeps[i].SetFlag(NonDependentRelFlag) - for j := uint64(0); j < uint64(txCnt); j++ { - if !slices.Contains(cache.TxIndexes, j) && j != uint64(i) { - txDAG.TxDeps[i].TxIndexes = append(txDAG.TxDeps[i].TxIndexes, j) + nd := make([]uint64, 0, totalCnt-1-len(txDAG.TxDeps[i].TxIndexes)) + for j := uint64(0); j < uint64(totalCnt); j++ { + if !slices.Contains(txDAG.TxDeps[i].TxIndexes, j) && j != uint64(i) { + nd = append(nd, j) } } + txDAG.TxDeps[i].TxIndexes = nd } - for i, j := txCnt, 0; i < totalCnt && j < len(extraTxDeps); i, j = i+1, j+1 { - txDAG.TxDeps[i] = extraTxDeps[j] - } - + s.txDepCache = txDAG.TxDeps return txDAG, nil } @@ -890,6 +973,11 @@ func (s *MVStates) FeeReceivers() []common.Address { return s.gasFeeReceivers } +func (s *MVStates) ReuseMem() { + rwSetsPool.Put(&s.rwSets) + txDepsPool.Put(&s.txDepCache) +} + func checkAccDependency(writeSet map[common.Address]map[AccountState]struct{}, readSet map[common.Address]map[AccountState]struct{}) bool { // check tx dependency, only check key, skip version for addr, sub := range writeSet { diff --git a/core/types/mvstates_test.go b/core/types/mvstates_test.go index 76678216b3..c7a90b1139 100644 --- a/core/types/mvstates_test.go +++ b/core/types/mvstates_test.go @@ -4,7 +4,6 @@ import ( "bytes" "compress/gzip" "fmt" - "sync" "testing" "time" @@ -66,7 +65,7 @@ func TestMVStates_ResolveTxDAG_Compare(t *testing.T) { ms2 := NewMVStates(txCnt, nil).EnableAsyncGen() ms3 := NewMVStates(txCnt, nil).EnableAsyncGen() for i, rwSet := range rwSets { - ms1.rwSets[i] = rwSet + ms1.rwSets = append(ms1.rwSets, *rwSet) require.NoError(t, ms2.FinaliseWithRWSet(rwSet)) ms3.handleRWEvents(mockRWEventItemsFromRWSet(i, rwSet)) } @@ -110,11 +109,37 @@ func TestMVStates_TxDAG_Compression(t *testing.T) { "time", float64(time.Since(start).Microseconds())/1000) } +var ( + mockRandRWSets []*RWSet + mockSameRWSets []*RWSet + mockDiffRWSets []*RWSet + mockRWEventItems [][]RWEventItem + mockSameRWEventItems [][]RWEventItem + mockDiffRWEventItems [][]RWEventItem +) + +func init() { + mockRandRWSets = mockRandomRWSet(mockRWSetSize) + mockSameRWSets = mockSameRWSet(mockRWSetSize) + mockDiffRWSets = mockDifferentRWSet(mockRWSetSize) + mockRWEventItems = make([][]RWEventItem, mockRWSetSize) + for i, rwSet := range mockRandRWSets { + mockRWEventItems[i] = mockRWEventItemsFromRWSet(i, rwSet) + } + mockSameRWEventItems = make([][]RWEventItem, mockRWSetSize) + for i, rwSet := range mockSameRWSets { + mockSameRWEventItems[i] = mockRWEventItemsFromRWSet(i, rwSet) + } + mockDiffRWEventItems = make([][]RWEventItem, mockRWSetSize) + for i, rwSet := range mockDiffRWSets { + mockDiffRWEventItems[i] = mockRWEventItemsFromRWSet(i, rwSet) + } +} + func BenchmarkResolveTxDAGInMVStates(b *testing.B) { - rwSets := mockRandomRWSet(mockRWSetSize) ms1 := NewMVStates(mockRWSetSize, nil).EnableAsyncGen() - for i, rwSet := range rwSets { - ms1.rwSets[i] = rwSet + for _, rwSet := range mockRandRWSets { + ms1.rwSets = append(ms1.rwSets, *rwSet) } b.ResetTimer() for i := 0; i < b.N; i++ { @@ -123,9 +148,8 @@ func BenchmarkResolveTxDAGInMVStates(b *testing.B) { } func BenchmarkResolveTxDAGByWritesInMVStates(b *testing.B) { - rwSets := mockRandomRWSet(mockRWSetSize) ms1 := NewMVStates(mockRWSetSize, nil).EnableAsyncGen() - for _, rwSet := range rwSets { + for _, rwSet := range mockRandRWSets { ms1.FinaliseWithRWSet(rwSet) } b.ResetTimer() @@ -135,25 +159,55 @@ func BenchmarkResolveTxDAGByWritesInMVStates(b *testing.B) { } func BenchmarkResolveTxDAGByWrites2InMVStates(b *testing.B) { - rwSets := mockRandomRWSet(mockRWSetSize) - items := make([][]RWEventItem, mockRWSetSize) ms1 := NewMVStates(mockRWSetSize, nil).EnableAsyncGen() - for i, rwSet := range rwSets { - items[i] = mockRWEventItemsFromRWSet(i, rwSet) - } b.ResetTimer() for i := 0; i < b.N; i++ { - for _, item := range items { + for _, item := range mockRWEventItems { ms1.handleRWEvents(item) } resolveDepsMapCacheByWrites2InMVStates(ms1) } } +func BenchmarkResolveTxDAG_RWEvent_RandRWSet(b *testing.B) { + benchmarkResolveTxDAGRWEvent(b, mockRWEventItems) +} + +func BenchmarkResolveTxDAG_RWEvent_SameRWSet(b *testing.B) { + benchmarkResolveTxDAGRWEvent(b, mockSameRWEventItems) +} + +func BenchmarkResolveTxDAG_RWEvent_DiffRWSet(b *testing.B) { + benchmarkResolveTxDAGRWEvent(b, mockDiffRWEventItems) +} + +func benchmarkResolveTxDAGRWEvent(b *testing.B, eventItems [][]RWEventItem) { + for i := 0; i < b.N; i++ { + s := NewMVStates(0, nil).EnableAsyncGen() + for _, items := range eventItems { + for _, item := range items { + switch item.Event { + case NewTxRWEvent: + s.RecordNewTx(item.Index) + case ReadAccRWEvent: + s.RecordAccountRead(item.Addr, item.State) + case ReadSlotRWEvent: + s.RecordStorageRead(item.Addr, item.Slot) + case WriteAccRWEvent: + s.RecordAccountWrite(item.Addr, item.State) + case WriteSlotRWEvent: + s.RecordStorageWrite(item.Addr, item.Slot) + } + } + } + s.ResolveTxDAG(mockRWSetSize) + s.Stop() + } +} + func BenchmarkResolveTxDAGByWritesInMVStates_100PercentConflict(b *testing.B) { - rwSets := mockSameRWSet(mockRWSetSize) ms1 := NewMVStates(mockRWSetSize, nil).EnableAsyncGen() - for _, rwSet := range rwSets { + for _, rwSet := range mockSameRWSets { ms1.FinaliseWithRWSet(rwSet) } b.ResetTimer() @@ -163,9 +217,8 @@ func BenchmarkResolveTxDAGByWritesInMVStates_100PercentConflict(b *testing.B) { } func BenchmarkResolveTxDAGByWritesInMVStates_0PercentConflict(b *testing.B) { - rwSets := mockDifferentRWSet(mockRWSetSize) ms1 := NewMVStates(mockRWSetSize, nil).EnableAsyncGen() - for _, rwSet := range rwSets { + for _, rwSet := range mockDiffRWSets { ms1.FinaliseWithRWSet(rwSet) } b.ResetTimer() @@ -185,73 +238,10 @@ func BenchmarkMVStates_Finalise(b *testing.B) { } } -func checkMap(m map[int][10]byte) { - for i, j := range m { - m[i] = j - } -} - -func BenchmarkEmptyMap(b *testing.B) { - for i := 0; i < b.N; i++ { - m := make(map[int][10]byte) - for j := 0; j < 10000; j++ { - m[i] = [10]byte{byte(j)} - } - checkMap(m) - } -} - -func BenchmarkInitMapWithSize(b *testing.B) { - for i := 0; i < b.N; i++ { - m := make(map[int][10]byte, 10) - for j := 0; j < 1000; j++ { - m[i] = [10]byte{byte(j)} - } - } -} - -func BenchmarkReuseMap(b *testing.B) { - sp := sync.Pool{New: func() interface{} { - return make(map[int]struct{}, 10) - }} - for i := 0; i < b.N; i++ { - m := sp.Get().(map[int]struct{}) - for j := 0; j < 1000; j++ { - m[i] = struct{}{} - } - for k := range m { - delete(m, k) - } - sp.Put(m) - } -} - -func BenchmarkExistArray(b *testing.B) { - for i := 0; i < b.N; i++ { - m := make(map[[20]byte]struct{}) - m[common.Address{1}] = struct{}{} - addr := common.Address{1} - if _, ok := m[addr]; ok { - continue - } - } -} - -func BenchmarkDonotExistArray(b *testing.B) { - for i := 0; i < b.N; i++ { - m := make(map[[20]byte]struct{}) - addr := common.Address{1} - if _, ok := m[addr]; !ok { - m[addr] = struct{}{} - delete(m, addr) - } - } -} - func resolveTxDAGInMVStates(s *MVStates, txCnt int) TxDAG { txDAG := NewPlainTxDAG(txCnt) for i := 0; i < txCnt; i++ { - s.resolveDepsCache(i, s.rwSets[i]) + s.resolveDepsCache(i, &s.rwSets[i]) txDAG.TxDeps[i] = s.txDepCache[i] } return txDAG @@ -261,7 +251,7 @@ func resolveDepsMapCacheByWritesInMVStates(s *MVStates) TxDAG { txCnt := s.nextFinaliseIndex txDAG := NewPlainTxDAG(txCnt) for i := 0; i < txCnt; i++ { - s.resolveDepsMapCacheByWrites(i, s.rwSets[i]) + s.resolveDepsMapCacheByWrites(i, &s.rwSets[i]) txDAG.TxDeps[i] = s.txDepCache[i] } return txDAG diff --git a/miner/worker.go b/miner/worker.go index 7a977a9934..871fddcc1f 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1038,13 +1038,13 @@ func (w *worker) generateDAGTx(statedb *state.StateDB, signer types.Signer, txIn // get txDAG data from the stateDB // txIndex is the index of this txDAG transaction + defer func() { + statedb.MVStates().Stop() + }() txDAG, err := statedb.ResolveTxDAG(txIndex, types.TxDep{Flags: &types.NonDependentRelFlag}) if txDAG == nil { return nil, err } - if metrics.EnabledExpensive { - go types.EvaluateTxDAGPerformance(txDAG) - } publicKey := sender.Public() publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) From c23147bf6e1e5abda26131730ae588c513a43df7 Mon Sep 17 00:00:00 2001 From: galaio Date: Thu, 12 Sep 2024 10:53:54 +0800 Subject: [PATCH 19/19] txdag: using a new mem pool; --- core/types/mvstates.go | 33 --------------------------------- 1 file changed, 33 deletions(-) diff --git a/core/types/mvstates.go b/core/types/mvstates.go index 27b204dc22..4b99261740 100644 --- a/core/types/mvstates.go +++ b/core/types/mvstates.go @@ -31,12 +31,6 @@ func init() { cache := make([]RWEventItem, 400) rwEventCachePool.Put(&cache) } - for i := 0; i < initSyncPoolSize; i++ { - rwSets := make([]RWSet, 4000) - rwSetsPool.Put(&rwSets) - txDeps := make([]TxDep, 4000) - txDepsPool.Put(&txDeps) - } } type ChanPool struct { @@ -313,10 +307,6 @@ func (w *StateWrites) Copy() *StateWrites { var ( rwEventsAllocMeter = metrics.GetOrRegisterMeter("mvstate/alloc/rwevents/cnt", nil) rwEventsAllocGauge = metrics.GetOrRegisterGauge("mvstate/alloc/rwevents/gauge", nil) - rwSetsAllocMeter = metrics.GetOrRegisterMeter("mvstate/alloc/rwsets/cnt", nil) - rwSetsAllocGauge = metrics.GetOrRegisterGauge("mvstate/alloc/rwsets/gauge", nil) - txDepsAllocMeter = metrics.GetOrRegisterMeter("mvstate/alloc/txdeps/cnt", nil) - txDepsAllocGauge = metrics.GetOrRegisterGauge("mvstate/alloc/txdeps/gauge", nil) ) var ( @@ -325,16 +315,6 @@ var ( buf := make([]RWEventItem, 0) return &buf }) - rwSetsPool = NewChanPool(initSyncPoolSize, func() any { - rwSetsAllocMeter.Mark(1) - buf := make([]RWSet, 0) - return &buf - }) - txDepsPool = NewChanPool(initSyncPoolSize, func() any { - txDepsAllocMeter.Mark(1) - buf := make([]TxDep, 0) - return &buf - }) ) type MVStates struct { @@ -367,11 +347,6 @@ func NewMVStates(txCount int, gasFeeReceivers []common.Address) *MVStates { rwEventCh: make(chan []RWEventItem, 100), gasFeeReceivers: gasFeeReceivers, } - - s.rwSets = *rwSetsPool.Get().(*[]RWSet) - s.rwSets = s.rwSets[:0] - s.txDepCache = *txDepsPool.Get().(*[]TxDep) - s.txDepCache = s.txDepCache[:0] return s } @@ -388,7 +363,6 @@ func (s *MVStates) EnableAsyncGen() *MVStates { func (s *MVStates) Stop() { s.stopAsyncRecorder() - s.ReuseMem() } func (s *MVStates) Copy() *MVStates { @@ -506,8 +480,6 @@ func (s *MVStates) RecordNewTx(index int) { } if index%2000 == 0 { rwEventsAllocGauge.Update(int64(len(rwEventCachePool.ch))) - rwSetsAllocGauge.Update(int64(len(rwSetsPool.ch))) - txDepsAllocGauge.Update(int64(len(txDepsPool.ch))) } if index%asyncSendInterval == 0 { s.BatchRecordHandle() @@ -973,11 +945,6 @@ func (s *MVStates) FeeReceivers() []common.Address { return s.gasFeeReceivers } -func (s *MVStates) ReuseMem() { - rwSetsPool.Put(&s.rwSets) - txDepsPool.Put(&s.txDepCache) -} - func checkAccDependency(writeSet map[common.Address]map[AccountState]struct{}, readSet map[common.Address]map[AccountState]struct{}) bool { // check tx dependency, only check key, skip version for addr, sub := range writeSet {