From 57fef1a63037836e11b03cb285bfed9a8f948ebf Mon Sep 17 00:00:00 2001 From: hazim-j Date: Sat, 27 Apr 2024 13:13:36 +1000 Subject: [PATCH 1/2] Bump Geth to v1.14.0 --- tracers/bundler_collector_next.go.template | 387 +++++++++++++++++++++ 1 file changed, 387 insertions(+) create mode 100644 tracers/bundler_collector_next.go.template diff --git a/tracers/bundler_collector_next.go.template b/tracers/bundler_collector_next.go.template new file mode 100644 index 0000000..5188791 --- /dev/null +++ b/tracers/bundler_collector_next.go.template @@ -0,0 +1,387 @@ +package native + +import ( + "encoding/json" + "math/big" + "regexp" + "strconv" + "strings" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/tracers" + "github.com/holiman/uint256" +) + +func init() { + tracers.DefaultDirectory.Register("bundlerCollectorTracer", newBundlerCollector, false) +} + +type partialStack = []*uint256.Int + +type contractSizeVal struct { + ContractSize int `json:"contractSize"` + Opcode string `json:"opcode"` +} + +type access struct { + Reads map[string]string `json:"reads"` + Writes map[string]uint64 `json:"writes"` +} + +type entryPointCall struct { + TopLevelMethodSig hexutil.Bytes `json:"topLevelMethodSig"` + TopLevelTargetAddress common.Address `json:"topLevelTargetAddress"` + Access map[common.Address]*access `json:"access"` + Opcodes map[string]uint64 `json:"opcodes"` + ExtCodeAccessInfo map[common.Address]string `json:"extCodeAccessInfo"` + ContractSize map[common.Address]*contractSizeVal `json:"contractSize"` + OOG bool `json:"oog"` +} + +type callsItem struct { + // Common + Type string `json:"type"` + + // Enter info + From common.Address `json:"from"` + To common.Address `json:"to"` + Method hexutil.Bytes `json:"method"` + Value *hexutil.Big `json:"value"` + Gas uint64 `json:"gas"` + + // Exit info + GasUsed uint64 `json:"gasUsed"` + Data hexutil.Bytes `json:"data"` +} + +type logsItem struct { + Data hexutil.Bytes `json:"data"` + Topic []hexutil.Bytes `json:"topic"` +} + +type lastThreeOpCodesItem struct { + Opcode string + StackTop3 partialStack +} + +type bundlerCollectorResults struct { + CallsFromEntryPoint []*entryPointCall `json:"callsFromEntryPoint"` + Keccak []hexutil.Bytes `json:"keccak"` + Logs []*logsItem `json:"logs"` + Calls []*callsItem `json:"calls"` +} + +type bundlerCollector struct { + vm *tracing.VMContext + + CallsFromEntryPoint []*entryPointCall + CurrentLevel *entryPointCall + Keccak []hexutil.Bytes + Calls []*callsItem + Logs []*logsItem + lastOp string + lastThreeOpCodes []*lastThreeOpCodesItem + allowedOpcodeRegex *regexp.Regexp + stopCollectingTopic string + stopCollecting bool +} + +func newBundlerCollector(ctx *tracers.Context, cfg json.RawMessage) (*tracers.Tracer, error) { + rgx, err := regexp.Compile( + `^(DUP\d+|PUSH\d+|SWAP\d+|POP|ADD|SUB|MUL|DIV|EQ|LTE?|S?GTE?|SLT|SH[LR]|AND|OR|NOT|ISZERO)$`, + ) + if err != nil { + return nil, err + } + // event sent after all validations are done: keccak("BeforeExecution()") + stopCollectingTopic := "0xbb47ee3e183a558b1a2ff0874b079f3fc5478b7454eacf2bfc5af2ff5878f972" + + t := &bundlerCollector{ + CallsFromEntryPoint: []*entryPointCall{}, + CurrentLevel: nil, + Keccak: []hexutil.Bytes{}, + Calls: []*callsItem{}, + Logs: []*logsItem{}, + lastOp: "", + lastThreeOpCodes: []*lastThreeOpCodesItem{}, + allowedOpcodeRegex: rgx, + stopCollectingTopic: stopCollectingTopic, + stopCollecting: false, + } + + return &tracers.Tracer{ + Hooks: &tracing.Hooks{ + OnTxStart: t.OnTxStart, + OnEnter: t.OnEnter, + OnExit: t.OnExit, + OnOpcode: t.OnOpcode, + }, + GetResult: t.GetResult, + }, nil +} + +func (b *bundlerCollector) isEXTorCALL(opcode string) bool { + return strings.HasPrefix(opcode, "EXT") || + opcode == "CALL" || + opcode == "CALLCODE" || + opcode == "DELEGATECALL" || + opcode == "STATICCALL" +} + +// not using 'isPrecompiled' to only allow the ones defined by the ERC-4337 as stateless precompiles +// [OP-062] +func (b *bundlerCollector) isAllowedPrecompile(addr common.Address) bool { + addrInt := addr.Big() + return addrInt.Cmp(big.NewInt(0)) == 1 && addrInt.Cmp(big.NewInt(10)) == -1 +} + +func (b *bundlerCollector) incrementCount(m map[string]uint64, k string) { + if _, ok := m[k]; !ok { + m[k] = 0 + } + m[k]++ +} + +func (b *bundlerCollector) OnTxStart( + vm *tracing.VMContext, + tx *types.Transaction, + from common.Address, +) { + b.vm = vm +} + +func (b *bundlerCollector) GetResult() (json.RawMessage, error) { + bcr := bundlerCollectorResults{ + CallsFromEntryPoint: b.CallsFromEntryPoint, + Keccak: b.Keccak, + Logs: b.Logs, + Calls: b.Calls, + } + + r, err := json.Marshal(bcr) + if err != nil { + return nil, err + } + return r, nil +} + +func (b *bundlerCollector) OnEnter( + depth int, + typ byte, + from common.Address, + to common.Address, + input []byte, + gas uint64, + value *big.Int, +) { + if b.stopCollecting { + return + } + + op := vm.OpCode(typ) + method := []byte{} + if len(input) >= 4 { + method = append(method, input[:4]...) + } + b.Calls = append(b.Calls, &callsItem{ + Type: op.String(), + From: from, + To: to, + Method: method, + Gas: gas, + Value: (*hexutil.Big)(value), + }) +} + +func (b *bundlerCollector) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { + if b.stopCollecting { + return + } + + rt := "RETURN" + if err != nil { + rt = "REVERT" + } + b.Calls = append(b.Calls, &callsItem{ + Type: rt, + GasUsed: gasUsed, + Data: output, + }) +} + +func (b *bundlerCollector) OnOpcode( + pc uint64, + op byte, + gas, cost uint64, + scope tracing.OpContext, + rData []byte, + depth int, + err error, +) { + if b.stopCollecting { + return + } + opcode := vm.OpCode(op).String() + + stackSize := len(scope.StackData()) + stackLast := stackSize - 1 + stackTop3 := partialStack{} + for i := 0; i < 3 && i < stackSize; i++ { + stackTop3 = append(stackTop3, scope.StackData()[stackLast-i].Clone()) + } + b.lastThreeOpCodes = append(b.lastThreeOpCodes, &lastThreeOpCodesItem{ + Opcode: opcode, + StackTop3: stackTop3, + }) + if len(b.lastThreeOpCodes) > 3 { + b.lastThreeOpCodes = b.lastThreeOpCodes[1:] + } + + if gas < cost || (opcode == "SSTORE" && gas < 2300) { + b.CurrentLevel.OOG = true + } + + if opcode == "REVERT" || opcode == "RETURN" { + // exit() is not called on top-level return/revert, so we reconstruct it from opcode + if depth == 1 { + ofs := scope.StackData()[stackLast].ToBig().Int64() + len := scope.StackData()[stackLast-1].ToBig().Int64() + data := make([]byte, len) + copy(data, scope.MemoryData()[ofs:ofs+len]) + b.Calls = append(b.Calls, &callsItem{ + Type: opcode, + GasUsed: 0, + Data: data, + }) + } + // NOTE: flushing all history after RETURN + b.lastThreeOpCodes = []*lastThreeOpCodesItem{} + } + + if depth == 1 { + if opcode == "CALL" || opcode == "STATICCALL" { + addr := common.HexToAddress(scope.StackData()[stackLast-1].Hex()) + ofs := scope.StackData()[stackLast-3].ToBig().Int64() + sig := make([]byte, 4) + copy(sig, scope.MemoryData()[ofs:ofs+4]) + + b.CurrentLevel = &entryPointCall{ + TopLevelMethodSig: sig, + TopLevelTargetAddress: addr, + Access: map[common.Address]*access{}, + Opcodes: map[string]uint64{}, + ExtCodeAccessInfo: map[common.Address]string{}, + ContractSize: map[common.Address]*contractSizeVal{}, + OOG: false, + } + b.CallsFromEntryPoint = append(b.CallsFromEntryPoint, b.CurrentLevel) + } else if opcode == "LOG1" && scope.StackData()[stackLast-2].Hex() == b.stopCollectingTopic { + b.stopCollecting = true + } + b.lastOp = "" + return + } + + var lastOpInfo *lastThreeOpCodesItem + if len(b.lastThreeOpCodes) >= 2 { + lastOpInfo = b.lastThreeOpCodes[len(b.lastThreeOpCodes)-2] + } + // store all addresses touched by EXTCODE* opcodes + if lastOpInfo != nil && strings.HasPrefix(lastOpInfo.Opcode, "EXT") { + addr := common.HexToAddress(lastOpInfo.StackTop3[0].Hex()) + ops := []string{} + for _, item := range b.lastThreeOpCodes { + ops = append(ops, item.Opcode) + } + last3OpcodeStr := strings.Join(ops, ",") + + // only store the last EXTCODE* opcode per address - could even be a boolean for our current use-case + // [OP-051] + if !strings.Contains(last3OpcodeStr, ",EXTCODESIZE,ISZERO") { + b.CurrentLevel.ExtCodeAccessInfo[addr] = opcode + } + } + + // [OP-041] + if b.isEXTorCALL(opcode) { + n := 0 + if !strings.HasPrefix(opcode, "EXT") { + n = 1 + } + addr := common.BytesToAddress(scope.StackData()[stackLast-n].Bytes()) + + if _, ok := b.CurrentLevel.ContractSize[addr]; !ok && !b.isAllowedPrecompile(addr) { + b.CurrentLevel.ContractSize[addr] = &contractSizeVal{ + ContractSize: len(b.vm.StateDB.GetCode(addr)), + Opcode: opcode, + } + } + } + + // [OP-012] + if b.lastOp == "GAS" && !strings.Contains(opcode, "CALL") { + b.incrementCount(b.CurrentLevel.Opcodes, "GAS") + } + // ignore "unimportant" opcodes + if opcode != "GAS" && !b.allowedOpcodeRegex.MatchString(opcode) { + b.incrementCount(b.CurrentLevel.Opcodes, opcode) + } + b.lastOp = opcode + + if opcode == "SLOAD" || opcode == "SSTORE" { + slot := common.BytesToHash(scope.StackData()[stackLast].Bytes()) + slotHex := slot.Hex() + addr := scope.Address() + if _, ok := b.CurrentLevel.Access[addr]; !ok { + b.CurrentLevel.Access[addr] = &access{ + Reads: map[string]string{}, + Writes: map[string]uint64{}, + } + } + access := *b.CurrentLevel.Access[addr] + + if opcode == "SLOAD" { + // read slot values before this UserOp was created + // (so saving it if it was written before the first read) + _, rOk := access.Reads[slotHex] + _, wOk := access.Writes[slotHex] + if !rOk && !wOk { + access.Reads[slotHex] = string(b.vm.StateDB.GetState(addr, slot).Hex()) + } + } else { + b.incrementCount(access.Writes, slotHex) + } + } + + if opcode == "KECCAK256" { + // collect keccak on 64-byte blocks + ofs := scope.StackData()[stackLast].ToBig().Int64() + len := scope.StackData()[stackLast-1].ToBig().Int64() + // currently, solidity uses only 2-word (6-byte) for a key. this might change..still, no need to + // return too much + if len > 20 && len < 512 { + data := make([]byte, len) + copy(data, scope.MemoryData()[ofs:ofs+len]) + b.Keccak = append(b.Keccak, data) + } + } else if strings.HasPrefix(opcode, "LOG") { + count, _ := strconv.Atoi(opcode[3:]) + ofs := scope.StackData()[stackLast].ToBig().Int64() + len := scope.StackData()[stackLast-1].ToBig().Int64() + topics := []hexutil.Bytes{} + for i := 0; i < count; i++ { + topics = append(topics, scope.StackData()[stackLast-2+i].Bytes()) + } + + data := make([]byte, len) + copy(data, scope.MemoryData()[ofs:ofs+len]) + b.Logs = append(b.Logs, &logsItem{ + Data: data, + Topic: topics, + }) + } +} From b34c3f987c1078ac254061751b1a11ac72f57c4c Mon Sep 17 00:00:00 2001 From: hazim-j Date: Thu, 20 Jun 2024 16:13:11 +1000 Subject: [PATCH 2/2] Update geth build scripts --- README.md | 2 +- build_scripts/go-ethereum.sh | 4 ++-- go-ethereum | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3c7c26a..c78b122 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ A repository for building Geth and other EVM execution clients with native ERC-4 | Client | Release version | Pre-built docker image | Build binary from source | | ------------------------- | --------------- | ---------------------- | ------------------------ | -| go-ethereum | `v1.13.15` | ✅ | ✅ | +| go-ethereum | `v1.14.5` | ✅ | ✅ | | bor (Polygon PoS) | `v1.3.3` | ❌ | ✅ | | nitro (Arbitrum) | `v3.0.2` | ✅ | ❌ | | op-geth (Optimism) | `v1.101315.1` | ✅ | ❌ | diff --git a/build_scripts/go-ethereum.sh b/build_scripts/go-ethereum.sh index 60636e0..f21ebb7 100755 --- a/build_scripts/go-ethereum.sh +++ b/build_scripts/go-ethereum.sh @@ -12,9 +12,9 @@ do done client_path=$(pwd)/go-ethereum -src_collector_tracer_path=$(pwd)/tracers/bundler_collector.go.template +src_collector_tracer_path=$(pwd)/tracers/bundler_collector_next.go.template src_executor_tracer_path=$(pwd)/tracers/bundler_executor.go.template -dest_collector_tracer_path=${client_path}/eth/tracers/native/bundler_collector.go +dest_collector_tracer_path=${client_path}/eth/tracers/native/bundler_collector_next.go dest_executor_tracer_path=${client_path}/eth/tracers/native/bundler_executor.go build_output_dir=$(pwd)/builds/go-ethereum/ diff --git a/go-ethereum b/go-ethereum index c5ba367..0dd173a 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit c5ba367eb6232e3eddd7d6226bfd374449c63164 +Subproject commit 0dd173a727dd2d2409b8e401b22e85d20c25b71f