diff --git a/.github/workflows/docker-build.yaml b/.github/workflows/docker-build.yaml index 0c4d1f2ad..60d50d2f6 100644 --- a/.github/workflows/docker-build.yaml +++ b/.github/workflows/docker-build.yaml @@ -12,14 +12,13 @@ on: env: REGION: us-east-1 ECR_REGISTRY_ID: 553885929720 - AWS_ASSUMED_ROLE_ARN: arn:aws:iam::553885929720:role/tf_nodereal_prod_ecr_cicd_deployment_assume_role # Notice: must modify here to fit your service config path GIT_SYNC_PATH: qa/gitops/qa-us/demo-app/values.yaml TAG_FIELD: .image.tag jobs: CI: - runs-on: [self-hosted,qa-infra-k8s] + runs-on: [self-hosted,prod-cicd-runners] steps: - uses: actions/checkout@v3 # - uses: actions/setup-go@v3 @@ -30,23 +29,9 @@ jobs: # with: # # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version # version: v1.45.2 - - name: aws assume role - id: aws-assume-role - run: | - UUID=$(cat /proc/sys/kernel/random/uuid) - OUT=$(aws sts assume-role --role-arn $AWS_ASSUMED_ROLE_ARN --role-session-name $UUID) - echo ::set-output name=aws_access_key_id::"$(echo $OUT | jq -r '.Credentials''.AccessKeyId')" - echo ::set-output name=aws_secret_key::"$(echo $OUT | jq -r '.Credentials''.SecretAccessKey')" - echo ::set-output name=aws_sessions_token::"$(echo $OUT | jq -r '.Credentials''.SessionToken')" - name: Build, tag, and push image to Amazon ECR - env: - AWS_ACCESS_KEY_ID: ${{ steps.aws-assume-role.outputs.aws_access_key_id }} - AWS_SECRET_ACCESS_KEY: ${{ steps.aws-assume-role.outputs.aws_secret_key }} - AWS_SESSION_TOKEN: ${{ steps.aws-assume-role.outputs.aws_sessions_token }} run: | aws ecr get-login-password --region ${REGION} | docker login --username AWS --password-stdin ${ECR_REGISTRY_ID}.dkr.ecr.${REGION}.amazonaws.com - aws ecr --region $REGION describe-repositories --registry-id $ECR_REGISTRY_ID --repository-names ${GITHUB_REPOSITORY#*/} || aws ecr --region $REGION create-repository --registry-id $ECR_REGISTRY_ID --repository-name ${GITHUB_REPOSITORY#*/} - aws ecr --region $REGION set-repository-policy --registry-id $ECR_REGISTRY_ID --repository-name ${GITHUB_REPOSITORY#*/} --policy-text file:///home/runner/repo-access-permissions.json docker build -t ${ECR_REGISTRY_ID}.dkr.ecr.${REGION}.amazonaws.com/${GITHUB_REPOSITORY#*/}:${GITHUB_SHA} . docker push ${ECR_REGISTRY_ID}.dkr.ecr.${REGION}.amazonaws.com/${GITHUB_REPOSITORY#*/}:${GITHUB_SHA} # CD: diff --git a/core/state_transition.go b/core/state_transition.go index e71b98075..636c90636 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -39,8 +39,10 @@ The state transitioning model does all the necessary work to work out a valid ne 3) Create a new state object if the recipient is \0*32 4) Value transfer == If contract creation == - 4a) Attempt to run transaction data - 4b) If valid, use result as code for the new state object + + 4a) Attempt to run transaction data + 4b) If valid, use result as code for the new state object + == end == 5) Run Script section 6) Derive new state root @@ -213,13 +215,13 @@ func (st *StateTransition) preCheck() error { // TransitionDb will transition the state by applying the current message and // returning the evm execution result with following fields. // -// - used gas: -// total gas used (including gas being refunded) -// - returndata: -// the returned data from evm -// - concrete execution error: -// various **EVM** error which aborts the execution, -// e.g. ErrOutOfGas, ErrExecutionReverted +// - used gas: +// total gas used (including gas being refunded) +// - returndata: +// the returned data from evm +// - concrete execution error: +// various **EVM** error which aborts the execution, +// e.g. ErrOutOfGas, ErrExecutionReverted // // However if any consensus issue encountered, return the error directly with // nil evm execution result. @@ -238,6 +240,14 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { if err := st.preCheck(); err != nil { return nil, err } + + if st.evm.VmConfig.Debug { + st.evm.VmConfig.Tracer.CaptureTxStart(st.initialGas) + defer func() { + st.evm.VmConfig.Tracer.CaptureTxEnd(st.gas) + }() + } + msg := st.msg sender := vm.AccountRef(msg.From()) homestead := st.evm.ChainConfig().IsHomestead(st.evm.Context.BlockNumber) diff --git a/core/vm/access_list_tracer.go b/core/vm/access_list_tracer.go index d7993b4f7..3f79d6d5a 100644 --- a/core/vm/access_list_tracer.go +++ b/core/vm/access_list_tracer.go @@ -180,3 +180,12 @@ func (a *AccessListTracer) AccessList() types.AccessList { func (a *AccessListTracer) Equal(other *AccessListTracer) bool { return a.list.equal(other.list) } + +// CaptureTxStart implements the Tracer interface and is invoked at the beginning of +// transaction processing. +func (t *AccessListTracer) CaptureTxStart(gasLimit uint64) { +} + +// CaptureTxStart implements the Tracer interface and is invoked at the end of +// transaction processing. +func (t *AccessListTracer) CaptureTxEnd(restGas uint64) {} diff --git a/core/vm/evm.go b/core/vm/evm.go index 1fcb6e9f5..06cf98e08 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -134,7 +134,7 @@ type EVM struct { chainRules params.Rules // virtual machine configuration options used to initialise the // evm. - vmConfig Config + VmConfig Config // global (to this context) ethereum virtual machine // used throughout the execution of the tx. interpreters []Interpreter @@ -155,7 +155,7 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig evm.Context = blockCtx evm.TxContext = txCtx evm.StateDB = statedb - evm.vmConfig = vmConfig + evm.VmConfig = vmConfig evm.chainConfig = chainConfig evm.chainRules = chainConfig.Rules(blockCtx.BlockNumber) evm.interpreters = make([]Interpreter, 0, 1) @@ -215,7 +215,7 @@ func (evm *EVM) Interpreter() Interpreter { // the necessary steps to create accounts and reverses the state in case of an // execution error or failed value transfer. func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) { - if evm.vmConfig.NoRecursion && evm.depth > 0 { + if evm.VmConfig.NoRecursion && evm.depth > 0 { return nil, gas, nil } // Fail if we're trying to execute above the call depth limit @@ -232,13 +232,13 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas if !evm.StateDB.Exist(addr) { if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 { // Calling a non existing account, don't do anything, but ping the tracer - if evm.vmConfig.Debug { + if evm.VmConfig.Debug { if evm.depth == 0 { - evm.vmConfig.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value) - evm.vmConfig.Tracer.CaptureEnd(ret, 0, 0, nil) + evm.VmConfig.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value) + evm.VmConfig.Tracer.CaptureEnd(ret, 0, 0, nil) } else { - evm.vmConfig.Tracer.CaptureEnter(CALL, caller.Address(), addr, input, gas, value) - evm.vmConfig.Tracer.CaptureExit(ret, 0, nil) + evm.VmConfig.Tracer.CaptureEnter(CALL, caller.Address(), addr, input, gas, value) + evm.VmConfig.Tracer.CaptureExit(ret, 0, nil) } } return nil, gas, nil @@ -248,17 +248,17 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value) // Capture the tracer start/end events in debug mode - if evm.vmConfig.Debug { + if evm.VmConfig.Debug { if evm.depth == 0 { - evm.vmConfig.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value) + evm.VmConfig.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value) defer func(startGas uint64, startTime time.Time) { // Lazy evaluation of the parameters - evm.vmConfig.Tracer.CaptureEnd(ret, startGas-gas, time.Since(startTime), err) + evm.VmConfig.Tracer.CaptureEnd(ret, startGas-gas, time.Since(startTime), err) }(gas, time.Now()) } else { // Handle tracer events for entering and exiting a call frame - evm.vmConfig.Tracer.CaptureEnter(CALL, caller.Address(), addr, input, gas, value) + evm.VmConfig.Tracer.CaptureEnter(CALL, caller.Address(), addr, input, gas, value) defer func(startGas uint64) { - evm.vmConfig.Tracer.CaptureExit(ret, startGas-gas, err) + evm.VmConfig.Tracer.CaptureExit(ret, startGas-gas, err) }(gas) } } @@ -304,7 +304,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas // CallCode differs from Call in the sense that it executes the given address' // code with the caller as context. func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) { - if evm.vmConfig.NoRecursion && evm.depth > 0 { + if evm.VmConfig.NoRecursion && evm.depth > 0 { return nil, gas, nil } // Fail if we're trying to execute above the call depth limit @@ -321,10 +321,10 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, var snapshot = evm.StateDB.Snapshot() // Invoke tracer hooks that signal entering/exiting a call frame - if evm.vmConfig.Debug { - evm.vmConfig.Tracer.CaptureEnter(CALLCODE, caller.Address(), addr, input, gas, value) + if evm.VmConfig.Debug { + evm.VmConfig.Tracer.CaptureEnter(CALLCODE, caller.Address(), addr, input, gas, value) defer func(startGas uint64) { - evm.vmConfig.Tracer.CaptureExit(ret, startGas-gas, err) + evm.VmConfig.Tracer.CaptureExit(ret, startGas-gas, err) }(gas) } @@ -355,7 +355,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, // DelegateCall differs from CallCode in the sense that it executes the given address' // code with the caller as context and the caller is set to the caller of the caller. func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) { - if evm.vmConfig.NoRecursion && evm.depth > 0 { + if evm.VmConfig.NoRecursion && evm.depth > 0 { return nil, gas, nil } // Fail if we're trying to execute above the call depth limit @@ -365,10 +365,10 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by var snapshot = evm.StateDB.Snapshot() // Invoke tracer hooks that signal entering/exiting a call frame - if evm.vmConfig.Debug { - evm.vmConfig.Tracer.CaptureEnter(DELEGATECALL, caller.Address(), addr, input, gas, nil) + if evm.VmConfig.Debug { + evm.VmConfig.Tracer.CaptureEnter(DELEGATECALL, caller.Address(), addr, input, gas, nil) defer func(startGas uint64) { - evm.vmConfig.Tracer.CaptureExit(ret, startGas-gas, err) + evm.VmConfig.Tracer.CaptureExit(ret, startGas-gas, err) }(gas) } @@ -397,7 +397,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by // Opcodes that attempt to perform such modifications will result in exceptions // instead of performing the modifications. func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) { - if evm.vmConfig.NoRecursion && evm.depth > 0 { + if evm.VmConfig.NoRecursion && evm.depth > 0 { return nil, gas, nil } // Fail if we're trying to execute above the call depth limit @@ -418,10 +418,10 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte evm.StateDB.AddBalance(addr, big0) // Invoke tracer hooks that signal entering/exiting a call frame - if evm.vmConfig.Debug { - evm.vmConfig.Tracer.CaptureEnter(STATICCALL, caller.Address(), addr, input, gas, nil) + if evm.VmConfig.Debug { + evm.VmConfig.Tracer.CaptureEnter(STATICCALL, caller.Address(), addr, input, gas, nil) defer func(startGas uint64) { - evm.vmConfig.Tracer.CaptureExit(ret, startGas-gas, err) + evm.VmConfig.Tracer.CaptureExit(ret, startGas-gas, err) }(gas) } @@ -498,15 +498,15 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, contract := NewContract(caller, AccountRef(address), value, gas) contract.SetCodeOptionalHash(&address, codeAndHash) - if evm.vmConfig.NoRecursion && evm.depth > 0 { + if evm.VmConfig.NoRecursion && evm.depth > 0 { return nil, address, gas, nil } - if evm.vmConfig.Debug { + if evm.VmConfig.Debug { if evm.depth == 0 { - evm.vmConfig.Tracer.CaptureStart(evm, caller.Address(), address, true, codeAndHash.code, gas, value) + evm.VmConfig.Tracer.CaptureStart(evm, caller.Address(), address, true, codeAndHash.code, gas, value) } else { - evm.vmConfig.Tracer.CaptureEnter(typ, caller.Address(), address, codeAndHash.code, gas, value) + evm.VmConfig.Tracer.CaptureEnter(typ, caller.Address(), address, codeAndHash.code, gas, value) } } @@ -546,11 +546,11 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, } } - if evm.vmConfig.Debug { + if evm.VmConfig.Debug { if evm.depth == 0 { - evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err) + evm.VmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err) } else { - evm.vmConfig.Tracer.CaptureExit(ret, gas-contract.Gas, err) + evm.VmConfig.Tracer.CaptureExit(ret, gas-contract.Gas, err) } } return ret, address, contract.Gas, err diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 0ecf28d59..68571ebd4 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -244,7 +244,7 @@ func opSha3(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt interpreter.hasher.Read(interpreter.hasherBuf[:]) evm := interpreter.evm - if evm.vmConfig.EnablePreimageRecording { + if evm.VmConfig.EnablePreimageRecording { evm.StateDB.AddPreimage(interpreter.hasherBuf, data) } @@ -390,16 +390,21 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) // opExtCodeHash returns the code hash of a specified account. // There are several cases when the function is called, while we can relay everything // to `state.GetCodeHash` function to ensure the correctness. -// (1) Caller tries to get the code hash of a normal contract account, state +// +// (1) Caller tries to get the code hash of a normal contract account, state +// // should return the relative code hash and set it as the result. // -// (2) Caller tries to get the code hash of a non-existent account, state should +// (2) Caller tries to get the code hash of a non-existent account, state should +// // return common.Hash{} and zero will be set as the result. // -// (3) Caller tries to get the code hash for an account without contract code, +// (3) Caller tries to get the code hash for an account without contract code, +// // state should return emptyCodeHash(0xc5d246...) as the result. // -// (4) Caller tries to get the code hash of a precompiled account, the result +// (4) Caller tries to get the code hash of a precompiled account, the result +// // should be zero or emptyCodeHash. // // It is worth noting that in order to avoid unnecessary create and clean, @@ -408,10 +413,12 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) // If the precompile account is not transferred any amount on a private or // customized chain, the return value will be zero. // -// (5) Caller tries to get the code hash for an account which is marked as suicided +// (5) Caller tries to get the code hash for an account which is marked as suicided +// // in the current transaction, the code hash of this account should be returned. // -// (6) Caller tries to get the code hash for an account which is marked as deleted, +// (6) Caller tries to get the code hash for an account which is marked as deleted, +// // this account should be regarded as a non-existent account and zero should be returned. func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { slot := scope.Stack.peek() diff --git a/core/vm/logger.go b/core/vm/logger.go index f8f08e726..131f83213 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -105,6 +105,10 @@ func (s *StructLog) ErrorString() string { // Note that reference types are actual VM data structures; make copies // if you need to retain them beyond the current call. type EVMLogger interface { + // Transaction level + CaptureTxStart(gasLimit uint64) + CaptureTxEnd(restGas uint64) + CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) @@ -147,6 +151,10 @@ func (l *StructLogger) Reset() { l.err = nil } +func (*StructLogger) CaptureTxStart(gasLimit uint64) {} + +func (*StructLogger) CaptureTxEnd(restGas uint64) {} + // CaptureStart implements the EVMLogger interface to initialize the tracing operation. func (l *StructLogger) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { l.env = env @@ -307,6 +315,15 @@ func NewMarkdownLogger(cfg *LogConfig, writer io.Writer) *mdLogger { return l } +// CaptureTxStart implements the Tracer interface and is invoked at the beginning of +// transaction processing. +func (t *mdLogger) CaptureTxStart(gasLimit uint64) { +} + +// CaptureTxStart implements the Tracer interface and is invoked at the end of +// transaction processing. +func (t *mdLogger) CaptureTxEnd(restGas uint64) {} + func (t *mdLogger) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { t.env = env if !create { diff --git a/core/vm/logger_json.go b/core/vm/logger_json.go index c249dba7e..8c4fbf3dc 100644 --- a/core/vm/logger_json.go +++ b/core/vm/logger_json.go @@ -42,6 +42,15 @@ func NewJSONLogger(cfg *LogConfig, writer io.Writer) *JSONLogger { return l } +// CaptureTxStart implements the Tracer interface and is invoked at the beginning of +// transaction processing. +func (t *JSONLogger) CaptureTxStart(gasLimit uint64) { +} + +// CaptureTxStart implements the Tracer interface and is invoked at the end of +// transaction processing. +func (t *JSONLogger) CaptureTxEnd(restGas uint64) {} + func (l *JSONLogger) CaptureStart(env *EVM, from, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { l.env = env } diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 8df152fd0..0e1f1b3f2 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -20,6 +20,7 @@ import ( "bufio" "bytes" "context" + "encoding/json" "errors" "fmt" "io/ioutil" @@ -172,6 +173,8 @@ type TraceConfig struct { Tracer *string Timeout *string Reexec *uint64 + + TracerConfig json.RawMessage } // TraceCallConfig is the config for traceCall API. It holds one more @@ -935,7 +938,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex return nil, err } } - if t, err := New(*config.Tracer, txctx); err != nil { + if t, err := New(*config.Tracer, txctx, config.TracerConfig); err != nil { return nil, err } else { deadlineCtx, cancel := context.WithTimeout(ctx, timeout) diff --git a/eth/tracers/js/tracer.go b/eth/tracers/js/tracer.go index b8e035e6f..399aeb8dd 100644 --- a/eth/tracers/js/tracer.go +++ b/eth/tracers/js/tracer.go @@ -424,7 +424,7 @@ type jsTracer struct { // New instantiates a new tracer instance. code specifies a Javascript snippet, // which must evaluate to an expression returning an object with 'step', 'fault' // and 'result' functions. -func newJsTracer(code string, ctx *tracers2.Context) (tracers2.Tracer, error) { +func newJsTracer(code string, ctx *tracers2.Context, cfg json.RawMessage) (tracers2.Tracer, error) { if c, ok := assetTracers[code]; ok { code = c } @@ -679,6 +679,15 @@ func wrapError(context string, err error) error { return fmt.Errorf("%v in server-side tracer function '%v'", err, context) } +// CaptureTxStart implements the Tracer interface and is invoked at the beginning of +// transaction processing. +func (jst *jsTracer) CaptureTxStart(gasLimit uint64) { +} + +// CaptureTxStart implements the Tracer interface and is invoked at the end of +// transaction processing. +func (t *jsTracer) CaptureTxEnd(restGas uint64) {} + // CaptureStart implements the Tracer interface to initialize the tracing operation. func (jst *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { jst.env = env diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go index 16ea75aa4..0804db9a0 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -56,13 +56,22 @@ type callTracer struct { // newCallTracer returns a native go tracer which tracks // call frames of a tx, and implements vm.EVMLogger. -func newCallTracer() tracers.Tracer { +func newCallTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { // First callframe contains tx context info // and is populated on start and end. t := &callTracer{callstack: make([]callFrame, 1)} - return t + return t, nil } +// CaptureTxStart implements the Tracer interface and is invoked at the beginning of +// transaction processing. +func (t *callTracer) CaptureTxStart(gasLimit uint64) { +} + +// CaptureTxStart implements the Tracer interface and is invoked at the end of +// transaction processing. +func (t *callTracer) CaptureTxEnd(restGas uint64) {} + // CaptureStart implements the EVMLogger interface to initialize the tracing operation. func (t *callTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { t.env = env diff --git a/eth/tracers/native/noop.go b/eth/tracers/native/noop.go index ee110ef7d..65ccd7017 100644 --- a/eth/tracers/native/noop.go +++ b/eth/tracers/native/noop.go @@ -35,8 +35,14 @@ func init() { type noopTracer struct{} // newNoopTracer returns a new noop tracer. -func newNoopTracer() tracers.Tracer { - return &noopTracer{} +func newNoopTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { + return &noopTracer{}, nil +} + +func (l *noopTracer) CaptureTxStart(gasLimit uint64) { +} + +func (l *noopTracer) CaptureTxEnd(restGas uint64) { } // CaptureStart implements the EVMLogger interface to initialize the tracing operation. diff --git a/eth/tracers/native/prestate.go b/eth/tracers/native/prestate.go new file mode 100644 index 000000000..fe4866e26 --- /dev/null +++ b/eth/tracers/native/prestate.go @@ -0,0 +1,276 @@ +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package native + +import ( + "encoding/json" + "github.com/ethereum/go-ethereum/common/hexutil" + "math/big" + "sync/atomic" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/tracers" +) + +func init() { + register("stateDiff", newStateDiffTracer) +} + +type state = map[common.Address]account +type account struct { + Balance string `json:"balance,omitempty"` + Nonce uint64 `json:"nonce,omitempty"` + storage map[common.Hash]common.Hash +} + +func (a account) exists() bool { + bi := new(big.Int) + bi.SetString(a.Balance[2:], 16) + return a.Nonce > 0 || (bi.Sign() != 0) +} + +type stateDiffTracer struct { + env *vm.EVM + pre state + post state + create bool + to common.Address + gasLimit uint64 // Amount of gas bought for the whole tx + interrupt uint32 // Atomic flag to signal execution interruption + reason error // Textual reason for the interruption + + config stateDiffTracerConfig + created map[common.Address]bool + deleted map[common.Address]bool +} + +type stateDiffTracerConfig struct { + DiffMode bool `json:"diffMode"` // If true, this tracer will return state modifications +} + +func newStateDiffTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { + var config stateDiffTracerConfig + if cfg != nil { + if err := json.Unmarshal(cfg, &config); err != nil { + return nil, err + } + } + // and is populated on start and end. + return &stateDiffTracer{ + pre: state{}, + post: state{}, + config: config, + created: make(map[common.Address]bool), + deleted: make(map[common.Address]bool), + }, nil +} + +// CaptureStart implements the EVMLogger interface to initialize the tracing operation. +func (t *stateDiffTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { + t.env = env + t.create = create + t.to = to + + t.lookupAccount(from) + t.lookupAccount(to) + t.lookupAccount(env.Context.Coinbase) + //t.lookupAccount(consensus.SystemAddress) + + // The recipient balance includes the value transferred. + toAccount := t.pre[to] + toBal := hexutil.MustDecodeBig(toAccount.Balance) + toBal = new(big.Int).Sub(toBal, value) + toAccount.Balance = hexutil.EncodeBig(toBal) + t.pre[to] = toAccount + + // The sender balance is after reducing: value and gasLimit. + // We need to re-add them to get the pre-tx balance. + fromAccount := t.pre[from] + fromBal := hexutil.MustDecodeBig(fromAccount.Balance) + gasPrice := env.TxContext.GasPrice + consumedGas := new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(t.gasLimit)) + fromBal.Add(fromBal, new(big.Int).Add(value, consumedGas)) + fromAccount.Balance = hexutil.EncodeBig(fromBal) + fromAccount.Nonce-- + t.pre[from] = fromAccount + + if create && t.config.DiffMode { + t.created[to] = true + } +} + +// CaptureEnd is called after the call finishes to finalize the tracing. +func (t *stateDiffTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) { + if t.config.DiffMode { + return + } + + if t.create { + // Keep existing account prior to contract creation at that address + if s := t.pre[t.to]; !s.exists() { + // Exclude newly created contract. + delete(t.pre, t.to) + } + } +} + +// CaptureState implements the EVMLogger interface to trace a single step of VM execution. +func (t *stateDiffTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { + stack := scope.Stack + stackData := stack.Data() + stackLen := len(stackData) + caller := scope.Contract.Address() + switch { + case stackLen >= 1 && (op == vm.SLOAD || op == vm.SSTORE): + slot := common.Hash(stackData[stackLen-1].Bytes32()) + t.lookupStorage(caller, slot) + case stackLen >= 1 && (op == vm.EXTCODECOPY || op == vm.EXTCODEHASH || op == vm.EXTCODESIZE || op == vm.BALANCE || op == vm.SELFDESTRUCT): + addr := common.Address(stackData[stackLen-1].Bytes20()) + t.lookupAccount(addr) + if op == vm.SELFDESTRUCT { + t.deleted[caller] = true + } + case stackLen >= 5 && (op == vm.DELEGATECALL || op == vm.CALL || op == vm.STATICCALL || op == vm.CALLCODE): + addr := common.Address(stackData[stackLen-2].Bytes20()) + t.lookupAccount(addr) + case op == vm.CREATE: + nonce := t.env.StateDB.GetNonce(caller) + addr := crypto.CreateAddress(caller, nonce) + t.lookupAccount(addr) + t.created[addr] = true + case stackLen >= 4 && op == vm.CREATE2: + offset := stackData[stackLen-2] + size := stackData[stackLen-3] + init := scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) + inithash := crypto.Keccak256(init) + salt := stackData[stackLen-4] + addr := crypto.CreateAddress2(caller, salt.Bytes32(), inithash) + t.lookupAccount(addr) + t.created[addr] = true + } +} + +// CaptureFault implements the EVMLogger interface to trace an execution fault. +func (t *stateDiffTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) { +} + +// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). +func (t *stateDiffTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +} + +// CaptureExit is called when EVM exits a scope, even if the scope didn't +// execute any code. +func (t *stateDiffTracer) CaptureExit(output []byte, gasUsed uint64, err error) { +} + +func (t *stateDiffTracer) CaptureTxStart(gasLimit uint64) { + t.gasLimit = gasLimit +} + +func (t *stateDiffTracer) CaptureTxEnd(restGas uint64) { + if !t.config.DiffMode { + return + } + + //for addr, state := range t.pre { + for addr, _ := range t.pre { + // The deleted account's state is pruned from `post` but kept in `pre` + if _, ok := t.deleted[addr]; ok { + continue + } + modified := false + postAccount := account{storage: make(map[common.Hash]common.Hash)} + newBalance := bigToHex(t.env.StateDB.GetBalance(addr)) + newNonce := t.env.StateDB.GetNonce(addr) + //newCode := t.env.StateDB.GetCode(addr) + + if newBalance != t.pre[addr].Balance { + modified = true + postAccount.Balance = newBalance + } + if newNonce != t.pre[addr].Nonce { + modified = true + postAccount.Nonce = newNonce + } + + if modified { + t.post[addr] = postAccount + } else { + // if state is not modified, then no need to include into the pre state + delete(t.pre, addr) + } + } + // the new created contracts' prestate were empty, so delete them + for a := range t.created { + // the created contract maybe exists in statedb before the creating tx + if s := t.pre[a]; !s.exists() { + delete(t.pre, a) + } + } +} + +// GetResult returns the json-encoded nested list of call traces, and any +// error arising from the encoding or forceful termination (via `Stop`). +func (t *stateDiffTracer) GetResult() (json.RawMessage, error) { + var res []byte + var err error + if t.config.DiffMode { + res, err = json.Marshal(struct { + Post state `json:"post"` + Pre state `json:"pre"` + }{t.post, t.pre}) + } else { + res, err = json.Marshal(t.pre) + } + if err != nil { + return nil, err + } + return json.RawMessage(res), t.reason +} + +// Stop terminates execution of the tracer at the first opportune moment. +func (t *stateDiffTracer) Stop(err error) { + t.reason = err + atomic.StoreUint32(&t.interrupt, 1) +} + +// lookupAccount fetches details of an account and adds it to the prestate +// if it doesn't exist there. +func (t *stateDiffTracer) lookupAccount(addr common.Address) { + if _, ok := t.pre[addr]; ok { + return + } + + t.pre[addr] = account{ + Balance: bigToHex(t.env.StateDB.GetBalance(addr)), + Nonce: t.env.StateDB.GetNonce(addr), + storage: make(map[common.Hash]common.Hash), + } +} + +// lookupStorage fetches the requested storage slot and adds +// it to the prestate of the given contract. It assumes `lookupAccount` +// has been performed on the contract before. +func (t *stateDiffTracer) lookupStorage(addr common.Address, key common.Hash) { + if _, ok := t.pre[addr].storage[key]; ok { + return + } + t.pre[addr].storage[key] = t.env.StateDB.GetState(addr, key) +} diff --git a/eth/tracers/native/tracer.go b/eth/tracers/native/tracer.go index 3158654f3..187d89fa1 100644 --- a/eth/tracers/native/tracer.go +++ b/eth/tracers/native/tracer.go @@ -27,14 +27,17 @@ Aside from implementing the tracer, it also needs to register itself, using the Example: ```golang -func init() { - register("noopTracerNative", newNoopTracer) -} + + func init() { + register("noopTracerNative", newNoopTracer) + } + ``` */ package native import ( + "encoding/json" "errors" "github.com/ethereum/go-ethereum/eth/tracers" @@ -45,6 +48,9 @@ func init() { tracers.RegisterLookup(false, lookup) } +// ctorFn is the constructor signature of a native tracer. +type ctorFn = func(*tracers.Context, json.RawMessage) (tracers.Tracer, error) + /* ctors is a map of package-local tracer constructors. @@ -57,23 +63,23 @@ The go spec (https://golang.org/ref/spec#Package_initialization) says Hence, we cannot make the map in init, but must make it upon first use. */ -var ctors map[string]func() tracers.Tracer +var ctors map[string]ctorFn // register is used by native tracers to register their presence. -func register(name string, ctor func() tracers.Tracer) { +func register(name string, ctor ctorFn) { if ctors == nil { - ctors = make(map[string]func() tracers.Tracer) + ctors = make(map[string]ctorFn) } ctors[name] = ctor } // lookup returns a tracer, if one can be matched to the given name. -func lookup(name string, ctx *tracers.Context) (tracers.Tracer, error) { +func lookup(name string, ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { if ctors == nil { - ctors = make(map[string]func() tracers.Tracer) + ctors = make(map[string]ctorFn) } if ctor, ok := ctors[name]; ok { - return ctor(), nil + return ctor(ctx, cfg) } return nil, errors.New("no tracer found") } diff --git a/eth/tracers/tracers.go b/eth/tracers/tracers.go index e8fdc08f1..710d3acd9 100644 --- a/eth/tracers/tracers.go +++ b/eth/tracers/tracers.go @@ -43,7 +43,7 @@ type Tracer interface { Stop(err error) } -type lookupFunc func(string, *Context) (Tracer, error) +type lookupFunc func(string, *Context, json.RawMessage) (Tracer, error) var ( lookups []lookupFunc @@ -63,9 +63,9 @@ func RegisterLookup(wildcard bool, lookup lookupFunc) { // New returns a new instance of a tracer, by iterating through the // registered lookups. -func New(code string, ctx *Context) (Tracer, error) { +func New(code string, ctx *Context, cfg json.RawMessage) (Tracer, error) { for _, lookup := range lookups { - if tracer, err := lookup(code, ctx); err == nil { + if tracer, err := lookup(code, ctx, cfg); err == nil { return tracer, nil } } diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index 578b10f09..421c7dafe 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -94,6 +94,16 @@ func (ec *Client) BlockNumber(ctx context.Context) (uint64, error) { return uint64(result), err } +// BlockReceipts returns the receipts of a given block number or hash. +func (ec *Client) BlockReceipts(ctx context.Context, hash common.Hash) ([]*types.Receipt, error) { + var r []*types.Receipt + err := ec.c.CallContext(ctx, &r, "eth_getBlockReceipts", hash.String()) + if err == nil && r == nil { + return nil, ethereum.NotFound + } + return r, err +} + type rpcBlock struct { Hash common.Hash `json:"hash"` Transactions []rpcTransaction `json:"transactions"` diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 1a02ec5ef..1828aa1f4 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -656,10 +656,10 @@ func (s *PublicBlockChainAPI) GetHeaderByHash(ctx context.Context, hash common.H } // GetBlockByNumber returns the requested canonical block. -// * When blockNr is -1 the chain head is returned. -// * When blockNr is -2 the pending chain head is returned. -// * When fullTx is true all transactions in the block are returned, otherwise -// only the transaction hash is returned. +// - When blockNr is -1 the chain head is returned. +// - When blockNr is -2 the pending chain head is returned. +// - When fullTx is true all transactions in the block are returned, otherwise +// only the transaction hash is returned. func (s *PublicBlockChainAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) { block, err := s.b.BlockByNumber(ctx, number) if block != nil && err == nil { @@ -816,6 +816,68 @@ func (args *CallArgs) ToMessage(globalGasCap uint64) types.Message { return msg } +// GetBlockReceipts returns the block receipts for the given block hash or number or tag. +func (api *PublicBlockChainAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]map[string]interface{}, error) { + block, err := api.b.BlockByNumberOrHash(ctx, blockNrOrHash) + if block == nil || err != nil { + // When the block doesn't exist, the RPC method should return JSON null + // as per specification. + return nil, nil + } + receipts, err := api.b.GetReceipts(ctx, block.Hash()) + if err != nil { + return nil, err + } + txs := block.Transactions() + if len(txs) != len(receipts) { + return nil, fmt.Errorf("receipts length mismatch: %d vs %d", len(txs), len(receipts)) + } + + blockHash := block.Hash() + blockNumber := block.NumberU64() + txReceipts := make([]map[string]interface{}, 0, len(txs)) + for i, receipt := range receipts { + tx := txs[i] + var signer types.Signer = types.FrontierSigner{} + if tx.Protected() { + signer = types.NewEIP155Signer(tx.ChainId()) + } + from, _ := types.Sender(signer, tx) + + fields := map[string]interface{}{ + "blockHash": blockHash, + "blockNumber": hexutil.Uint64(blockNumber), + "transactionHash": tx.Hash(), + "transactionIndex": hexutil.Uint64(i), + "from": from, + "to": tx.To(), + "gasUsed": hexutil.Uint64(receipt.GasUsed), + "cumulativeGasUsed": hexutil.Uint64(receipt.CumulativeGasUsed), + "contractAddress": nil, + "logs": receipt.Logs, + "logsBloom": receipt.Bloom, + } + + // Assign receipt status or post state. + if len(receipt.PostState) > 0 { + fields["root"] = hexutil.Bytes(receipt.PostState) + } else { + fields["status"] = hexutil.Uint(receipt.Status) + } + if receipt.Logs == nil { + fields["logs"] = [][]*types.Log{} + } + // If the ContractAddress is 20 0x0 bytes, assume it is not a contract creation + if receipt.ContractAddress != (common.Address{}) { + fields["contractAddress"] = receipt.ContractAddress + } + + txReceipts = append(txReceipts, fields) + } + + return txReceipts, nil +} + // OverrideAccount indicates the overriding fields of account during the execution // of a message call. // Note, state and stateDiff can't be specified at the same time. If state is