From 3ae9c9657f7464ed7271bf79bec17c92fbf34000 Mon Sep 17 00:00:00 2001 From: Dror Tirosh Date: Tue, 13 Aug 2024 11:23:33 +0300 Subject: [PATCH] validateAccountAndPaymaster called by process, tracing --- core/state_processor_rip7560.go | 157 +++++++++++++---------- eth/tracers/api_tracing_rip7560.go | 7 +- eth/tracers/native/rip7560_validation.go | 39 +++--- 3 files changed, 113 insertions(+), 90 deletions(-) diff --git a/core/state_processor_rip7560.go b/core/state_processor_rip7560.go index 61a0828dbd..a881e433a1 100644 --- a/core/state_processor_rip7560.go +++ b/core/state_processor_rip7560.go @@ -1,6 +1,7 @@ package core import ( + "bytes" "errors" "fmt" "github.com/ethereum/go-ethereum/common" @@ -16,10 +17,19 @@ import ( var AA_ENTRY_POINT = common.HexToAddress("0x0000000000000000000000000000000000007560") var AA_SENDER_CREATOR = common.HexToAddress("0x00000000000000000000000000000000ffff7560") +type EntryPointCallEntry struct { + From common.Address + Input []byte +} type EntryPointCall struct { - OnEnterSuper tracing.EnterHook - Input []byte - err error + entries []EntryPointCallEntry + err error +} + +func NewEntryPointCall() *EntryPointCall { + return &EntryPointCall{ + entries: make([]EntryPointCallEntry, 0), + } } type ValidationPhaseResult struct { @@ -151,6 +161,49 @@ func CheckNonceRip7560(tx *types.Rip7560AccountAbstractionTx, st *state.StateDB) return nil } +// finalize validation return data from account and paymaster +func ValidateAccountAndPaymaster(time uint64, sender, paymaster *common.Address, epc *EntryPointCall) (*AcceptAccountData, *AcceptPaymasterData, error) { + if epc.err != nil { + return nil, nil, epc.err + } + if len(epc.entries) == 0 || epc.entries[0].From.Cmp(*sender) != 0 { + return nil, nil, errors.New("account validation did not call the EntryPoint 'acceptAccount' callback") + } + if paymaster == nil { + if len(epc.entries) > 1 { + return nil, nil, errors.New("EntryPoint callback called more than once") + } + } else { + if len(epc.entries) < 2 || epc.entries[1].From.Cmp(*paymaster) != 0 { + return nil, nil, errors.New("paymaster validation did not call the EntryPoint 'acceptPaymaster' callback") + } + if len(epc.entries) > 2 { + return nil, nil, errors.New("EntryPoint callback called too many times") + } + } + aad, err := ValidateAccountEntryPointCall(sender, epc.entries[0].Input) + if err != nil { + return nil, nil, err + } + + err = validateValidityTimeRange(time, aad.ValidAfter.Uint64(), aad.ValidUntil.Uint64()) + if err != nil { + return nil, nil, err + } + var apd *AcceptPaymasterData + if paymaster != nil { + apd, err = validatePaymasterEntryPointCall(paymaster, epc.entries[1].Input) + if err != nil { + return nil, nil, err + } + err = validateValidityTimeRange(time, apd.ValidAfter.Uint64(), apd.ValidUntil.Uint64()) + if err != nil { + return nil, nil, err + } + } + return aad, apd, nil +} + func ApplyRip7560ValidationPhases(chainConfig *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, cfg vm.Config) (*ValidationPhaseResult, error) { aatx := tx.Rip7560TransactionData() err := CheckNonceRip7560(aatx, statedb) @@ -171,21 +224,19 @@ func ApplyRip7560ValidationPhases(chainConfig *params.ChainConfig, bc ChainConte blockContext := NewEVMBlockContext(header, bc, author) sender := tx.Rip7560TransactionData().Sender + paymaster := tx.Rip7560TransactionData().Paymaster txContext := vm.TxContext{ Origin: *sender, GasPrice: gasPrice, } evm := vm.NewEVM(blockContext, txContext, statedb, chainConfig, cfg) - epc := &EntryPointCall{} + var epc *EntryPointCall if evm.Config.Tracer == nil { + epc = NewEntryPointCall() evm.Config.Tracer = &tracing.Hooks{ OnEnter: epc.OnEnter, } - } else { - // keep the original tracer's OnEnter hook - epc.OnEnterSuper = evm.Config.Tracer.OnEnter - evm.Config.Tracer.OnEnter = epc.OnEnter } if evm.Config.Tracer.OnTxStart != nil { @@ -228,70 +279,54 @@ func ApplyRip7560ValidationPhases(chainConfig *params.ChainConfig, bc ChainConte if resultAccountValidation.Err != nil { return nil, resultAccountValidation.Err } - aad, err := validateAccountEntryPointCall(epc) - if err != nil { - return nil, err - } - - // clear the EntryPoint calls array after parsing - epc.err = nil - epc.Input = nil - - err = validateValidityTimeRange(header.Time, aad.ValidAfter.Uint64(), aad.ValidUntil.Uint64()) - if err != nil { - return nil, err - } vpr := &ValidationPhaseResult{} - paymasterContext, pmValidationUsedGas, pmValidAfter, pmValidUntil, err := applyPaymasterValidationFrame(epc, tx, chainConfig, signingHash, evm, gp, statedb, header) + pmValidationUsedGas, err := applyPaymasterValidationFrame(tx, chainConfig, signingHash, evm, gp, statedb, header) if err != nil { return nil, err } + aad, apd, err := ValidateAccountAndPaymaster(header.Time, sender, paymaster, epc) + if err != nil { + return nil, err + } vpr.Tx = tx vpr.TxHash = tx.Hash() vpr.PreCharge = preCharge vpr.EffectiveGasPrice = gasPriceUint256 - vpr.PaymasterContext = paymasterContext + vpr.PaymasterContext = apd.Context vpr.DeploymentUsedGas = deploymentUsedGas vpr.ValidationUsedGas = resultAccountValidation.UsedGas vpr.PmValidationUsedGas = pmValidationUsedGas vpr.SenderValidAfter = aad.ValidAfter.Uint64() vpr.SenderValidUntil = aad.ValidUntil.Uint64() - vpr.PmValidAfter = pmValidAfter - vpr.PmValidUntil = pmValidUntil + vpr.PmValidAfter = apd.ValidAfter.Uint64() + vpr.PmValidUntil = apd.ValidUntil.Uint64() + statedb.Finalise(true) return vpr, nil } -func applyPaymasterValidationFrame(epc *EntryPointCall, tx *types.Transaction, chainConfig *params.ChainConfig, signingHash common.Hash, evm *vm.EVM, gp *GasPool, statedb *state.StateDB, header *types.Header) ([]byte, uint64, uint64, uint64, error) { +func applyPaymasterValidationFrame(tx *types.Transaction, chainConfig *params.ChainConfig, signingHash common.Hash, evm *vm.EVM, gp *GasPool, statedb *state.StateDB, header *types.Header) (uint64, error) { /*** Paymaster Validation Frame ***/ var pmValidationUsedGas uint64 paymasterMsg, err := preparePaymasterValidationMessage(tx, chainConfig, signingHash) if paymasterMsg == nil || err != nil { - return nil, 0, 0, 0, err + return 0, err } resultPm, err := ApplyMessage(evm, paymasterMsg, gp) if err != nil { - return nil, 0, 0, 0, err + return 0, err } if resultPm.Failed() { - return nil, 0, 0, 0, resultPm.Err + return 0, resultPm.Err } if resultPm.Failed() { - return nil, 0, 0, 0, errors.New("paymaster validation failed - invalid transaction") + return 0, errors.New("paymaster validation failed - invalid transaction") } pmValidationUsedGas = resultPm.UsedGas - apd, err := validatePaymasterEntryPointCall(epc) - if err != nil { - return nil, 0, 0, 0, err - } - err = validateValidityTimeRange(header.Time, apd.ValidAfter.Uint64(), apd.ValidUntil.Uint64()) - if err != nil { - return nil, 0, 0, 0, err - } - return apd.Context, pmValidationUsedGas, apd.ValidAfter.Uint64(), apd.ValidUntil.Uint64(), nil + return pmValidationUsedGas, nil } func applyPaymasterPostOpFrame(vpr *ValidationPhaseResult, executionResult *ExecutionResult, evm *vm.EVM, gp *GasPool, statedb *state.StateDB, header *types.Header) (*ExecutionResult, error) { @@ -466,31 +501,18 @@ func preparePostOpMessage(vpr *ValidationPhaseResult, chainConfig *params.ChainC }, nil } -func validateAccountEntryPointCall(epc *EntryPointCall) (*AcceptAccountData, error) { - if epc.err != nil { - return nil, epc.err - } - if epc.Input == nil { - return nil, errors.New("account validation did not call the EntryPoint 'acceptAccount' callback") - } - if len(epc.Input) != 68 { +func ValidateAccountEntryPointCall(sender *common.Address, input []byte) (*AcceptAccountData, error) { + if len(input) != 68 { return nil, errors.New("invalid account return data length") } - return abiDecodeAcceptAccount(epc.Input) + return abiDecodeAcceptAccount(input) } -func validatePaymasterEntryPointCall(epc *EntryPointCall) (*AcceptPaymasterData, error) { - if epc.err != nil { - return nil, epc.err - } - if epc.Input == nil { - return nil, errors.New("paymaster validation did not call the EntryPoint 'acceptPaymaster' callback") - } - - if len(epc.Input) < 100 { +func validatePaymasterEntryPointCall(paymaster *common.Address, input []byte) (*AcceptPaymasterData, error) { + if len(input) < 100 { return nil, errors.New("invalid paymaster callback data length") } - apd, err := abiDecodeAcceptPaymaster(epc.Input) + apd, err := abiDecodeAcceptPaymaster(input) if err != nil { return nil, err } @@ -514,25 +536,18 @@ func validateValidityTimeRange(time uint64, validAfter uint64, validUntil uint64 } func (epc *EntryPointCall) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { - if epc.OnEnterSuper != nil { - epc.OnEnterSuper(depth, typ, from, to, input, gas, value) - } isRip7560EntryPoint := to.Cmp(AA_ENTRY_POINT) == 0 if !isRip7560EntryPoint { return } if depth != 1 { - println("ONENTER WITH WRONG DEPTH!") - epc.err = errors.New("same") + epc.err = errors.New("called EntryPoint not from top level account") + println(epc.err) return } - if epc.Input != nil { - println("repeated call to ep callback") - epc.err = errors.New("same") - return - } - - epc.Input = make([]byte, len(input)) - copy(epc.Input, input) + epc.entries = append(epc.entries, EntryPointCallEntry{ + From: from, + Input: bytes.Clone(input), + }) } diff --git a/eth/tracers/api_tracing_rip7560.go b/eth/tracers/api_tracing_rip7560.go index 315e825ec3..95dd122c74 100644 --- a/eth/tracers/api_tracing_rip7560.go +++ b/eth/tracers/api_tracing_rip7560.go @@ -9,6 +9,7 @@ import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/tracers/native" "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/rpc" "math/big" @@ -132,7 +133,9 @@ func (api *Rip7560API) traceTx(ctx context.Context, tx *types.Transaction, txctx gp := new(core.GasPool).AddGas(10000000) // TODO: this is added to allow our bundler checking the 'TraceValidation' API is supported on Geth - if tx.Rip7560TransactionData().Sender.Cmp(common.HexToAddress("0x0000000000000000000000000000000000000000")) == 0 { + sender := tx.Rip7560TransactionData().Sender + paymaster := tx.Rip7560TransactionData().Paymaster + if sender.Cmp(common.HexToAddress("0x0000000000000000000000000000000000000000")) == 0 { return tracer.GetResult() } @@ -141,5 +144,7 @@ func (api *Rip7560API) traceTx(ctx context.Context, tx *types.Transaction, txctx if err != nil { return nil, fmt.Errorf("tracing failed: %w", err) } + x:= tracer.(*native.Rip7560ValidationTracer).EntryPointCall.entries + core.ValidateAccountAndPaymaster(0, sender, paymaster, tracer.) return tracer.GetResult() } diff --git a/eth/tracers/native/rip7560_validation.go b/eth/tracers/native/rip7560_validation.go index 504c68d369..76356b58ed 100644 --- a/eth/tracers/native/rip7560_validation.go +++ b/eth/tracers/native/rip7560_validation.go @@ -4,6 +4,7 @@ import ( "encoding/json" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" @@ -69,7 +70,7 @@ func newRip7560Tracer(ctx *tracers.Context, cfg json.RawMessage) (*tracers.Trace return nil, err } // TODO FIX mock fields - t := &rip7560ValidationTracer{ + t := &Rip7560ValidationTracer{ TraceResults: make([]stateMap, ValidationFramesMaxCount), UsedOpcodes: make([]map[string]bool, ValidationFramesMaxCount), Created: make([]map[common.Address]bool, ValidationFramesMaxCount), @@ -119,13 +120,13 @@ type logsItem struct { } // Array fields contain of all access details of all validation frames -type rip7560ValidationTracer struct { +type Rip7560ValidationTracer struct { //rip7560TxData *types.Rip7560AccountAbstractionTx - - env *tracing.VMContext - TraceResults []stateMap `json:"traceResults"` - UsedOpcodes []map[string]bool `json:"usedOpcodes"` - Created []map[common.Address]bool `json:"created"` + EntryPointCall core.EntryPointCall + env *tracing.VMContext + TraceResults []stateMap `json:"traceResults"` + UsedOpcodes []map[string]bool `json:"usedOpcodes"` + Created []map[common.Address]bool `json:"created"` //Deleted []map[common.Address]bool `json:"deleted"` lastThreeOpCodes []*lastThreeOpCodesItem @@ -142,7 +143,9 @@ type rip7560ValidationTracer struct { //reason error // Textual reason for the interruption } -func (b *rip7560ValidationTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +func (b *Rip7560ValidationTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + + b.entryPointCall.OnEnter(depth, typ, from, to, input, gas, value) if depth == 0 { b.createNewTopLevelFrame(to) } @@ -157,7 +160,7 @@ func (b *rip7560ValidationTracer) OnEnter(depth int, typ byte, from common.Addre }) } -func (b *rip7560ValidationTracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { +func (b *Rip7560ValidationTracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { typ := "RETURN" if err != nil { typ = "REVERT" @@ -169,12 +172,12 @@ func (b *rip7560ValidationTracer) OnExit(depth int, output []byte, gasUsed uint6 }) } -func (b *rip7560ValidationTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) { +func (b *Rip7560ValidationTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) { b.env = env //b.rip7560TxData = tx.Rip7560TransactionData() } -func (b *rip7560ValidationTracer) createNewTopLevelFrame(addr common.Address) { +func (b *Rip7560ValidationTracer) createNewTopLevelFrame(addr common.Address) { b.CurrentLevel = &entryPointCall{ TopLevelTargetAddress: addr, Access: map[common.Address]*access{}, @@ -188,10 +191,10 @@ func (b *rip7560ValidationTracer) createNewTopLevelFrame(addr common.Address) { return } -func (b *rip7560ValidationTracer) OnTxEnd(receipt *types.Receipt, err error) { +func (b *Rip7560ValidationTracer) OnTxEnd(receipt *types.Receipt, err error) { } -func (b *rip7560ValidationTracer) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { +func (b *Rip7560ValidationTracer) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { opcode := vm.OpCode(op).String() stackSize := len(scope.StackData()) @@ -344,7 +347,7 @@ func StackBack(stackData []uint256.Int, n int) *uint256.Int { return &stackData[len(stackData)-n-1] } -func (b *rip7560ValidationTracer) isEXTorCALL(opcode string) bool { +func (b *Rip7560ValidationTracer) isEXTorCALL(opcode string) bool { return strings.HasPrefix(opcode, "EXT") || opcode == "CALL" || opcode == "CALLCODE" || @@ -354,22 +357,22 @@ func (b *rip7560ValidationTracer) isEXTorCALL(opcode string) bool { // not using 'isPrecompiled' to only allow the ones defined by the ERC-7562 as stateless precompiles // [OP-062] -func (b *rip7560ValidationTracer) isAllowedPrecompile(addr common.Address) bool { +func (b *Rip7560ValidationTracer) isAllowedPrecompile(addr common.Address) bool { addrInt := addr.Big() return addrInt.Cmp(big.NewInt(0)) == 1 && addrInt.Cmp(big.NewInt(10)) == -1 } -func (b *rip7560ValidationTracer) incrementCount(m map[string]uint64, k string) { +func (b *Rip7560ValidationTracer) incrementCount(m map[string]uint64, k string) { if _, ok := m[k]; !ok { m[k] = 0 } m[k]++ } -func (b *rip7560ValidationTracer) GetResult() (json.RawMessage, error) { +func (b *Rip7560ValidationTracer) GetResult() (json.RawMessage, error) { jsonResult, err := json.MarshalIndent(*b, "", " ") return jsonResult, err } -func (b *rip7560ValidationTracer) Stop(err error) { +func (b *Rip7560ValidationTracer) Stop(err error) { }