diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go index 4ed3c9161d..f4a971bd5b 100644 --- a/core/parallel_state_processor.go +++ b/core/parallel_state_processor.go @@ -230,7 +230,7 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat } statedb.AddBalance(task.blockContext.Coinbase, task.result.FeeTipped) - output1 := new(big.Int).SetBytes(task.result.senderInitBalance.Bytes()) + output1 := new(big.Int).SetBytes(task.result.SenderInitBalance.Bytes()) output2 := new(big.Int).SetBytes(coinbaseBalance.Bytes()) // Deprecating transfer log and will be removed in future fork. PLEASE DO NOT USE this transfer log going forward. Parameters won't get updated as expected going forward with EIP1559 @@ -242,7 +242,7 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat task.blockContext.Coinbase, task.result.FeeTipped, - task.result.senderInitBalance, + task.result.SenderInitBalance, coinbaseBalance, output1.Sub(output1, task.result.FeeTipped), output2.Add(output2, task.result.FeeTipped), diff --git a/core/state/statedb.go b/core/state/statedb.go index bfc6821d2a..ce2a6e72d3 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -353,6 +353,61 @@ func (sw *StateDB) ApplyMVWriteSet(writes []blockstm.WriteDescriptor) { } } +type DumpStruct struct { + TxIdx int + TxInc int + VerIdx int + VerInc int + Path []byte + Op string +} + +// get readMap Dump of format: "TxIdx, Inc, Path, Read" +func (s *StateDB) GetReadMapDump() []DumpStruct { + readList := s.MVReadList() + res := make([]DumpStruct, 0, len(readList)) + + for _, val := range readList { + temp := &DumpStruct{ + TxIdx: s.txIndex, + TxInc: s.incarnation, + VerIdx: val.V.TxnIndex, + VerInc: val.V.Incarnation, + Path: val.Path, + Op: "Read\n", + } + res = append(res, *temp) + } + + return res +} + +// get writeMap Dump of format: "TxIdx, Inc, Path, Write" +func (s *StateDB) GetWriteMapDump() []DumpStruct { + writeList := s.MVReadList() + res := make([]DumpStruct, 0, len(writeList)) + + for _, val := range writeList { + temp := &DumpStruct{ + TxIdx: s.txIndex, + TxInc: s.incarnation, + VerIdx: val.V.TxnIndex, + VerInc: val.V.Incarnation, + Path: val.Path, + Op: "Write\n", + } + res = append(res, *temp) + } + + return res +} + +// add empty MVHashMap to StateDB +func (s *StateDB) AddEmptyMVHashMap() { + mvh := blockstm.MakeMVHashMap() + s.mvHashmap = mvh +} + // StartPrefetcher initializes a new trie prefetcher to pull in nodes from the // state trie concurrently while the state is mutated so that when we reach the // commit phase, most of the needed data is already hot. diff --git a/core/state_transition.go b/core/state_transition.go index dee8ef271f..44b18d578c 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -92,7 +92,7 @@ type ExecutionResult struct { UsedGas uint64 // Total used gas but include the refunded gas Err error // Any error encountered during the execution(listed in core/vm/errors.go) ReturnData []byte // Returned data from evm(function result or data supplied with revert opcode) - senderInitBalance *big.Int + SenderInitBalance *big.Int FeeBurnt *big.Int BurntContractAddress common.Address FeeTipped *big.Int @@ -403,7 +403,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { UsedGas: st.gasUsed(), Err: vmerr, ReturnData: ret, - senderInitBalance: input1, + SenderInitBalance: input1, FeeBurnt: burnAmount, BurntContractAddress: burntContractAddress, FeeTipped: amount, diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 08c17601e4..b3361932ac 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -20,10 +20,13 @@ import ( "bufio" "bytes" "context" + "encoding/hex" "errors" "fmt" "io/ioutil" + "math/big" "os" + "path/filepath" "runtime" "sync" "time" @@ -61,6 +64,10 @@ const ( // For non-archive nodes, this limit _will_ be overblown, as disk-backed tries // will only be found every ~15K blocks or so. defaultTracechainMemLimit = common.StorageSize(500 * 1024 * 1024) + + defaultPath = string(".") + + defaultIOFlag = false ) // Backend interface provides the common API services (that are provided by @@ -170,6 +177,8 @@ type TraceConfig struct { Tracer *string Timeout *string Reexec *uint64 + Path *string + IOFlag *bool } // TraceCallConfig is the config for traceCall API. It holds one more @@ -567,18 +576,37 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac if block.NumberU64() == 0 { return nil, errors.New("genesis is not traceable") } + parent, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(block.NumberU64()-1), block.ParentHash()) if err != nil { return nil, err } + reexec := defaultTraceReexec if config != nil && config.Reexec != nil { reexec = *config.Reexec } + + path := defaultPath + if config != nil && config.Path != nil { + path = *config.Path + } + + ioflag := defaultIOFlag + if config != nil && config.IOFlag != nil { + ioflag = *config.IOFlag + } + statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false) if err != nil { return nil, err } + + // create and add empty mvHashMap in statedb as StateAtBlock does not have mvHashmap in it. + if ioflag { + statedb.AddEmptyMVHashMap() + } + // Execute all the transaction contained within the block concurrently var ( signer = types.MakeSigner(api.backend.ChainConfig(), block.Number()) @@ -615,10 +643,31 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac } }() } + + var IOdump string + + var RWstruct []state.DumpStruct + + var london bool + + if ioflag { + IOdump = "TransactionIndex, Incarnation, VersionTxIdx, VersionInc, Path, Operation\n" + RWstruct = []state.DumpStruct{} + } // Feed the transactions into the tracers and return var failed error blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) + + if ioflag { + london = api.backend.ChainConfig().IsLondon(block.Number()) + } + for i, tx := range txs { + if ioflag { + // copy of statedb + statedb = statedb.Copy() + } + // Send the trace task over for execution jobs <- &txTraceTask{statedb: statedb.Copy(), index: i} @@ -626,14 +675,73 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac msg, _ := tx.AsMessage(signer, block.BaseFee()) statedb.Prepare(tx.Hash(), i) vmenv := vm.NewEVM(blockCtx, core.NewEVMTxContext(msg), statedb, api.backend.ChainConfig(), vm.Config{}) - if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); err != nil { - failed = err - break + + if !ioflag { + if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); err != nil { + failed = err + break + } + // Finalize the state so any modifications are written to the trie + // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect + statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) + } else { + coinbaseBalance := statedb.GetBalance(blockCtx.Coinbase) + + result, err := core.ApplyMessageNoFeeBurnOrTip(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())) + + if err != nil { + failed = err + break + } + + if london { + statedb.AddBalance(result.BurntContractAddress, result.FeeBurnt) + } + + statedb.AddBalance(blockCtx.Coinbase, result.FeeTipped) + output1 := new(big.Int).SetBytes(result.SenderInitBalance.Bytes()) + output2 := new(big.Int).SetBytes(coinbaseBalance.Bytes()) + + // Deprecating transfer log and will be removed in future fork. PLEASE DO NOT USE this transfer log going forward. Parameters won't get updated as expected going forward with EIP1559 + // add transfer log + core.AddFeeTransferLog( + statedb, + + msg.From(), + blockCtx.Coinbase, + + result.FeeTipped, + result.SenderInitBalance, + coinbaseBalance, + output1.Sub(output1, result.FeeTipped), + output2.Add(output2, result.FeeTipped), + ) + + // Finalize the state so any modifications are written to the trie + // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect + statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) + statedb.FlushMVWriteSet() + + structRead := statedb.GetReadMapDump() + structWrite := statedb.GetWriteMapDump() + + RWstruct = append(RWstruct, structRead...) + RWstruct = append(RWstruct, structWrite...) } - // Finalize the state so any modifications are written to the trie - // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect - statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) } + + if ioflag { + for _, val := range RWstruct { + IOdump += fmt.Sprintf("%v , %v, %v , %v, ", val.TxIdx, val.TxInc, val.VerIdx, val.VerInc) + hex.EncodeToString(val.Path) + ", " + val.Op + } + + // make sure that the file exists and write IOdump + err = ioutil.WriteFile(filepath.Join(path, "data.csv"), []byte(fmt.Sprint(IOdump)), 0600) + if err != nil { + return nil, err + } + } + close(jobs) pend.Wait() diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index d2ed9c2179..4b8257756a 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -415,6 +415,83 @@ func TestTraceBlock(t *testing.T) { } } +func TestIOdump(t *testing.T) { + t.Parallel() + + // Initialize test accounts + accounts := newAccounts(5) + genesis := &core.Genesis{Alloc: core.GenesisAlloc{ + accounts[0].addr: {Balance: big.NewInt(params.Ether)}, + accounts[1].addr: {Balance: big.NewInt(params.Ether)}, + accounts[2].addr: {Balance: big.NewInt(params.Ether)}, + accounts[3].addr: {Balance: big.NewInt(params.Ether)}, + accounts[4].addr: {Balance: big.NewInt(params.Ether)}, + }} + genBlocks := 1 + signer := types.HomesteadSigner{} + api := NewAPI(newTestBackend(t, genBlocks, genesis, func(i int, b *core.BlockGen) { + // Transfer from account[0] to account[1], account[1] to account[2], account[2] to account[3], account[3] to account[4], account[4] to account[0] + // value: 1000 wei + // fee: 0 wei + + for j := 0; j < 5; j++ { + tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[(j+1)%5].addr, big.NewInt(1000), params.TxGas, b.BaseFee(), nil), signer, accounts[j].key) + b.AddTx(tx) + } + })) + + ioflag := new(bool) + + *ioflag = true + + var testSuite = []struct { + blockNumber rpc.BlockNumber + config *TraceConfig + want string + expectErr error + }{ + // Trace head block + { + config: &TraceConfig{ + IOFlag: ioflag, + }, + blockNumber: rpc.BlockNumber(genBlocks), + want: `[{"result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}},{"result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}},{"result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}},{"result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}},{"result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}}]`, + }, + } + + for i, tc := range testSuite { + result, err := api.TraceBlockByNumber(context.Background(), tc.blockNumber, tc.config) + if tc.expectErr != nil { + if err == nil { + t.Errorf("test %d, want error %v", i, tc.expectErr) + continue + } + + if !reflect.DeepEqual(err, tc.expectErr) { + t.Errorf("test %d: error mismatch, want %v, get %v", i, tc.expectErr, err) + } + + continue + } + + if err != nil { + t.Errorf("test %d, want no error, have %v", i, err) + continue + } + + have, err := json.Marshal(result) + if err != nil { + t.Errorf("Error in Marshal: %v", err) + } + + want := tc.want + if string(have) != want { + t.Errorf("test %d, result mismatch, have\n%v\n, want\n%v\n", i, string(have), want) + } + } +} + func TestTracingWithOverrides(t *testing.T) { t.Parallel() // Initialize test accounts