From bd55b27a6d7a61a4d7023775feafd66b5ab2c93a Mon Sep 17 00:00:00 2001 From: Jesse Date: Thu, 24 Jul 2025 15:20:55 +0100 Subject: [PATCH 01/72] init simulateV1 method --- api/error.go | 8 ++- api/eth.go | 47 +++++++++++++ api/simulate.go | 181 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 235 insertions(+), 1 deletion(-) create mode 100644 api/simulate.go diff --git a/api/error.go b/api/error.go index 8d3da36d8..bd89fc1d3 100644 --- a/api/error.go +++ b/api/error.go @@ -113,4 +113,10 @@ var ( ErrAuthorizationInvalidSignature = errors.New("EIP-7702 authorization has invalid signature") ErrAuthorizationDestinationHasCode = errors.New("EIP-7702 authorization destination is a contract") ErrAuthorizationNonceMismatch = errors.New("EIP-7702 authorization nonce does not match current account nonce") -) \ No newline at end of file +) + +type callError struct { + Message string `json:"message"` + Code int `json:"code"` + Data string `json:"data,omitempty"` +} \ No newline at end of file diff --git a/api/eth.go b/api/eth.go index 439ba0745..63cab8349 100644 --- a/api/eth.go +++ b/api/eth.go @@ -339,6 +339,53 @@ func (s *PublicBlockChainAPI) Call(ctx *rpc.CallContext, args TransactionArgs, b return res, err } +func (s *PublicBlockChainAPI) SimulateV1(ctx *rpc.CallContext, opts simOpts, blockNrOrHash *vm.BlockNumberOrHash) ([]*simBlockResult, error) { + if len(opts.BlockStateCalls) == 0 { + return nil, fmt.Errorf("empty input") + } else if len(opts.BlockStateCalls) > maxSimulateBlocks { + return nil, fmt.Errorf("too many blocks") + } + + bNrOrHash := vm.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) + if blockNrOrHash != nil { + bNrOrHash = *blockNrOrHash + } + + var results []*simBlockResult + + err := s.evmmgr.View(bNrOrHash, &vm.Config{NoBaseFee: !opts.Validation}, ctx, func(statedb state.StateDB, baseHeader *types.Header, evmFn func(state.StateDB, *vm.Config, common.Address, *big.Int) *vm.EVM, chaincfg *params.ChainConfig) error { + gasCap := s.gasLimit(baseHeader) + if gasCap == 0 { + gasCap = math.MaxUint64 + } + + sim := &simulator{ + timeout: 30 * time.Second, + state: statedb.Copy(), + base: baseHeader, + chainConfig: chaincfg, + gp: new(GasPool).AddGas(gasCap), + traceTransfers: opts.TraceTransfers, + validate: opts.Validation, + fullTx: opts.ReturnFullTransactions, + evmFn: evmFn, + } + var err error + results, err = sim.execute(ctx, opts.BlockStateCalls) + return err + }) + + if err != nil { + switch err.(type) { + case codedError: + default: + err = evmError{err} + } + return nil, err + } + return results, nil +} + // type estimateGasError struct { error string // Concrete error type if it's failed to estimate gas usage diff --git a/api/simulate.go b/api/simulate.go new file mode 100644 index 000000000..8c216d320 --- /dev/null +++ b/api/simulate.go @@ -0,0 +1,181 @@ +package api + +import ( + "math/big" + "time" + "context" + "fmt" + "errors" + + "github.com/openrelayxyz/cardinal-evm/common" + "github.com/openrelayxyz/cardinal-evm/params" + "github.com/openrelayxyz/cardinal-evm/state" + "github.com/openrelayxyz/cardinal-evm/types" + "github.com/openrelayxyz/cardinal-evm/vm" + rpc "github.com/openrelayxyz/cardinal-rpc" + ctypes "github.com/openrelayxyz/cardinal-types" + "github.com/openrelayxyz/cardinal-types/hexutil" + ptypes "github.com/openrelayxyz/plugeth-utils/restricted/types" +) + +const ( + // maxSimulateBlocks is the maximum number of blocks that can be simulated + // in a single request. + maxSimulateBlocks = 256 + + // timestampIncrement is the default increment between block timestamps. + timestampIncrement = 1 +) + +// BlockOverrides is a set of header fields to override. +type BlockOverrides struct { + Number *hexutil.Big + Difficulty *hexutil.Big // No-op if we're simulating post-merge calls. + Time *hexutil.Uint64 + GasLimit *hexutil.Uint64 + FeeRecipient *common.Address + PrevRandao *ctypes.Hash + BaseFeePerGas *hexutil.Big + BlobBaseFee *hexutil.Big +} + +// simOpts are the inputs to eth_simulateV1. +type simOpts struct { + BlockStateCalls []simBlock + TraceTransfers bool + Validation bool + ReturnFullTransactions bool +} + +// simBlock is a batch of calls to be simulated sequentially. +type simBlock struct { + BlockOverrides *BlockOverrides + StateOverrides *StateOverride + Calls []TransactionArgs +} + +// simulator is a stateful object that simulates a series of blocks. +// it is not safe for concurrent use. +type simulator struct { + timeout time.Duration + state state.StateDB + base *types.Header + chainConfig *params.ChainConfig + gp *GasPool + traceTransfers bool + validate bool + fullTx bool + evmFn func(state.StateDB, *vm.Config, common.Address, *big.Int) *vm.EVM +} + +type simBlockResult struct { + fullTx bool + chainConfig *params.ChainConfig + Block *ptypes.Block + Calls []simCallResult + // senders is a map of transaction hashes to their senders. + senders map[ctypes.Hash]common.Address +} + +// simCallResult is the result of a simulated call. +type simCallResult struct { + ReturnValue hexutil.Bytes `json:"returnData"` + Logs []*types.Log `json:"logs"` + GasUsed hexutil.Uint64 `json:"gasUsed"` + Status hexutil.Uint64 `json:"status"` + Error *callError `json:"error,omitempty"` +} + +func (s *simulator) execute(ctx *rpc.CallContext, blocks []simBlock) ([]*simBlockResult, error){ + if err := ctx.Context().Err(); err != nil { + return nil, err + } + + var cancel context.CancelFunc + var execCtx context.Context + if s.timeout > 0 { + execCtx, cancel = context.WithTimeout(ctx.Context(), s.timeout) + } else { + execCtx, cancel = context.WithCancel(ctx.Context()) + } + defer cancel() + + santizedblocks, err := s.sanitizeChain(blocks) + if err != nil { + return nil, err + } + + // yet to implement + headers, err := s.makeHeaders(santizedblocks) + if err != nil { + return nil, err + } + +} + +// sanitizeChain checks the chain integrity. Specifically it checks that +// block numbers and timestamp are strictly increasing, setting default values +// when necessary. Gaps in block numbers are filled with empty blocks. +func (s *simulator) sanitizeChain(blocks []simBlock) ([]simBlock, error) { + var ( + res = make([]simBlock, 0, len(blocks)) + base = s.base + prevNumber = base.Number + prevTimestamp = base.Time + ) + + for _, block := range blocks { + if block.BlockOverrides == nil { + block.BlockOverrides = &BlockOverrides{} + } + + if block.BlockOverrides.Number == nil { + n := new(big.Int).Add(prevNumber, big.NewInt(1)) + block.BlockOverrides.Number = (*hexutil.Big)(n) + } + + diff := new(big.Int).Sub(block.BlockOverrides.Number.ToInt(), prevNumber) + if diff.Cmp(big.NewInt(0)) <= 0 { + return nil, fmt.Errorf("block numbers must be in order: %d <= %d", + block.BlockOverrides.Number.ToInt().Uint64(), prevNumber.Uint64()) + } + + if total := new(big.Int).Sub(block.BlockOverrides.Number.ToInt(), base.Number); total.Cmp(big.NewInt(maxSimulateBlocks)) > 0 { + return nil, fmt.Errorf("too many blocks") + } + + // Fill gaps with empty blocks + if diff.Cmp(big.NewInt(1)) > 0 { + gap := new(big.Int).Sub(diff, big.NewInt(1)) + for i := uint64(0); i < gap.Uint64(); i++ { + n := new(big.Int).Add(prevNumber, big.NewInt(int64(i+1))) + t := prevTimestamp + timestampIncrement + emptyBlock := simBlock{ + BlockOverrides: &BlockOverrides{ + Number: (*hexutil.Big)(n), + Time: (*hexutil.Uint64)(&t), + }, + } + prevTimestamp = t + res = append(res, emptyBlock) + } + } + + prevNumber = block.BlockOverrides.Number.ToInt() + var t uint64 + if block.BlockOverrides.Time == nil { + t = prevTimestamp + timestampIncrement + block.BlockOverrides.Time = (*hexutil.Uint64)(&t) + } else { + t = uint64(*block.BlockOverrides.Time) + if t <= prevTimestamp { + return nil, fmt.Errorf("block timestamps must be in order: %d <= %d", t, prevTimestamp) + } + } + prevTimestamp = t + res = append(res, block) + } + + return res, nil +} + From 8881f7dd8667bc8d40dc9f3b0a7cedbe3ea6c53b Mon Sep 17 00:00:00 2001 From: Jesse Date: Tue, 5 Aug 2025 21:35:24 +0100 Subject: [PATCH 02/72] simulatev1 wip --- api/error.go | 29 ++++- api/eth.go | 5 +- api/simulate.go | 239 ++++++++++++++++++++++++++++---------- api/transaction_args.go | 115 ++++++++++++++++++ eips/eip1559/eip.go | 71 +++++++++++ params/protocol_params.go | 12 +- types/block.go | 111 ++++++++++++++---- 7 files changed, 495 insertions(+), 87 deletions(-) create mode 100644 eips/eip1559/eip.go diff --git a/api/error.go b/api/error.go index bd89fc1d3..9f7c374ad 100644 --- a/api/error.go +++ b/api/error.go @@ -104,6 +104,23 @@ var ( ) +const ( + errCodeNonceTooHigh = -38011 + errCodeNonceTooLow = -38010 + errCodeIntrinsicGas = -38013 + errCodeInsufficientFunds = -38014 + errCodeBlockGasLimitReached = -38015 + errCodeBlockNumberInvalid = -38020 + errCodeBlockTimestampInvalid = -38021 + errCodeSenderIsNotEOA = -38024 + errCodeMaxInitCodeSizeExceeded = -38025 + errCodeClientLimitExceeded = -38026 + errCodeInternalError = -32603 + errCodeInvalidParams = -32602 + errCodeReverted = -32000 + errCodeVMError = -32015 +) + // EIP-7702 state transition errors. // Note these are just informational, and do not cause tx execution abort. @@ -119,4 +136,14 @@ type callError struct { Message string `json:"message"` Code int `json:"code"` Data string `json:"data,omitempty"` -} \ No newline at end of file +} + +type invalidParamsError struct{ message string } + +func (e *invalidParamsError) Error() string { return e.message } +func (e *invalidParamsError) ErrorCode() int { return errCodeInvalidParams } + +type clientLimitExceededError struct{ message string } + +func (e *clientLimitExceededError) Error() string { return e.message } +func (e *clientLimitExceededError) ErrorCode() int { return errCodeClientLimitExceeded } \ No newline at end of file diff --git a/api/eth.go b/api/eth.go index 63cab8349..a3f29f954 100644 --- a/api/eth.go +++ b/api/eth.go @@ -341,9 +341,9 @@ func (s *PublicBlockChainAPI) Call(ctx *rpc.CallContext, args TransactionArgs, b func (s *PublicBlockChainAPI) SimulateV1(ctx *rpc.CallContext, opts simOpts, blockNrOrHash *vm.BlockNumberOrHash) ([]*simBlockResult, error) { if len(opts.BlockStateCalls) == 0 { - return nil, fmt.Errorf("empty input") + return nil, &invalidParamsError{message: "empty input"} } else if len(opts.BlockStateCalls) > maxSimulateBlocks { - return nil, fmt.Errorf("too many blocks") + return nil, &clientLimitExceededError{message: "too many blocks"} } bNrOrHash := vm.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) @@ -364,6 +364,7 @@ func (s *PublicBlockChainAPI) SimulateV1(ctx *rpc.CallContext, opts simOpts, blo state: statedb.Copy(), base: baseHeader, chainConfig: chaincfg, + // Each tx and all the series of txes shouldn't consume more gas than cap gp: new(GasPool).AddGas(gasCap), traceTransfers: opts.TraceTransfers, validate: opts.Validation, diff --git a/api/simulate.go b/api/simulate.go index 8c216d320..89dd9b61c 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -1,13 +1,16 @@ package api import ( - "math/big" - "time" "context" "fmt" + "math/big" "errors" + "time" "github.com/openrelayxyz/cardinal-evm/common" + "github.com/openrelayxyz/cardinal-evm/crypto" + "github.com/openrelayxyz/cardinal-evm/eips/eip1559" + "github.com/openrelayxyz/cardinal-evm/eips/eip4844" "github.com/openrelayxyz/cardinal-evm/params" "github.com/openrelayxyz/cardinal-evm/state" "github.com/openrelayxyz/cardinal-evm/types" @@ -15,6 +18,7 @@ import ( rpc "github.com/openrelayxyz/cardinal-rpc" ctypes "github.com/openrelayxyz/cardinal-types" "github.com/openrelayxyz/cardinal-types/hexutil" + "github.com/openrelayxyz/plugeth-utils/core" ptypes "github.com/openrelayxyz/plugeth-utils/restricted/types" ) @@ -49,7 +53,6 @@ type simOpts struct { // simBlock is a batch of calls to be simulated sequentially. type simBlock struct { - BlockOverrides *BlockOverrides StateOverrides *StateOverride Calls []TransactionArgs } @@ -71,7 +74,7 @@ type simulator struct { type simBlockResult struct { fullTx bool chainConfig *params.ChainConfig - Block *ptypes.Block + Block *types.Block Calls []simCallResult // senders is a map of transaction hashes to their senders. senders map[ctypes.Hash]common.Address @@ -86,7 +89,7 @@ type simCallResult struct { Error *callError `json:"error,omitempty"` } -func (s *simulator) execute(ctx *rpc.CallContext, blocks []simBlock) ([]*simBlockResult, error){ +func (s *simulator) execute(ctx *rpc.CallContext, blocks []simBlock) ([]*simBlockResult, error) { if err := ctx.Context().Err(); err != nil { return nil, err } @@ -99,83 +102,199 @@ func (s *simulator) execute(ctx *rpc.CallContext, blocks []simBlock) ([]*simBloc execCtx, cancel = context.WithCancel(ctx.Context()) } defer cancel() - - santizedblocks, err := s.sanitizeChain(blocks) - if err != nil { - return nil, err - } - // yet to implement - headers, err := s.makeHeaders(santizedblocks) - if err != nil { - return nil, err + var ( + results = make([]*simBlockResult, len(blocks)) + headers = make([]*types.Header, 0, len(blocks)) + parent = s.base + ) + + for bi, block := range blocks { + header := types.CopyHeader(s.base) + header.Number = new(big.Int).Add(s.base.Number, big.NewInt(int64(bi+1))) + header.ParentHash = parent.Hash() + header.Time = s.base.Time + uint64(bi+1)*12 + + if err := execCtx.Err(); err != nil { + return nil, err + } + result, callResults, senders, err := s.processBlock(ctx, &block, header, parent, headers, s.timeout) + if err != nil { + return nil, err + } + results[bi] = &simBlockResult{fullTx: s.fullTx, chainConfig: s.chainConfig, Block: result, Calls: callResults, senders: senders} + headers = append(headers, result.Header()) + parent = result.Header() + + s.state.Finalise() } + return results, nil } -// sanitizeChain checks the chain integrity. Specifically it checks that -// block numbers and timestamp are strictly increasing, setting default values -// when necessary. Gaps in block numbers are filled with empty blocks. -func (s *simulator) sanitizeChain(blocks []simBlock) ([]simBlock, error) { +func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, parent *types.Header, headers []*types.Header, timeout time.Duration) (*types.Block, []simCallResult, map[ctypes.Hash]common.Address, error) { + // Set header fields that depend only on parent block. + // Parent hash is needed for evm.GetHashFn to work. + header.ParentHash = parent.Hash() + + if s.chainConfig.IsLondon(header.Number) { + if header.BaseFee == nil { + if s.validate { + header.BaseFee = eip1559.CalcBaseFee(s.chainConfig, parent) + } else { + header.BaseFee = big.NewInt(0) + } + } + } + if s.chainConfig.IsCancun(header.Number, new(big.Int).SetUint64(header.Time)) { + var excess uint64 + if s.chainConfig.IsCancun(parent.Number, new(big.Int).SetUint64(parent.Time)) { + parentExcess := uint64(0) + if parent.ExcessBlobGas != nil { + parentExcess = *parent.ExcessBlobGas + } + parentBlobGasUsed := uint64(0) + if parent.BlobGasUsed != nil { + parentBlobGasUsed = *parent.BlobGasUsed + } + excess = eip4844.CalcExcessBlobGas(parentExcess, parentBlobGasUsed) + } + header.ExcessBlobGas = &excess + } + + if block.StateOverrides != nil { + if err := block.StateOverrides.Apply(s.state); err != nil { + return nil, nil, nil, err + } + } + var ( - res = make([]simBlock, 0, len(blocks)) - base = s.base - prevNumber = base.Number - prevTimestamp = base.Time + blobGasUsed uint64 + gasUsed uint64 + txes = make([]*types.Transaction, len(block.Calls)) + callResults = make([]simCallResult, len(block.Calls)) + receipts = make([]*ptypes.Receipt, len(block.Calls)) + senders = make(map[ctypes.Hash]common.Address) + allLogs []*types.Log ) - for _, block := range blocks { - if block.BlockOverrides == nil { - block.BlockOverrides = &BlockOverrides{} + getHashFn := func(n uint64) ctypes.Hash { + for _, h := range headers { + if h.Number.Uint64() == n { + return h.Hash() + } } + if parent.Number.Uint64() == n { + return parent.Hash() + } + return ctypes.Hash{} + } - if block.BlockOverrides.Number == nil { - n := new(big.Int).Add(prevNumber, big.NewInt(1)) - block.BlockOverrides.Number = (*hexutil.Big)(n) + for i, call := range block.Calls { + if err := ctx.Context().Err(); err != nil { + return nil, nil, nil, err } - diff := new(big.Int).Sub(block.BlockOverrides.Number.ToInt(), prevNumber) - if diff.Cmp(big.NewInt(0)) <= 0 { - return nil, fmt.Errorf("block numbers must be in order: %d <= %d", - block.BlockOverrides.Number.ToInt().Uint64(), prevNumber.Uint64()) + if err := call.setDefaults(ctx, s.evmFn, s.state, header, vm.BlockNumberOrHashWithHash(header.Hash(), false)); err != nil { + return nil, nil, nil, err + } + if gasUsed+uint64(*call.Gas) > header.GasLimit { + return nil, nil, nil, fmt.Errorf("block gas limit exceeded") } + tx := call.ToTransaction(types.DynamicFeeTxType) + txes[i] = tx + senders[tx.Hash()] = call.from() - if total := new(big.Int).Sub(block.BlockOverrides.Number.ToInt(), base.Number); total.Cmp(big.NewInt(maxSimulateBlocks)) > 0 { - return nil, fmt.Errorf("too many blocks") + evm := s.evmFn(s.state, &vm.Config{ + NoBaseFee: !s.validate, + }, call.from(), call.GasPrice.ToInt()) + + if evm.Context.GetHash == nil { + evm.Context.GetHash = getHashFn } - // Fill gaps with empty blocks - if diff.Cmp(big.NewInt(1)) > 0 { - gap := new(big.Int).Sub(diff, big.NewInt(1)) - for i := uint64(0); i < gap.Uint64(); i++ { - n := new(big.Int).Add(prevNumber, big.NewInt(int64(i+1))) - t := prevTimestamp + timestampIncrement - emptyBlock := simBlock{ - BlockOverrides: &BlockOverrides{ - Number: (*hexutil.Big)(n), - Time: (*hexutil.Uint64)(&t), - }, - } - prevTimestamp = t - res = append(res, emptyBlock) + if s.chainConfig.IsPrague(header.Number, new(big.Int).SetUint64(header.Time)) { + // Process parent block hash for EIP-2935 + if header.ParentHash != (ctypes.Hash{}) { + evm.StateDB.SetState( + params.HistoryStorageAddress, + ctypes.BigToHash(header.Number), + header.ParentHash, + ) } } - prevNumber = block.BlockOverrides.Number.ToInt() - var t uint64 - if block.BlockOverrides.Time == nil { - t = prevTimestamp + timestampIncrement - block.BlockOverrides.Time = (*hexutil.Uint64)(&t) + msg, err := call.ToMessage(s.gp.Gas(), header.BaseFee) + if err != nil { + return nil, nil, nil, err + } + result, err := ApplyMessage(evm, msg, s.gp) + if err != nil { + return nil, nil, nil, fmt.Errorf("transaction execution failed: %v", err) + } + gasUsed += result.UsedGas + + var root []byte + if s.chainConfig.IsByzantium(header.Number) { + s.state.Finalise() } else { - t = uint64(*block.BlockOverrides.Time) - if t <= prevTimestamp { - return nil, fmt.Errorf("block timestamps must be in order: %d <= %d", t, prevTimestamp) + root = nil + } + header.Root = s.base.Root + + receipt := &ptypes.Receipt{ + Type: tx.Type(), + PostState: root, + Status: ptypes.ReceiptStatusSuccessful, + CumulativeGasUsed: gasUsed, + Bloom: ptypes.Bloom{}, + TxHash: core.Hash(tx.Hash()), + GasUsed: result.UsedGas, + TransactionIndex: uint(i), + // Logs: result.Logs, + } + if tx.To() == nil { + receipt.ContractAddress = core.Address(crypto.CreateAddress(*call.From, tx.Nonce())) + } + if result.Failed() { + receipt.Status = ptypes.ReceiptStatusFailed + } + + // Handle blob gas for Cancun + // if s.chainConfig.IsCancun(header.Number, new(big.Int).SetUint64(header.Time)) { + // if tx.Type() == types.BlobTxType { + // receipt.BlobGasUsed = tx.BlobGas() + // blobGasUsed += receipt.BlobGasUsed + // } + // } + + receipts[i] = receipt + + callRes := simCallResult{ + GasUsed: hexutil.Uint64(result.UsedGas), + ReturnValue: result.ReturnData, + // Logs: result.Logs, + } + if result.Failed() { + callRes.Status = hexutil.Uint64(ptypes.ReceiptStatusFailed) + if errors.Is(result.Err, vm.ErrExecutionReverted) { + // If the result contains a revert reason, try to unpack it. + revertErr := newRevertError(result) + callRes.Error = &callError{Message: revertErr.Error(), Code: errCodeReverted, Data: revertErr.ErrorData().(string)} + } else { + callRes.Error = &callError{Message: result.Err.Error(), Code: errCodeVMError} } + } else { + callRes.Status = hexutil.Uint64(ptypes.ReceiptStatusSuccessful) + allLogs = append(allLogs, callRes.Logs...) } - prevTimestamp = t - res = append(res, block) + callResults[i] = callRes + s.state.Finalise() } - return res, nil -} + header.GasUsed = gasUsed + if s.chainConfig.IsCancun(header.Number, new(big.Int).SetUint64(header.Time)) { + header.BlobGasUsed = &blobGasUsed + } +} diff --git a/api/transaction_args.go b/api/transaction_args.go index 6608c5e90..0e948de7e 100644 --- a/api/transaction_args.go +++ b/api/transaction_args.go @@ -24,10 +24,13 @@ import ( "github.com/openrelayxyz/cardinal-evm/common" "github.com/openrelayxyz/cardinal-evm/common/math" "github.com/openrelayxyz/cardinal-evm/state" + "github.com/openrelayxyz/cardinal-evm/crypto/kzg4844" "github.com/openrelayxyz/cardinal-evm/types" "github.com/openrelayxyz/cardinal-evm/vm" "github.com/openrelayxyz/cardinal-rpc" "github.com/openrelayxyz/cardinal-types/hexutil" + "github.com/holiman/uint256" + ctypes "github.com/openrelayxyz/cardinal-types" ) // TransactionArgs represents the arguments to construct a new transaction @@ -52,6 +55,15 @@ type TransactionArgs struct { AccessList *types.AccessList `json:"accessList,omitempty"` ChainID *hexutil.Big `json:"chainId,omitempty"` + // For BlobTxType + BlobFeeCap *hexutil.Big `json:"maxFeePerBlobGas"` + BlobHashes []ctypes.Hash `json:"blobVersionedHashes,omitempty"` + + // For BlobTxType transactions with blob sidecar + Blobs []kzg4844.Blob `json:"blobs"` + Commitments []kzg4844.Commitment `json:"commitments"` + Proofs []kzg4844.Proof `json:"proofs"` + AuthList []types.Authorization `json:"authorizationList,omitempty"` } @@ -178,6 +190,109 @@ func (args *TransactionArgs) setDefaults(ctx *rpc.CallContext, getEVM func(state return nil } +// ToTransaction converts the arguments to a transaction. +// This assumes that setDefaults has been called. +func (args *TransactionArgs) ToTransaction(defaultType int) *types.Transaction { + usedType := types.LegacyTxType + switch { + case args.AuthList != nil || defaultType == types.SetCodeTxType: + usedType = types.SetCodeTxType + case args.BlobHashes != nil || defaultType == types.BlobTxType: + usedType = types.BlobTxType + case args.MaxFeePerGas != nil || defaultType == types.DynamicFeeTxType: + usedType = types.DynamicFeeTxType + case args.AccessList != nil || defaultType == types.AccessListTxType: + usedType = types.AccessListTxType + } + // Make it possible to default to newer tx, but use legacy if gasprice is provided + if args.GasPrice != nil { + usedType = types.LegacyTxType + } + var data types.TxData + switch usedType { + case types.SetCodeTxType: + al := types.AccessList{} + if args.AccessList != nil { + al = *args.AccessList + } + authList := []types.Authorization{} + if args.AuthList != nil { + authList = args.AuthList + } + data = &types.SetCodeTx{ + To: *args.To, + ChainID: uint256.MustFromBig(args.ChainID.ToInt()), + Nonce: uint64(*args.Nonce), + Gas: uint64(*args.Gas), + GasFeeCap: uint256.MustFromBig((*big.Int)(args.MaxFeePerGas)), + GasTipCap: uint256.MustFromBig((*big.Int)(args.MaxPriorityFeePerGas)), + Value: uint256.MustFromBig((*big.Int)(args.Value)), + Data: args.data(), + AccessList: al, + AuthList: authList, + } + + case types.BlobTxType: + al := types.AccessList{} + if args.AccessList != nil { + al = *args.AccessList + } + data = &types.BlobTx{ + To: *args.To, + ChainID: uint256.MustFromBig((*big.Int)(args.ChainID)), + Nonce: uint64(*args.Nonce), + Gas: uint64(*args.Gas), + GasFeeCap: uint256.MustFromBig((*big.Int)(args.MaxFeePerGas)), + GasTipCap: uint256.MustFromBig((*big.Int)(args.MaxPriorityFeePerGas)), + Value: uint256.MustFromBig((*big.Int)(args.Value)), + Data: args.data(), + AccessList: al, + BlobHashes: args.BlobHashes, + BlobFeeCap: uint256.MustFromBig((*big.Int)(args.BlobFeeCap)), + } + + case types.DynamicFeeTxType: + al := types.AccessList{} + if args.AccessList != nil { + al = *args.AccessList + } + data = &types.DynamicFeeTx{ + To: args.To, + ChainID: (*big.Int)(args.ChainID), + Nonce: uint64(*args.Nonce), + Gas: uint64(*args.Gas), + GasFeeCap: (*big.Int)(args.MaxFeePerGas), + GasTipCap: (*big.Int)(args.MaxPriorityFeePerGas), + Value: (*big.Int)(args.Value), + Data: args.data(), + AccessList: al, + } + + case types.AccessListTxType: + data = &types.AccessListTx{ + To: args.To, + ChainID: (*big.Int)(args.ChainID), + Nonce: uint64(*args.Nonce), + Gas: uint64(*args.Gas), + GasPrice: (*big.Int)(args.GasPrice), + Value: (*big.Int)(args.Value), + Data: args.data(), + AccessList: *args.AccessList, + } + + default: + data = &types.LegacyTx{ + To: args.To, + Nonce: uint64(*args.Nonce), + Gas: uint64(*args.Gas), + GasPrice: (*big.Int)(args.GasPrice), + Value: (*big.Int)(args.Value), + Data: args.data(), + } + } + return types.NewTx(data) +} + // // // setDefaults fills in default values for unspecified tx fields. // func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error { diff --git a/eips/eip1559/eip.go b/eips/eip1559/eip.go new file mode 100644 index 000000000..f81901dcc --- /dev/null +++ b/eips/eip1559/eip.go @@ -0,0 +1,71 @@ +// Copyright 2021 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 eip1559 + +import ( + "math/big" + + "github.com/openrelayxyz/cardinal-evm/types" + "github.com/openrelayxyz/cardinal-evm/common" + "github.com/openrelayxyz/cardinal-evm/params" +) + + +// CalcBaseFee calculates the basefee of the header. +func CalcBaseFee(config *params.ChainConfig, parent *types.Header) *big.Int { + // If the current block is the first EIP-1559 block, return the InitialBaseFee. + if !config.IsLondon(parent.Number) { + return new(big.Int).SetUint64(params.InitialBaseFee) + } + + parentGasTarget := parent.GasLimit / config.ElasticityMultiplier() + // If the parent gasUsed is the same as the target, the baseFee remains unchanged. + if parent.GasUsed == parentGasTarget { + return new(big.Int).Set(parent.BaseFee) + } + + var ( + num = new(big.Int) + denom = new(big.Int) + ) + + if parent.GasUsed > parentGasTarget { + // If the parent block used more gas than its target, the baseFee should increase. + // max(1, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator) + num.SetUint64(parent.GasUsed - parentGasTarget) + num.Mul(num, parent.BaseFee) + num.Div(num, denom.SetUint64(parentGasTarget)) + num.Div(num, denom.SetUint64(config.BaseFeeChangeDenominator())) + if num.Cmp(common.Big1) < 0 { + return num.Add(parent.BaseFee, common.Big1) + } + return num.Add(parent.BaseFee, num) + } else { + // Otherwise if the parent block used less gas than its target, the baseFee should decrease. + // max(0, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator) + num.SetUint64(parentGasTarget - parent.GasUsed) + num.Mul(num, parent.BaseFee) + num.Div(num, denom.SetUint64(parentGasTarget)) + num.Div(num, denom.SetUint64(config.BaseFeeChangeDenominator())) + + baseFee := num.Sub(parent.BaseFee, num) + if baseFee.Cmp(common.Big0) < 0 { + baseFee = common.Big0 + } + return baseFee + } +} diff --git a/params/protocol_params.go b/params/protocol_params.go index 312895da4..c4a34f5cb 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -16,7 +16,11 @@ package params -import "math/big" +import ( + "math/big" + + "github.com/openrelayxyz/cardinal-evm/common" +) const ( GasLimitBoundDivisor uint64 = 1024 // The bound divisor of the gas limit, used in update calculations. @@ -195,3 +199,9 @@ var ( MinimumDifficulty = big.NewInt(131072) // The minimum that the difficulty may ever be. DurationLimit = big.NewInt(13) // The decision boundary on the blocktime duration used to determine whether difficulty should go up or not. ) + +var ( + // EIP-2935 - Serve historical block hashes from state + HistoryStorageAddress = common.HexToAddress("0x0000F90827F1C53a10cb7A02335B175320002935") + HistoryStorageCode = common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe14604657602036036042575f35600143038111604257611fff81430311604257611fff9006545f5260205ff35b5f5ffd5b5f35611fff60014303065500") +) \ No newline at end of file diff --git a/types/block.go b/types/block.go index 1b8999e46..c05fa515c 100644 --- a/types/block.go +++ b/types/block.go @@ -1,38 +1,103 @@ package types import ( - "github.com/openrelayxyz/cardinal-evm/common" - "github.com/openrelayxyz/cardinal-types" "math/big" + "sync/atomic" + "time" + + "github.com/openrelayxyz/cardinal-evm/common" + types "github.com/openrelayxyz/cardinal-types" ) type Header struct { - ParentHash types.Hash - UncleHash types.Hash - Coinbase common.Address - Root types.Hash - TxHash types.Hash - ReceiptHash types.Hash - Bloom [256]byte - Difficulty *big.Int - Number *big.Int - GasLimit uint64 - GasUsed uint64 - Time uint64 - Extra []byte - MixDigest types.Hash - Nonce [8]byte - BaseFee *big.Int `rlp:"optional"` - WithdrawalsHash *types.Hash `rlp:"optional"` + ParentHash types.Hash + UncleHash types.Hash + Coinbase common.Address + Root types.Hash + TxHash types.Hash + ReceiptHash types.Hash + Bloom [256]byte + Difficulty *big.Int + Number *big.Int + GasLimit uint64 + GasUsed uint64 + Time uint64 + Extra []byte + MixDigest types.Hash + Nonce [8]byte + BaseFee *big.Int `rlp:"optional"` + WithdrawalsHash *types.Hash `rlp:"optional"` // BlobGasUsed was added by EIP-4844 and is ignored in legacy headers. - BlobGasUsed *uint64 `rlp:"optional"` + BlobGasUsed *uint64 `rlp:"optional"` // ExcessBlobGas was added by EIP-4844 and is ignored in legacy headers. - ExcessBlobGas *uint64 `rlp:"optional"` + ExcessBlobGas *uint64 `rlp:"optional"` // BeaconRoot was added by EIP-4788 and is ignored in legacy headers. - BeaconRoot *types.Hash `rlp:"optional"` + BeaconRoot *types.Hash `rlp:"optional"` // RequestsHash was added by EIP-7685 and is ignored in legacy headers. - RequestsHash *types.Hash `json:"requestsHash" rlp:"optional"` + RequestsHash *types.Hash `json:"requestsHash" rlp:"optional"` +} + +type Block struct { + header *Header + uncles []*Header + transactions Transactions + withdrawals Withdrawals + + // caches + hash atomic.Pointer[types.Hash] + size atomic.Uint64 + + // These fields are used by package eth to track + // inter-peer block relay. + ReceivedAt time.Time + ReceivedFrom interface{} +} + +// Header returns the block header (as a copy). +func (b *Block) Header() *Header { + return CopyHeader(b.header) +} + +// CopyHeader creates a deep copy of a block header. +func CopyHeader(h *Header) *Header { + cpy := *h + if cpy.Difficulty = new(big.Int); h.Difficulty != nil { + cpy.Difficulty.Set(h.Difficulty) + } + if cpy.Number = new(big.Int); h.Number != nil { + cpy.Number.Set(h.Number) + } + if h.BaseFee != nil { + cpy.BaseFee = new(big.Int).Set(h.BaseFee) + } + if len(h.Extra) > 0 { + cpy.Extra = make([]byte, len(h.Extra)) + copy(cpy.Extra, h.Extra) + } + if h.WithdrawalsHash != nil { + cpy.WithdrawalsHash = new(types.Hash) + *cpy.WithdrawalsHash = *h.WithdrawalsHash + } + if h.ExcessBlobGas != nil { + cpy.ExcessBlobGas = new(uint64) + *cpy.ExcessBlobGas = *h.ExcessBlobGas + } + if h.BlobGasUsed != nil { + cpy.BlobGasUsed = new(uint64) + *cpy.BlobGasUsed = *h.BlobGasUsed + } + if h.RequestsHash != nil { + cpy.RequestsHash = new(types.Hash) + *cpy.RequestsHash = *h.RequestsHash + } + return &cpy +} + +// Hash returns the block hash of the header, which is simply the keccak256 hash of its +// RLP encoding. +func (h *Header) Hash() types.Hash { + return rlpHash(h) } From 075cd377a473fd9b4178972d24bc3cbc321de848 Mon Sep 17 00:00:00 2001 From: Jesse Date: Thu, 7 Aug 2025 16:21:55 +0100 Subject: [PATCH 03/72] complete simulateV1 processblock function, import receipt type --- api/eth.go | 30 +++++++- api/simulate.go | 89 ++++++++++++++--------- state/interface.go | 2 + state/statedb.go | 22 ++++++ types/block.go | 15 +++- types/bloom9.go | 176 +++++++++++++++++++++++++++++++++++++++++++++ types/receipt.go | 166 ++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 465 insertions(+), 35 deletions(-) create mode 100644 types/bloom9.go create mode 100644 types/receipt.go diff --git a/api/eth.go b/api/eth.go index a3f29f954..5ce55d0f9 100644 --- a/api/eth.go +++ b/api/eth.go @@ -195,6 +195,27 @@ func (diff *StateOverride) Apply(state state.StateDB) error { return nil } +func applyMessageWithEVM(ctx *rpc.CallContext, evm *vm.EVM, msg *Msg, timeout time.Duration, gp *GasPool) (*ExecutionResult, error) { + // Wait for the context to be done and cancel the evm. Even if the + // EVM has finished, cancelling may be done (repeatedly) + go func() { + <-ctx.Context().Done() + evm.Cancel() + }() + + // Execute the message. + result, err := ApplyMessage(evm, msg, gp) + + // If the timer caused an abort, return an appropriate error message + if evm.Cancelled() { + return nil, fmt.Errorf("execution aborted (timeout = %v)", timeout) + } + if err != nil { + return result, fmt.Errorf("err: %w (supplied gas %d)", err, msg.gasLimit) + } + return result, nil +} + func DoCall(cctx *rpc.CallContext, getEVM func(state.StateDB, *vm.Config, common.Address, *big.Int) *vm.EVM, args TransactionArgs, prevState *PreviousState, blockNrOrHash vm.BlockNumberOrHash, overrides *StateOverride, timeout time.Duration, globalGasCap uint64) (*ExecutionResult, *PreviousState, error) { defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) if prevState == nil || prevState.header == nil || prevState.state == nil { @@ -339,6 +360,13 @@ func (s *PublicBlockChainAPI) Call(ctx *rpc.CallContext, args TransactionArgs, b return res, err } +// SimulateV1 executes series of transactions on top of a base state. +// The transactions are packed into blocks. For each block, block header +// fields can be overridden. The state can also be overridden prior to +// execution of each block. +// +// Note, this function doesn't make any changes in the state/blockchain and is +// useful to execute and retrieve values. func (s *PublicBlockChainAPI) SimulateV1(ctx *rpc.CallContext, opts simOpts, blockNrOrHash *vm.BlockNumberOrHash) ([]*simBlockResult, error) { if len(opts.BlockStateCalls) == 0 { return nil, &invalidParamsError{message: "empty input"} @@ -362,7 +390,7 @@ func (s *PublicBlockChainAPI) SimulateV1(ctx *rpc.CallContext, opts simOpts, blo sim := &simulator{ timeout: 30 * time.Second, state: statedb.Copy(), - base: baseHeader, + base: types.CopyHeader(baseHeader), chainConfig: chaincfg, // Each tx and all the series of txes shouldn't consume more gas than cap gp: new(GasPool).AddGas(gasCap), diff --git a/api/simulate.go b/api/simulate.go index 89dd9b61c..8196e1353 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -2,9 +2,9 @@ package api import ( "context" + "errors" "fmt" "math/big" - "errors" "time" "github.com/openrelayxyz/cardinal-evm/common" @@ -18,8 +18,6 @@ import ( rpc "github.com/openrelayxyz/cardinal-rpc" ctypes "github.com/openrelayxyz/cardinal-types" "github.com/openrelayxyz/cardinal-types/hexutil" - "github.com/openrelayxyz/plugeth-utils/core" - ptypes "github.com/openrelayxyz/plugeth-utils/restricted/types" ) const ( @@ -89,6 +87,23 @@ type simCallResult struct { Error *callError `json:"error,omitempty"` } +type simpleTrieHasher struct { + data []byte +} + +func (h *simpleTrieHasher) Reset() { + h.data = h.data[:0] +} + +func (h *simpleTrieHasher) Update(key, value []byte) { + h.data = append(h.data, key...) + h.data = append(h.data, value...) +} + +func (h *simpleTrieHasher) Hash() ctypes.Hash { + return crypto.Keccak256Hash(h.data) +} + func (s *simulator) execute(ctx *rpc.CallContext, blocks []simBlock) ([]*simBlockResult, error) { if err := ctx.Context().Err(); err != nil { return nil, err @@ -113,7 +128,9 @@ func (s *simulator) execute(ctx *rpc.CallContext, blocks []simBlock) ([]*simBloc header := types.CopyHeader(s.base) header.Number = new(big.Int).Add(s.base.Number, big.NewInt(int64(bi+1))) header.ParentHash = parent.Hash() - header.Time = s.base.Time + uint64(bi+1)*12 + header.Time = parent.Time + uint64(bi+1)*12 + + s.gp = new(GasPool).AddGas(header.GasLimit) if err := execCtx.Err(); err != nil { return nil, err @@ -136,7 +153,6 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, // Set header fields that depend only on parent block. // Parent hash is needed for evm.GetHashFn to work. header.ParentHash = parent.Hash() - if s.chainConfig.IsLondon(header.Number) { if header.BaseFee == nil { if s.validate { @@ -162,6 +178,7 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, header.ExcessBlobGas = &excess } + // State overrides are applied prior to execution of a block if block.StateOverrides != nil { if err := block.StateOverrides.Apply(s.state); err != nil { return nil, nil, nil, err @@ -173,7 +190,7 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, gasUsed uint64 txes = make([]*types.Transaction, len(block.Calls)) callResults = make([]simCallResult, len(block.Calls)) - receipts = make([]*ptypes.Receipt, len(block.Calls)) + receipts = make([]*types.Receipt, len(block.Calls)) senders = make(map[ctypes.Hash]common.Address) allLogs []*types.Log ) @@ -194,7 +211,6 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, if err := ctx.Context().Err(); err != nil { return nil, nil, nil, err } - if err := call.setDefaults(ctx, s.evmFn, s.state, header, vm.BlockNumberOrHashWithHash(header.Hash(), false)); err != nil { return nil, nil, nil, err } @@ -204,6 +220,7 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, tx := call.ToTransaction(types.DynamicFeeTxType) txes[i] = tx senders[tx.Hash()] = call.from() + s.state.SetTxContext(tx.Hash(), i) evm := s.evmFn(s.state, &vm.Config{ NoBaseFee: !s.validate, @@ -213,14 +230,10 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, evm.Context.GetHash = getHashFn } - if s.chainConfig.IsPrague(header.Number, new(big.Int).SetUint64(header.Time)) { + if s.chainConfig.IsPrague(header.Number, new(big.Int).SetUint64(header.Time)) { // Process parent block hash for EIP-2935 if header.ParentHash != (ctypes.Hash{}) { - evm.StateDB.SetState( - params.HistoryStorageAddress, - ctypes.BigToHash(header.Number), - header.ParentHash, - ) + evm.StateDB.SetState(params.HistoryStorageAddress,ctypes.BigToHash(header.Number), header.ParentHash,) } } @@ -228,11 +241,10 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, if err != nil { return nil, nil, nil, err } - result, err := ApplyMessage(evm, msg, s.gp) + result, err := applyMessageWithEVM(ctx, evm, &msg, timeout, s.gp) if err != nil { return nil, nil, nil, fmt.Errorf("transaction execution failed: %v", err) } - gasUsed += result.UsedGas var root []byte if s.chainConfig.IsByzantium(header.Number) { @@ -240,43 +252,44 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, } else { root = nil } + gasUsed += result.UsedGas header.Root = s.base.Root - receipt := &ptypes.Receipt{ + receipt := &types.Receipt{ Type: tx.Type(), PostState: root, - Status: ptypes.ReceiptStatusSuccessful, + Status: types.ReceiptStatusSuccessful, CumulativeGasUsed: gasUsed, - Bloom: ptypes.Bloom{}, - TxHash: core.Hash(tx.Hash()), + TxHash: tx.Hash(), GasUsed: result.UsedGas, TransactionIndex: uint(i), - // Logs: result.Logs, } + receipt.Logs = s.state.GetLogs(tx.Hash(), header.Number.Uint64(), header.Hash()) + receipt.Bloom = types.CreateBloom([]*types.Receipt{receipt}) if tx.To() == nil { - receipt.ContractAddress = core.Address(crypto.CreateAddress(*call.From, tx.Nonce())) + receipt.ContractAddress = crypto.CreateAddress(*call.From, tx.Nonce()) } if result.Failed() { - receipt.Status = ptypes.ReceiptStatusFailed + receipt.Status = types.ReceiptStatusFailed } // Handle blob gas for Cancun - // if s.chainConfig.IsCancun(header.Number, new(big.Int).SetUint64(header.Time)) { - // if tx.Type() == types.BlobTxType { - // receipt.BlobGasUsed = tx.BlobGas() - // blobGasUsed += receipt.BlobGasUsed - // } - // } + if s.chainConfig.IsCancun(header.Number, new(big.Int).SetUint64(header.Time)) { + if tx.Type() == types.BlobTxType { + receipt.BlobGasUsed = tx.BlobGas() + blobGasUsed += receipt.BlobGasUsed + } + } receipts[i] = receipt callRes := simCallResult{ - GasUsed: hexutil.Uint64(result.UsedGas), + GasUsed: hexutil.Uint64(result.UsedGas), ReturnValue: result.ReturnData, - // Logs: result.Logs, + Logs: receipt.Logs, } if result.Failed() { - callRes.Status = hexutil.Uint64(ptypes.ReceiptStatusFailed) + callRes.Status = hexutil.Uint64(types.ReceiptStatusFailed) if errors.Is(result.Err, vm.ErrExecutionReverted) { // If the result contains a revert reason, try to unpack it. revertErr := newRevertError(result) @@ -285,8 +298,8 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, callRes.Error = &callError{Message: result.Err.Error(), Code: errCodeVMError} } } else { - callRes.Status = hexutil.Uint64(ptypes.ReceiptStatusSuccessful) - allLogs = append(allLogs, callRes.Logs...) + callRes.Status = hexutil.Uint64(types.ReceiptStatusSuccessful) + allLogs = append(allLogs, receipt.Logs...) } callResults[i] = callRes s.state.Finalise() @@ -297,4 +310,14 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, header.BlobGasUsed = &blobGasUsed } + hasher := &simpleTrieHasher{} + + header.TxHash = types.DeriveSha(types.Transactions(txes), hasher) + header.ReceiptHash = types.DeriveSha(types.Receipts(receipts), hasher) + header.Bloom = types.CreateBloom(receipts) + header.Root = s.base.Root + + blck := types.NewBlockWithHeader(header) + + return blck, callResults, senders, nil } diff --git a/state/interface.go b/state/interface.go index b31b8a96f..3ccd4fb6a 100644 --- a/state/interface.go +++ b/state/interface.go @@ -66,6 +66,8 @@ type StateDB interface { Copy() StateDB ALCalcCopy() StateDB Finalise() + GetLogs(hash ctypes.Hash, blockNumber uint64, blockHash ctypes.Hash) []*types.Log + SetTxContext(thash ctypes.Hash, ti int) // ForEachStorage(common.Address, func(ctypes.Hash, ctypes.Hash) bool) error } diff --git a/state/statedb.go b/state/statedb.go index 192bf3124..599553666 100644 --- a/state/statedb.go +++ b/state/statedb.go @@ -43,6 +43,9 @@ type stateDB struct { refund uint64 accessList *accessList alcalc bool + logs map[ctypes.Hash][]*types.Log + thash ctypes.Hash + txIndex int } func NewStateDB(tx storage.Transaction, chainid int64) StateDB { @@ -356,3 +359,22 @@ func (sdb *stateDB) AddPreimage(ctypes.Hash, []byte) { } // func (sdb *stateDB) ForEachStorage(addr common.Address, func(ctypes.Hash, ctypes.Hash) bool) error {return nil} + +// GetLogs returns the logs matching the specified transaction hash, and annotates +// them with the given blockNumber and blockHash. +func (s *stateDB) GetLogs(hash ctypes.Hash, blockNumber uint64, blockHash ctypes.Hash) []*types.Log { + logs := s.logs[hash] + for _, l := range logs { + l.BlockNumber = blockNumber + l.BlockHash = blockHash + } + return logs +} + +// SetTxContext sets the current transaction hash and index which are +// used when the EVM emits new state logs. It should be invoked before +// transaction execution. +func (s *stateDB) SetTxContext(thash ctypes.Hash, ti int) { + s.thash = thash + s.txIndex = ti +} diff --git a/types/block.go b/types/block.go index c05fa515c..654dcac6d 100644 --- a/types/block.go +++ b/types/block.go @@ -6,7 +6,13 @@ import ( "time" "github.com/openrelayxyz/cardinal-evm/common" - types "github.com/openrelayxyz/cardinal-types" + "github.com/openrelayxyz/cardinal-types" +) + +var ( + // EmptyUncleHash is the known hash of the empty uncle set. + EmptyUncleHash = rlpHash([]*Header(nil)) // 1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347 + ) type Header struct { @@ -101,3 +107,10 @@ func CopyHeader(h *Header) *Header { func (h *Header) Hash() types.Hash { return rlpHash(h) } + +// NewBlockWithHeader creates a block with the given header data. The +// header data is copied, changes to header and to the field values +// will not affect the block. +func NewBlockWithHeader(header *Header) *Block { + return &Block{header: CopyHeader(header)} +} \ No newline at end of file diff --git a/types/bloom9.go b/types/bloom9.go new file mode 100644 index 000000000..53f285c66 --- /dev/null +++ b/types/bloom9.go @@ -0,0 +1,176 @@ +// Copyright 2014 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 types + +import ( + "encoding/binary" + "fmt" + "math/big" + + "github.com/openrelayxyz/cardinal-types/hexutil" + "github.com/openrelayxyz/cardinal-evm/crypto" +) + +type bytesBacked interface { + Bytes() []byte +} + +const ( + // BloomByteLength represents the number of bytes used in a header log bloom. + BloomByteLength = 256 + + // BloomBitLength represents the number of bits used in a header log bloom. + BloomBitLength = 8 * BloomByteLength +) + +// Bloom represents a 2048 bit bloom filter. +type Bloom [BloomByteLength]byte + +// BytesToBloom converts a byte slice to a bloom filter. +// It panics if b is not of suitable size. +func BytesToBloom(b []byte) Bloom { + var bloom Bloom + bloom.SetBytes(b) + return bloom +} + +// SetBytes sets the content of b to the given bytes. +// It panics if d is not of suitable size. +func (b *Bloom) SetBytes(d []byte) { + if len(b) < len(d) { + panic(fmt.Sprintf("bloom bytes too big %d %d", len(b), len(d))) + } + copy(b[BloomByteLength-len(d):], d) +} + +// Add adds d to the filter. Future calls of Test(d) will return true. +func (b *Bloom) Add(d []byte) { + b.add(d, make([]byte, 6)) +} + +// add is internal version of Add, which takes a scratch buffer for reuse (needs to be at least 6 bytes) +func (b *Bloom) add(d []byte, buf []byte) { + i1, v1, i2, v2, i3, v3 := bloomValues(d, buf) + b[i1] |= v1 + b[i2] |= v2 + b[i3] |= v3 +} + +// Big converts b to a big integer. +// Note: Converting a bloom filter to a big.Int and then calling GetBytes +// does not return the same bytes, since big.Int will trim leading zeroes +func (b Bloom) Big() *big.Int { + return new(big.Int).SetBytes(b[:]) +} + +// Bytes returns the backing byte slice of the bloom +func (b Bloom) Bytes() []byte { + return b[:] +} + +// Test checks if the given topic is present in the bloom filter +func (b Bloom) Test(topic []byte) bool { + i1, v1, i2, v2, i3, v3 := bloomValues(topic, make([]byte, 6)) + return v1 == v1&b[i1] && + v2 == v2&b[i2] && + v3 == v3&b[i3] +} + +// MarshalText encodes b as a hex string with 0x prefix. +func (b Bloom) MarshalText() ([]byte, error) { + return hexutil.Bytes(b[:]).MarshalText() +} + +// UnmarshalText b as a hex string with 0x prefix. +func (b *Bloom) UnmarshalText(input []byte) error { + return hexutil.UnmarshalFixedText("Bloom", input, b[:]) +} + +// CreateBloom creates a bloom filter out of the give Receipts (+Logs) +func CreateBloom(receipts Receipts) Bloom { + buf := make([]byte, 6) + var bin Bloom + for _, receipt := range receipts { + for _, log := range receipt.Logs { + bin.add(log.Address.Bytes(), buf) + for _, b := range log.Topics { + bin.add(b[:], buf) + } + } + } + return bin +} + +// LogsBloom returns the bloom bytes for the given logs +func LogsBloom(logs []*Log) []byte { + buf := make([]byte, 6) + var bin Bloom + for _, log := range logs { + bin.add(log.Address.Bytes(), buf) + for _, b := range log.Topics { + bin.add(b[:], buf) + } + } + return bin[:] +} + +// Bloom9 returns the bloom filter for the given data +func Bloom9(data []byte) []byte { + var b Bloom + b.SetBytes(data) + return b.Bytes() +} + +// bloomValues returns the bytes (index-value pairs) to set for the given data +func bloomValues(data []byte, hashbuf []byte) (uint, byte, uint, byte, uint, byte) { + sha := hasherPool.Get().(crypto.KeccakState) + sha.Reset() + sha.Write(data) + sha.Read(hashbuf) + hasherPool.Put(sha) + // The actual bits to flip + v1 := byte(1 << (hashbuf[1] & 0x7)) + v2 := byte(1 << (hashbuf[3] & 0x7)) + v3 := byte(1 << (hashbuf[5] & 0x7)) + // The indices for the bytes to OR in + i1 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf)&0x7ff)>>3) - 1 + i2 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf[2:])&0x7ff)>>3) - 1 + i3 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf[4:])&0x7ff)>>3) - 1 + + return i1, v1, i2, v2, i3, v3 +} + +// BloomLookup is a convenience-method to check presence in the bloom filter +func BloomLookup(bin Bloom, topic bytesBacked) bool { + return bin.Test(topic.Bytes()) +} + +// MergeBloom merges the precomputed bloom filters in the Receipts without +// recalculating them. It assumes that each receipt’s Bloom field is already +// correctly populated. +func MergeBloom(receipts Receipts) Bloom { + var bin Bloom + for _, receipt := range receipts { + if len(receipt.Logs) != 0 { + bl := receipt.Bloom.Bytes() + for i := range bin { + bin[i] |= bl[i] + } + } + } + return bin +} \ No newline at end of file diff --git a/types/receipt.go b/types/receipt.go new file mode 100644 index 000000000..9ac86a0c9 --- /dev/null +++ b/types/receipt.go @@ -0,0 +1,166 @@ +// Copyright 2014 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 types + +import ( + "bytes" + "errors" + "fmt" + "math/big" + + "github.com/openrelayxyz/cardinal-evm/common" + "github.com/openrelayxyz/cardinal-types/hexutil" + "github.com/openrelayxyz/cardinal-evm/rlp" + "github.com/openrelayxyz/cardinal-types" +) + +//go:generate go run github.com/fjl/gencodec -type Receipt -field-override receiptMarshaling -out gen_receipt_json.go + +var ( + receiptStatusFailedRLP = []byte{} + receiptStatusSuccessfulRLP = []byte{0x01} +) + +var errShortTypedReceipt = errors.New("typed receipt too short") + +const ( + // ReceiptStatusFailed is the status code of a transaction if execution failed. + ReceiptStatusFailed = uint64(0) + + // ReceiptStatusSuccessful is the status code of a transaction if execution succeeded. + ReceiptStatusSuccessful = uint64(1) +) + + +// Receipt represents the results of a transaction. +type Receipt struct { + // Consensus fields: These fields are defined by the Yellow Paper + Type uint8 `json:"type,omitempty"` + PostState []byte `json:"root"` + Status uint64 `json:"status"` + CumulativeGasUsed uint64 `json:"cumulativeGasUsed" gencodec:"required"` + Bloom Bloom `json:"logsBloom" gencodec:"required"` + Logs []*Log `json:"logs" gencodec:"required"` + + // Implementation fields: These fields are added by geth when processing a transaction. + TxHash types.Hash `json:"transactionHash" gencodec:"required"` + ContractAddress common.Address `json:"contractAddress"` + GasUsed uint64 `json:"gasUsed" gencodec:"required"` + EffectiveGasPrice *big.Int `json:"effectiveGasPrice"` // required, but tag omitted for backwards compatibility + BlobGasUsed uint64 `json:"blobGasUsed,omitempty"` + BlobGasPrice *big.Int `json:"blobGasPrice,omitempty"` + + // Inclusion information: These fields provide information about the inclusion of the + // transaction corresponding to this receipt. + BlockHash types.Hash `json:"blockHash,omitempty"` + BlockNumber *big.Int `json:"blockNumber,omitempty"` + TransactionIndex uint `json:"transactionIndex"` +} + +type receiptMarshaling struct { + Type hexutil.Uint64 + PostState hexutil.Bytes + Status hexutil.Uint64 + CumulativeGasUsed hexutil.Uint64 + GasUsed hexutil.Uint64 + EffectiveGasPrice *hexutil.Big + BlobGasUsed hexutil.Uint64 + BlobGasPrice *hexutil.Big + BlockNumber *hexutil.Big + TransactionIndex hexutil.Uint +} + +// receiptRLP is the consensus encoding of a receipt. +type receiptRLP struct { + PostStateOrStatus []byte + CumulativeGasUsed uint64 + Bloom Bloom + Logs []*Log +} + +// storedReceiptRLP is the storage encoding of a receipt. +type storedReceiptRLP struct { + PostStateOrStatus []byte + CumulativeGasUsed uint64 + Logs []*Log +} + + + +func (r *Receipt) setStatus(postStateOrStatus []byte) error { + switch { + case bytes.Equal(postStateOrStatus, receiptStatusSuccessfulRLP): + r.Status = ReceiptStatusSuccessful + case bytes.Equal(postStateOrStatus, receiptStatusFailedRLP): + r.Status = ReceiptStatusFailed + case len(postStateOrStatus) == len(types.Hash{}): + r.PostState = postStateOrStatus + default: + return fmt.Errorf("invalid receipt status %x", postStateOrStatus) + } + return nil +} + +func (r *Receipt) statusEncoding() []byte { + if len(r.PostState) == 0 { + if r.Status == ReceiptStatusFailed { + return receiptStatusFailedRLP + } + return receiptStatusSuccessfulRLP + } + return r.PostState +} + +// DeriveReceiptContext holds the contextual information needed to derive a receipt +type DeriveReceiptContext struct { + BlockHash types.Hash + BlockNumber uint64 + BlockTime uint64 + BaseFee *big.Int + BlobGasPrice *big.Int + GasUsed uint64 + LogIndex uint // Number of logs in the block until this receipt + Tx *Transaction + TxIndex uint +} + + + +// Receipts implements DerivableList for receipts. +type Receipts []*Receipt + +// Len returns the number of receipts in this list. +func (rs Receipts) Len() int { return len(rs) } + +// EncodeIndex encodes the i'th receipt to w. +func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) { + r := rs[i] + data := &receiptRLP{r.statusEncoding(), r.CumulativeGasUsed, r.Bloom, r.Logs} + if r.Type == LegacyTxType { + rlp.Encode(w, data) + return + } + w.WriteByte(r.Type) + switch r.Type { + case AccessListTxType, DynamicFeeTxType, BlobTxType, SetCodeTxType: + rlp.Encode(w, data) + default: + // For unsupported types, write nothing. Since this is for + // DeriveSha, the error will be caught matching the derived hash + // to the block. + } +} From b7929fa0360a1bd839bdb19b9a07ad9459b82fb4 Mon Sep 17 00:00:00 2001 From: Jesse Date: Fri, 8 Aug 2025 20:15:00 +0100 Subject: [PATCH 04/72] finalize changes in Apply() --- api/eth.go | 5 +++ api/simulate.go | 3 +- types/block.go | 82 ++++++++++++++++++++++++++++++++++++++++++++++--- types/hashes.go | 48 +++++++++++++++++++++++++++++ 4 files changed, 132 insertions(+), 6 deletions(-) create mode 100644 types/hashes.go diff --git a/api/eth.go b/api/eth.go index 5ce55d0f9..9d8b798b9 100644 --- a/api/eth.go +++ b/api/eth.go @@ -192,6 +192,11 @@ func (diff *StateOverride) Apply(state state.StateDB) error { } } } + + // Now finalize the changes. Finalize is normally performed between transactions. + // By using finalize, the overrides are semantically behaving as + // if they were created in a transaction just before the tracing occur. + state.Finalise() return nil } diff --git a/api/simulate.go b/api/simulate.go index 8196e1353..938776073 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -317,7 +317,8 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, header.Bloom = types.CreateBloom(receipts) header.Root = s.base.Root - blck := types.NewBlockWithHeader(header) + blockBody := &types.Body{Transactions: txes} + blck := types.NewBlock(header, blockBody, receipts, hasher) return blck, callResults, senders, nil } diff --git a/types/block.go b/types/block.go index 654dcac6d..b26d68a1d 100644 --- a/types/block.go +++ b/types/block.go @@ -4,16 +4,12 @@ import ( "math/big" "sync/atomic" "time" + "slices" "github.com/openrelayxyz/cardinal-evm/common" "github.com/openrelayxyz/cardinal-types" ) -var ( - // EmptyUncleHash is the known hash of the empty uncle set. - EmptyUncleHash = rlpHash([]*Header(nil)) // 1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347 - -) type Header struct { ParentHash types.Hash @@ -108,9 +104,85 @@ func (h *Header) Hash() types.Hash { return rlpHash(h) } +// Body is a simple (mutable, non-safe) data container for storing and moving +// a block's data contents (transactions and uncles) together. +type Body struct { + Transactions []*Transaction + Uncles []*Header + Withdrawals []*Withdrawal `rlp:"optional"` +} + // NewBlockWithHeader creates a block with the given header data. The // header data is copied, changes to header and to the field values // will not affect the block. func NewBlockWithHeader(header *Header) *Block { return &Block{header: CopyHeader(header)} +} + +// NewBlock creates a new block. The input data is copied, changes to header and to the +// field values will not affect the block. +// +// The body elements and the receipts are used to recompute and overwrite the +// relevant portions of the header. +// +// The receipt's bloom must already calculated for the block's bloom to be +// correctly calculated. +func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher TrieHasher) *Block { + if body == nil { + body = &Body{} + } + var ( + b = NewBlockWithHeader(header) + txs = body.Transactions + uncles = body.Uncles + withdrawals = body.Withdrawals + ) + + if len(txs) == 0 { + b.header.TxHash = EmptyTxsHash + } else { + b.header.TxHash = DeriveSha(Transactions(txs), hasher) + b.transactions = make(Transactions, len(txs)) + copy(b.transactions, txs) + } + + if len(receipts) == 0 { + b.header.ReceiptHash = EmptyReceiptsHash + } else { + b.header.ReceiptHash = DeriveSha(Receipts(receipts), hasher) + // Receipts must go through MakeReceipt to calculate the receipt's bloom + // already. Merge the receipt's bloom together instead of recalculating + // everything. + b.header.Bloom = MergeBloom(receipts) + } + + if len(uncles) == 0 { + b.header.UncleHash = EmptyUncleHash + } else { + b.header.UncleHash = CalcUncleHash(uncles) + b.uncles = make([]*Header, len(uncles)) + for i := range uncles { + b.uncles[i] = CopyHeader(uncles[i]) + } + } + + if withdrawals == nil { + b.header.WithdrawalsHash = nil + } else if len(withdrawals) == 0 { + b.header.WithdrawalsHash = &EmptyWithdrawalsHash + b.withdrawals = Withdrawals{} + } else { + hash := DeriveSha(Withdrawals(withdrawals), hasher) + b.header.WithdrawalsHash = &hash + b.withdrawals = slices.Clone(withdrawals) + } + + return b +} + +func CalcUncleHash(uncles []*Header) types.Hash { + if len(uncles) == 0 { + return EmptyUncleHash + } + return rlpHash(uncles) } \ No newline at end of file diff --git a/types/hashes.go b/types/hashes.go new file mode 100644 index 000000000..06e0e3a80 --- /dev/null +++ b/types/hashes.go @@ -0,0 +1,48 @@ +// Copyright 2023 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 types + +import ( + "github.com/openrelayxyz/cardinal-types" + "github.com/openrelayxyz/cardinal-evm/crypto" +) + +var ( + // EmptyRootHash is the known root hash of an empty merkle trie. + EmptyRootHash = types.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + + // EmptyUncleHash is the known hash of the empty uncle set. + EmptyUncleHash = rlpHash([]*Header(nil)) // 1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347 + + // EmptyCodeHash is the known hash of the empty EVM bytecode. + EmptyCodeHash = crypto.Keccak256Hash(nil) // c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 + + // EmptyTxsHash is the known hash of the empty transaction set. + EmptyTxsHash = types.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + + // EmptyReceiptsHash is the known hash of the empty receipt set. + EmptyReceiptsHash = types.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + + // EmptyWithdrawalsHash is the known hash of the empty withdrawal set. + EmptyWithdrawalsHash = types.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + + // EmptyRequestsHash is the known hash of an empty request set, sha256(""). + EmptyRequestsHash = types.HexToHash("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") + + // EmptyVerkleHash is the known hash of an empty verkle trie. + EmptyVerkleHash = types.Hash{} +) From 8400532062bada11b181a6caeb4a72f9d3b5c136 Mon Sep 17 00:00:00 2001 From: Jesse Date: Fri, 8 Aug 2025 21:02:49 +0100 Subject: [PATCH 05/72] add debug logs --- api/simulate.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/api/simulate.go b/api/simulate.go index 938776073..1c12a3e9a 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -7,6 +7,7 @@ import ( "math/big" "time" + log "github.com/inconshreveable/log15" "github.com/openrelayxyz/cardinal-evm/common" "github.com/openrelayxyz/cardinal-evm/crypto" "github.com/openrelayxyz/cardinal-evm/eips/eip1559" @@ -177,12 +178,17 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, } header.ExcessBlobGas = &excess } + + addr := common.HexToAddress("0x1000000000000000000000000000000000000001") + log.Error(fmt.Sprintf("balance before: %v\n", s.state.GetBalance(addr))) // State overrides are applied prior to execution of a block if block.StateOverrides != nil { + log.Error(fmt.Sprintf("applying state overrides for %d accounts\n", len(*block.StateOverrides))) if err := block.StateOverrides.Apply(s.state); err != nil { return nil, nil, nil, err } + log.Error(fmt.Sprintf("balance after: %v\n", s.state.GetBalance(addr))) } var ( From f12fb8aca31b5051516fb3acadda9d69e2d77ade Mon Sep 17 00:00:00 2001 From: Jesse Date: Fri, 8 Aug 2025 21:27:22 +0100 Subject: [PATCH 06/72] debug if balance is set --- api/eth.go | 11 ++++++++++- api/simulate.go | 5 ----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/api/eth.go b/api/eth.go index 9d8b798b9..4276c852e 100644 --- a/api/eth.go +++ b/api/eth.go @@ -176,7 +176,16 @@ func (diff *StateOverride) Apply(state state.StateDB) error { } // Override account balance. if account.Balance != nil { - state.SetBalance(addr, (*big.Int)(*account.Balance)) + if account.Balance != nil { + balanceValue := (*big.Int)(*account.Balance) + log.Error(fmt.Sprintf("Setting balance to: %v", balanceValue)) + state.SetBalance(addr, balanceValue) + + newBalance := state.GetBalance(addr) + log.Error(fmt.Sprintf("Balance after SetBalance: %v", newBalance)) + } else { + log.Error("account.Balance is nil!") + } } if account.State != nil && account.StateDiff != nil { return fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex()) diff --git a/api/simulate.go b/api/simulate.go index 1c12a3e9a..98c411570 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -7,7 +7,6 @@ import ( "math/big" "time" - log "github.com/inconshreveable/log15" "github.com/openrelayxyz/cardinal-evm/common" "github.com/openrelayxyz/cardinal-evm/crypto" "github.com/openrelayxyz/cardinal-evm/eips/eip1559" @@ -179,16 +178,12 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, header.ExcessBlobGas = &excess } - addr := common.HexToAddress("0x1000000000000000000000000000000000000001") - log.Error(fmt.Sprintf("balance before: %v\n", s.state.GetBalance(addr))) // State overrides are applied prior to execution of a block if block.StateOverrides != nil { - log.Error(fmt.Sprintf("applying state overrides for %d accounts\n", len(*block.StateOverrides))) if err := block.StateOverrides.Apply(s.state); err != nil { return nil, nil, nil, err } - log.Error(fmt.Sprintf("balance after: %v\n", s.state.GetBalance(addr))) } var ( From 0aed2e7d608e7ae7d8d71a435ed33f63500234e9 Mon Sep 17 00:00:00 2001 From: Jesse Date: Fri, 8 Aug 2025 21:40:12 +0100 Subject: [PATCH 07/72] remove double pointer --- api/eth.go | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/api/eth.go b/api/eth.go index 4276c852e..87bb2d609 100644 --- a/api/eth.go +++ b/api/eth.go @@ -133,7 +133,7 @@ func (s *PublicBlockChainAPI) GetStorageAt(ctx *rpc.CallContext, address common. type OverrideAccount struct { Nonce *hexutil.Uint64 `json:"nonce"` Code *hexutil.Bytes `json:"code"` - Balance **hexutil.Big `json:"balance"` + Balance *hexutil.Big `json:"balance"` State *map[ctypes.Hash]ctypes.Hash `json:"state"` StateDiff *map[ctypes.Hash]ctypes.Hash `json:"stateDiff"` } @@ -176,16 +176,7 @@ func (diff *StateOverride) Apply(state state.StateDB) error { } // Override account balance. if account.Balance != nil { - if account.Balance != nil { - balanceValue := (*big.Int)(*account.Balance) - log.Error(fmt.Sprintf("Setting balance to: %v", balanceValue)) - state.SetBalance(addr, balanceValue) - - newBalance := state.GetBalance(addr) - log.Error(fmt.Sprintf("Balance after SetBalance: %v", newBalance)) - } else { - log.Error("account.Balance is nil!") - } + state.SetBalance(addr, (*big.Int)(account.Balance)) } if account.State != nil && account.StateDiff != nil { return fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex()) From 1c0a77e54f683165601b5d7efea5ba6163456b79 Mon Sep 17 00:00:00 2001 From: Jesse Date: Fri, 8 Aug 2025 22:07:39 +0100 Subject: [PATCH 08/72] remove extra finalise call --- api/simulate.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/api/simulate.go b/api/simulate.go index 98c411570..048b62d72 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -193,7 +193,7 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, callResults = make([]simCallResult, len(block.Calls)) receipts = make([]*types.Receipt, len(block.Calls)) senders = make(map[ctypes.Hash]common.Address) - allLogs []*types.Log + // allLogs []*types.Log ) getHashFn := func(n uint64) ctypes.Hash { @@ -300,10 +300,9 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, } } else { callRes.Status = hexutil.Uint64(types.ReceiptStatusSuccessful) - allLogs = append(allLogs, receipt.Logs...) + // allLogs = append(allLogs, receipt.Logs...) } callResults[i] = callRes - s.state.Finalise() } header.GasUsed = gasUsed From 22824ed36dea19a22373660621bb13dd52c1fb9c Mon Sep 17 00:00:00 2001 From: Jesse Date: Tue, 12 Aug 2025 19:46:03 +0100 Subject: [PATCH 09/72] prune codebase, add AddLogs --- api/simulate.go | 7 --- params/protocol_params.go | 8 ---- state/statedb.go | 18 ++++++-- types/bloom9.go | 94 --------------------------------------- 4 files changed, 14 insertions(+), 113 deletions(-) diff --git a/api/simulate.go b/api/simulate.go index 048b62d72..541d28025 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -231,13 +231,6 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, evm.Context.GetHash = getHashFn } - if s.chainConfig.IsPrague(header.Number, new(big.Int).SetUint64(header.Time)) { - // Process parent block hash for EIP-2935 - if header.ParentHash != (ctypes.Hash{}) { - evm.StateDB.SetState(params.HistoryStorageAddress,ctypes.BigToHash(header.Number), header.ParentHash,) - } - } - msg, err := call.ToMessage(s.gp.Gas(), header.BaseFee) if err != nil { return nil, nil, nil, err diff --git a/params/protocol_params.go b/params/protocol_params.go index c4a34f5cb..c537a37de 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -18,8 +18,6 @@ package params import ( "math/big" - - "github.com/openrelayxyz/cardinal-evm/common" ) const ( @@ -198,10 +196,4 @@ var ( GenesisDifficulty = big.NewInt(131072) // Difficulty of the Genesis block. MinimumDifficulty = big.NewInt(131072) // The minimum that the difficulty may ever be. DurationLimit = big.NewInt(13) // The decision boundary on the blocktime duration used to determine whether difficulty should go up or not. -) - -var ( - // EIP-2935 - Serve historical block hashes from state - HistoryStorageAddress = common.HexToAddress("0x0000F90827F1C53a10cb7A02335B175320002935") - HistoryStorageCode = common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe14604657602036036042575f35600143038111604257611fff81430311604257611fff9006545f5260205ff35b5f5ffd5b5f35611fff60014303065500") ) \ No newline at end of file diff --git a/state/statedb.go b/state/statedb.go index 599553666..cadd837b8 100644 --- a/state/statedb.go +++ b/state/statedb.go @@ -46,6 +46,7 @@ type stateDB struct { logs map[ctypes.Hash][]*types.Log thash ctypes.Hash txIndex int + logSize uint } func NewStateDB(tx storage.Transaction, chainid int64) StateDB { @@ -348,10 +349,19 @@ func (sdb *stateDB) RevertToSnapshot(snap int) { sdb.journal = sdb.journal[:snap] } func (sdb *stateDB) Snapshot() int { return len(sdb.journal) } -func (sdb *stateDB) AddLog(*types.Log) { - // At this time, I don't think we have any features that require logs to - // actually be tracked, but we'll leave this as a placeholder so if we ever - // need it we don't have to rework it back into the EVM +func (sdb *stateDB) AddLog(log *types.Log) { + sdb.journal = append(sdb.journal, journalEntry{nil, func(sdb *stateDB) { + logs := sdb.logs[sdb.thash] + if len(logs) > 0 { + sdb.logs[sdb.thash] = logs[:len(logs)-1] + sdb.logSize-- + } + }}) + log.TxHash = sdb.thash + log.TxIndex = uint(sdb.txIndex) + log.Index = sdb.logSize + sdb.logs[sdb.thash] = append(sdb.logs[sdb.thash], log) + sdb.logSize++ } func (sdb *stateDB) AddPreimage(ctypes.Hash, []byte) { // I doubt we'll ever support preimage tracking, but easier to leave a diff --git a/types/bloom9.go b/types/bloom9.go index 53f285c66..1811deb43 100644 --- a/types/bloom9.go +++ b/types/bloom9.go @@ -1,27 +1,7 @@ -// Copyright 2014 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 types import ( "encoding/binary" - "fmt" - "math/big" - - "github.com/openrelayxyz/cardinal-types/hexutil" "github.com/openrelayxyz/cardinal-evm/crypto" ) @@ -33,35 +13,11 @@ const ( // BloomByteLength represents the number of bytes used in a header log bloom. BloomByteLength = 256 - // BloomBitLength represents the number of bits used in a header log bloom. - BloomBitLength = 8 * BloomByteLength ) // Bloom represents a 2048 bit bloom filter. type Bloom [BloomByteLength]byte -// BytesToBloom converts a byte slice to a bloom filter. -// It panics if b is not of suitable size. -func BytesToBloom(b []byte) Bloom { - var bloom Bloom - bloom.SetBytes(b) - return bloom -} - -// SetBytes sets the content of b to the given bytes. -// It panics if d is not of suitable size. -func (b *Bloom) SetBytes(d []byte) { - if len(b) < len(d) { - panic(fmt.Sprintf("bloom bytes too big %d %d", len(b), len(d))) - } - copy(b[BloomByteLength-len(d):], d) -} - -// Add adds d to the filter. Future calls of Test(d) will return true. -func (b *Bloom) Add(d []byte) { - b.add(d, make([]byte, 6)) -} - // add is internal version of Add, which takes a scratch buffer for reuse (needs to be at least 6 bytes) func (b *Bloom) add(d []byte, buf []byte) { i1, v1, i2, v2, i3, v3 := bloomValues(d, buf) @@ -70,36 +26,11 @@ func (b *Bloom) add(d []byte, buf []byte) { b[i3] |= v3 } -// Big converts b to a big integer. -// Note: Converting a bloom filter to a big.Int and then calling GetBytes -// does not return the same bytes, since big.Int will trim leading zeroes -func (b Bloom) Big() *big.Int { - return new(big.Int).SetBytes(b[:]) -} - // Bytes returns the backing byte slice of the bloom func (b Bloom) Bytes() []byte { return b[:] } -// Test checks if the given topic is present in the bloom filter -func (b Bloom) Test(topic []byte) bool { - i1, v1, i2, v2, i3, v3 := bloomValues(topic, make([]byte, 6)) - return v1 == v1&b[i1] && - v2 == v2&b[i2] && - v3 == v3&b[i3] -} - -// MarshalText encodes b as a hex string with 0x prefix. -func (b Bloom) MarshalText() ([]byte, error) { - return hexutil.Bytes(b[:]).MarshalText() -} - -// UnmarshalText b as a hex string with 0x prefix. -func (b *Bloom) UnmarshalText(input []byte) error { - return hexutil.UnmarshalFixedText("Bloom", input, b[:]) -} - // CreateBloom creates a bloom filter out of the give Receipts (+Logs) func CreateBloom(receipts Receipts) Bloom { buf := make([]byte, 6) @@ -115,26 +46,6 @@ func CreateBloom(receipts Receipts) Bloom { return bin } -// LogsBloom returns the bloom bytes for the given logs -func LogsBloom(logs []*Log) []byte { - buf := make([]byte, 6) - var bin Bloom - for _, log := range logs { - bin.add(log.Address.Bytes(), buf) - for _, b := range log.Topics { - bin.add(b[:], buf) - } - } - return bin[:] -} - -// Bloom9 returns the bloom filter for the given data -func Bloom9(data []byte) []byte { - var b Bloom - b.SetBytes(data) - return b.Bytes() -} - // bloomValues returns the bytes (index-value pairs) to set for the given data func bloomValues(data []byte, hashbuf []byte) (uint, byte, uint, byte, uint, byte) { sha := hasherPool.Get().(crypto.KeccakState) @@ -154,11 +65,6 @@ func bloomValues(data []byte, hashbuf []byte) (uint, byte, uint, byte, uint, byt return i1, v1, i2, v2, i3, v3 } -// BloomLookup is a convenience-method to check presence in the bloom filter -func BloomLookup(bin Bloom, topic bytesBacked) bool { - return bin.Test(topic.Bytes()) -} - // MergeBloom merges the precomputed bloom filters in the Receipts without // recalculating them. It assumes that each receipt’s Bloom field is already // correctly populated. From 505131ca540b1b93876082cd0241faeb27fc75cf Mon Sep 17 00:00:00 2001 From: Philip Morlier Date: Tue, 26 Aug 2025 15:02:32 -0700 Subject: [PATCH 10/72] Added blockOverrides to simBlock object --- api/simulate.go | 27 +++++++++++++++++++++++++++ types/block.go | 4 ++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/api/simulate.go b/api/simulate.go index 541d28025..8d6797d4b 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -51,6 +51,7 @@ type simOpts struct { // simBlock is a batch of calls to be simulated sequentially. type simBlock struct { + BlockOverrides *BlockOverrides StateOverrides *StateOverride Calls []TransactionArgs } @@ -124,12 +125,38 @@ func (s *simulator) execute(ctx *rpc.CallContext, blocks []simBlock) ([]*simBloc parent = s.base ) +// type BlockOverrides struct { +// Number *hexutil.Big +// Difficulty *hexutil.Big // No-op if we're simulating post-merge calls. +// Time *hexutil.Uint64 +// GasLimit *hexutil.Uint64 +// FeeRecipient *common.Address +// PrevRandao *ctypes.Hash +// BaseFeePerGas *hexutil.Big +// BlobBaseFee *hexutil.Big +// } + for bi, block := range blocks { header := types.CopyHeader(s.base) header.Number = new(big.Int).Add(s.base.Number, big.NewInt(int64(bi+1))) header.ParentHash = parent.Hash() header.Time = parent.Time + uint64(bi+1)*12 + override := *block.BlockOverrides + + if override.Number != nil {header.Number = override.Number.ToInt()} + if override.Difficulty != nil {header.Difficulty = override.Difficulty.ToInt()} + if override.Time != nil {header.Time = uint64(*override.Time)} + if override.GasLimit != nil {header.GasLimit = uint64(*override.GasLimit)} + if override.FeeRecipient != nil {header.Coinbase = *override.FeeRecipient} + if override.PrevRandao != nil {header.MixDigest = *override.PrevRandao} + if override.BaseFeePerGas != nil {header.BaseFee = override.BaseFeePerGas.ToInt()} + if override.BlobBaseFee != nil { + val := *override.BlobBaseFee.ToInt() + ptr := val.Uint64() + header.ExcessBlobGas = &ptr + } + s.gp = new(GasPool).AddGas(header.GasLimit) if err := execCtx.Err(); err != nil { diff --git a/types/block.go b/types/block.go index b26d68a1d..685fe41e8 100644 --- a/types/block.go +++ b/types/block.go @@ -14,7 +14,7 @@ import ( type Header struct { ParentHash types.Hash UncleHash types.Hash - Coinbase common.Address + Coinbase common.Address // fee reciptient Root types.Hash TxHash types.Hash ReceiptHash types.Hash @@ -25,7 +25,7 @@ type Header struct { GasUsed uint64 Time uint64 Extra []byte - MixDigest types.Hash + MixDigest types.Hash // prevrandao Nonce [8]byte BaseFee *big.Int `rlp:"optional"` WithdrawalsHash *types.Hash `rlp:"optional"` From d731135cc9e4c749da060a375cc2c38e26af99e6 Mon Sep 17 00:00:00 2001 From: Philip Morlier Date: Tue, 26 Aug 2025 15:27:29 -0700 Subject: [PATCH 11/72] interative error experiment --- api/simulate.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/api/simulate.go b/api/simulate.go index 8d6797d4b..2d96f8c75 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -144,18 +144,18 @@ func (s *simulator) execute(ctx *rpc.CallContext, blocks []simBlock) ([]*simBloc override := *block.BlockOverrides - if override.Number != nil {header.Number = override.Number.ToInt()} - if override.Difficulty != nil {header.Difficulty = override.Difficulty.ToInt()} - if override.Time != nil {header.Time = uint64(*override.Time)} - if override.GasLimit != nil {header.GasLimit = uint64(*override.GasLimit)} - if override.FeeRecipient != nil {header.Coinbase = *override.FeeRecipient} - if override.PrevRandao != nil {header.MixDigest = *override.PrevRandao} + // if override.Number != nil {header.Number = override.Number.ToInt()} + // if override.Difficulty != nil {header.Difficulty = override.Difficulty.ToInt()} + // if override.Time != nil {header.Time = uint64(*override.Time)} + // if override.GasLimit != nil {header.GasLimit = uint64(*override.GasLimit)} + // if override.FeeRecipient != nil {header.Coinbase = *override.FeeRecipient} + // if override.PrevRandao != nil {header.MixDigest = *override.PrevRandao} if override.BaseFeePerGas != nil {header.BaseFee = override.BaseFeePerGas.ToInt()} - if override.BlobBaseFee != nil { - val := *override.BlobBaseFee.ToInt() - ptr := val.Uint64() - header.ExcessBlobGas = &ptr - } + // if override.BlobBaseFee != nil { + // val := *override.BlobBaseFee.ToInt() + // ptr := val.Uint64() + // header.ExcessBlobGas = &ptr + // } s.gp = new(GasPool).AddGas(header.GasLimit) From 7b67e89e96ae9e640d7969d9072fc15d6764779c Mon Sep 17 00:00:00 2001 From: Philip Morlier Date: Tue, 26 Aug 2025 17:12:03 -0700 Subject: [PATCH 12/72] Added normalize utility function to transaction args --- api/eth.go | 1 + api/ethercattle.go | 1 + api/simulate.go | 22 +++++++++++----------- api/transaction_args.go | 14 ++++++++++++++ 4 files changed, 27 insertions(+), 11 deletions(-) diff --git a/api/eth.go b/api/eth.go index 87bb2d609..6e19dba83 100644 --- a/api/eth.go +++ b/api/eth.go @@ -566,6 +566,7 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx *rpc.CallContext, args Transaction var gas hexutil.Uint64 err := s.evmmgr.View(bNrOrHash, args.From, &vm.Config{NoBaseFee: true}, ctx, func(statedb state.StateDB, header *types.Header, evmFn func(state.StateDB, *vm.Config, common.Address, *big.Int) *vm.EVM) error { var err error + args.normalize() gas, _, err = DoEstimateGas(ctx, evmFn, args, &PreviousState{statedb, header}, bNrOrHash, s.gasLimit(header), false) return err }) diff --git a/api/ethercattle.go b/api/ethercattle.go index 8fb58f418..3933d84de 100644 --- a/api/ethercattle.go +++ b/api/ethercattle.go @@ -33,6 +33,7 @@ func (s *EtherCattleBlockChainAPI) EstimateGasList(ctx *rpc.CallContext, argsLis gasCap = s.gasLimit(header) ) for idx, args := range argsList { + args.normalize() gas, stateData, err = DoEstimateGas(ctx, evmFn, args, stateData, blockNrOrHash, gasCap, fast) // DoEstimateGas(ctx, s.b, args, stateData, blockNrOrHash, gasCap, fast) if err != nil { diff --git a/api/simulate.go b/api/simulate.go index 2d96f8c75..8d6797d4b 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -144,18 +144,18 @@ func (s *simulator) execute(ctx *rpc.CallContext, blocks []simBlock) ([]*simBloc override := *block.BlockOverrides - // if override.Number != nil {header.Number = override.Number.ToInt()} - // if override.Difficulty != nil {header.Difficulty = override.Difficulty.ToInt()} - // if override.Time != nil {header.Time = uint64(*override.Time)} - // if override.GasLimit != nil {header.GasLimit = uint64(*override.GasLimit)} - // if override.FeeRecipient != nil {header.Coinbase = *override.FeeRecipient} - // if override.PrevRandao != nil {header.MixDigest = *override.PrevRandao} + if override.Number != nil {header.Number = override.Number.ToInt()} + if override.Difficulty != nil {header.Difficulty = override.Difficulty.ToInt()} + if override.Time != nil {header.Time = uint64(*override.Time)} + if override.GasLimit != nil {header.GasLimit = uint64(*override.GasLimit)} + if override.FeeRecipient != nil {header.Coinbase = *override.FeeRecipient} + if override.PrevRandao != nil {header.MixDigest = *override.PrevRandao} if override.BaseFeePerGas != nil {header.BaseFee = override.BaseFeePerGas.ToInt()} - // if override.BlobBaseFee != nil { - // val := *override.BlobBaseFee.ToInt() - // ptr := val.Uint64() - // header.ExcessBlobGas = &ptr - // } + if override.BlobBaseFee != nil { + val := *override.BlobBaseFee.ToInt() + ptr := val.Uint64() + header.ExcessBlobGas = &ptr + } s.gp = new(GasPool).AddGas(header.GasLimit) diff --git a/api/transaction_args.go b/api/transaction_args.go index 0e948de7e..2f9f0d74e 100644 --- a/api/transaction_args.go +++ b/api/transaction_args.go @@ -67,6 +67,19 @@ type TransactionArgs struct { AuthList []types.Authorization `json:"authorizationList,omitempty"` } +// this utility is being added to confrom to eip1559 protocol which set ups a mutually exclusive condition for args.GasPrice and args.MaxFeePerGas or args.MaxPriorityFeePerGas +func (args *TransactionArgs) normalize() { + if args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil { + // 1559 style: drop legacy field + args.GasPrice = nil + } else if args.GasPrice != nil { + // Legacy style: drop 1559 fields + args.MaxFeePerGas = nil + args.MaxPriorityFeePerGas = nil + } +} + + // from retrieves the transaction sender address. func (arg *TransactionArgs) from() common.Address { if arg.From == nil { @@ -181,6 +194,7 @@ func (args *TransactionArgs) setDefaults(ctx *rpc.CallContext, getEVM func(state args.Nonce = (*hexutil.Uint64)(&nonce) } if args.Gas == nil { + args.normalize() gas, _, err := DoEstimateGas(ctx, getEVM, *args, &PreviousState{db.ALCalcCopy(), header}, blockNrOrHash, header.GasLimit, true) if err != nil { return err From 7de6a5d436c10c010499896dd751678661664a9e Mon Sep 17 00:00:00 2001 From: Philip Morlier Date: Tue, 26 Aug 2025 19:57:52 -0700 Subject: [PATCH 13/72] txArgs logging --- api/eth.go | 2 ++ api/ethercattle.go | 2 ++ api/transaction_args.go | 2 ++ 3 files changed, 6 insertions(+) diff --git a/api/eth.go b/api/eth.go index 6e19dba83..b6b2b5bf2 100644 --- a/api/eth.go +++ b/api/eth.go @@ -566,7 +566,9 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx *rpc.CallContext, args Transaction var gas hexutil.Uint64 err := s.evmmgr.View(bNrOrHash, args.From, &vm.Config{NoBaseFee: true}, ctx, func(statedb state.StateDB, header *types.Header, evmFn func(state.StateDB, *vm.Config, common.Address, *big.Int) *vm.EVM) error { var err error + log.Error("before EstimateGas function", "args", args) args.normalize() + log.Error("after EstimateGas function", "args", args) gas, _, err = DoEstimateGas(ctx, evmFn, args, &PreviousState{statedb, header}, bNrOrHash, s.gasLimit(header), false) return err }) diff --git a/api/ethercattle.go b/api/ethercattle.go index 3933d84de..0a5f5d9b9 100644 --- a/api/ethercattle.go +++ b/api/ethercattle.go @@ -33,7 +33,9 @@ func (s *EtherCattleBlockChainAPI) EstimateGasList(ctx *rpc.CallContext, argsLis gasCap = s.gasLimit(header) ) for idx, args := range argsList { + log.Error("before EtherCattle function", "args", args) args.normalize() + log.Error("after EtherCattle function", "args", args) gas, stateData, err = DoEstimateGas(ctx, evmFn, args, stateData, blockNrOrHash, gasCap, fast) // DoEstimateGas(ctx, s.b, args, stateData, blockNrOrHash, gasCap, fast) if err != nil { diff --git a/api/transaction_args.go b/api/transaction_args.go index 2f9f0d74e..c19dc22e2 100644 --- a/api/transaction_args.go +++ b/api/transaction_args.go @@ -194,7 +194,9 @@ func (args *TransactionArgs) setDefaults(ctx *rpc.CallContext, getEVM func(state args.Nonce = (*hexutil.Uint64)(&nonce) } if args.Gas == nil { + log.Error("before txArgs function", "args", args) args.normalize() + log.Error("after txArgs function", "args", args) gas, _, err := DoEstimateGas(ctx, getEVM, *args, &PreviousState{db.ALCalcCopy(), header}, blockNrOrHash, header.GasLimit, true) if err != nil { return err From b0cd8599bd3fe811014b8d0591bf1726bceaf9ff Mon Sep 17 00:00:00 2001 From: Philip Morlier Date: Tue, 26 Aug 2025 20:10:16 -0700 Subject: [PATCH 14/72] Added normalize to toMessage() --- api/transaction_args.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/api/transaction_args.go b/api/transaction_args.go index c19dc22e2..6064a9517 100644 --- a/api/transaction_args.go +++ b/api/transaction_args.go @@ -103,6 +103,9 @@ func (arg *TransactionArgs) data() []byte { // core evm. This method is used in calls and traces that do not require a real // live transaction. func (args *TransactionArgs) ToMessage(globalGasCap uint64, baseFee *big.Int) (Msg, error) { + log.Error("before toMessage", "args", args) + args.normalize() + log.Error("before toMessage", "args", args) // Reject invalid combinations of pre- and post-1559 fee styles if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) { return Msg{}, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") From b67ea8d3571aae6ad53742bb3cb51e4891b3d80b Mon Sep 17 00:00:00 2001 From: Philip Morlier Date: Tue, 26 Aug 2025 20:46:04 -0700 Subject: [PATCH 15/72] added basefee added to context --- api/eth.go | 3 --- api/ethercattle.go | 3 --- api/simulate.go | 13 ++----------- api/transaction_args.go | 5 ----- 4 files changed, 2 insertions(+), 22 deletions(-) diff --git a/api/eth.go b/api/eth.go index b6b2b5bf2..87bb2d609 100644 --- a/api/eth.go +++ b/api/eth.go @@ -566,9 +566,6 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx *rpc.CallContext, args Transaction var gas hexutil.Uint64 err := s.evmmgr.View(bNrOrHash, args.From, &vm.Config{NoBaseFee: true}, ctx, func(statedb state.StateDB, header *types.Header, evmFn func(state.StateDB, *vm.Config, common.Address, *big.Int) *vm.EVM) error { var err error - log.Error("before EstimateGas function", "args", args) - args.normalize() - log.Error("after EstimateGas function", "args", args) gas, _, err = DoEstimateGas(ctx, evmFn, args, &PreviousState{statedb, header}, bNrOrHash, s.gasLimit(header), false) return err }) diff --git a/api/ethercattle.go b/api/ethercattle.go index 0a5f5d9b9..8fb58f418 100644 --- a/api/ethercattle.go +++ b/api/ethercattle.go @@ -33,9 +33,6 @@ func (s *EtherCattleBlockChainAPI) EstimateGasList(ctx *rpc.CallContext, argsLis gasCap = s.gasLimit(header) ) for idx, args := range argsList { - log.Error("before EtherCattle function", "args", args) - args.normalize() - log.Error("after EtherCattle function", "args", args) gas, stateData, err = DoEstimateGas(ctx, evmFn, args, stateData, blockNrOrHash, gasCap, fast) // DoEstimateGas(ctx, s.b, args, stateData, blockNrOrHash, gasCap, fast) if err != nil { diff --git a/api/simulate.go b/api/simulate.go index 8d6797d4b..67c81ad58 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -125,17 +125,6 @@ func (s *simulator) execute(ctx *rpc.CallContext, blocks []simBlock) ([]*simBloc parent = s.base ) -// type BlockOverrides struct { -// Number *hexutil.Big -// Difficulty *hexutil.Big // No-op if we're simulating post-merge calls. -// Time *hexutil.Uint64 -// GasLimit *hexutil.Uint64 -// FeeRecipient *common.Address -// PrevRandao *ctypes.Hash -// BaseFeePerGas *hexutil.Big -// BlobBaseFee *hexutil.Big -// } - for bi, block := range blocks { header := types.CopyHeader(s.base) header.Number = new(big.Int).Add(s.base.Number, big.NewInt(int64(bi+1))) @@ -254,6 +243,8 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, NoBaseFee: !s.validate, }, call.from(), call.GasPrice.ToInt()) + evm.Context.BaseFee = header.BaseFee + if evm.Context.GetHash == nil { evm.Context.GetHash = getHashFn } diff --git a/api/transaction_args.go b/api/transaction_args.go index 6064a9517..0b0cc18cb 100644 --- a/api/transaction_args.go +++ b/api/transaction_args.go @@ -103,9 +103,7 @@ func (arg *TransactionArgs) data() []byte { // core evm. This method is used in calls and traces that do not require a real // live transaction. func (args *TransactionArgs) ToMessage(globalGasCap uint64, baseFee *big.Int) (Msg, error) { - log.Error("before toMessage", "args", args) args.normalize() - log.Error("before toMessage", "args", args) // Reject invalid combinations of pre- and post-1559 fee styles if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) { return Msg{}, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") @@ -197,9 +195,6 @@ func (args *TransactionArgs) setDefaults(ctx *rpc.CallContext, getEVM func(state args.Nonce = (*hexutil.Uint64)(&nonce) } if args.Gas == nil { - log.Error("before txArgs function", "args", args) - args.normalize() - log.Error("after txArgs function", "args", args) gas, _, err := DoEstimateGas(ctx, getEVM, *args, &PreviousState{db.ALCalcCopy(), header}, blockNrOrHash, header.GasLimit, true) if err != nil { return err From 60ac312f4be936b48b0032360f19fd47a12ebd4a Mon Sep 17 00:00:00 2001 From: Philip Morlier Date: Thu, 28 Aug 2025 14:08:17 -0700 Subject: [PATCH 16/72] First experiment with successful build with marshalling logic --- api/simulate.go | 42 +++++-- go.mod | 6 +- go.sum | 4 + types/block.go | 85 ++++++++++---- types/gen_authorization.go | 83 +++++++------ types/legacy_tx.go | 2 +- types/marshalling.go | 84 +++++++++++++ types/rpc.go | 234 +++++++++++++++++++++++++++++++++++++ types/transaction.go | 46 ++++++++ types/tx_setcode.go | 17 ++- 10 files changed, 531 insertions(+), 72 deletions(-) create mode 100644 types/marshalling.go create mode 100644 types/rpc.go diff --git a/api/simulate.go b/api/simulate.go index 67c81ad58..fe4bcc772 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -6,6 +6,7 @@ import ( "fmt" "math/big" "time" + "encoding/json" "github.com/openrelayxyz/cardinal-evm/common" "github.com/openrelayxyz/cardinal-evm/crypto" @@ -70,6 +71,24 @@ type simulator struct { evmFn func(state.StateDB, *vm.Config, common.Address, *big.Int) *vm.EVM } +// simCallResult is the result of a simulated call. +type simCallResult struct { + ReturnValue hexutil.Bytes `json:"returnData"` + Logs []*types.Log `json:"logs"` + GasUsed hexutil.Uint64 `json:"gasUsed"` + Status hexutil.Uint64 `json:"status"` + Error *callError `json:"error,omitempty"` +} + +func (r *simCallResult) MarshalJSON() ([]byte, error) { + type callResultAlias simCallResult + // Marshal logs to be an empty array instead of nil when empty + if r.Logs == nil { + r.Logs = []*types.Log{} + } + return json.Marshal((*callResultAlias)(r)) +} + type simBlockResult struct { fullTx bool chainConfig *params.ChainConfig @@ -79,13 +98,22 @@ type simBlockResult struct { senders map[ctypes.Hash]common.Address } -// simCallResult is the result of a simulated call. -type simCallResult struct { - ReturnValue hexutil.Bytes `json:"returnData"` - Logs []*types.Log `json:"logs"` - GasUsed hexutil.Uint64 `json:"gasUsed"` - Status hexutil.Uint64 `json:"status"` - Error *callError `json:"error,omitempty"` +func (r *simBlockResult) MarshalJSON() ([]byte, error) { + blockData := types.RPCMarshalBlock(r.Block, true, r.fullTx, r.chainConfig) + blockData["calls"] = r.Calls + // Set tx sender if user requested full tx objects. + if r.fullTx { + if raw, ok := blockData["transactions"].([]any); ok { + for _, tx := range raw { + if tx, ok := tx.(*types.RPCTransaction); ok { + tx.From = r.senders[tx.Hash] + } else { + return nil, errors.New("simulated transaction result has invalid type") + } + } + } + } + return json.Marshal(blockData) } type simpleTrieHasher struct { diff --git a/go.mod b/go.mod index 1bc4e2c37..1c49ca9ff 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/openrelayxyz/cardinal-evm -go 1.19 +go 1.22 + +toolchain go1.22.2 require ( github.com/Shopify/sarama v1.28.0 @@ -9,6 +11,7 @@ require ( github.com/crate-crypto/go-kzg-4844 v0.7.0 github.com/davecgh/go-spew v1.1.1 github.com/ethereum/c-kzg-4844/bindings/go v0.0.0-20230126171313-363c7d7593b4 + github.com/ethereum/go-verkle v0.2.2 github.com/google/gofuzz v1.2.0 github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d github.com/holiman/uint256 v1.2.4 @@ -37,6 +40,7 @@ require ( github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/consensys/bavard v0.1.13 // indirect + github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect github.com/dgraph-io/badger/v3 v3.2103.1 // indirect github.com/dgraph-io/ristretto v0.1.0 // indirect diff --git a/go.sum b/go.sum index 5600275f0..e3b51ecae 100644 --- a/go.sum +++ b/go.sum @@ -37,6 +37,8 @@ github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c h1:uQYC5Z1mdLRPrZhHjHxufI8+2UG/i25QG92j0Er9p6I= +github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs= github.com/crate-crypto/go-kzg-4844 v0.7.0 h1:C0vgZRk4q4EZ/JgPfzuSoxdCq3C3mOZMBShovmncxvA= github.com/crate-crypto/go-kzg-4844 v0.7.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -61,6 +63,8 @@ github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/ethereum/c-kzg-4844/bindings/go v0.0.0-20230126171313-363c7d7593b4 h1:B2mpK+MNqgPqk2/KNi1LbqwtZDy5F7iy0mynQiBr8VA= github.com/ethereum/c-kzg-4844/bindings/go v0.0.0-20230126171313-363c7d7593b4/go.mod h1:y4GA2JbAUama1S4QwYjC2hefgGLU8Ul0GMtL/ADMF1c= +github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8= +github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY= diff --git a/types/block.go b/types/block.go index 685fe41e8..5db33c6b5 100644 --- a/types/block.go +++ b/types/block.go @@ -5,38 +5,81 @@ import ( "sync/atomic" "time" "slices" + "encoding/binary" + + "github.com/ethereum/go-verkle" "github.com/openrelayxyz/cardinal-evm/common" + "github.com/openrelayxyz/cardinal-types/hexutil" "github.com/openrelayxyz/cardinal-types" ) +// A BlockNonce is a 64-bit hash which proves (combined with the +// mix-hash) that a sufficient amount of computation has been carried +// out on a block. +type BlockNonce [8]byte + +// EncodeNonce converts the given integer to a block nonce. +func EncodeNonce(i uint64) BlockNonce { + var n BlockNonce + binary.BigEndian.PutUint64(n[:], i) + return n +} + +// Uint64 returns the integer value of a block nonce. +func (n BlockNonce) Uint64() uint64 { + return binary.BigEndian.Uint64(n[:]) +} + +// MarshalText encodes n as a hex string with 0x prefix. +func (n BlockNonce) MarshalText() ([]byte, error) { + return hexutil.Bytes(n[:]).MarshalText() +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (n *BlockNonce) UnmarshalText(input []byte) error { + return hexutil.UnmarshalFixedText("BlockNonce", input, n[:]) +} +// ExecutionWitness represents the witness + proof used in a verkle context, +// to provide the ability to execute a block statelessly. +type ExecutionWitness struct { + StateDiff verkle.StateDiff `json:"stateDiff"` + VerkleProof *verkle.VerkleProof `json:"verkleProof"` +} + +// Header represents a block header in the Ethereum blockchain. type Header struct { - ParentHash types.Hash - UncleHash types.Hash - Coinbase common.Address // fee reciptient - Root types.Hash - TxHash types.Hash - ReceiptHash types.Hash - Bloom [256]byte - Difficulty *big.Int - Number *big.Int - GasLimit uint64 - GasUsed uint64 - Time uint64 - Extra []byte - MixDigest types.Hash // prevrandao - Nonce [8]byte - BaseFee *big.Int `rlp:"optional"` - WithdrawalsHash *types.Hash `rlp:"optional"` + ParentHash types.Hash `json:"parentHash" gencodec:"required"` + UncleHash types.Hash `json:"sha3Uncles" gencodec:"required"` + Coinbase common.Address `json:"miner"` + Root types.Hash `json:"stateRoot" gencodec:"required"` + TxHash types.Hash `json:"transactionsRoot" gencodec:"required"` + ReceiptHash types.Hash `json:"receiptsRoot" gencodec:"required"` + Bloom Bloom `json:"logsBloom" gencodec:"required"` + Difficulty *big.Int `json:"difficulty" gencodec:"required"` + Number *big.Int `json:"number" gencodec:"required"` + GasLimit uint64 `json:"gasLimit" gencodec:"required"` + GasUsed uint64 `json:"gasUsed" gencodec:"required"` + Time uint64 `json:"timestamp" gencodec:"required"` + Extra []byte `json:"extraData" gencodec:"required"` + MixDigest types.Hash `json:"mixHash"` + Nonce BlockNonce `json:"nonce"` + + // BaseFee was added by EIP-1559 and is ignored in legacy headers. + BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"` + + // WithdrawalsHash was added by EIP-4895 and is ignored in legacy headers. + WithdrawalsHash *types.Hash `json:"withdrawalsRoot" rlp:"optional"` + // BlobGasUsed was added by EIP-4844 and is ignored in legacy headers. - BlobGasUsed *uint64 `rlp:"optional"` + BlobGasUsed *uint64 `json:"blobGasUsed" rlp:"optional"` // ExcessBlobGas was added by EIP-4844 and is ignored in legacy headers. - ExcessBlobGas *uint64 `rlp:"optional"` + ExcessBlobGas *uint64 `json:"excessBlobGas" rlp:"optional"` - // BeaconRoot was added by EIP-4788 and is ignored in legacy headers. - BeaconRoot *types.Hash `rlp:"optional"` + // ParentBeaconRoot was added by EIP-4788 and is ignored in legacy headers. + ParentBeaconRoot *types.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` // RequestsHash was added by EIP-7685 and is ignored in legacy headers. RequestsHash *types.Hash `json:"requestsHash" rlp:"optional"` diff --git a/types/gen_authorization.go b/types/gen_authorization.go index 1c361e215..8e3575f4d 100644 --- a/types/gen_authorization.go +++ b/types/gen_authorization.go @@ -1,75 +1,92 @@ -// Code generated by github.com/fjl/gencodec. DO NOT EDIT. - package types import ( "encoding/json" "errors" - "github.com/holiman/uint256" "github.com/openrelayxyz/cardinal-evm/common" "github.com/openrelayxyz/cardinal-types/hexutil" + "github.com/holiman/uint256" ) +// field type overrides for gencodec +type authorizationMarshaling struct { + ChainID U256 + Nonce hexutil.Uint64 + V hexutil.Uint64 + R U256 + S U256 +} + +// SetCodeAuthorization is an authorization from an account to deploy code at its address. +type SetCodeAuthorization struct { + ChainID uint256.Int `json:"chainId" gencodec:"required"` + Address common.Address `json:"address" gencodec:"required"` + Nonce uint64 `json:"nonce" gencodec:"required"` + V uint8 `json:"yParity" gencodec:"required"` + R uint256.Int `json:"r" gencodec:"required"` + S uint256.Int `json:"s" gencodec:"required"` +} + var _ = (*authorizationMarshaling)(nil) // MarshalJSON marshals as JSON. -func (a Authorization) MarshalJSON() ([]byte, error) { - type Authorization struct { - ChainID U256 `json:"chainId" gencodec:"required"` +func (s SetCodeAuthorization) MarshalJSON() ([]byte, error) { + type SetCodeAuthorization struct { + ChainID U256 `json:"chainId" gencodec:"required"` Address common.Address `json:"address" gencodec:"required"` Nonce hexutil.Uint64 `json:"nonce" gencodec:"required"` V hexutil.Uint64 `json:"yParity" gencodec:"required"` - R U256 `json:"r" gencodec:"required"` - S U256 `json:"s" gencodec:"required"` + R U256 `json:"r" gencodec:"required"` + S U256 `json:"s" gencodec:"required"` } - var enc Authorization - enc.ChainID = U256(a.ChainID) - enc.Address = a.Address - enc.Nonce = hexutil.Uint64(a.Nonce) - enc.V = hexutil.Uint64(a.V) - enc.R = U256(a.R) - enc.S = U256(a.S) + var enc SetCodeAuthorization + enc.ChainID = U256(s.ChainID) + enc.Address = s.Address + enc.Nonce = hexutil.Uint64(s.Nonce) + enc.V = hexutil.Uint64(s.V) + enc.R = U256(s.R) + enc.S = U256(s.S) return json.Marshal(&enc) } // UnmarshalJSON unmarshals from JSON. -func (a *Authorization) UnmarshalJSON(input []byte) error { - type Authorization struct { - ChainID *U256 `json:"chainId" gencodec:"required"` +func (s *SetCodeAuthorization) UnmarshalJSON(input []byte) error { + type SetCodeAuthorization struct { + ChainID *U256 `json:"chainId" gencodec:"required"` Address *common.Address `json:"address" gencodec:"required"` Nonce *hexutil.Uint64 `json:"nonce" gencodec:"required"` V *hexutil.Uint64 `json:"yParity" gencodec:"required"` - R *U256 `json:"r" gencodec:"required"` - S *U256 `json:"s" gencodec:"required"` + R *U256 `json:"r" gencodec:"required"` + S *U256 `json:"s" gencodec:"required"` } - var dec Authorization + var dec SetCodeAuthorization if err := json.Unmarshal(input, &dec); err != nil { return err } if dec.ChainID == nil { - return errors.New("missing required field 'chainId' for Authorization") + return errors.New("missing required field 'chainId' for SetCodeAuthorization") } - a.ChainID = uint256.Int(*dec.ChainID) + s.ChainID = uint256.Int(*dec.ChainID) if dec.Address == nil { - return errors.New("missing required field 'address' for Authorization") + return errors.New("missing required field 'address' for SetCodeAuthorization") } - a.Address = *dec.Address + s.Address = *dec.Address if dec.Nonce == nil { - return errors.New("missing required field 'nonce' for Authorization") + return errors.New("missing required field 'nonce' for SetCodeAuthorization") } - a.Nonce = uint64(*dec.Nonce) + s.Nonce = uint64(*dec.Nonce) if dec.V == nil { - return errors.New("missing required field 'yParity' for Authorization") + return errors.New("missing required field 'yParity' for SetCodeAuthorization") } - a.V = uint8(*dec.V) + s.V = uint8(*dec.V) if dec.R == nil { - return errors.New("missing required field 'r' for Authorization") + return errors.New("missing required field 'r' for SetCodeAuthorization") } - a.R = uint256.Int(*dec.R) + s.R = uint256.Int(*dec.R) if dec.S == nil { - return errors.New("missing required field 's' for Authorization") + return errors.New("missing required field 's' for SetCodeAuthorization") } - a.S = uint256.Int(*dec.S) + s.S = uint256.Int(*dec.S) return nil } diff --git a/types/legacy_tx.go b/types/legacy_tx.go index 331c551fc..e6c00cd9a 100644 --- a/types/legacy_tx.go +++ b/types/legacy_tx.go @@ -123,4 +123,4 @@ func (tx *LegacyTx) encode(b *bytes.Buffer) error { func (tx *LegacyTx) decode(input []byte) error { return rlp.DecodeBytes(input, tx) -} \ No newline at end of file +} diff --git a/types/marshalling.go b/types/marshalling.go new file mode 100644 index 000000000..48628f8a6 --- /dev/null +++ b/types/marshalling.go @@ -0,0 +1,84 @@ +package types + +import ( + // "github.com/openrelayxyz/cardinal-evm/api" + "github.com/openrelayxyz/cardinal-evm/params" + "github.com/openrelayxyz/cardinal-types/hexutil" + "github.com/openrelayxyz/cardinal-types" +) + +// RPCMarshalHeader converts the given header to the RPC output . +func RPCMarshalHeader(head *Header) map[string]interface{} { + result := map[string]interface{}{ + "number": (*hexutil.Big)(head.Number), + "hash": head.Hash(), + "parentHash": head.ParentHash, + "nonce": head.Nonce, + "mixHash": head.MixDigest, + "sha3Uncles": head.UncleHash, + "logsBloom": head.Bloom, + "stateRoot": head.Root, + "miner": head.Coinbase, + "difficulty": (*hexutil.Big)(head.Difficulty), + "extraData": hexutil.Bytes(head.Extra), + "gasLimit": hexutil.Uint64(head.GasLimit), + "gasUsed": hexutil.Uint64(head.GasUsed), + "timestamp": hexutil.Uint64(head.Time), + "transactionsRoot": head.TxHash, + "receiptsRoot": head.ReceiptHash, + } + if head.BaseFee != nil { + result["baseFeePerGas"] = (*hexutil.Big)(head.BaseFee) + } + if head.WithdrawalsHash != nil { + result["withdrawalsRoot"] = head.WithdrawalsHash + } + if head.BlobGasUsed != nil { + result["blobGasUsed"] = hexutil.Uint64(*head.BlobGasUsed) + } + if head.ExcessBlobGas != nil { + result["excessBlobGas"] = hexutil.Uint64(*head.ExcessBlobGas) + } + if head.ParentBeaconRoot != nil { + result["parentBeaconBlockRoot"] = head.ParentBeaconRoot + } + if head.RequestsHash != nil { + result["requestsHash"] = head.RequestsHash + } + return result +} + +// RPCMarshalBlock converts the given block to the RPC output which depends on fullTx. If inclTx is true transactions are +// returned. When fullTx is true the returned block contains full transaction details, otherwise it will only contain +// transaction hashes. +func RPCMarshalBlock(block *Block, inclTx bool, fullTx bool, config *params.ChainConfig) map[string]interface{} { + fields := RPCMarshalHeader(block.Header()) + fields["size"] = hexutil.Uint64(hexutil.Uint64(hexutil.Uint64(block.size.Load()))) + + if inclTx { + formatTx := func(idx int, tx *Transaction) interface{} { + return tx.Hash() + } + if fullTx { + formatTx = func(idx int, tx *Transaction) interface{} { + return NewRPCTransactionFromBlockIndex(block, uint64(idx), config) + } + } + txs := block.transactions + transactions := make([]interface{}, len(txs)) + for i, tx := range txs { + transactions[i] = formatTx(i, tx) + } + fields["transactions"] = transactions + } + uncles := block.uncles + uncleHashes := make([]types.Hash, len(uncles)) + for i, uncle := range uncles { + uncleHashes[i] = uncle.Hash() + } + fields["uncles"] = uncleHashes + if block.withdrawals != nil { + fields["withdrawals"] = block.withdrawals + } + return fields +} diff --git a/types/rpc.go b/types/rpc.go new file mode 100644 index 000000000..35c85fd08 --- /dev/null +++ b/types/rpc.go @@ -0,0 +1,234 @@ +package types + +import ( + "math/big" + + // "github.com/openrelayxyz/cardinal-evm/common" + "github.com/openrelayxyz/cardinal-evm/params" + "github.com/openrelayxyz/cardinal-types/hexutil" + + "github.com/openrelayxyz/cardinal-types" +) + +// // RPCTransaction represents a transaction that will serialize to the RPC representation of a transaction +// type RPCTransaction struct { +// BlockHash *Hash `json:"blockHash"` +// BlockNumber *hexutil.Big `json:"blockNumber"` +// From common.Address `json:"from"` +// Gas hexutil.Uint64 `json:"gas"` +// GasPrice *hexutil.Big `json:"gasPrice"` +// GasFeeCap *hexutil.Big `json:"maxFeePerGas,omitempty"` +// GasTipCap *hexutil.Big `json:"maxPriorityFeePerGas,omitempty"` +// MaxFeePerBlobGas *hexutil.Big `json:"maxFeePerBlobGas,omitempty"` +// Hash Hash `json:"hash"` +// Input hexutil.Bytes `json:"input"` +// Nonce hexutil.Uint64 `json:"nonce"` +// To *common.Address `json:"to"` +// TransactionIndex *hexutil.Uint64 `json:"transactionIndex"` +// Value *hexutil.Big `json:"value"` +// Type hexutil.Uint64 `json:"type"` +// Accesses *types.AccessList `json:"accessList,omitempty"` +// ChainID *hexutil.Big `json:"chainId,omitempty"` +// BlobVersionedHashes []Hash `json:"blobVersionedHashes,omitempty"` +// AuthorizationList []SetCodeAuthorization `json:"authorizationList,omitempty"` +// V *hexutil.Big `json:"v"` +// R *hexutil.Big `json:"r"` +// S *hexutil.Big `json:"s"` +// YParity *hexutil.Uint64 `json:"yParity,omitempty"` +// } + +// newRPCTransaction returns a transaction that will serialize to the RPC +// representation, with the given location metadata set (if available). +// func newRPCTransaction(tx *Transaction, blockHash types.Hash, blockNumber uint64, blockTime uint64, index uint64, baseFee *big.Int, config *params.ChainConfig) { +func newRPCTransaction(tx *Transaction, blockHash types.Hash, blockNumber uint64, blockTime uint64, index uint64, baseFee *big.Int, config *params.ChainConfig) *RPCTransaction { + signer := MakeSigner(config, new(big.Int).SetUint64(blockNumber), blockTime) + from, _ := Sender(signer, tx) + v, r, s := tx.RawSignatureValues() + result := &RPCTransaction{ + Type: hexutil.Uint64(tx.Type()), + From: from, + Gas: hexutil.Uint64(tx.Gas()), + GasPrice: (*hexutil.Big)(tx.GasPrice()), + Hash: tx.Hash(), + Input: hexutil.Bytes(tx.Data()), + Nonce: hexutil.Uint64(tx.Nonce()), + To: tx.To(), + Value: (*hexutil.Big)(tx.Value()), + V: (*hexutil.Big)(v), + R: (*hexutil.Big)(r), + S: (*hexutil.Big)(s), + } + if blockHash != (types.Hash{}) { + result.BlockHash = &blockHash + result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber)) + result.TransactionIndex = (*hexutil.Uint64)(&index) + } + + switch tx.Type() { + case LegacyTxType: + // if a legacy transaction has an EIP-155 chain id, include it explicitly + if id := tx.ChainId(); id.Sign() != 0 { + result.ChainID = (*hexutil.Big)(id) + } + + case AccessListTxType: + al := tx.AccessList() + yparity := hexutil.Uint64(v.Sign()) + result.Accesses = &al + result.ChainID = (*hexutil.Big)(tx.ChainId()) + result.YParity = &yparity + + case DynamicFeeTxType: + al := tx.AccessList() + yparity := hexutil.Uint64(v.Sign()) + result.Accesses = &al + result.ChainID = (*hexutil.Big)(tx.ChainId()) + result.YParity = &yparity + result.GasFeeCap = (*hexutil.Big)(tx.GasFeeCap()) + result.GasTipCap = (*hexutil.Big)(tx.GasTipCap()) + // if the transaction has been mined, compute the effective gas price + if baseFee != nil && blockHash != (types.Hash{}) { + // price = min(gasTipCap + baseFee, gasFeeCap) + result.GasPrice = (*hexutil.Big)(effectiveGasPrice(tx, baseFee)) + } else { + result.GasPrice = (*hexutil.Big)(tx.GasFeeCap()) + } + + case BlobTxType: + al := tx.AccessList() + yparity := hexutil.Uint64(v.Sign()) + result.Accesses = &al + result.ChainID = (*hexutil.Big)(tx.ChainId()) + result.YParity = &yparity + result.GasFeeCap = (*hexutil.Big)(tx.GasFeeCap()) + result.GasTipCap = (*hexutil.Big)(tx.GasTipCap()) + // if the transaction has been mined, compute the effective gas price + if baseFee != nil && blockHash != (types.Hash{}) { + result.GasPrice = (*hexutil.Big)(effectiveGasPrice(tx, baseFee)) + } else { + result.GasPrice = (*hexutil.Big)(tx.GasFeeCap()) + } + result.MaxFeePerBlobGas = (*hexutil.Big)(tx.BlobGasFeeCap()) + result.BlobVersionedHashes = tx.BlobHashes() + + case SetCodeTxType: + al := tx.AccessList() + yparity := hexutil.Uint64(v.Sign()) + result.Accesses = &al + result.ChainID = (*hexutil.Big)(tx.ChainId()) + result.YParity = &yparity + result.GasFeeCap = (*hexutil.Big)(tx.GasFeeCap()) + result.GasTipCap = (*hexutil.Big)(tx.GasTipCap()) + // if the transaction has been mined, compute the effective gas price + if baseFee != nil && blockHash != (types.Hash{}) { + result.GasPrice = (*hexutil.Big)(effectiveGasPrice(tx, baseFee)) + } else { + result.GasPrice = (*hexutil.Big)(tx.GasFeeCap()) + } + result.AuthorizationList = tx.SetCodeAuthorizations() + } + return result +} + +// newRPCTransactionFromBlockIndex returns a transaction that will serialize to the RPC representation. +// func newRPCTransactionFromBlockIndex(b *Block, index uint64, config *params.ChainConfig) { +func newRPCTransactionFromBlockIndex(b *Block, index uint64, config *params.ChainConfig) *RPCTransaction { + txs := b.transactions + if index >= uint64(len(txs)) { + return nil + } + return newRPCTransaction(txs[index], b.header.Hash(), b.header.Number.Uint64(), b.header.Time, index, b.header.BaseFee, config) +} + +// func NewRPCTransactionFromBlockIndex(b *Block, index uint64, config *params.ChainConfig) { +func NewRPCTransactionFromBlockIndex(b *Block, index uint64, config *params.ChainConfig) *RPCTransaction { + return newRPCTransactionFromBlockIndex(b, index, config) +} + +// effectiveGasPrice computes the transaction gas fee, based on the given basefee value. +// +// price = min(gasTipCap + baseFee, gasFeeCap) +func effectiveGasPrice(tx *Transaction, baseFee *big.Int) *big.Int { + fee := tx.GasTipCap() + fee = fee.Add(fee, baseFee) + if tx.GasFeeCapIntCmp(fee) < 0 { + return tx.GasFeeCap() + } + return fee +} + +// // RPCMarshalHeader converts the given header to the RPC output . +// func RPCMarshalHeader(head *types.Header) map[string]interface{} { +// result := map[string]interface{}{ +// "number": (*hexutil.Big)(head.Number), +// "hash": head.Hash(), +// "parentHash": head.ParentHash, +// "nonce": head.Nonce, +// "mixHash": head.MixDigest, +// "sha3Uncles": head.UncleHash, +// "logsBloom": head.Bloom, +// "stateRoot": head.Root, +// "miner": head.Coinbase, +// "difficulty": (*hexutil.Big)(head.Difficulty), +// "extraData": hexutil.Bytes(head.Extra), +// "gasLimit": hexutil.Uint64(head.GasLimit), +// "gasUsed": hexutil.Uint64(head.GasUsed), +// "timestamp": hexutil.Uint64(head.Time), +// "transactionsRoot": head.TxHash, +// "receiptsRoot": head.ReceiptHash, +// } +// if head.BaseFee != nil { +// result["baseFeePerGas"] = (*hexutil.Big)(head.BaseFee) +// } +// if head.WithdrawalsHash != nil { +// result["withdrawalsRoot"] = head.WithdrawalsHash +// } +// if head.BlobGasUsed != nil { +// result["blobGasUsed"] = hexutil.Uint64(*head.BlobGasUsed) +// } +// if head.ExcessBlobGas != nil { +// result["excessBlobGas"] = hexutil.Uint64(*head.ExcessBlobGas) +// } +// if head.ParentBeaconRoot != nil { +// result["parentBeaconBlockRoot"] = head.ParentBeaconRoot +// } +// if head.RequestsHash != nil { +// result["requestsHash"] = head.RequestsHash +// } +// return result +// } + +// RPCMarshalBlock converts the given block to the RPC output which depends on fullTx. If inclTx is true transactions are +// // returned. When fullTx is true the returned block contains full transaction details, otherwise it will only contain +// // transaction hashes. +// func RPCMarshalBlock(block *types.Block, inclTx bool, fullTx bool, config *params.ChainConfig) map[string]interface{} { +// fields := RPCMarshalHeader(block.Header()) +// fields["size"] = hexutil.Uint64(block.Size()) + +// if inclTx { +// formatTx := func(idx int, tx *types.Transaction) interface{} { +// return tx.Hash() +// } +// if fullTx { +// formatTx = func(idx int, tx *types.Transaction) interface{} { +// return newRPCTransactionFromBlockIndex(block, uint64(idx), config) +// } +// } +// txs := block.Transactions() +// transactions := make([]interface{}, len(txs)) +// for i, tx := range txs { +// transactions[i] = formatTx(i, tx) +// } +// fields["transactions"] = transactions +// } +// uncles := block.Uncles() +// uncleHashes := make([]Hash, len(uncles)) +// for i, uncle := range uncles { +// uncleHashes[i] = uncle.Hash() +// } +// fields["uncles"] = uncleHashes +// if block.Withdrawals() != nil { +// fields["withdrawals"] = block.Withdrawals() +// } +// return fields +// } \ No newline at end of file diff --git a/types/transaction.go b/types/transaction.go index bb4e30a54..cdbacc39a 100644 --- a/types/transaction.go +++ b/types/transaction.go @@ -29,6 +29,7 @@ import ( "github.com/openrelayxyz/cardinal-evm/common/math" "github.com/openrelayxyz/cardinal-evm/crypto" "github.com/openrelayxyz/cardinal-evm/rlp" + "github.com/openrelayxyz/cardinal-types/hexutil" ctypes "github.com/openrelayxyz/cardinal-types" ) @@ -305,6 +306,24 @@ func (tx *Transaction) BlobGas() uint64 { return tx.inner.blobGas() } func (tx *Transaction) BlobHashes() []ctypes.Hash { return tx.inner.blobHashes() } +// SetCodeAuthorizations returns the authorizations list of the transaction. +func (tx *Transaction) SetCodeAuthorizations() []SetCodeAuthorization { + // setcodetx, ok := tx.inner.(*SetCodeTx) + // if !ok { + // return nil + // } + // return setcodetx.AuthList + setcodetx, ok := tx.inner.(*SetCodeTx) + if !ok { + return nil + } + auths := make([]SetCodeAuthorization, len(setcodetx.AuthList)) + for i, a := range setcodetx.AuthList { + auths[i] = SetCodeAuthorization(a) // requires they are type-compatible + } + return auths +} + // To returns the recipient address of the transaction. // For contract-creation transactions, To returns nil. func (tx *Transaction) To() *common.Address { @@ -682,4 +701,31 @@ func copyAddressPtr(a *common.Address) *common.Address { } cpy := *a return &cpy +} + +// RPCTransaction represents a transaction that will serialize to the RPC representation of a transaction +type RPCTransaction struct { + BlockHash *ctypes.Hash `json:"blockHash"` + BlockNumber *hexutil.Big `json:"blockNumber"` + From common.Address `json:"from"` + Gas hexutil.Uint64 `json:"gas"` + GasPrice *hexutil.Big `json:"gasPrice"` + GasFeeCap *hexutil.Big `json:"maxFeePerGas,omitempty"` + GasTipCap *hexutil.Big `json:"maxPriorityFeePerGas,omitempty"` + MaxFeePerBlobGas *hexutil.Big `json:"maxFeePerBlobGas,omitempty"` + Hash ctypes.Hash `json:"hash"` + Input hexutil.Bytes `json:"input"` + Nonce hexutil.Uint64 `json:"nonce"` + To *common.Address `json:"to"` + TransactionIndex *hexutil.Uint64 `json:"transactionIndex"` + Value *hexutil.Big `json:"value"` + Type hexutil.Uint64 `json:"type"` + Accesses *AccessList `json:"accessList,omitempty"` + ChainID *hexutil.Big `json:"chainId,omitempty"` + BlobVersionedHashes []ctypes.Hash `json:"blobVersionedHashes,omitempty"` + AuthorizationList []SetCodeAuthorization `json:"authorizationList,omitempty"` + V *hexutil.Big `json:"v"` + R *hexutil.Big `json:"r"` + S *hexutil.Big `json:"s"` + YParity *hexutil.Uint64 `json:"yParity,omitempty"` } \ No newline at end of file diff --git a/types/tx_setcode.go b/types/tx_setcode.go index 92750aef3..29f86e1ed 100644 --- a/types/tx_setcode.go +++ b/types/tx_setcode.go @@ -26,7 +26,6 @@ import ( "github.com/openrelayxyz/cardinal-evm/crypto" "github.com/openrelayxyz/cardinal-evm/rlp" ctypes "github.com/openrelayxyz/cardinal-types" - "github.com/openrelayxyz/cardinal-types/hexutil" "github.com/holiman/uint256" ) @@ -79,14 +78,14 @@ type Authorization struct { S uint256.Int `json:"s" gencodec:"required"` } -// field type overrides for gencodec -type authorizationMarshaling struct { - ChainID U256 - Nonce hexutil.Uint64 - V hexutil.Uint64 - R U256 - S U256 -} +// // field type overrides for gencodec +// type authorizationMarshaling struct { +// ChainID U256 +// Nonce hexutil.Uint64 +// V hexutil.Uint64 +// R U256 +// S U256 +// } // SignAuth signs the provided authorization. func SignAuth(auth Authorization, prv *ecdsa.PrivateKey) (Authorization, error) { From 3470b5777672a15020c5a5a976071527b3471468 Mon Sep 17 00:00:00 2001 From: Philip Morlier Date: Tue, 9 Sep 2025 12:33:52 -0700 Subject: [PATCH 17/72] Calling Bytes() on logsBloom when marshalling header --- types/marshalling.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/marshalling.go b/types/marshalling.go index 48628f8a6..c523fc117 100644 --- a/types/marshalling.go +++ b/types/marshalling.go @@ -16,7 +16,7 @@ func RPCMarshalHeader(head *Header) map[string]interface{} { "nonce": head.Nonce, "mixHash": head.MixDigest, "sha3Uncles": head.UncleHash, - "logsBloom": head.Bloom, + "logsBloom": head.Bloom.Bytes(), "stateRoot": head.Root, "miner": head.Coinbase, "difficulty": (*hexutil.Big)(head.Difficulty), From f774ef16ba806ecdf32236379dda9803eec6bea8 Mon Sep 17 00:00:00 2001 From: Philip Morlier Date: Tue, 9 Sep 2025 12:43:09 -0700 Subject: [PATCH 18/72] hexutil.Ecode() logBloom byte string to hex string --- types/marshalling.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/marshalling.go b/types/marshalling.go index c523fc117..f7df2de40 100644 --- a/types/marshalling.go +++ b/types/marshalling.go @@ -16,7 +16,7 @@ func RPCMarshalHeader(head *Header) map[string]interface{} { "nonce": head.Nonce, "mixHash": head.MixDigest, "sha3Uncles": head.UncleHash, - "logsBloom": head.Bloom.Bytes(), + "logsBloom": hexutil.Encode(head.Bloom.Bytes()), "stateRoot": head.Root, "miner": head.Coinbase, "difficulty": (*hexutil.Big)(head.Difficulty), From d44e804d653929217d6d2765a20cbee9a96745b7 Mon Sep 17 00:00:00 2001 From: Philip Morlier Date: Tue, 9 Sep 2025 12:57:07 -0700 Subject: [PATCH 19/72] Uncommented logs types in processBlock --- api/simulate.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/simulate.go b/api/simulate.go index fe4bcc772..80f0e3ff1 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -237,7 +237,7 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, callResults = make([]simCallResult, len(block.Calls)) receipts = make([]*types.Receipt, len(block.Calls)) senders = make(map[ctypes.Hash]common.Address) - // allLogs []*types.Log + allLogs []*types.Log ) getHashFn := func(n uint64) ctypes.Hash { @@ -339,7 +339,7 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, } } else { callRes.Status = hexutil.Uint64(types.ReceiptStatusSuccessful) - // allLogs = append(allLogs, receipt.Logs...) + allLogs = append(allLogs, receipt.Logs...) } callResults[i] = callRes } From 4b24b7b1d678fbf02d4113b36088a2f52aa5561f Mon Sep 17 00:00:00 2001 From: Philip Morlier Date: Tue, 9 Sep 2025 13:46:49 -0700 Subject: [PATCH 20/72] Initialize state db logs map --- api/simulate.go | 2 ++ state/statedb.go | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/api/simulate.go b/api/simulate.go index 80f0e3ff1..f7a6f1533 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -361,3 +361,5 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, return blck, callResults, senders, nil } + +// there is a virtual log being returned by geth on any eth transfer. It is present in geth and we need to figure out how to implement it in EVM. diff --git a/state/statedb.go b/state/statedb.go index cadd837b8..7bab23b8e 100644 --- a/state/statedb.go +++ b/state/statedb.go @@ -349,7 +349,9 @@ func (sdb *stateDB) RevertToSnapshot(snap int) { sdb.journal = sdb.journal[:snap] } func (sdb *stateDB) Snapshot() int { return len(sdb.journal) } + func (sdb *stateDB) AddLog(log *types.Log) { + sdb.journal = append(sdb.journal, journalEntry{nil, func(sdb *stateDB) { logs := sdb.logs[sdb.thash] if len(logs) > 0 { @@ -385,6 +387,9 @@ func (s *stateDB) GetLogs(hash ctypes.Hash, blockNumber uint64, blockHash ctypes // used when the EVM emits new state logs. It should be invoked before // transaction execution. func (s *stateDB) SetTxContext(thash ctypes.Hash, ti int) { + if s.logs == nil { + s.logs = make(map[ctypes.Hash][]*types.Log) + } s.thash = thash s.txIndex = ti } From ee0f0ce76befe46622098802cf333d0d328a3283 Mon Sep 17 00:00:00 2001 From: Philip Morlier Date: Wed, 10 Sep 2025 10:05:07 -0700 Subject: [PATCH 21/72] First attempt getting withdrawals through --- api/simulate.go | 12 +++++++++++- types/{withdrawals.go => withdrawal.go} | 18 ++++++++++++------ 2 files changed, 23 insertions(+), 7 deletions(-) rename types/{withdrawals.go => withdrawal.go} (84%) diff --git a/api/simulate.go b/api/simulate.go index f7a6f1533..ef80b9940 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -40,6 +40,8 @@ type BlockOverrides struct { PrevRandao *ctypes.Hash BaseFeePerGas *hexutil.Big BlobBaseFee *hexutil.Big + BeaconRoot *ctypes.Hash + Withdrawals *types.Withdrawals } // simOpts are the inputs to eth_simulateV1. @@ -356,10 +358,18 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, header.Bloom = types.CreateBloom(receipts) header.Root = s.base.Root - blockBody := &types.Body{Transactions: txes} + blockBody := &types.Body{Transactions: txes, Withdrawals: *block.BlockOverrides.Withdrawals} blck := types.NewBlock(header, blockBody, receipts, hasher) return blck, callResults, senders, nil } // there is a virtual log being returned by geth on any eth transfer. It is present in geth and we need to figure out how to implement it in EVM. +// aparently geth removes the extra data field from the block which we ran the call against, while evm includes it. +// look into how the block hash is being produced -- Probably not worth trying to do. +// evm includes mix hash where geth does not. +// look into parent beacon block root and why it appears to be left off from geth. +// receipt root should be achievable (maybe geth is including the virtual log in the receipts) +// geth returns a size value where evm appears not to. +// research why evm is producing a different tx hash +// evm is not including withdrawals and withdrawals hash in what we return but should. diff --git a/types/withdrawals.go b/types/withdrawal.go similarity index 84% rename from types/withdrawals.go rename to types/withdrawal.go index d92f59925..8ac137e67 100644 --- a/types/withdrawals.go +++ b/types/withdrawal.go @@ -1,4 +1,3 @@ - // Copyright 2022 The go-ethereum Authors // This file is part of the go-ethereum library. // @@ -19,10 +18,11 @@ package types import ( "bytes" + "reflect" - "github.com/openrelayxyz/plugeth-utils/core" - "github.com/openrelayxyz/plugeth-utils/restricted/hexutil" - "github.com/openrelayxyz/plugeth-utils/restricted/rlp" + "github.com/openrelayxyz/cardinal-evm/common" + "github.com/openrelayxyz/cardinal-evm/rlp" + "github.com/openrelayxyz/cardinal-types/hexutil" ) //go:generate go run github.com/fjl/gencodec -type Withdrawal -field-override withdrawalMarshaling -out gen_withdrawal_json.go @@ -32,7 +32,7 @@ import ( type Withdrawal struct { Index uint64 `json:"index"` // monotonically increasing identifier issued by consensus layer Validator uint64 `json:"validatorIndex"` // index of validator associated with withdrawal - Address core.Address `json:"address"` // target address for withdrawn ether + Address common.Address `json:"address"` // target address for withdrawn ether Amount uint64 `json:"amount"` // value of withdrawal in Gwei } @@ -49,9 +49,15 @@ type Withdrawals []*Withdrawal // Len returns the length of s. func (s Withdrawals) Len() int { return len(s) } +var withdrawalSize = int(reflect.TypeFor[Withdrawal]().Size()) + +func (s Withdrawals) Size() int { + return withdrawalSize * len(s) +} + // EncodeIndex encodes the i'th withdrawal to w. Note that this does not check for errors // because we assume that *Withdrawal will only ever contain valid withdrawals that were either // constructed by decoding or via public API in this package. func (s Withdrawals) EncodeIndex(i int, w *bytes.Buffer) { rlp.Encode(w, s[i]) -} \ No newline at end of file +} From 9bfb7065a170187a5c7108a9f543ec28ff116dc5 Mon Sep 17 00:00:00 2001 From: Philip Morlier Date: Wed, 17 Sep 2025 08:59:12 -0700 Subject: [PATCH 22/72] Added fd5 config --- params/config.go | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/params/config.go b/params/config.go index ee3c6730c..3344d0388 100644 --- a/params/config.go +++ b/params/config.go @@ -401,6 +401,45 @@ var ( }, } + Fd5ChainConfig = &ChainConfig{ + ChainID: big.NewInt(7092415936), + HomesteadBlock: big.NewInt(0), + DAOForkBlock: nil, + DAOForkSupport: true, + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + ShanghaiTime: big.NewInt(0), + CancunTime: big.NewInt(0), + PragueTime: big.NewInt(0), + OsakaTime: big.NewInt(1757611104), + Ethash: new(EthashConfig), + Engine: ETHashEngine, + BlobSchedule: []*BlobConfig{ + // Cancun + &BlobConfig{ + ActivationTime: 0, + Target: 3, + Max : 6, + UpdateFraction: 3338477, + }, + // Prague + &BlobConfig{ + ActivationTime: 0, + Target: 6, + Max : 9, + UpdateFraction: 5007716, + }, + }, + } + // AllEthashProtocolChanges contains every protocol change (EIPs) introduced // and accepted by the Ethereum core developers into the Ethash consensus. // @@ -432,6 +471,7 @@ var ChainLookup = map[int64]*ChainConfig{ 1337802: KilnChainConfig, 11155111: SepoliaChainConfig, 560048: HoodiChainConfig, + 7092415936: Fd5ChainConfig, } // ChainConfig is the core config which determines the blockchain settings. From 2e47bcf25faaba75912370f8ca7236efd851de8c Mon Sep 17 00:00:00 2001 From: Austin Roberts Date: Tue, 23 Sep 2025 16:51:36 -0500 Subject: [PATCH 23/72] Set waiting = true for null broker --- streams/consumer.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/streams/consumer.go b/streams/consumer.go index aaa6c0b2e..db84c233b 100644 --- a/streams/consumer.go +++ b/streams/consumer.go @@ -110,6 +110,12 @@ func (m *StreamManager) Start() error { safeNumKey := fmt.Sprintf("c/%x/n/safe", m.chainid) finalizedNumKey := fmt.Sprintf("c/%x/n/finalized", m.chainid) waiting := false + select { + case <-m.consumer.Ready(): + waiting = true + default: + } + waitCh := make(chan struct{}) go func() { <-m.consumer.Ready() From c494d1bb6946e6b98d2b632669fa6110cf34f976 Mon Sep 17 00:00:00 2001 From: Philip Morlier Date: Wed, 24 Sep 2025 09:07:28 -0700 Subject: [PATCH 24/72] Updated select in start function, null broker --- streams/consumer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/streams/consumer.go b/streams/consumer.go index db84c233b..0f3d9623a 100644 --- a/streams/consumer.go +++ b/streams/consumer.go @@ -113,7 +113,7 @@ func (m *StreamManager) Start() error { select { case <-m.consumer.Ready(): waiting = true - default: + case <-time.After(500*time.Millisecond): } waitCh := make(chan struct{}) From 01ea092d1e14f349137f01669206cf81fb7cbc1c Mon Sep 17 00:00:00 2001 From: Philip Morlier Date: Wed, 1 Oct 2025 12:29:59 -0700 Subject: [PATCH 25/72] Added nil check on args.Gas EstimateGas --- api/eth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/eth.go b/api/eth.go index 606221fec..551190ba0 100644 --- a/api/eth.go +++ b/api/eth.go @@ -386,7 +386,7 @@ func DoEstimateGas(ctx *rpc.CallContext, getEVM func(state.StateDB, *vm.Config, } // Verify tx gas limit does not exceed EIP-7825 cap. - if chainConfig.IsOsaka(new(big.Int).SetUint64(prevState.header.Time), prevState.header.Number) && uint64(*args.Gas) > params.MaxTxGas{ + if chainConfig.IsOsaka(new(big.Int).SetUint64(prevState.header.Time), prevState.header.Number) && args.Gas != nil && uint64(*args.Gas) > params.MaxTxGas{ return 0, nil, ErrGasLimitTooHigh } From 0274f6e2da3cf197565cfd43b79b25de08a9f4c3 Mon Sep 17 00:00:00 2001 From: Philip Morlier Date: Wed, 1 Oct 2025 12:47:24 -0700 Subject: [PATCH 26/72] added IsOsaka case to chain config rules --- vm/evm.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vm/evm.go b/vm/evm.go index 2f9a1827e..e8e8b7831 100644 --- a/vm/evm.go +++ b/vm/evm.go @@ -47,6 +47,8 @@ type ( func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) { var precompiles map[common.Address]PrecompiledContract switch { + case evm.chainRules.IsOsaka: + precompiles = PrecompiledContractsOsaka case evm.chainRules.IsPrague && evm.chainRules.IsNapoli: precompiles = PrecompiledContractsNapoliPrague case evm.chainRules.IsPrague: From e9b9303bb4ac15f72898b8850cca0ce2d769a3c9 Mon Sep 17 00:00:00 2001 From: Jesse Date: Thu, 2 Oct 2025 17:03:26 +0100 Subject: [PATCH 27/72] set empty withdrawals --- api/simulate.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/api/simulate.go b/api/simulate.go index ef80b9940..97b581c6c 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -161,6 +161,14 @@ func (s *simulator) execute(ctx *rpc.CallContext, blocks []simBlock) ([]*simBloc header.ParentHash = parent.Hash() header.Time = parent.Time + uint64(bi+1)*12 + if block.BlockOverrides == nil { + block.BlockOverrides = &BlockOverrides{} + } + if block.BlockOverrides.Withdrawals == nil { + emptyWithdrawals := types.Withdrawals{} + block.BlockOverrides.Withdrawals = &emptyWithdrawals + } + override := *block.BlockOverrides if override.Number != nil {header.Number = override.Number.ToInt()} From 55bb8145b69eb785e5b236c0a4d24d93dbad95b2 Mon Sep 17 00:00:00 2001 From: Jesse Date: Thu, 2 Oct 2025 21:02:45 +0100 Subject: [PATCH 28/72] sanitize calls and chain --- api/error.go | 14 ++++++- api/simulate.go | 100 ++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 98 insertions(+), 16 deletions(-) diff --git a/api/error.go b/api/error.go index 9f7c374ad..fdc2b101a 100644 --- a/api/error.go +++ b/api/error.go @@ -145,5 +145,17 @@ func (e *invalidParamsError) ErrorCode() int { return errCodeInvalidParams } type clientLimitExceededError struct{ message string } +type invalidBlockNumberError struct{ message string } +func (e *invalidBlockNumberError) Error() string { return e.message } +func (e *invalidBlockNumberError) ErrorCode() int { return errCodeBlockNumberInvalid } + +type invalidBlockTimestampError struct{ message string } +func (e *invalidBlockTimestampError) Error() string { return e.message } + func (e *clientLimitExceededError) Error() string { return e.message } -func (e *clientLimitExceededError) ErrorCode() int { return errCodeClientLimitExceeded } \ No newline at end of file +func (e *clientLimitExceededError) ErrorCode() int { return errCodeClientLimitExceeded } + +type blockGasLimitReachedError struct{ message string } + +func (e *blockGasLimitReachedError) Error() string { return e.message } +func (e *blockGasLimitReachedError) ErrorCode() int { return errCodeBlockGasLimitReached } diff --git a/api/simulate.go b/api/simulate.go index 97b581c6c..707bcc778 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -155,22 +155,19 @@ func (s *simulator) execute(ctx *rpc.CallContext, blocks []simBlock) ([]*simBloc parent = s.base ) + var err error + blocks, err = s.sanitizeChain(blocks) + if err != nil { + return nil, err + } + for bi, block := range blocks { header := types.CopyHeader(s.base) header.Number = new(big.Int).Add(s.base.Number, big.NewInt(int64(bi+1))) header.ParentHash = parent.Hash() header.Time = parent.Time + uint64(bi+1)*12 - if block.BlockOverrides == nil { - block.BlockOverrides = &BlockOverrides{} - } - if block.BlockOverrides.Withdrawals == nil { - emptyWithdrawals := types.Withdrawals{} - block.BlockOverrides.Withdrawals = &emptyWithdrawals - } - override := *block.BlockOverrides - if override.Number != nil {header.Number = override.Number.ToInt()} if override.Difficulty != nil {header.Difficulty = override.Difficulty.ToInt()} if override.Time != nil {header.Time = uint64(*override.Time)} @@ -232,7 +229,6 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, header.ExcessBlobGas = &excess } - // State overrides are applied prior to execution of a block if block.StateOverrides != nil { if err := block.StateOverrides.Apply(s.state); err != nil { @@ -263,6 +259,10 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, } for i, call := range block.Calls { + evm := s.evmFn(s.state, &vm.Config{ + NoBaseFee: !s.validate, + }, call.from(), call.GasPrice.ToInt()) + if err := ctx.Context().Err(); err != nil { return nil, nil, nil, err } @@ -270,17 +270,22 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, return nil, nil, nil, err } if gasUsed+uint64(*call.Gas) > header.GasLimit { - return nil, nil, nil, fmt.Errorf("block gas limit exceeded") + return nil,nil,nil, &blockGasLimitReachedError{fmt.Sprintf("block gas limit reached: %d >= %d", gasUsed, header.GasLimit)} + } + if call.Nonce == nil { + nonce := evm.StateDB.GetNonce(call.from()) + call.Nonce = (*hexutil.Uint64)(&nonce) + } + // Let the call run wild unless explicitly specified. + if call.Gas == nil { + remaining := header.GasLimit - gasUsed + call.Gas = (*hexutil.Uint64)(&remaining) } tx := call.ToTransaction(types.DynamicFeeTxType) txes[i] = tx senders[tx.Hash()] = call.from() s.state.SetTxContext(tx.Hash(), i) - evm := s.evmFn(s.state, &vm.Config{ - NoBaseFee: !s.validate, - }, call.from(), call.GasPrice.ToInt()) - evm.Context.BaseFee = header.BaseFee if evm.Context.GetHash == nil { @@ -372,6 +377,71 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, return blck, callResults, senders, nil } +// sanitizeChain checks the chain integrity. Specifically it checks that +// block numbers and timestamp are strictly increasing, setting default values +// when necessary. Gaps in block numbers are filled with empty blocks. +// Note: It modifies the block's override object. +func (s *simulator) sanitizeChain(blocks []simBlock) ([]simBlock, error) { + var ( + res = make([]simBlock, 0, len(blocks)) + base = s.base + prevNumber = base.Number + prevTimestamp = base.Time + ) + for _, block := range blocks { + if block.BlockOverrides == nil { + block.BlockOverrides = new(BlockOverrides) + } + if block.BlockOverrides.Number == nil { + n := new(big.Int).Add(prevNumber, big.NewInt(1)) + block.BlockOverrides.Number = (*hexutil.Big)(n) + } + if block.BlockOverrides.Withdrawals == nil { + block.BlockOverrides.Withdrawals = &types.Withdrawals{} + } + diff := new(big.Int).Sub(block.BlockOverrides.Number.ToInt(), prevNumber) + if diff.Cmp(common.Big0) <= 0 { + return nil, &invalidBlockNumberError{fmt.Sprintf("block numbers must be in order: %d <= %d", block.BlockOverrides.Number.ToInt().Uint64(), prevNumber)} + } + if total := new(big.Int).Sub(block.BlockOverrides.Number.ToInt(), base.Number); total.Cmp(big.NewInt(maxSimulateBlocks)) > 0 { + return nil, &clientLimitExceededError{message: "too many blocks"} + } + if diff.Cmp(big.NewInt(1)) > 0 { + // Fill the gap with empty blocks. + gap := new(big.Int).Sub(diff, big.NewInt(1)) + // Assign block number to the empty blocks. + for i := uint64(0); i < gap.Uint64(); i++ { + n := new(big.Int).Add(prevNumber, big.NewInt(int64(i+1))) + t := prevTimestamp + timestampIncrement + b := simBlock{ + BlockOverrides: &BlockOverrides{ + Number: (*hexutil.Big)(n), + Time: (*hexutil.Uint64)(&t), + Withdrawals: &types.Withdrawals{}, + }, + } + prevTimestamp = t + res = append(res, b) + } + } + // Only append block after filling a potential gap. + prevNumber = block.BlockOverrides.Number.ToInt() + var t uint64 + if block.BlockOverrides.Time == nil { + t = prevTimestamp + timestampIncrement + block.BlockOverrides.Time = (*hexutil.Uint64)(&t) + } else { + t = uint64(*block.BlockOverrides.Time) + if t <= prevTimestamp { + return nil, &invalidBlockTimestampError{fmt.Sprintf("block timestamps must be in order: %d <= %d", t, prevTimestamp)} + } + } + prevTimestamp = t + res = append(res, block) + } + return res, nil +} + // there is a virtual log being returned by geth on any eth transfer. It is present in geth and we need to figure out how to implement it in EVM. // aparently geth removes the extra data field from the block which we ran the call against, while evm includes it. // look into how the block hash is being produced -- Probably not worth trying to do. From 0f8ba97fa649b61d23ee40f164aeb3aaf9e83152 Mon Sep 17 00:00:00 2001 From: Jesse Date: Thu, 2 Oct 2025 21:59:53 +0100 Subject: [PATCH 29/72] first pass at tracer logs --- api/logtracer.go | 82 ++++++++++++++++++++++++++++++++++++++++++++++++ api/simulate.go | 9 ++++-- 2 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 api/logtracer.go diff --git a/api/logtracer.go b/api/logtracer.go new file mode 100644 index 000000000..94904407f --- /dev/null +++ b/api/logtracer.go @@ -0,0 +1,82 @@ +package api + +import ( + "math/big" + "time" + "github.com/openrelayxyz/cardinal-evm/types" + "github.com/openrelayxyz/cardinal-evm/vm" + "github.com/openrelayxyz/cardinal-evm/common" + ctypes "github.com/openrelayxyz/cardinal-types" +) + +var ( + // keccak256("Transfer(address,address,uint256)") + transferTopic = ctypes.HexToHash("ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef") + // ERC-7528 + transferAddress = common.HexToAddress("0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE") +) + + +type tracer struct { + // logs keeps logs for all open call frames. + // This lets us clear logs for failed calls. + logs []*types.Log + count int + traceTransfers bool + blockNumber uint64 + blockHash ctypes.Hash + txHash ctypes.Hash + txIdx uint +} + +func newTracer(traceTransfers bool, blockNumber uint64, blockHash, txHash ctypes.Hash, txIndex uint) *tracer { + return &tracer{ + logs: make([]*types.Log, 0), + traceTransfers: traceTransfers, + blockNumber: blockNumber, + blockHash: blockHash, + txHash: txHash, + txIdx: txIndex, + } +} + +func (t *tracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + if !t.traceTransfers { + return + } + if typ != vm.DELEGATECALL && value != nil && value.Sign() > 0 { + topics := []ctypes.Hash{ + transferTopic, + ctypes.BytesToHash(from.Bytes()), + ctypes.BytesToHash(to.Bytes()), + } + t.logs = append(t.logs, &types.Log{ + Address: transferAddress, + Topics: topics, + Data: ctypes.BigToHash(value).Bytes(), + BlockNumber: t.blockNumber, + BlockHash: t.blockHash, + TxHash: t.txHash, + TxIndex: t.txIdx, + Index: uint(t.count), + }) + t.count++ + } +} + +func (t *tracer) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int){} +func (t *tracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error){} +func (t *tracer) CaptureExit(output []byte, gasUsed uint64, err error){} +func (t *tracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error){} +func (t *tracer) CaptureEnd(output []byte, gasUsed uint64, time time.Duration, err error){} + +// reset prepares the tracer for the next transaction. +func (t *tracer) reset(txHash ctypes.Hash, txIdx uint) { + t.logs = nil + t.txHash = txHash + t.txIdx = txIdx +} + +func (t *tracer) Logs() []*types.Log { + return t.logs +} \ No newline at end of file diff --git a/api/simulate.go b/api/simulate.go index 707bcc778..dc57cc723 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -243,7 +243,6 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, callResults = make([]simCallResult, len(block.Calls)) receipts = make([]*types.Receipt, len(block.Calls)) senders = make(map[ctypes.Hash]common.Address) - allLogs []*types.Log ) getHashFn := func(n uint64) ctypes.Hash { @@ -259,8 +258,9 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, } for i, call := range block.Calls { + tracer := newTracer(s.traceTransfers, header.Number.Uint64(), header.Hash(), ctypes.Hash{}, uint(i)) evm := s.evmFn(s.state, &vm.Config{ - NoBaseFee: !s.validate, + NoBaseFee: !s.validate, Tracer: tracer, }, call.from(), call.GasPrice.ToInt()) if err := ctx.Context().Err(); err != nil { @@ -284,6 +284,7 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, tx := call.ToTransaction(types.DynamicFeeTxType) txes[i] = tx senders[tx.Hash()] = call.from() + tracer.reset(tx.Hash(), uint(i)) s.state.SetTxContext(tx.Hash(), i) evm.Context.BaseFee = header.BaseFee @@ -320,6 +321,9 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, TransactionIndex: uint(i), } receipt.Logs = s.state.GetLogs(tx.Hash(), header.Number.Uint64(), header.Hash()) + if s.traceTransfers { + receipt.Logs = append(receipt.Logs, tracer.Logs()...) + } receipt.Bloom = types.CreateBloom([]*types.Receipt{receipt}) if tx.To() == nil { receipt.ContractAddress = crypto.CreateAddress(*call.From, tx.Nonce()) @@ -354,7 +358,6 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, } } else { callRes.Status = hexutil.Uint64(types.ReceiptStatusSuccessful) - allLogs = append(allLogs, receipt.Logs...) } callResults[i] = callRes } From 8ff51be08da1690604b90dc3ba26b7db27f4b052 Mon Sep 17 00:00:00 2001 From: Jesse Date: Thu, 2 Oct 2025 22:23:23 +0100 Subject: [PATCH 30/72] add debug logs --- api/logtracer.go | 9 +++++++-- api/simulate.go | 4 +++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/api/logtracer.go b/api/logtracer.go index 94904407f..d5524c7d7 100644 --- a/api/logtracer.go +++ b/api/logtracer.go @@ -7,6 +7,7 @@ import ( "github.com/openrelayxyz/cardinal-evm/vm" "github.com/openrelayxyz/cardinal-evm/common" ctypes "github.com/openrelayxyz/cardinal-types" + log "github.com/inconshreveable/log15" ) var ( @@ -41,6 +42,7 @@ func newTracer(traceTransfers bool, blockNumber uint64, blockHash, txHash ctypes } func (t *tracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + log.Error("inside captureenter") if !t.traceTransfers { return } @@ -64,7 +66,9 @@ func (t *tracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Addr } } -func (t *tracer) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int){} +func (t *tracer) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int){ + log.Error("inside capturestart") +} func (t *tracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error){} func (t *tracer) CaptureExit(output []byte, gasUsed uint64, err error){} func (t *tracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error){} @@ -72,7 +76,8 @@ func (t *tracer) CaptureEnd(output []byte, gasUsed uint64, time time.Duration, e // reset prepares the tracer for the next transaction. func (t *tracer) reset(txHash ctypes.Hash, txIdx uint) { - t.logs = nil + t.logs = make([]*types.Log, 0) + t.count = 0 t.txHash = txHash t.txIdx = txIdx } diff --git a/api/simulate.go b/api/simulate.go index dc57cc723..9b1fdd70b 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -19,6 +19,7 @@ import ( rpc "github.com/openrelayxyz/cardinal-rpc" ctypes "github.com/openrelayxyz/cardinal-types" "github.com/openrelayxyz/cardinal-types/hexutil" + log "github.com/inconshreveable/log15" ) const ( @@ -321,7 +322,8 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, TransactionIndex: uint(i), } receipt.Logs = s.state.GetLogs(tx.Hash(), header.Number.Uint64(), header.Hash()) - if s.traceTransfers { + if s.traceTransfers && tracer != nil{ + log.Error("tracer captured %d transfer logs", len(tracer.Logs())) receipt.Logs = append(receipt.Logs, tracer.Logs()...) } receipt.Bloom = types.CreateBloom([]*types.Receipt{receipt}) From c9a60f231a77343cd95f4317755561991acd2ffa Mon Sep 17 00:00:00 2001 From: Jesse Date: Fri, 3 Oct 2025 13:51:33 +0100 Subject: [PATCH 31/72] add tracer debug logs, use txHash --- api/logtracer.go | 7 +++++-- api/simulate.go | 9 +++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/api/logtracer.go b/api/logtracer.go index d5524c7d7..05e387c63 100644 --- a/api/logtracer.go +++ b/api/logtracer.go @@ -1,13 +1,15 @@ package api import ( + "fmt" "math/big" "time" + + log "github.com/inconshreveable/log15" + "github.com/openrelayxyz/cardinal-evm/common" "github.com/openrelayxyz/cardinal-evm/types" "github.com/openrelayxyz/cardinal-evm/vm" - "github.com/openrelayxyz/cardinal-evm/common" ctypes "github.com/openrelayxyz/cardinal-types" - log "github.com/inconshreveable/log15" ) var ( @@ -31,6 +33,7 @@ type tracer struct { } func newTracer(traceTransfers bool, blockNumber uint64, blockHash, txHash ctypes.Hash, txIndex uint) *tracer { + log.Error(fmt.Sprintf("creating tracer traceTransfers: %v, txHash: %s", traceTransfers, txHash.Hex())) return &tracer{ logs: make([]*types.Log, 0), traceTransfers: traceTransfers, diff --git a/api/simulate.go b/api/simulate.go index 9b1fdd70b..698ead849 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -259,7 +259,9 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, } for i, call := range block.Calls { - tracer := newTracer(s.traceTransfers, header.Number.Uint64(), header.Hash(), ctypes.Hash{}, uint(i)) + tx := call.ToTransaction(types.DynamicFeeTxType) + txes[i] = tx + tracer := newTracer(s.traceTransfers, header.Number.Uint64(), header.Hash(), tx.Hash(), uint(i)) evm := s.evmFn(s.state, &vm.Config{ NoBaseFee: !s.validate, Tracer: tracer, }, call.from(), call.GasPrice.ToInt()) @@ -282,8 +284,7 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, remaining := header.GasLimit - gasUsed call.Gas = (*hexutil.Uint64)(&remaining) } - tx := call.ToTransaction(types.DynamicFeeTxType) - txes[i] = tx + senders[tx.Hash()] = call.from() tracer.reset(tx.Hash(), uint(i)) s.state.SetTxContext(tx.Hash(), i) @@ -323,7 +324,7 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, } receipt.Logs = s.state.GetLogs(tx.Hash(), header.Number.Uint64(), header.Hash()) if s.traceTransfers && tracer != nil{ - log.Error("tracer captured %d transfer logs", len(tracer.Logs())) + log.Error(fmt.Sprintf("tracer captured %d transfer logs", len(tracer.Logs()))) receipt.Logs = append(receipt.Logs, tracer.Logs()...) } receipt.Bloom = types.CreateBloom([]*types.Receipt{receipt}) From 54d72160a21e9704dc0e1772cf768b7c46f09479 Mon Sep 17 00:00:00 2001 From: Jesse Date: Fri, 3 Oct 2025 15:01:14 +0100 Subject: [PATCH 32/72] reorder execution flow --- api/simulate.go | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/api/simulate.go b/api/simulate.go index 698ead849..ca53d0622 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -259,36 +259,31 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, } for i, call := range block.Calls { - tx := call.ToTransaction(types.DynamicFeeTxType) - txes[i] = tx - tracer := newTracer(s.traceTransfers, header.Number.Uint64(), header.Hash(), tx.Hash(), uint(i)) - evm := s.evmFn(s.state, &vm.Config{ - NoBaseFee: !s.validate, Tracer: tracer, - }, call.from(), call.GasPrice.ToInt()) - if err := ctx.Context().Err(); err != nil { return nil, nil, nil, err } if err := call.setDefaults(ctx, s.evmFn, s.state, header, vm.BlockNumberOrHashWithHash(header.Hash(), false)); err != nil { return nil, nil, nil, err } - if gasUsed+uint64(*call.Gas) > header.GasLimit { - return nil,nil,nil, &blockGasLimitReachedError{fmt.Sprintf("block gas limit reached: %d >= %d", gasUsed, header.GasLimit)} - } - if call.Nonce == nil { - nonce := evm.StateDB.GetNonce(call.from()) - call.Nonce = (*hexutil.Uint64)(&nonce) - } // Let the call run wild unless explicitly specified. if call.Gas == nil { remaining := header.GasLimit - gasUsed call.Gas = (*hexutil.Uint64)(&remaining) } + if gasUsed+uint64(*call.Gas) > header.GasLimit { + return nil,nil,nil, &blockGasLimitReachedError{fmt.Sprintf("block gas limit reached: %d >= %d", gasUsed, header.GasLimit)} + } + tx := call.ToTransaction(types.DynamicFeeTxType) + txes[i] = tx senders[tx.Hash()] = call.from() - tracer.reset(tx.Hash(), uint(i)) s.state.SetTxContext(tx.Hash(), i) + tracer := newTracer(s.traceTransfers, header.Number.Uint64(), header.Hash(), tx.Hash(), uint(i)) + evm := s.evmFn(s.state, &vm.Config{ + NoBaseFee: !s.validate, Tracer: tracer, + }, call.from(), call.GasPrice.ToInt()) + evm.Context.BaseFee = header.BaseFee if evm.Context.GetHash == nil { From 308cbac4de7a094f8396e6e6153f07897d6d7cc9 Mon Sep 17 00:00:00 2001 From: Jesse Date: Fri, 3 Oct 2025 15:25:38 +0100 Subject: [PATCH 33/72] set Debug true --- api/simulate.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/simulate.go b/api/simulate.go index ca53d0622..f95b44b48 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -281,7 +281,7 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, tracer := newTracer(s.traceTransfers, header.Number.Uint64(), header.Hash(), tx.Hash(), uint(i)) evm := s.evmFn(s.state, &vm.Config{ - NoBaseFee: !s.validate, Tracer: tracer, + NoBaseFee: !s.validate, Tracer: tracer, Debug: s.traceTransfers, }, call.from(), call.GasPrice.ToInt()) evm.Context.BaseFee = header.BaseFee From b02bfdd8eb25adac7e90d15caa4b7655d577afb0 Mon Sep 17 00:00:00 2001 From: Jesse Date: Fri, 3 Oct 2025 15:59:31 +0100 Subject: [PATCH 34/72] Attempt to collect logs in capture start --- api/logtracer.go | 67 +++++++++++++++++++++++++++++++----------------- 1 file changed, 44 insertions(+), 23 deletions(-) diff --git a/api/logtracer.go b/api/logtracer.go index 05e387c63..4eece1963 100644 --- a/api/logtracer.go +++ b/api/logtracer.go @@ -45,32 +45,53 @@ func newTracer(traceTransfers bool, blockNumber uint64, blockHash, txHash ctypes } func (t *tracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { - log.Error("inside captureenter") - if !t.traceTransfers { - return - } - if typ != vm.DELEGATECALL && value != nil && value.Sign() > 0 { - topics := []ctypes.Hash{ - transferTopic, - ctypes.BytesToHash(from.Bytes()), - ctypes.BytesToHash(to.Bytes()), - } - t.logs = append(t.logs, &types.Log{ - Address: transferAddress, - Topics: topics, - Data: ctypes.BigToHash(value).Bytes(), - BlockNumber: t.blockNumber, - BlockHash: t.blockHash, - TxHash: t.txHash, - TxIndex: t.txIdx, - Index: uint(t.count), - }) - t.count++ - } + // log.Error("inside captureenter") + // if !t.traceTransfers { + // return + // } + // if typ != vm.DELEGATECALL && value != nil && value.Sign() > 0 { + // topics := []ctypes.Hash{ + // transferTopic, + // ctypes.BytesToHash(from.Bytes()), + // ctypes.BytesToHash(to.Bytes()), + // } + // t.logs = append(t.logs, &types.Log{ + // Address: transferAddress, + // Topics: topics, + // Data: ctypes.BigToHash(value).Bytes(), + // BlockNumber: t.blockNumber, + // BlockHash: t.blockHash, + // TxHash: t.txHash, + // TxIndex: t.txIdx, + // Index: uint(t.count), + // }) + // t.count++ + // } } func (t *tracer) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int){ - log.Error("inside capturestart") + if !t.traceTransfers { + return + } + // Capture the initial transaction value transfer + if !create && value != nil && value.Sign() > 0 { + topics := []ctypes.Hash{ + transferTopic, + ctypes.BytesToHash(from.Bytes()), + ctypes.BytesToHash(to.Bytes()), + } + t.logs = append(t.logs, &types.Log{ + Address: transferAddress, + Topics: topics, + Data: ctypes.BigToHash(value).Bytes(), + BlockNumber: t.blockNumber, + BlockHash: t.blockHash, + TxHash: t.txHash, + TxIndex: t.txIdx, + Index: uint(t.count), + }) + t.count++ + } } func (t *tracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error){} func (t *tracer) CaptureExit(output []byte, gasUsed uint64, err error){} From 9e613c237d8a4cc66b514211e0f21c476700ab74 Mon Sep 17 00:00:00 2001 From: Jesse Date: Fri, 3 Oct 2025 16:34:52 +0100 Subject: [PATCH 35/72] add log formatting code --- api/logtracer.go | 32 +------------- types/gen_log_json.go | 97 +++++++++++++++++++++++++++++++++++++++++++ types/log.go | 14 ++++--- 3 files changed, 106 insertions(+), 37 deletions(-) create mode 100644 types/gen_log_json.go diff --git a/api/logtracer.go b/api/logtracer.go index 4eece1963..2f8db70f6 100644 --- a/api/logtracer.go +++ b/api/logtracer.go @@ -44,30 +44,7 @@ func newTracer(traceTransfers bool, blockNumber uint64, blockHash, txHash ctypes } } -func (t *tracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { - // log.Error("inside captureenter") - // if !t.traceTransfers { - // return - // } - // if typ != vm.DELEGATECALL && value != nil && value.Sign() > 0 { - // topics := []ctypes.Hash{ - // transferTopic, - // ctypes.BytesToHash(from.Bytes()), - // ctypes.BytesToHash(to.Bytes()), - // } - // t.logs = append(t.logs, &types.Log{ - // Address: transferAddress, - // Topics: topics, - // Data: ctypes.BigToHash(value).Bytes(), - // BlockNumber: t.blockNumber, - // BlockHash: t.blockHash, - // TxHash: t.txHash, - // TxIndex: t.txIdx, - // Index: uint(t.count), - // }) - // t.count++ - // } -} +func (t *tracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {} func (t *tracer) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int){ if !t.traceTransfers { @@ -98,13 +75,6 @@ func (t *tracer) CaptureExit(output []byte, gasUsed uint64, err error){} func (t *tracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error){} func (t *tracer) CaptureEnd(output []byte, gasUsed uint64, time time.Duration, err error){} -// reset prepares the tracer for the next transaction. -func (t *tracer) reset(txHash ctypes.Hash, txIdx uint) { - t.logs = make([]*types.Log, 0) - t.count = 0 - t.txHash = txHash - t.txIdx = txIdx -} func (t *tracer) Logs() []*types.Log { return t.logs diff --git a/types/gen_log_json.go b/types/gen_log_json.go new file mode 100644 index 000000000..63512da1a --- /dev/null +++ b/types/gen_log_json.go @@ -0,0 +1,97 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package types + +import ( + "encoding/json" + "errors" + + "github.com/openrelayxyz/cardinal-evm/common" + "github.com/openrelayxyz/cardinal-types/hexutil" + "github.com/openrelayxyz/cardinal-types" +) + +var _ = (*logMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (l Log) MarshalJSON() ([]byte, error) { + type Log struct { + Address common.Address `json:"address" gencodec:"required"` + Topics []types.Hash `json:"topics" gencodec:"required"` + Data hexutil.Bytes `json:"data" gencodec:"required"` + BlockNumber hexutil.Uint64 `json:"blockNumber" rlp:"-"` + TxHash types.Hash `json:"transactionHash" gencodec:"required" rlp:"-"` + TxIndex hexutil.Uint `json:"transactionIndex" rlp:"-"` + BlockHash types.Hash `json:"blockHash" rlp:"-"` + BlockTimestamp hexutil.Uint64 `json:"blockTimestamp" rlp:"-"` + Index hexutil.Uint `json:"logIndex" rlp:"-"` + Removed bool `json:"removed" rlp:"-"` + } + var enc Log + enc.Address = l.Address + enc.Topics = l.Topics + enc.Data = l.Data + enc.BlockNumber = hexutil.Uint64(l.BlockNumber) + enc.TxHash = l.TxHash + enc.TxIndex = hexutil.Uint(l.TxIndex) + enc.BlockHash = l.BlockHash + enc.BlockTimestamp = hexutil.Uint64(l.BlockTimestamp) + enc.Index = hexutil.Uint(l.Index) + enc.Removed = l.Removed + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (l *Log) UnmarshalJSON(input []byte) error { + type Log struct { + Address *common.Address `json:"address" gencodec:"required"` + Topics []types.Hash `json:"topics" gencodec:"required"` + Data *hexutil.Bytes `json:"data" gencodec:"required"` + BlockNumber *hexutil.Uint64 `json:"blockNumber" rlp:"-"` + TxHash *types.Hash `json:"transactionHash" gencodec:"required" rlp:"-"` + TxIndex *hexutil.Uint `json:"transactionIndex" rlp:"-"` + BlockHash *types.Hash `json:"blockHash" rlp:"-"` + BlockTimestamp *hexutil.Uint64 `json:"blockTimestamp" rlp:"-"` + Index *hexutil.Uint `json:"logIndex" rlp:"-"` + Removed *bool `json:"removed" rlp:"-"` + } + var dec Log + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Address == nil { + return errors.New("missing required field 'address' for Log") + } + l.Address = *dec.Address + if dec.Topics == nil { + return errors.New("missing required field 'topics' for Log") + } + l.Topics = dec.Topics + if dec.Data == nil { + return errors.New("missing required field 'data' for Log") + } + l.Data = *dec.Data + if dec.BlockNumber != nil { + l.BlockNumber = uint64(*dec.BlockNumber) + } + if dec.TxHash == nil { + return errors.New("missing required field 'transactionHash' for Log") + } + l.TxHash = *dec.TxHash + if dec.TxIndex != nil { + l.TxIndex = uint(*dec.TxIndex) + } + if dec.BlockHash != nil { + l.BlockHash = *dec.BlockHash + } + if dec.BlockTimestamp != nil { + l.BlockTimestamp = uint64(*dec.BlockTimestamp) + } + if dec.Index != nil { + l.Index = uint(*dec.Index) + } + if dec.Removed != nil { + l.Removed = *dec.Removed + } + return nil +} diff --git a/types/log.go b/types/log.go index 947587061..cdafa19af 100644 --- a/types/log.go +++ b/types/log.go @@ -41,19 +41,21 @@ type Log struct { // Derived fields. These fields are filled in by the node // but not secured by consensus. // block in which the transaction was included - BlockNumber uint64 `json:"blockNumber"` + BlockNumber uint64 `json:"blockNumber" rlp:"-"` // hash of the transaction - TxHash types.Hash `json:"transactionHash" gencodec:"required"` + TxHash types.Hash `json:"transactionHash" gencodec:"required" rlp:"-"` // index of the transaction in the block - TxIndex uint `json:"transactionIndex"` + TxIndex uint `json:"transactionIndex" rlp:"-"` // hash of the block in which the transaction was included - BlockHash types.Hash `json:"blockHash"` + BlockHash types.Hash `json:"blockHash" rlp:"-"` + // timestamp of the block in which the transaction was included + BlockTimestamp uint64 `json:"blockTimestamp" rlp:"-"` // index of the log in the block - Index uint `json:"logIndex"` + Index uint `json:"logIndex" rlp:"-"` // The Removed field is true if this log was reverted due to a chain reorganisation. // You must pay attention to this field if you receive logs through a filter query. - Removed bool `json:"removed"` + Removed bool `json:"removed" rlp:"-"` } type logMarshaling struct { From 30fe8e59ff609d3bdf75f84e0d6adab6020b2ec2 Mon Sep 17 00:00:00 2001 From: Jesse Date: Thu, 9 Oct 2025 03:14:45 +0100 Subject: [PATCH 36/72] rearrange tx order --- api/logtracer.go | 7 +++++++ api/simulate.go | 16 ++++++++++------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/api/logtracer.go b/api/logtracer.go index 2f8db70f6..da7f277ef 100644 --- a/api/logtracer.go +++ b/api/logtracer.go @@ -75,6 +75,13 @@ func (t *tracer) CaptureExit(output []byte, gasUsed uint64, err error){} func (t *tracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error){} func (t *tracer) CaptureEnd(output []byte, gasUsed uint64, time time.Duration, err error){} +// reset prepares the tracer for the next transaction. +func (t *tracer) reset(txHash ctypes.Hash, txIdx uint) { + t.logs = make([]*types.Log, 0) + t.count = 0 + t.txHash = txHash + t.txIdx = txIdx +} func (t *tracer) Logs() []*types.Log { return t.logs diff --git a/api/simulate.go b/api/simulate.go index f95b44b48..18c5edf86 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -259,6 +259,8 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, } for i, call := range block.Calls { + tracer := newTracer(s.traceTransfers, header.Number.Uint64(), ctypes.Hash{}, ctypes.Hash{}, uint(i)) + if err := ctx.Context().Err(); err != nil { return nil, nil, nil, err } @@ -274,18 +276,20 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, return nil,nil,nil, &blockGasLimitReachedError{fmt.Sprintf("block gas limit reached: %d >= %d", gasUsed, header.GasLimit)} } + evm := s.evmFn(s.state, &vm.Config{ + NoBaseFee: !s.validate, Tracer: tracer, Debug: s.traceTransfers, + }, call.from(), call.GasPrice.ToInt()) + tx := call.ToTransaction(types.DynamicFeeTxType) txes[i] = tx senders[tx.Hash()] = call.from() - s.state.SetTxContext(tx.Hash(), i) - tracer := newTracer(s.traceTransfers, header.Number.Uint64(), header.Hash(), tx.Hash(), uint(i)) - evm := s.evmFn(s.state, &vm.Config{ - NoBaseFee: !s.validate, Tracer: tracer, Debug: s.traceTransfers, - }, call.from(), call.GasPrice.ToInt()) + //update tracer with real tx hash + tracer.reset(tx.Hash(), uint(i)) + s.state.SetTxContext(tx.Hash(), i) + evm.Context.BaseFee = header.BaseFee - if evm.Context.GetHash == nil { evm.Context.GetHash = getHashFn } From 62b2ad582e05fbf58067bec46349938ef848928c Mon Sep 17 00:00:00 2001 From: Jesse Date: Thu, 9 Oct 2025 03:38:11 +0100 Subject: [PATCH 37/72] use manual defaults for simulate --- api/simulate.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/api/simulate.go b/api/simulate.go index 18c5edf86..5512555b5 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -264,13 +264,22 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, if err := ctx.Context().Err(); err != nil { return nil, nil, nil, err } - if err := call.setDefaults(ctx, s.evmFn, s.state, header, vm.BlockNumberOrHashWithHash(header.Hash(), false)); err != nil { - return nil, nil, nil, err + if call.From == nil { + call.From = &(common.Address{}) + } + if call.Nonce == nil { + nonce := s.state.GetNonce(call.from()) + call.Nonce = (*hexutil.Uint64)(&nonce) } - // Let the call run wild unless explicitly specified. if call.Gas == nil { remaining := header.GasLimit - gasUsed - call.Gas = (*hexutil.Uint64)(&remaining) + call.Gas = (*hexutil.Uint64)(&remaining) + } + if call.GasPrice == nil { + call.GasPrice = (*hexutil.Big)(header.BaseFee) + } + if call.Value == nil { + call.Value = new(hexutil.Big) } if gasUsed+uint64(*call.Gas) > header.GasLimit { return nil,nil,nil, &blockGasLimitReachedError{fmt.Sprintf("block gas limit reached: %d >= %d", gasUsed, header.GasLimit)} From ad68ceb71192c44b6b934e9816ddcbf3d0b7ce8d Mon Sep 17 00:00:00 2001 From: Jesse Date: Thu, 9 Oct 2025 03:59:58 +0100 Subject: [PATCH 38/72] log out transaction params --- api/simulate.go | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/api/simulate.go b/api/simulate.go index 5512555b5..6dd997d48 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -264,22 +264,13 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, if err := ctx.Context().Err(); err != nil { return nil, nil, nil, err } - if call.From == nil { - call.From = &(common.Address{}) - } - if call.Nonce == nil { - nonce := s.state.GetNonce(call.from()) - call.Nonce = (*hexutil.Uint64)(&nonce) + if err := call.setDefaults(ctx, s.evmFn, s.state, header, vm.BlockNumberOrHashWithHash(header.Hash(), false)); err != nil { + return nil, nil, nil, err } + // Let the call run wild unless explicitly specified. if call.Gas == nil { remaining := header.GasLimit - gasUsed - call.Gas = (*hexutil.Uint64)(&remaining) - } - if call.GasPrice == nil { - call.GasPrice = (*hexutil.Big)(header.BaseFee) - } - if call.Value == nil { - call.Value = new(hexutil.Big) + call.Gas = (*hexutil.Uint64)(&remaining) } if gasUsed+uint64(*call.Gas) > header.GasLimit { return nil,nil,nil, &blockGasLimitReachedError{fmt.Sprintf("block gas limit reached: %d >= %d", gasUsed, header.GasLimit)} @@ -289,10 +280,13 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, NoBaseFee: !s.validate, Tracer: tracer, Debug: s.traceTransfers, }, call.from(), call.GasPrice.ToInt()) + log.Error("before ToTransaction", "nonce", *call.Nonce, "gas", *call.Gas, "gasPrice", call.GasPrice, "maxFee", call.MaxFeePerGas, "maxPriority", call.MaxPriorityFeePerGas, "chainID", call.ChainID, "to", call.To, "value", call.Value) tx := call.ToTransaction(types.DynamicFeeTxType) txes[i] = tx senders[tx.Hash()] = call.from() + log.Error("after ToTransaction", "nonce", *call.Nonce, "gas", *call.Gas, "gasPrice", call.GasPrice, "maxFee", call.MaxFeePerGas, "maxPriority", call.MaxPriorityFeePerGas, "chainID", call.ChainID, "to", call.To, "value", call.Value) + //update tracer with real tx hash tracer.reset(tx.Hash(), uint(i)) From 43bb8c50266f1c74639c314c0018e36637571433 Mon Sep 17 00:00:00 2001 From: Jesse Date: Thu, 9 Oct 2025 04:39:42 +0100 Subject: [PATCH 39/72] add missing fields --- api/simulate.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/api/simulate.go b/api/simulate.go index 6dd997d48..1d996cb30 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -267,6 +267,15 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, if err := call.setDefaults(ctx, s.evmFn, s.state, header, vm.BlockNumberOrHashWithHash(header.Hash(), false)); err != nil { return nil, nil, nil, err } + if call.ChainID == nil { + call.ChainID = (*hexutil.Big)(s.chainConfig.ChainID) + } + if call.MaxFeePerGas == nil { + call.MaxFeePerGas = (*hexutil.Big)(header.BaseFee) + } + if call.MaxPriorityFeePerGas == nil { + call.MaxPriorityFeePerGas = new(hexutil.Big) + } // Let the call run wild unless explicitly specified. if call.Gas == nil { remaining := header.GasLimit - gasUsed From cdacaccec9dd7479945501dab07107f13d3d9d3b Mon Sep 17 00:00:00 2001 From: Jesse Date: Thu, 9 Oct 2025 05:00:20 +0100 Subject: [PATCH 40/72] make gasprice assignment conditional --- api/transaction_args.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/transaction_args.go b/api/transaction_args.go index 0b0cc18cb..7151aa9c4 100644 --- a/api/transaction_args.go +++ b/api/transaction_args.go @@ -184,7 +184,7 @@ func (args *TransactionArgs) setDefaults(ctx *rpc.CallContext, getEVM func(state // if args.MaxFeePerGas == nil { // args.MaxFeePerGas = (*hexutil.Big)(header.BaseFee) // } - if args.GasPrice == nil { + if args.GasPrice == nil && args.MaxFeePerGas == nil { args.GasPrice = (*hexutil.Big)(header.BaseFee) } if args.Value == nil { From 1d6ead2627345429a6d37c9c7d43156796963abb Mon Sep 17 00:00:00 2001 From: Jesse Date: Thu, 9 Oct 2025 15:14:09 +0100 Subject: [PATCH 41/72] format, fix return fields --- api/logtracer.go | 3 --- api/simulate.go | 26 +++++++------------------- api/transaction_args.go | 20 ++++++++++++++------ types/block.go | 2 +- types/gen_log_json.go | 4 ++-- types/marshalling.go | 2 +- 6 files changed, 25 insertions(+), 32 deletions(-) diff --git a/api/logtracer.go b/api/logtracer.go index da7f277ef..b4a1bc2f4 100644 --- a/api/logtracer.go +++ b/api/logtracer.go @@ -1,11 +1,9 @@ package api import ( - "fmt" "math/big" "time" - log "github.com/inconshreveable/log15" "github.com/openrelayxyz/cardinal-evm/common" "github.com/openrelayxyz/cardinal-evm/types" "github.com/openrelayxyz/cardinal-evm/vm" @@ -33,7 +31,6 @@ type tracer struct { } func newTracer(traceTransfers bool, blockNumber uint64, blockHash, txHash ctypes.Hash, txIndex uint) *tracer { - log.Error(fmt.Sprintf("creating tracer traceTransfers: %v, txHash: %s", traceTransfers, txHash.Hex())) return &tracer{ logs: make([]*types.Log, 0), traceTransfers: traceTransfers, diff --git a/api/simulate.go b/api/simulate.go index 1d996cb30..cec804e1c 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -19,7 +19,7 @@ import ( rpc "github.com/openrelayxyz/cardinal-rpc" ctypes "github.com/openrelayxyz/cardinal-types" "github.com/openrelayxyz/cardinal-types/hexutil" - log "github.com/inconshreveable/log15" + // log "github.com/inconshreveable/log15" ) const ( @@ -167,6 +167,7 @@ func (s *simulator) execute(ctx *rpc.CallContext, blocks []simBlock) ([]*simBloc header.Number = new(big.Int).Add(s.base.Number, big.NewInt(int64(bi+1))) header.ParentHash = parent.Hash() header.Time = parent.Time + uint64(bi+1)*12 + header.Extra = []byte{} override := *block.BlockOverrides if override.Number != nil {header.Number = override.Number.ToInt()} @@ -267,15 +268,6 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, if err := call.setDefaults(ctx, s.evmFn, s.state, header, vm.BlockNumberOrHashWithHash(header.Hash(), false)); err != nil { return nil, nil, nil, err } - if call.ChainID == nil { - call.ChainID = (*hexutil.Big)(s.chainConfig.ChainID) - } - if call.MaxFeePerGas == nil { - call.MaxFeePerGas = (*hexutil.Big)(header.BaseFee) - } - if call.MaxPriorityFeePerGas == nil { - call.MaxPriorityFeePerGas = new(hexutil.Big) - } // Let the call run wild unless explicitly specified. if call.Gas == nil { remaining := header.GasLimit - gasUsed @@ -289,13 +281,10 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, NoBaseFee: !s.validate, Tracer: tracer, Debug: s.traceTransfers, }, call.from(), call.GasPrice.ToInt()) - log.Error("before ToTransaction", "nonce", *call.Nonce, "gas", *call.Gas, "gasPrice", call.GasPrice, "maxFee", call.MaxFeePerGas, "maxPriority", call.MaxPriorityFeePerGas, "chainID", call.ChainID, "to", call.To, "value", call.Value) tx := call.ToTransaction(types.DynamicFeeTxType) txes[i] = tx senders[tx.Hash()] = call.from() - log.Error("after ToTransaction", "nonce", *call.Nonce, "gas", *call.Gas, "gasPrice", call.GasPrice, "maxFee", call.MaxFeePerGas, "maxPriority", call.MaxPriorityFeePerGas, "chainID", call.ChainID, "to", call.To, "value", call.Value) - //update tracer with real tx hash tracer.reset(tx.Hash(), uint(i)) @@ -322,7 +311,7 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, root = nil } gasUsed += result.UsedGas - header.Root = s.base.Root + // header.Root = s.base.Root receipt := &types.Receipt{ Type: tx.Type(), @@ -334,10 +323,6 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, TransactionIndex: uint(i), } receipt.Logs = s.state.GetLogs(tx.Hash(), header.Number.Uint64(), header.Hash()) - if s.traceTransfers && tracer != nil{ - log.Error(fmt.Sprintf("tracer captured %d transfer logs", len(tracer.Logs()))) - receipt.Logs = append(receipt.Logs, tracer.Logs()...) - } receipt.Bloom = types.CreateBloom([]*types.Receipt{receipt}) if tx.To() == nil { receipt.ContractAddress = crypto.CreateAddress(*call.From, tx.Nonce()) @@ -361,6 +346,9 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, ReturnValue: result.ReturnData, Logs: receipt.Logs, } + if s.traceTransfers && tracer != nil{ + callRes.Logs = append(callRes.Logs, tracer.Logs()...) + } if result.Failed() { callRes.Status = hexutil.Uint64(types.ReceiptStatusFailed) if errors.Is(result.Err, vm.ErrExecutionReverted) { @@ -386,7 +374,7 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, header.TxHash = types.DeriveSha(types.Transactions(txes), hasher) header.ReceiptHash = types.DeriveSha(types.Receipts(receipts), hasher) header.Bloom = types.CreateBloom(receipts) - header.Root = s.base.Root + // header.Root = s.base.Root blockBody := &types.Body{Transactions: txes, Withdrawals: *block.BlockOverrides.Withdrawals} blck := types.NewBlock(header, blockBody, receipts, hasher) diff --git a/api/transaction_args.go b/api/transaction_args.go index 7151aa9c4..53090fc9e 100644 --- a/api/transaction_args.go +++ b/api/transaction_args.go @@ -181,12 +181,6 @@ func (args *TransactionArgs) setDefaults(ctx *rpc.CallContext, getEVM func(state // if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) { // return errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") // } - // if args.MaxFeePerGas == nil { - // args.MaxFeePerGas = (*hexutil.Big)(header.BaseFee) - // } - if args.GasPrice == nil && args.MaxFeePerGas == nil { - args.GasPrice = (*hexutil.Big)(header.BaseFee) - } if args.Value == nil { args.Value = new(hexutil.Big) } @@ -194,6 +188,20 @@ func (args *TransactionArgs) setDefaults(ctx *rpc.CallContext, getEVM func(state nonce := db.GetNonce(*args.From) args.Nonce = (*hexutil.Uint64)(&nonce) } + if header.BaseFee == nil { + // If there's no basefee, then it must be a non-1559 execution + if args.GasPrice == nil { + args.GasPrice = new(hexutil.Big) + } + } else { + // A basefee is provided, necessitating 1559-type execution + if args.MaxFeePerGas == nil { + args.MaxFeePerGas = new(hexutil.Big) + } + if args.MaxPriorityFeePerGas == nil { + args.MaxPriorityFeePerGas = new(hexutil.Big) + } + } if args.Gas == nil { gas, _, err := DoEstimateGas(ctx, getEVM, *args, &PreviousState{db.ALCalcCopy(), header}, blockNrOrHash, header.GasLimit, true) if err != nil { diff --git a/types/block.go b/types/block.go index 5db33c6b5..2279338a3 100644 --- a/types/block.go +++ b/types/block.go @@ -53,7 +53,7 @@ type Header struct { ParentHash types.Hash `json:"parentHash" gencodec:"required"` UncleHash types.Hash `json:"sha3Uncles" gencodec:"required"` Coinbase common.Address `json:"miner"` - Root types.Hash `json:"stateRoot" gencodec:"required"` + // Root types.Hash `json:"stateRoot" gencodec:"required"` TxHash types.Hash `json:"transactionsRoot" gencodec:"required"` ReceiptHash types.Hash `json:"receiptsRoot" gencodec:"required"` Bloom Bloom `json:"logsBloom" gencodec:"required"` diff --git a/types/gen_log_json.go b/types/gen_log_json.go index 63512da1a..d7ab0a29a 100644 --- a/types/gen_log_json.go +++ b/types/gen_log_json.go @@ -23,7 +23,7 @@ func (l Log) MarshalJSON() ([]byte, error) { TxHash types.Hash `json:"transactionHash" gencodec:"required" rlp:"-"` TxIndex hexutil.Uint `json:"transactionIndex" rlp:"-"` BlockHash types.Hash `json:"blockHash" rlp:"-"` - BlockTimestamp hexutil.Uint64 `json:"blockTimestamp" rlp:"-"` + // BlockTimestamp hexutil.Uint64 `json:"blockTimestamp" rlp:"-"` Index hexutil.Uint `json:"logIndex" rlp:"-"` Removed bool `json:"removed" rlp:"-"` } @@ -35,7 +35,7 @@ func (l Log) MarshalJSON() ([]byte, error) { enc.TxHash = l.TxHash enc.TxIndex = hexutil.Uint(l.TxIndex) enc.BlockHash = l.BlockHash - enc.BlockTimestamp = hexutil.Uint64(l.BlockTimestamp) + // enc.BlockTimestamp = hexutil.Uint64(l.BlockTimestamp) enc.Index = hexutil.Uint(l.Index) enc.Removed = l.Removed return json.Marshal(&enc) diff --git a/types/marshalling.go b/types/marshalling.go index f7df2de40..e7004f723 100644 --- a/types/marshalling.go +++ b/types/marshalling.go @@ -17,7 +17,7 @@ func RPCMarshalHeader(head *Header) map[string]interface{} { "mixHash": head.MixDigest, "sha3Uncles": head.UncleHash, "logsBloom": hexutil.Encode(head.Bloom.Bytes()), - "stateRoot": head.Root, + // "stateRoot": head.Root, "miner": head.Coinbase, "difficulty": (*hexutil.Big)(head.Difficulty), "extraData": hexutil.Bytes(head.Extra), From 3c4d57cc2a2e5f2d3b00ebba5ea22614e0794fe0 Mon Sep 17 00:00:00 2001 From: Jesse Date: Thu, 9 Oct 2025 15:17:45 +0100 Subject: [PATCH 42/72] Update block.go --- types/block.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/block.go b/types/block.go index 2279338a3..5db33c6b5 100644 --- a/types/block.go +++ b/types/block.go @@ -53,7 +53,7 @@ type Header struct { ParentHash types.Hash `json:"parentHash" gencodec:"required"` UncleHash types.Hash `json:"sha3Uncles" gencodec:"required"` Coinbase common.Address `json:"miner"` - // Root types.Hash `json:"stateRoot" gencodec:"required"` + Root types.Hash `json:"stateRoot" gencodec:"required"` TxHash types.Hash `json:"transactionsRoot" gencodec:"required"` ReceiptHash types.Hash `json:"receiptsRoot" gencodec:"required"` Bloom Bloom `json:"logsBloom" gencodec:"required"` From 1b95c17ca8db126e2e089192ec19b2edebc36216 Mon Sep 17 00:00:00 2001 From: Jesse Date: Thu, 9 Oct 2025 15:32:23 +0100 Subject: [PATCH 43/72] fix txHash --- api/simulate.go | 4 ++++ api/transaction_args.go | 6 ++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/api/simulate.go b/api/simulate.go index cec804e1c..13fd7fe98 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -273,6 +273,10 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, remaining := header.GasLimit - gasUsed call.Gas = (*hexutil.Uint64)(&remaining) } + if call.ChainID == nil { + call.ChainID = (*hexutil.Big)(s.chainConfig.ChainID) + } + if gasUsed+uint64(*call.Gas) > header.GasLimit { return nil,nil,nil, &blockGasLimitReachedError{fmt.Sprintf("block gas limit reached: %d >= %d", gasUsed, header.GasLimit)} } diff --git a/api/transaction_args.go b/api/transaction_args.go index 53090fc9e..1d72605ee 100644 --- a/api/transaction_args.go +++ b/api/transaction_args.go @@ -189,17 +189,15 @@ func (args *TransactionArgs) setDefaults(ctx *rpc.CallContext, getEVM func(state args.Nonce = (*hexutil.Uint64)(&nonce) } if header.BaseFee == nil { - // If there's no basefee, then it must be a non-1559 execution if args.GasPrice == nil { args.GasPrice = new(hexutil.Big) } } else { - // A basefee is provided, necessitating 1559-type execution if args.MaxFeePerGas == nil { - args.MaxFeePerGas = new(hexutil.Big) + args.MaxFeePerGas = (*hexutil.Big)(header.BaseFee) } if args.MaxPriorityFeePerGas == nil { - args.MaxPriorityFeePerGas = new(hexutil.Big) + args.MaxPriorityFeePerGas = new(hexutil.Big) } } if args.Gas == nil { From b17965af7ac01003c3742c98d5354bc6481f48f7 Mon Sep 17 00:00:00 2001 From: Jesse Date: Thu, 9 Oct 2025 16:00:30 +0100 Subject: [PATCH 44/72] attempt to fix parentBeaconRoot --- api/simulate.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/api/simulate.go b/api/simulate.go index 13fd7fe98..622543a81 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -175,7 +175,9 @@ func (s *simulator) execute(ctx *rpc.CallContext, blocks []simBlock) ([]*simBloc if override.Time != nil {header.Time = uint64(*override.Time)} if override.GasLimit != nil {header.GasLimit = uint64(*override.GasLimit)} if override.FeeRecipient != nil {header.Coinbase = *override.FeeRecipient} - if override.PrevRandao != nil {header.MixDigest = *override.PrevRandao} + if override.PrevRandao != nil {header.MixDigest = *override.PrevRandao} else{ + header.MixDigest = ctypes.Hash{} + } if override.BaseFeePerGas != nil {header.BaseFee = override.BaseFeePerGas.ToInt()} if override.BlobBaseFee != nil { val := *override.BlobBaseFee.ToInt() @@ -183,6 +185,15 @@ func (s *simulator) execute(ctx *rpc.CallContext, blocks []simBlock) ([]*simBloc header.ExcessBlobGas = &ptr } + var parentBeaconRoot *ctypes.Hash + if s.chainConfig.IsCancun(override.Number.ToInt(), big.NewInt(int64(*override.Time))) { + parentBeaconRoot = &ctypes.Hash{} + if override.BeaconRoot != nil { + parentBeaconRoot = override.BeaconRoot + } + } + header.ParentBeaconRoot = parentBeaconRoot + s.gp = new(GasPool).AddGas(header.GasLimit) if err := execCtx.Err(); err != nil { @@ -276,7 +287,7 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, if call.ChainID == nil { call.ChainID = (*hexutil.Big)(s.chainConfig.ChainID) } - + if gasUsed+uint64(*call.Gas) > header.GasLimit { return nil,nil,nil, &blockGasLimitReachedError{fmt.Sprintf("block gas limit reached: %d >= %d", gasUsed, header.GasLimit)} } From 1ae84c74baef1abae013024a914fbc3a5aba3a2a Mon Sep 17 00:00:00 2001 From: Jesse Date: Thu, 9 Oct 2025 17:04:37 +0100 Subject: [PATCH 45/72] implement proper trie hashing --- api/simulate.go | 27 +-- trie/bytepool.go | 101 +++++++++++ trie/encoding.go | 156 +++++++++++++++++ trie/hasher.go | 220 +++++++++++++++++++++++ trie/node.go | 266 ++++++++++++++++++++++++++++ trie/node_enc.go | 106 +++++++++++ trie/stacktrie.go | 437 ++++++++++++++++++++++++++++++++++++++++++++++ types/block.go | 3 - types/hashing.go | 2 +- 9 files changed, 1290 insertions(+), 28 deletions(-) create mode 100644 trie/bytepool.go create mode 100644 trie/encoding.go create mode 100644 trie/hasher.go create mode 100644 trie/node.go create mode 100644 trie/node_enc.go create mode 100644 trie/stacktrie.go diff --git a/api/simulate.go b/api/simulate.go index 622543a81..f4d31e68e 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -2,11 +2,11 @@ package api import ( "context" + "encoding/json" "errors" "fmt" "math/big" "time" - "encoding/json" "github.com/openrelayxyz/cardinal-evm/common" "github.com/openrelayxyz/cardinal-evm/crypto" @@ -14,6 +14,7 @@ import ( "github.com/openrelayxyz/cardinal-evm/eips/eip4844" "github.com/openrelayxyz/cardinal-evm/params" "github.com/openrelayxyz/cardinal-evm/state" + "github.com/openrelayxyz/cardinal-evm/trie" "github.com/openrelayxyz/cardinal-evm/types" "github.com/openrelayxyz/cardinal-evm/vm" rpc "github.com/openrelayxyz/cardinal-rpc" @@ -119,23 +120,6 @@ func (r *simBlockResult) MarshalJSON() ([]byte, error) { return json.Marshal(blockData) } -type simpleTrieHasher struct { - data []byte -} - -func (h *simpleTrieHasher) Reset() { - h.data = h.data[:0] -} - -func (h *simpleTrieHasher) Update(key, value []byte) { - h.data = append(h.data, key...) - h.data = append(h.data, value...) -} - -func (h *simpleTrieHasher) Hash() ctypes.Hash { - return crypto.Keccak256Hash(h.data) -} - func (s *simulator) execute(ctx *rpc.CallContext, blocks []simBlock) ([]*simBlockResult, error) { if err := ctx.Context().Err(); err != nil { return nil, err @@ -383,16 +367,11 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, if s.chainConfig.IsCancun(header.Number, new(big.Int).SetUint64(header.Time)) { header.BlobGasUsed = &blobGasUsed } - - hasher := &simpleTrieHasher{} - - header.TxHash = types.DeriveSha(types.Transactions(txes), hasher) - header.ReceiptHash = types.DeriveSha(types.Receipts(receipts), hasher) header.Bloom = types.CreateBloom(receipts) // header.Root = s.base.Root blockBody := &types.Body{Transactions: txes, Withdrawals: *block.BlockOverrides.Withdrawals} - blck := types.NewBlock(header, blockBody, receipts, hasher) + blck := types.NewBlock(header, blockBody, receipts, trie.NewStackTrie(nil)) return blck, callResults, senders, nil } diff --git a/trie/bytepool.go b/trie/bytepool.go new file mode 100644 index 000000000..31be7ae74 --- /dev/null +++ b/trie/bytepool.go @@ -0,0 +1,101 @@ +// Copyright 2024 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 trie + +// bytesPool is a pool for byte slices. It is safe for concurrent use. +type bytesPool struct { + c chan []byte + w int +} + +// newBytesPool creates a new bytesPool. The sliceCap sets the capacity of +// newly allocated slices, and the nitems determines how many items the pool +// will hold, at maximum. +func newBytesPool(sliceCap, nitems int) *bytesPool { + return &bytesPool{ + c: make(chan []byte, nitems), + w: sliceCap, + } +} + +// get returns a slice. Safe for concurrent use. +func (bp *bytesPool) get() []byte { + select { + case b := <-bp.c: + return b + default: + return make([]byte, 0, bp.w) + } +} + +// getWithSize returns a slice with specified byte slice size. +func (bp *bytesPool) getWithSize(s int) []byte { + b := bp.get() + if cap(b) < s { + return make([]byte, s) + } + return b[:s] +} + +// put returns a slice to the pool. Safe for concurrent use. This method +// will ignore slices that are too small or too large (>3x the cap) +func (bp *bytesPool) put(b []byte) { + if c := cap(b); c < bp.w || c > 3*bp.w { + return + } + select { + case bp.c <- b: + default: + } +} + +// unsafeBytesPool is a pool for byte slices. It is not safe for concurrent use. +type unsafeBytesPool struct { + items [][]byte + w int +} + +// newUnsafeBytesPool creates a new unsafeBytesPool. The sliceCap sets the +// capacity of newly allocated slices, and the nitems determines how many +// items the pool will hold, at maximum. +func newUnsafeBytesPool(sliceCap, nitems int) *unsafeBytesPool { + return &unsafeBytesPool{ + items: make([][]byte, 0, nitems), + w: sliceCap, + } +} + +// Get returns a slice with pre-allocated space. +func (bp *unsafeBytesPool) get() []byte { + if len(bp.items) > 0 { + last := bp.items[len(bp.items)-1] + bp.items = bp.items[:len(bp.items)-1] + return last + } + return make([]byte, 0, bp.w) +} + +// put returns a slice to the pool. This method will ignore slices that are +// too small or too large (>3x the cap) +func (bp *unsafeBytesPool) put(b []byte) { + if c := cap(b); c < bp.w || c > 3*bp.w { + return + } + if len(bp.items) < cap(bp.items) { + bp.items = append(bp.items, b) + } +} diff --git a/trie/encoding.go b/trie/encoding.go new file mode 100644 index 000000000..4cd29f531 --- /dev/null +++ b/trie/encoding.go @@ -0,0 +1,156 @@ +// Copyright 2014 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 trie + +// Trie keys are dealt with in three distinct encodings: +// +// KEYBYTES encoding contains the actual key and nothing else. This encoding is the +// input to most API functions. +// +// HEX encoding contains one byte for each nibble of the key and an optional trailing +// 'terminator' byte of value 0x10 which indicates whether or not the node at the key +// contains a value. Hex key encoding is used for nodes loaded in memory because it's +// convenient to access. +// +// COMPACT encoding is defined by the Ethereum Yellow Paper (it's called "hex prefix +// encoding" there) and contains the bytes of the key and a flag. The high nibble of the +// first byte contains the flag; the lowest bit encoding the oddness of the length and +// the second-lowest encoding whether the node at the key is a value node. The low nibble +// of the first byte is zero in the case of an even number of nibbles and the first nibble +// in the case of an odd number. All remaining nibbles (now an even number) fit properly +// into the remaining bytes. Compact encoding is used for nodes stored on disk. + +func hexToCompact(hex []byte) []byte { + terminator := byte(0) + if hasTerm(hex) { + terminator = 1 + hex = hex[:len(hex)-1] + } + buf := make([]byte, len(hex)/2+1) + buf[0] = terminator << 5 // the flag byte + if len(hex)&1 == 1 { + buf[0] |= 1 << 4 // odd flag + buf[0] |= hex[0] // first nibble is contained in the first byte + hex = hex[1:] + } + decodeNibbles(hex, buf[1:]) + return buf +} + +// hexToCompactInPlace places the compact key in input buffer, returning the compacted key. +func hexToCompactInPlace(hex []byte) []byte { + var ( + hexLen = len(hex) // length of the hex input + firstByte = byte(0) + ) + // Check if we have a terminator there + if hexLen > 0 && hex[hexLen-1] == 16 { + firstByte = 1 << 5 + hexLen-- // last part was the terminator, ignore that + } + var ( + binLen = hexLen/2 + 1 + ni = 0 // index in hex + bi = 1 // index in bin (compact) + ) + if hexLen&1 == 1 { + firstByte |= 1 << 4 // odd flag + firstByte |= hex[0] // first nibble is contained in the first byte + ni++ + } + for ; ni < hexLen; bi, ni = bi+1, ni+2 { + hex[bi] = hex[ni]<<4 | hex[ni+1] + } + hex[0] = firstByte + return hex[:binLen] +} + +func compactToHex(compact []byte) []byte { + if len(compact) == 0 { + return compact + } + base := keybytesToHex(compact) + // delete terminator flag + if base[0] < 2 { + base = base[:len(base)-1] + } + // apply odd flag + chop := 2 - base[0]&1 + return base[chop:] +} + +func keybytesToHex(str []byte) []byte { + l := len(str)*2 + 1 + var nibbles = make([]byte, l) + for i, b := range str { + nibbles[i*2] = b / 16 + nibbles[i*2+1] = b % 16 + } + nibbles[l-1] = 16 + return nibbles +} + +// writeHexKey writes the hexkey into the given slice. +// OBS! This method omits the termination flag. +// OBS! The dst slice must be at least 2x as large as the key +func writeHexKey(dst []byte, key []byte) []byte { + _ = dst[2*len(key)-1] + for i, b := range key { + dst[i*2] = b / 16 + dst[i*2+1] = b % 16 + } + return dst[:2*len(key)] +} + +// hexToKeybytes turns hex nibbles into key bytes. +// This can only be used for keys of even length. +func hexToKeybytes(hex []byte) []byte { + if hasTerm(hex) { + hex = hex[:len(hex)-1] + } + if len(hex)&1 != 0 { + panic("can't convert hex key of odd length") + } + key := make([]byte, len(hex)/2) + decodeNibbles(hex, key) + return key +} + +func decodeNibbles(nibbles []byte, bytes []byte) { + for bi, ni := 0, 0; ni < len(nibbles); bi, ni = bi+1, ni+2 { + bytes[bi] = nibbles[ni]<<4 | nibbles[ni+1] + } +} + +// prefixLen returns the length of the common prefix of a and b. +func prefixLen(a, b []byte) int { + var i, length = 0, len(a) + if len(b) < length { + length = len(b) + } + for ; i < length; i++ { + if a[i] != b[i] { + break + } + } + return i +} + +// hasTerm returns whether a hex key has the terminator flag. +func hasTerm(s []byte) bool { + return len(s) > 0 && s[len(s)-1] == 16 +} diff --git a/trie/hasher.go b/trie/hasher.go new file mode 100644 index 000000000..78a32040a --- /dev/null +++ b/trie/hasher.go @@ -0,0 +1,220 @@ +// Copyright 2016 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 trie + +import ( + "bytes" + "fmt" + "sync" + + "github.com/openrelayxyz/cardinal-evm/crypto" + "github.com/openrelayxyz/cardinal-evm/rlp" +) + +// hasher is a type used for the trie Hash operation. A hasher has some +// internal preallocated temp space +type hasher struct { + sha crypto.KeccakState + tmp []byte + encbuf rlp.EncoderBuffer + parallel bool // Whether to use parallel threads when hashing +} + +// hasherPool holds pureHashers +var hasherPool = sync.Pool{ + New: func() any { + return &hasher{ + tmp: make([]byte, 0, 550), // cap is as large as a full fullNode. + sha: crypto.NewKeccakState(), + encbuf: rlp.NewEncoderBuffer(nil), + } + }, +} + +func newHasher(parallel bool) *hasher { + h := hasherPool.Get().(*hasher) + h.parallel = parallel + return h +} + +func returnHasherToPool(h *hasher) { + hasherPool.Put(h) +} + +// hash collapses a node down into a hash node. +func (h *hasher) hash(n node, force bool) []byte { + // Return the cached hash if it's available + if hash, _ := n.cache(); hash != nil { + return hash + } + // Trie not processed yet, walk the children + switch n := n.(type) { + case *shortNode: + enc := h.encodeShortNode(n) + if len(enc) < 32 && !force { + // Nodes smaller than 32 bytes are embedded directly in their parent. + // In such cases, return the raw encoded blob instead of the node hash. + // It's essential to deep-copy the node blob, as the underlying buffer + // of enc will be reused later. + buf := make([]byte, len(enc)) + copy(buf, enc) + return buf + } + hash := h.hashData(enc) + n.flags.hash = hash + return hash + + case *fullNode: + enc := h.encodeFullNode(n) + if len(enc) < 32 && !force { + // Nodes smaller than 32 bytes are embedded directly in their parent. + // In such cases, return the raw encoded blob instead of the node hash. + // It's essential to deep-copy the node blob, as the underlying buffer + // of enc will be reused later. + buf := make([]byte, len(enc)) + copy(buf, enc) + return buf + } + hash := h.hashData(enc) + n.flags.hash = hash + return hash + + case hashNode: + // hash nodes don't have children, so they're left as were + return n + + default: + panic(fmt.Errorf("unexpected node type, %T", n)) + } +} + +// encodeShortNode encodes the provided shortNode into the bytes. Notably, the +// return slice must be deep-copied explicitly, otherwise the underlying slice +// will be reused later. +func (h *hasher) encodeShortNode(n *shortNode) []byte { + // Encode leaf node + if hasTerm(n.Key) { + var ln leafNodeEncoder + ln.Key = hexToCompact(n.Key) + ln.Val = n.Val.(valueNode) + ln.encode(h.encbuf) + return h.encodedBytes() + } + // Encode extension node + var en extNodeEncoder + en.Key = hexToCompact(n.Key) + en.Val = h.hash(n.Val, false) + en.encode(h.encbuf) + return h.encodedBytes() +} + +// fnEncoderPool is the pool for storing shared fullNode encoder to mitigate +// the significant memory allocation overhead. +var fnEncoderPool = sync.Pool{ + New: func() interface{} { + var enc fullnodeEncoder + return &enc + }, +} + +// encodeFullNode encodes the provided fullNode into the bytes. Notably, the +// return slice must be deep-copied explicitly, otherwise the underlying slice +// will be reused later. +func (h *hasher) encodeFullNode(n *fullNode) []byte { + fn := fnEncoderPool.Get().(*fullnodeEncoder) + fn.reset() + + if h.parallel { + var wg sync.WaitGroup + for i := 0; i < 16; i++ { + if n.Children[i] == nil { + continue + } + wg.Add(1) + go func(i int) { + defer wg.Done() + + h := newHasher(false) + fn.Children[i] = h.hash(n.Children[i], false) + returnHasherToPool(h) + }(i) + } + wg.Wait() + } else { + for i := 0; i < 16; i++ { + if child := n.Children[i]; child != nil { + fn.Children[i] = h.hash(child, false) + } + } + } + if n.Children[16] != nil { + fn.Children[16] = n.Children[16].(valueNode) + } + fn.encode(h.encbuf) + fnEncoderPool.Put(fn) + + return h.encodedBytes() +} + +// encodedBytes returns the result of the last encoding operation on h.encbuf. +// This also resets the encoder buffer. +// +// All node encoding must be done like this: +// +// node.encode(h.encbuf) +// enc := h.encodedBytes() +// +// This convention exists because node.encode can only be inlined/escape-analyzed when +// called on a concrete receiver type. +func (h *hasher) encodedBytes() []byte { + h.tmp = h.encbuf.AppendToBytes(h.tmp[:0]) + h.encbuf.Reset(nil) + return h.tmp +} + +// hashData hashes the provided data. It is safe to modify the returned slice after +// the function returns. +func (h *hasher) hashData(data []byte) []byte { + n := make([]byte, 32) + h.sha.Reset() + h.sha.Write(data) + h.sha.Read(n) + return n +} + +// hashDataTo hashes the provided data to the given destination buffer. The caller +// must ensure that the dst buffer is of appropriate size. +func (h *hasher) hashDataTo(dst, data []byte) { + h.sha.Reset() + h.sha.Write(data) + h.sha.Read(dst) +} + +// proofHash is used to construct trie proofs, returning the rlp-encoded node blobs. +// Note, only resolved node (shortNode or fullNode) is expected for proofing. +// +// It is safe to modify the returned slice after the function returns. +func (h *hasher) proofHash(original node) []byte { + switch n := original.(type) { + case *shortNode: + return bytes.Clone(h.encodeShortNode(n)) + case *fullNode: + return bytes.Clone(h.encodeFullNode(n)) + default: + panic(fmt.Errorf("unexpected node type, %T", original)) + } +} diff --git a/trie/node.go b/trie/node.go new file mode 100644 index 000000000..801f547ea --- /dev/null +++ b/trie/node.go @@ -0,0 +1,266 @@ +// Copyright 2014 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 trie + +import ( + "fmt" + "io" + "strings" + + "github.com/openrelayxyz/cardinal-evm/common" + ctypes "github.com/openrelayxyz/cardinal-types" + "github.com/openrelayxyz/cardinal-evm/rlp" +) + +var indices = []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "[17]"} + +type node interface { + cache() (hashNode, bool) + encode(w rlp.EncoderBuffer) + fstring(string) string +} + +type ( + fullNode struct { + Children [17]node // Actual trie node data to encode/decode (needs custom encoder) + flags nodeFlag + } + shortNode struct { + Key []byte + Val node + flags nodeFlag + } + hashNode []byte + valueNode []byte + + // fullnodeEncoder is a type used exclusively for encoding fullNode. + // Briefly instantiating a fullnodeEncoder and initializing with + // existing slices is less memory intense than using the fullNode type. + fullnodeEncoder struct { + Children [17][]byte + } + + // extNodeEncoder is a type used exclusively for encoding extension node. + // Briefly instantiating a extNodeEncoder and initializing with existing + // slices is less memory intense than using the shortNode type. + extNodeEncoder struct { + Key []byte + Val []byte + } + + // leafNodeEncoder is a type used exclusively for encoding leaf node. + leafNodeEncoder struct { + Key []byte + Val []byte + } +) + +// EncodeRLP encodes a full node into the consensus RLP format. +func (n *fullNode) EncodeRLP(w io.Writer) error { + eb := rlp.NewEncoderBuffer(w) + n.encode(eb) + return eb.Flush() +} + +// nodeFlag contains caching-related metadata about a node. +type nodeFlag struct { + hash hashNode // cached hash of the node (may be nil) + dirty bool // whether the node has changes that must be written to the database +} + +func (n nodeFlag) copy() nodeFlag { + return nodeFlag{ + hash: common.CopyBytes(n.hash), + dirty: n.dirty, + } +} + +func (n *fullNode) cache() (hashNode, bool) { return n.flags.hash, n.flags.dirty } +func (n *shortNode) cache() (hashNode, bool) { return n.flags.hash, n.flags.dirty } +func (n hashNode) cache() (hashNode, bool) { return nil, true } +func (n valueNode) cache() (hashNode, bool) { return nil, true } + +// Pretty printing. +func (n *fullNode) String() string { return n.fstring("") } +func (n *shortNode) String() string { return n.fstring("") } +func (n hashNode) String() string { return n.fstring("") } +func (n valueNode) String() string { return n.fstring("") } + +func (n *fullNode) fstring(ind string) string { + resp := fmt.Sprintf("[\n%s ", ind) + for i, node := range &n.Children { + if node == nil { + resp += fmt.Sprintf("%s: ", indices[i]) + } else { + resp += fmt.Sprintf("%s: %v", indices[i], node.fstring(ind+" ")) + } + } + return resp + fmt.Sprintf("\n%s] ", ind) +} + +func (n *shortNode) fstring(ind string) string { + return fmt.Sprintf("{%x: %v} ", n.Key, n.Val.fstring(ind+" ")) +} +func (n hashNode) fstring(ind string) string { + return fmt.Sprintf("<%x> ", []byte(n)) +} +func (n valueNode) fstring(ind string) string { + return fmt.Sprintf("%x ", []byte(n)) +} + +// mustDecodeNode is a wrapper of decodeNode and panic if any error is encountered. +func mustDecodeNode(hash, buf []byte) node { + n, err := decodeNode(hash, buf) + if err != nil { + panic(fmt.Sprintf("node %x: %v", hash, err)) + } + return n +} + +// mustDecodeNodeUnsafe is a wrapper of decodeNodeUnsafe and panic if any error is +// encountered. +func mustDecodeNodeUnsafe(hash, buf []byte) node { + n, err := decodeNodeUnsafe(hash, buf) + if err != nil { + panic(fmt.Sprintf("node %x: %v", hash, err)) + } + return n +} + +// decodeNode parses the RLP encoding of a trie node. It will deep-copy the passed +// byte slice for decoding, so it's safe to modify the byte slice afterwards. The- +// decode performance of this function is not optimal, but it is suitable for most +// scenarios with low performance requirements and hard to determine whether the +// byte slice be modified or not. +func decodeNode(hash, buf []byte) (node, error) { + return decodeNodeUnsafe(hash, common.CopyBytes(buf)) +} + +// decodeNodeUnsafe parses the RLP encoding of a trie node. The passed byte slice +// will be directly referenced by node without bytes deep copy, so the input MUST +// not be changed after. +func decodeNodeUnsafe(hash, buf []byte) (node, error) { + if len(buf) == 0 { + return nil, io.ErrUnexpectedEOF + } + elems, _, err := rlp.SplitList(buf) + if err != nil { + return nil, fmt.Errorf("decode error: %v", err) + } + switch c, _ := rlp.CountValues(elems); c { + case 2: + n, err := decodeShort(hash, elems) + return n, wrapError(err, "short") + case 17: + n, err := decodeFull(hash, elems) + return n, wrapError(err, "full") + default: + return nil, fmt.Errorf("invalid number of list elements: %v", c) + } +} + +func decodeShort(hash, elems []byte) (node, error) { + kbuf, rest, err := rlp.SplitString(elems) + if err != nil { + return nil, err + } + flag := nodeFlag{hash: hash} + key := compactToHex(kbuf) + if hasTerm(key) { + // value node + val, _, err := rlp.SplitString(rest) + if err != nil { + return nil, fmt.Errorf("invalid value node: %v", err) + } + return &shortNode{key, valueNode(val), flag}, nil + } + r, _, err := decodeRef(rest) + if err != nil { + return nil, wrapError(err, "val") + } + return &shortNode{key, r, flag}, nil +} + +func decodeFull(hash, elems []byte) (*fullNode, error) { + n := &fullNode{flags: nodeFlag{hash: hash}} + for i := 0; i < 16; i++ { + cld, rest, err := decodeRef(elems) + if err != nil { + return n, wrapError(err, fmt.Sprintf("[%d]", i)) + } + n.Children[i], elems = cld, rest + } + val, _, err := rlp.SplitString(elems) + if err != nil { + return n, err + } + if len(val) > 0 { + n.Children[16] = valueNode(val) + } + return n, nil +} + +const hashLen = len(ctypes.Hash{}) + +func decodeRef(buf []byte) (node, []byte, error) { + kind, val, rest, err := rlp.Split(buf) + if err != nil { + return nil, buf, err + } + switch { + case kind == rlp.List: + // 'embedded' node reference. The encoding must be smaller + // than a hash in order to be valid. + if size := len(buf) - len(rest); size > hashLen { + err := fmt.Errorf("oversized embedded node (size is %d bytes, want size < %d)", size, hashLen) + return nil, buf, err + } + // The buffer content has already been copied or is safe to use; + // no additional copy is required. + n, err := decodeNodeUnsafe(nil, buf) + return n, rest, err + case kind == rlp.String && len(val) == 0: + // empty node + return nil, rest, nil + case kind == rlp.String && len(val) == 32: + return hashNode(val), rest, nil + default: + return nil, nil, fmt.Errorf("invalid RLP string size %d (want 0 or 32)", len(val)) + } +} + +// wraps a decoding error with information about the path to the +// invalid child node (for debugging encoding issues). +type decodeError struct { + what error + stack []string +} + +func wrapError(err error, ctx string) error { + if err == nil { + return nil + } + if decErr, ok := err.(*decodeError); ok { + decErr.stack = append(decErr.stack, ctx) + return decErr + } + return &decodeError{err, []string{ctx}} +} + +func (err *decodeError) Error() string { + return fmt.Sprintf("%v (decode path: %s)", err.what, strings.Join(err.stack, "<-")) +} diff --git a/trie/node_enc.go b/trie/node_enc.go new file mode 100644 index 000000000..d1498d518 --- /dev/null +++ b/trie/node_enc.go @@ -0,0 +1,106 @@ +// 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 trie + +import ( + "github.com/openrelayxyz/cardinal-evm/rlp" +) + +func nodeToBytes(n node) []byte { + w := rlp.NewEncoderBuffer(nil) + n.encode(w) + result := w.ToBytes() + w.Flush() + return result +} + +func (n *fullNode) encode(w rlp.EncoderBuffer) { + offset := w.List() + for _, c := range n.Children { + if c != nil { + c.encode(w) + } else { + w.Write(rlp.EmptyString) + } + } + w.ListEnd(offset) +} + +func (n *fullnodeEncoder) encode(w rlp.EncoderBuffer) { + offset := w.List() + for i, c := range n.Children { + if len(c) == 0 { + w.Write(rlp.EmptyString) + } else { + // valueNode or hashNode + if i == 16 || len(c) >= 32 { + w.WriteBytes(c) + } else { + w.Write(c) // rawNode + } + } + } + w.ListEnd(offset) +} + +func (n *fullnodeEncoder) reset() { + for i, c := range n.Children { + if len(c) != 0 { + n.Children[i] = n.Children[i][:0] + } + } +} + +func (n *shortNode) encode(w rlp.EncoderBuffer) { + offset := w.List() + w.WriteBytes(n.Key) + if n.Val != nil { + n.Val.encode(w) + } else { + w.Write(rlp.EmptyString) + } + w.ListEnd(offset) +} + +func (n *extNodeEncoder) encode(w rlp.EncoderBuffer) { + offset := w.List() + w.WriteBytes(n.Key) + + if n.Val == nil { + w.Write(rlp.EmptyString) // theoretically impossible to happen + } else if len(n.Val) < 32 { + w.Write(n.Val) // rawNode + } else { + w.WriteBytes(n.Val) // hashNode + } + w.ListEnd(offset) +} + +func (n *leafNodeEncoder) encode(w rlp.EncoderBuffer) { + offset := w.List() + w.WriteBytes(n.Key) // Compact format key + w.WriteBytes(n.Val) // Value node, must be non-nil + w.ListEnd(offset) +} + +func (n hashNode) encode(w rlp.EncoderBuffer) { + w.WriteBytes(n) +} + +func (n valueNode) encode(w rlp.EncoderBuffer) { + w.WriteBytes(n) +} diff --git a/trie/stacktrie.go b/trie/stacktrie.go new file mode 100644 index 000000000..2044375b6 --- /dev/null +++ b/trie/stacktrie.go @@ -0,0 +1,437 @@ +// Copyright 2020 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 trie + +import ( + "bytes" + "errors" + "sync" + + "github.com/openrelayxyz/cardinal-evm/common" + "github.com/openrelayxyz/cardinal-evm/types" + ctypes "github.com/openrelayxyz/cardinal-types" +) + +var ( + stPool = sync.Pool{New: func() any { return new(stNode) }} + bPool = newBytesPool(32, 100) + _ = types.TrieHasher((*StackTrie)(nil)) +) + +// OnTrieNode is a callback method invoked when a trie node is committed +// by the stack trie. The node is only committed if it's considered complete. +// +// The caller should not modify the contents of the returned path and blob +// slice, and their contents may be changed after the call. It is up to the +// `onTrieNode` receiver function to deep-copy the data if it wants to retain +// it after the call ends. +type OnTrieNode func(path []byte, hash ctypes.Hash, blob []byte) + +// StackTrie is a trie implementation that expects keys to be inserted +// in order. Once it determines that a subtree will no longer be inserted +// into, it will hash it and free up the memory it uses. +type StackTrie struct { + root *stNode + h *hasher + last []byte + onTrieNode OnTrieNode + kBuf []byte // buf space used for hex-key during insertions + pBuf []byte // buf space used for path during insertions + vPool *unsafeBytesPool +} + +// NewStackTrie allocates and initializes an empty trie. The committed nodes +// will be discarded immediately if no callback is configured. +func NewStackTrie(onTrieNode OnTrieNode) *StackTrie { + return &StackTrie{ + root: stPool.Get().(*stNode), + h: newHasher(false), + onTrieNode: onTrieNode, + kBuf: make([]byte, 64), + pBuf: make([]byte, 64), + vPool: newUnsafeBytesPool(300, 20), + } +} + +func (t *StackTrie) grow(key []byte) { + if cap(t.kBuf) < 2*len(key) { + t.kBuf = make([]byte, 2*len(key)) + } + if cap(t.pBuf) < 2*len(key) { + t.pBuf = make([]byte, 2*len(key)) + } +} + +// Update inserts a (key, value) pair into the stack trie. +// +// Note the supplied key value pair is copied and managed internally, +// they are safe to be modified after this method returns. +func (t *StackTrie) Update(key, value []byte) error { + if len(value) == 0 { + return errors.New("trying to insert empty (deletion)") + } + t.grow(key) + k := writeHexKey(t.kBuf, key) + if bytes.Compare(t.last, k) >= 0 { + return errors.New("non-ascending key order") + } + if t.last == nil { + t.last = append([]byte{}, k...) // allocate key slice + } else { + t.last = append(t.last[:0], k...) // reuse key slice + } + vBuf := t.vPool.get() + if cap(vBuf) < len(value) { + vBuf = common.CopyBytes(value) + } else { + vBuf = vBuf[:len(value)] + copy(vBuf, value) + } + t.insert(t.root, k, vBuf, t.pBuf[:0]) + return nil +} + +// Reset resets the stack trie object to empty state. +func (t *StackTrie) Reset() { + t.root = stPool.Get().(*stNode) + t.last = nil +} + +// TrieKey returns the internal key representation for the given user key. +func (t *StackTrie) TrieKey(key []byte) []byte { + k := keybytesToHex(key) + k = k[:len(k)-1] // chop the termination flag + return k +} + +// stNode represents a node within a StackTrie +type stNode struct { + typ uint8 // node type (as in branch, ext, leaf) + key []byte // exclusive owned key chunk covered by this (leaf|ext) node + val []byte // exclusive owned value contained by this node (leaf: value; hash: hash) + children [16]*stNode // list of children (for branch and ext) +} + +// newLeaf constructs a leaf node with provided node key and value. +// +// The key is deep-copied within the function, so it can be safely modified +// afterwards. The value is retained directly without copying, as it is +// exclusively owned by the stackTrie. +func newLeaf(key, val []byte) *stNode { + st := stPool.Get().(*stNode) + st.typ = leafNode + st.key = append(st.key, key...) + st.val = val + return st +} + +// newExt constructs an extension node with provided node key and child. The +// key will be deep-copied in the function and safe to modify afterwards. +func newExt(key []byte, child *stNode) *stNode { + st := stPool.Get().(*stNode) + st.typ = extNode + st.key = append(st.key, key...) + st.children[0] = child + return st +} + +// List all values that stNode#nodeType can hold +const ( + emptyNode = iota + branchNode + extNode + leafNode + hashedNode +) + +func (n *stNode) reset() *stNode { + if n.typ == hashedNode { + // On hashnodes, we 'own' the val: it is guaranteed to be not held + // by external caller. Hence, when we arrive here, we can put it + // back into the pool + bPool.put(n.val) + } + n.key = n.key[:0] + n.val = nil + for i := range n.children { + n.children[i] = nil + } + n.typ = emptyNode + return n +} + +// Helper function that, given a full key, determines the index +// at which the chunk pointed by st.keyOffset is different from +// the same chunk in the full key. +func (n *stNode) getDiffIndex(key []byte) int { + for idx, nibble := range n.key { + if nibble != key[idx] { + return idx + } + } + return len(n.key) +} + +// Helper function to that inserts a (key, value) pair into the trie. +func (t *StackTrie) insert(st *stNode, key, value []byte, path []byte) { + switch st.typ { + case branchNode: /* Branch */ + idx := int(key[0]) + + // Unresolve elder siblings + for i := idx - 1; i >= 0; i-- { + if st.children[i] != nil { + if st.children[i].typ != hashedNode { + t.hash(st.children[i], append(path, byte(i))) + } + break + } + } + + // Add new child + if st.children[idx] == nil { + st.children[idx] = newLeaf(key[1:], value) + } else { + t.insert(st.children[idx], key[1:], value, append(path, key[0])) + } + + case extNode: /* Ext */ + // Compare both key chunks and see where they differ + diffidx := st.getDiffIndex(key) + + // Check if chunks are identical. If so, recurse into + // the child node. Otherwise, the key has to be split + // into 1) an optional common prefix, 2) the fullnode + // representing the two differing path, and 3) a leaf + // for each of the differentiated subtrees. + if diffidx == len(st.key) { + // Ext key and key segment are identical, recurse into + // the child node. + t.insert(st.children[0], key[diffidx:], value, append(path, key[:diffidx]...)) + return + } + // Save the original part. Depending if the break is + // at the extension's last byte or not, create an + // intermediate extension or use the extension's child + // node directly. + var n *stNode + if diffidx < len(st.key)-1 { + // Break on the non-last byte, insert an intermediate + // extension. The path prefix of the newly-inserted + // extension should also contain the different byte. + n = newExt(st.key[diffidx+1:], st.children[0]) + t.hash(n, append(path, st.key[:diffidx+1]...)) + } else { + // Break on the last byte, no need to insert + // an extension node: reuse the current node. + // The path prefix of the original part should + // still be same. + n = st.children[0] + t.hash(n, append(path, st.key...)) + } + var p *stNode + if diffidx == 0 { + // the break is on the first byte, so the current node + // is converted into a branch node. + st.children[0] = nil + st.typ = branchNode + p = st + } else { + // the common prefix is at least one byte long, insert + // a new intermediate branch node. + st.children[0] = stPool.Get().(*stNode) + st.children[0].typ = branchNode + p = st.children[0] + } + // Create a leaf for the inserted part + o := newLeaf(key[diffidx+1:], value) + + // Insert both child leaves where they belong: + origIdx := st.key[diffidx] + newIdx := key[diffidx] + p.children[origIdx] = n + p.children[newIdx] = o + st.key = st.key[:diffidx] + + case leafNode: /* Leaf */ + // Compare both key chunks and see where they differ + diffidx := st.getDiffIndex(key) + + // Overwriting a key isn't supported, which means that + // the current leaf is expected to be split into 1) an + // optional extension for the common prefix of these 2 + // keys, 2) a fullnode selecting the path on which the + // keys differ, and 3) one leaf for the differentiated + // component of each key. + if diffidx >= len(st.key) { + panic("Trying to insert into existing key") + } + + // Check if the split occurs at the first nibble of the + // chunk. In that case, no prefix extnode is necessary. + // Otherwise, create that + var p *stNode + if diffidx == 0 { + // Convert current leaf into a branch + st.typ = branchNode + st.children[0] = nil + p = st + } else { + // Convert current node into an ext, + // and insert a child branch node. + st.typ = extNode + st.children[0] = stPool.Get().(*stNode) + st.children[0].typ = branchNode + p = st.children[0] + } + + // Create the two child leaves: one containing the original + // value and another containing the new value. The child leaf + // is hashed directly in order to free up some memory. + origIdx := st.key[diffidx] + p.children[origIdx] = newLeaf(st.key[diffidx+1:], st.val) + t.hash(p.children[origIdx], append(path, st.key[:diffidx+1]...)) + + newIdx := key[diffidx] + p.children[newIdx] = newLeaf(key[diffidx+1:], value) + + // Finally, cut off the key part that has been passed + // over to the children. + st.key = st.key[:diffidx] + st.val = nil + + case emptyNode: /* Empty */ + *st = *newLeaf(key, value) + + case hashedNode: + panic("trying to insert into hash") + + default: + panic("invalid type") + } +} + +// hash converts st into a 'hashedNode', if possible. Possible outcomes: +// +// 1. The rlp-encoded value was >= 32 bytes: +// - Then the 32-byte `hash` will be accessible in `st.val`. +// - And the 'st.type' will be 'hashedNode' +// +// 2. The rlp-encoded value was < 32 bytes +// - Then the <32 byte rlp-encoded value will be accessible in 'st.val'. +// - And the 'st.type' will be 'hashedNode' AGAIN +// +// This method also sets 'st.type' to hashedNode, and clears 'st.key'. +func (t *StackTrie) hash(st *stNode, path []byte) { + var blob []byte // RLP-encoded node blob + switch st.typ { + case hashedNode: + return + + case emptyNode: + st.val = types.EmptyRootHash.Bytes() + st.key = st.key[:0] + st.typ = hashedNode + return + + case branchNode: + var nodes fullnodeEncoder + for i, child := range st.children { + if child == nil { + continue + } + t.hash(child, append(path, byte(i))) + nodes.Children[i] = child.val + } + nodes.encode(t.h.encbuf) + blob = t.h.encodedBytes() + + for i, child := range st.children { + if child == nil { + continue + } + st.children[i] = nil + stPool.Put(child.reset()) // Release child back to pool. + } + + case extNode: + // recursively hash and commit child as the first step + t.hash(st.children[0], append(path, st.key...)) + + // encode the extension node + n := extNodeEncoder{ + Key: hexToCompactInPlace(st.key), + Val: st.children[0].val, + } + n.encode(t.h.encbuf) + blob = t.h.encodedBytes() + + stPool.Put(st.children[0].reset()) // Release child back to pool. + st.children[0] = nil + + case leafNode: + st.key = append(st.key, byte(16)) + n := leafNodeEncoder{ + Key: hexToCompactInPlace(st.key), + Val: st.val, + } + n.encode(t.h.encbuf) + blob = t.h.encodedBytes() + + default: + panic("invalid node type") + } + // Convert the node type to hashNode and reset the key slice. + st.typ = hashedNode + st.key = st.key[:0] + + // Release reference to value slice which is exclusively owned + // by stackTrie itself. + if cap(st.val) > 0 && t.vPool != nil { + t.vPool.put(st.val) + } + st.val = nil + + // Skip committing the non-root node if the size is smaller than 32 bytes + // as tiny nodes are always embedded in their parent except root node. + if len(blob) < 32 && len(path) > 0 { + st.val = bPool.getWithSize(len(blob)) + copy(st.val, blob) + return + } + // Write the hash to the 'val'. We allocate a new val here to not mutate + // input values. + st.val = bPool.getWithSize(32) + t.h.hashDataTo(st.val, blob) + + // Invoke the callback it's provided. Notably, the path and blob slices are + // volatile, please deep-copy the slices in callback if the contents need + // to be retained. + if t.onTrieNode != nil { + t.onTrieNode(path, ctypes.BytesToHash(st.val), blob) + } +} + +// Hash will firstly hash the entire trie if it's still not hashed and then commit +// all leftover nodes to the associated database. Actually most of the trie nodes +// have been committed already. The main purpose here is to commit the nodes on +// right boundary. +func (t *StackTrie) Hash() ctypes.Hash { + n := t.root + t.hash(n, nil) + return ctypes.BytesToHash(n.val) +} diff --git a/types/block.go b/types/block.go index 5db33c6b5..0ba36c03e 100644 --- a/types/block.go +++ b/types/block.go @@ -193,9 +193,6 @@ func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher TrieHasher b.header.ReceiptHash = EmptyReceiptsHash } else { b.header.ReceiptHash = DeriveSha(Receipts(receipts), hasher) - // Receipts must go through MakeReceipt to calculate the receipt's bloom - // already. Merge the receipt's bloom together instead of recalculating - // everything. b.header.Bloom = MergeBloom(receipts) } diff --git a/types/hashing.go b/types/hashing.go index 83ba5adaf..4c1abef30 100644 --- a/types/hashing.go +++ b/types/hashing.go @@ -63,7 +63,7 @@ func prefixedRlpHash(prefix byte, x interface{}) (h ctypes.Hash) { // This is internal, do not use. type TrieHasher interface { Reset() - Update([]byte, []byte) + Update(key []byte, value []byte) error Hash() ctypes.Hash } From bc1e2b8fc9b2b1a086057442cfe842c900ebb588 Mon Sep 17 00:00:00 2001 From: Jesse Date: Thu, 9 Oct 2025 18:47:37 +0100 Subject: [PATCH 46/72] set block size and correct timestamp --- api/simulate.go | 2 +- types/marshalling.go | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/api/simulate.go b/api/simulate.go index f4d31e68e..f1686cba4 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -150,7 +150,7 @@ func (s *simulator) execute(ctx *rpc.CallContext, blocks []simBlock) ([]*simBloc header := types.CopyHeader(s.base) header.Number = new(big.Int).Add(s.base.Number, big.NewInt(int64(bi+1))) header.ParentHash = parent.Hash() - header.Time = parent.Time + uint64(bi+1)*12 + header.Time = parent.Time + 12 header.Extra = []byte{} override := *block.BlockOverrides diff --git a/types/marshalling.go b/types/marshalling.go index e7004f723..c615501ea 100644 --- a/types/marshalling.go +++ b/types/marshalling.go @@ -3,8 +3,9 @@ package types import ( // "github.com/openrelayxyz/cardinal-evm/api" "github.com/openrelayxyz/cardinal-evm/params" - "github.com/openrelayxyz/cardinal-types/hexutil" + "github.com/openrelayxyz/cardinal-evm/rlp" "github.com/openrelayxyz/cardinal-types" + "github.com/openrelayxyz/cardinal-types/hexutil" ) // RPCMarshalHeader converts the given header to the RPC output . @@ -53,6 +54,8 @@ func RPCMarshalHeader(head *Header) map[string]interface{} { // transaction hashes. func RPCMarshalBlock(block *Block, inclTx bool, fullTx bool, config *params.ChainConfig) map[string]interface{} { fields := RPCMarshalHeader(block.Header()) + encoded,_ := rlp.EncodeToBytes(block) + block.size.Store(uint64(len(encoded))) fields["size"] = hexutil.Uint64(hexutil.Uint64(hexutil.Uint64(block.size.Load()))) if inclTx { From b8aab8b27381c01c1c02b6d80ad0e79f93deaea9 Mon Sep 17 00:00:00 2001 From: Jesse Date: Thu, 9 Oct 2025 20:17:39 +0100 Subject: [PATCH 47/72] attempt to fix size and timestamp fields --- api/simulate.go | 1 - types/block.go | 13 +++++++++++++ types/marshalling.go | 5 +---- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/api/simulate.go b/api/simulate.go index f1686cba4..6ab8f5df3 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -150,7 +150,6 @@ func (s *simulator) execute(ctx *rpc.CallContext, blocks []simBlock) ([]*simBloc header := types.CopyHeader(s.base) header.Number = new(big.Int).Add(s.base.Number, big.NewInt(int64(bi+1))) header.ParentHash = parent.Hash() - header.Time = parent.Time + 12 header.Extra = []byte{} override := *block.BlockOverrides diff --git a/types/block.go b/types/block.go index 0ba36c03e..91421089d 100644 --- a/types/block.go +++ b/types/block.go @@ -11,6 +11,7 @@ import ( "github.com/openrelayxyz/cardinal-evm/common" "github.com/openrelayxyz/cardinal-types/hexutil" + "github.com/openrelayxyz/cardinal-evm/rlp" "github.com/openrelayxyz/cardinal-types" ) @@ -225,4 +226,16 @@ func CalcUncleHash(uncles []*Header) types.Hash { return EmptyUncleHash } return rlpHash(uncles) +} + +// Size returns the true RLP encoded storage size of the block, either by encoding +// and returning it, or returning a previously cached value. +func (b *Block) Size() uint64 { + if size := b.size.Load(); size > 0 { + return size + } + c := writeCounter(0) + rlp.Encode(&c, b) + b.size.Store(uint64(c)) + return uint64(c) } \ No newline at end of file diff --git a/types/marshalling.go b/types/marshalling.go index c615501ea..35ad88530 100644 --- a/types/marshalling.go +++ b/types/marshalling.go @@ -3,7 +3,6 @@ package types import ( // "github.com/openrelayxyz/cardinal-evm/api" "github.com/openrelayxyz/cardinal-evm/params" - "github.com/openrelayxyz/cardinal-evm/rlp" "github.com/openrelayxyz/cardinal-types" "github.com/openrelayxyz/cardinal-types/hexutil" ) @@ -54,9 +53,7 @@ func RPCMarshalHeader(head *Header) map[string]interface{} { // transaction hashes. func RPCMarshalBlock(block *Block, inclTx bool, fullTx bool, config *params.ChainConfig) map[string]interface{} { fields := RPCMarshalHeader(block.Header()) - encoded,_ := rlp.EncodeToBytes(block) - block.size.Store(uint64(len(encoded))) - fields["size"] = hexutil.Uint64(hexutil.Uint64(hexutil.Uint64(block.size.Load()))) + fields["size"] = hexutil.Uint64(block.Size()) if inclTx { formatTx := func(idx int, tx *Transaction) interface{} { From 8b471afb41ce1e5ebf9bc4e962605812842b8edb Mon Sep 17 00:00:00 2001 From: Jesse Date: Thu, 9 Oct 2025 21:03:32 +0100 Subject: [PATCH 48/72] Update simulate.go --- api/simulate.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/simulate.go b/api/simulate.go index 6ab8f5df3..4f528a56f 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -29,7 +29,7 @@ const ( maxSimulateBlocks = 256 // timestampIncrement is the default increment between block timestamps. - timestampIncrement = 1 + timestampIncrement = 12 ) // BlockOverrides is a set of header fields to override. From faf6cabb307c06286c5d5b1ced3faa7f6d2f4832 Mon Sep 17 00:00:00 2001 From: Jesse Date: Thu, 9 Oct 2025 21:50:28 +0100 Subject: [PATCH 49/72] debug logs inside excessblobgas calculation logic --- api/simulate.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/simulate.go b/api/simulate.go index 4f528a56f..4c85cf6be 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -20,7 +20,7 @@ import ( rpc "github.com/openrelayxyz/cardinal-rpc" ctypes "github.com/openrelayxyz/cardinal-types" "github.com/openrelayxyz/cardinal-types/hexutil" - // log "github.com/inconshreveable/log15" + log "github.com/inconshreveable/log15" ) const ( @@ -220,7 +220,9 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, if parent.BlobGasUsed != nil { parentBlobGasUsed = *parent.BlobGasUsed } + log.Error("blob gas calculation", "parentExcess", parentExcess, "parentBlobGasUsed", parentBlobGasUsed) excess = eip4844.CalcExcessBlobGas(parentExcess, parentBlobGasUsed) + log.Error("calculated excess", "excess", excess) } header.ExcessBlobGas = &excess } From 7a2bf340eb748af05c7d095c6a3e97573ece4f0f Mon Sep 17 00:00:00 2001 From: Jesse Date: Thu, 9 Oct 2025 23:10:19 +0100 Subject: [PATCH 50/72] change BlobTxTargetBlobGasPerBlock to represent blob target ** BlobTxBlobGasPerBlob --- params/protocol_params.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/params/protocol_params.go b/params/protocol_params.go index c537a37de..1f2574c0e 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -172,7 +172,8 @@ const ( BlobTxMinDataGasprice = 1 // Minimum gas price for data blobs BlobTxDataGaspriceUpdateFraction = 2225652 // Controls the maximum rate of change for data gas price BlobTxPointEvaluationPrecompileGas = 50000 // Gas price for the point evaluation precompile. - BlobTxTargetBlobGasPerBlock = 1 << 18 // Target consumable blob gas for data blobs per block (for 1559-like pricing) + //placeholder to represent blob target ** BlobTxBlobGasPerBlob + BlobTxTargetBlobGasPerBlock = 3 * (1 << 17) BlobTxMinBlobGasprice = 1 // Minimum gas price for data blobs BlobTxBlobGaspriceUpdateFraction = 2225652 // Controls the maximum rate of change for blob gas price From 0aae30583cc5256e91a80a0549b2bb12742ee3a7 Mon Sep 17 00:00:00 2001 From: Jesse Date: Mon, 13 Oct 2025 14:55:38 +0100 Subject: [PATCH 51/72] attempt to resolve baseFee issue --- api/simulate.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/simulate.go b/api/simulate.go index 5f3f62a83..66d18a0d7 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -151,6 +151,7 @@ func (s *simulator) execute(ctx *rpc.CallContext, blocks []simBlock) ([]*simBloc header.Number = new(big.Int).Add(s.base.Number, big.NewInt(int64(bi+1))) header.ParentHash = parent.Hash() header.Extra = []byte{} + header.BaseFee = nil override := *block.BlockOverrides if override.Number != nil {header.Number = override.Number.ToInt()} @@ -246,7 +247,7 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, } for i, call := range block.Calls { - tracer := newTracer(s.traceTransfers, header.Number.Uint64(), ctypes.Hash{}, ctypes.Hash{}, uint(i)) + tracer := newTracer(s.traceTransfers, header.Number.Uint64(), header.Hash(), ctypes.Hash{}, uint(i)) if err := ctx.Context().Err(); err != nil { return nil, nil, nil, err From 23750abe251e89d71a55c8c5ad13cf8241f1c7a7 Mon Sep 17 00:00:00 2001 From: Jesse Date: Mon, 13 Oct 2025 15:51:58 +0100 Subject: [PATCH 52/72] attempt to debug requestHash --- api/simulate.go | 7 +++++-- types/block.go | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/api/simulate.go b/api/simulate.go index 66d18a0d7..a9069827c 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -20,7 +20,7 @@ import ( rpc "github.com/openrelayxyz/cardinal-rpc" ctypes "github.com/openrelayxyz/cardinal-types" "github.com/openrelayxyz/cardinal-types/hexutil" - // log "github.com/inconshreveable/log15" + log "github.com/inconshreveable/log15" ) const ( @@ -273,6 +273,7 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, }, call.from(), call.GasPrice.ToInt()) tx := call.ToTransaction(types.DynamicFeeTxType) + log.Error(fmt.Sprintf("TX: nonce=%d gas=%d gasFeeCap=%s gasTipCap=%s chainID=%s to=%s value=%s data=%s hash=%s\n", tx.Nonce(), tx.Gas(), tx.GasFeeCap(), tx.GasTipCap(), tx.ChainId(), tx.To().Hex(), tx.Value(), hexutil.Encode(tx.Data()), tx.Hash().Hex())) txes[i] = tx senders[tx.Hash()] = call.from() @@ -360,7 +361,9 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, header.BlobGasUsed = &blobGasUsed } header.Bloom = types.CreateBloom(receipts) - // header.Root = s.base.Root + + reqHash := types.CalcRequestsHash([][]byte{}) + header.RequestsHash = &reqHash blockBody := &types.Body{Transactions: txes, Withdrawals: *block.BlockOverrides.Withdrawals} blck := types.NewBlock(header, blockBody, receipts, trie.NewStackTrie(nil)) diff --git a/types/block.go b/types/block.go index 0ba36c03e..f116e5a56 100644 --- a/types/block.go +++ b/types/block.go @@ -2,6 +2,7 @@ package types import ( "math/big" + "crypto/sha256" "sync/atomic" "time" "slices" @@ -225,4 +226,19 @@ func CalcUncleHash(uncles []*Header) types.Hash { return EmptyUncleHash } return rlpHash(uncles) +} + +// CalcRequestsHash creates the block requestsHash value for a list of requests. +func CalcRequestsHash(requests [][]byte) types.Hash { + h1, h2 := sha256.New(), sha256.New() + var buf types.Hash + for _, item := range requests { + if len(item) > 1 { // skip items with only requestType and no data. + h1.Reset() + h1.Write(item) + h2.Write(h1.Sum(buf[:0])) + } + } + h2.Sum(buf[:0]) + return buf } \ No newline at end of file From 5c26825791e0fc7d4b26dc8be276b63720372795 Mon Sep 17 00:00:00 2001 From: Philip Morlier Date: Wed, 17 Sep 2025 08:59:12 -0700 Subject: [PATCH 53/72] Added fd5 config --- params/config.go | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/params/config.go b/params/config.go index 40e7628d6..d098ae5a7 100644 --- a/params/config.go +++ b/params/config.go @@ -506,6 +506,45 @@ var ( }, } + Fd5ChainConfig = &ChainConfig{ + ChainID: big.NewInt(7092415936), + HomesteadBlock: big.NewInt(0), + DAOForkBlock: nil, + DAOForkSupport: true, + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + ShanghaiTime: big.NewInt(0), + CancunTime: big.NewInt(0), + PragueTime: big.NewInt(0), + OsakaTime: big.NewInt(1757611104), + Ethash: new(EthashConfig), + Engine: ETHashEngine, + BlobSchedule: []*BlobConfig{ + // Cancun + &BlobConfig{ + ActivationTime: 0, + Target: 3, + Max : 6, + UpdateFraction: 3338477, + }, + // Prague + &BlobConfig{ + ActivationTime: 0, + Target: 6, + Max : 9, + UpdateFraction: 5007716, + }, + }, + } + // AllEthashProtocolChanges contains every protocol change (EIPs) introduced // and accepted by the Ethereum core developers into the Ethash consensus. // From fa5bbc7ae06b93760bcca7cad5dcb2663231db66 Mon Sep 17 00:00:00 2001 From: Philip Morlier Date: Mon, 13 Oct 2025 09:44:02 -0700 Subject: [PATCH 54/72] Removed duplicate fd5 chain config --- params/config.go | 39 --------------------------------------- 1 file changed, 39 deletions(-) diff --git a/params/config.go b/params/config.go index d098ae5a7..40e7628d6 100644 --- a/params/config.go +++ b/params/config.go @@ -506,45 +506,6 @@ var ( }, } - Fd5ChainConfig = &ChainConfig{ - ChainID: big.NewInt(7092415936), - HomesteadBlock: big.NewInt(0), - DAOForkBlock: nil, - DAOForkSupport: true, - EIP150Block: big.NewInt(0), - EIP155Block: big.NewInt(0), - EIP158Block: big.NewInt(0), - ByzantiumBlock: big.NewInt(0), - ConstantinopleBlock: big.NewInt(0), - PetersburgBlock: big.NewInt(0), - IstanbulBlock: big.NewInt(0), - MuirGlacierBlock: big.NewInt(0), - BerlinBlock: big.NewInt(0), - LondonBlock: big.NewInt(0), - ShanghaiTime: big.NewInt(0), - CancunTime: big.NewInt(0), - PragueTime: big.NewInt(0), - OsakaTime: big.NewInt(1757611104), - Ethash: new(EthashConfig), - Engine: ETHashEngine, - BlobSchedule: []*BlobConfig{ - // Cancun - &BlobConfig{ - ActivationTime: 0, - Target: 3, - Max : 6, - UpdateFraction: 3338477, - }, - // Prague - &BlobConfig{ - ActivationTime: 0, - Target: 6, - Max : 9, - UpdateFraction: 5007716, - }, - }, - } - // AllEthashProtocolChanges contains every protocol change (EIPs) introduced // and accepted by the Ethereum core developers into the Ethash consensus. // From 30a59aeda27a84282c98ac604105823a4d4f15c6 Mon Sep 17 00:00:00 2001 From: Jesse Date: Tue, 14 Oct 2025 14:19:06 +0100 Subject: [PATCH 55/72] Update transaction_args.go --- api/transaction_args.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/transaction_args.go b/api/transaction_args.go index 3a036b128..a9de62b8d 100644 --- a/api/transaction_args.go +++ b/api/transaction_args.go @@ -202,7 +202,7 @@ func (args *TransactionArgs) setDefaults(ctx *rpc.CallContext, chainConfig *para } } if args.Gas == nil { - gas, _, err := DoEstimateGas(ctx, getEVM, *args, &PreviousState{db.ALCalcCopy(), header}, blockNrOrHash, header.GasLimit, true, chainConfig) + gas, _, err := DoEstimateGas(ctx, getEVM, *args, &PreviousState{db, header}, blockNrOrHash, header.GasLimit, true, chainConfig) if err != nil { return err } From d0aae2d6cfeace76fb51c17ab98b4486a8fe0d75 Mon Sep 17 00:00:00 2001 From: Jesse Date: Tue, 14 Oct 2025 14:51:29 +0100 Subject: [PATCH 56/72] add debug logs before setdefaults --- api/simulate.go | 2 +- api/transaction_args.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/simulate.go b/api/simulate.go index a9069827c..c1b7388ca 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -252,6 +252,7 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, if err := ctx.Context().Err(); err != nil { return nil, nil, nil, err } + log.Error(fmt.Sprintf("call %d, balance of %s is %s", i, call.from().Hex(), s.state.GetBalance(call.from()).String())) if err := call.setDefaults(ctx, s.chainConfig, s.evmFn, s.state, header, vm.BlockNumberOrHashWithHash(header.Hash(), false)); err != nil { return nil, nil, nil, err } @@ -273,7 +274,6 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, }, call.from(), call.GasPrice.ToInt()) tx := call.ToTransaction(types.DynamicFeeTxType) - log.Error(fmt.Sprintf("TX: nonce=%d gas=%d gasFeeCap=%s gasTipCap=%s chainID=%s to=%s value=%s data=%s hash=%s\n", tx.Nonce(), tx.Gas(), tx.GasFeeCap(), tx.GasTipCap(), tx.ChainId(), tx.To().Hex(), tx.Value(), hexutil.Encode(tx.Data()), tx.Hash().Hex())) txes[i] = tx senders[tx.Hash()] = call.from() diff --git a/api/transaction_args.go b/api/transaction_args.go index a9de62b8d..3a036b128 100644 --- a/api/transaction_args.go +++ b/api/transaction_args.go @@ -202,7 +202,7 @@ func (args *TransactionArgs) setDefaults(ctx *rpc.CallContext, chainConfig *para } } if args.Gas == nil { - gas, _, err := DoEstimateGas(ctx, getEVM, *args, &PreviousState{db, header}, blockNrOrHash, header.GasLimit, true, chainConfig) + gas, _, err := DoEstimateGas(ctx, getEVM, *args, &PreviousState{db.ALCalcCopy(), header}, blockNrOrHash, header.GasLimit, true, chainConfig) if err != nil { return err } From b81e1e636eaee7794f3021f8c8ae5a521c84608b Mon Sep 17 00:00:00 2001 From: Jesse Date: Tue, 14 Oct 2025 15:18:58 +0100 Subject: [PATCH 57/72] add logs to statedb and set fakebalance when copying state --- api/eth.go | 1 + state/state_object.go | 4 ++++ state/statedb.go | 3 ++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/api/eth.go b/api/eth.go index c2f519b40..dafb09545 100644 --- a/api/eth.go +++ b/api/eth.go @@ -177,6 +177,7 @@ func (diff *StateOverride) Apply(state state.StateDB) error { // Override account balance. if account.Balance != nil { state.SetBalance(addr, (*big.Int)(account.Balance)) + log.Error("balance", "b", account.Balance.String(), "address", addr.String()) } if account.State != nil && account.StateDiff != nil { return fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex()) diff --git a/state/state_object.go b/state/state_object.go index aa10d9f5d..f87b03f38 100644 --- a/state/state_object.go +++ b/state/state_object.go @@ -128,6 +128,9 @@ func (s *stateObject) copy() *stateObject { if s.nonce != nil { state.nonce = &(*s.nonce) } + if s.fakeBalance != nil { + state.fakeBalance = new(big.Int).Set(s.fakeBalance) + } return state } @@ -228,6 +231,7 @@ func (s *stateObject) getBalance() *big.Int { return new(big.Int).Add(delta, balance) } func (s *stateObject) setBalance(balance *big.Int) journalEntry { + log.Error("balance inside setBalance", "b", balance.String(), "address", s.address.String()) old := s.fakeBalance s.fakeBalance = balance return journalEntry{&s.address, func(sdb *stateDB) { sdb.getAccount(s.address).fakeBalance = old }} diff --git a/state/statedb.go b/state/statedb.go index 7bab23b8e..51be86263 100644 --- a/state/statedb.go +++ b/state/statedb.go @@ -17,7 +17,7 @@ package state import ( - // log "github.com/inconshreveable/log15" + log "github.com/inconshreveable/log15" "github.com/openrelayxyz/cardinal-evm/common" "github.com/openrelayxyz/cardinal-evm/crypto" "github.com/openrelayxyz/cardinal-evm/params" @@ -248,6 +248,7 @@ func (sdb *stateDB) SetStorage(addr common.Address, storage map[ctypes.Hash]ctyp func (sdb *stateDB) SetBalance(addr common.Address, balance *big.Int) { sobj := sdb.getAccount(addr) sdb.journal = append(sdb.journal, sobj.setBalance(balance)) + log.Error("balance inside setBalance", "b", balance.String(), "address", addr.String()) } func (sdb *stateDB) Suicide(addr common.Address) bool { From a92640f9463b132304c4e2877631e2efe48b1c0c Mon Sep 17 00:00:00 2001 From: Jesse Date: Tue, 14 Oct 2025 15:29:20 +0100 Subject: [PATCH 58/72] add more debug logs --- api/eth.go | 2 +- api/simulate.go | 2 +- api/transaction_args.go | 1 + state/state_object.go | 4 +++- state/statedb.go | 2 +- 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/api/eth.go b/api/eth.go index dafb09545..83ddb4e38 100644 --- a/api/eth.go +++ b/api/eth.go @@ -177,7 +177,7 @@ func (diff *StateOverride) Apply(state state.StateDB) error { // Override account balance. if account.Balance != nil { state.SetBalance(addr, (*big.Int)(account.Balance)) - log.Error("balance", "b", account.Balance.String(), "address", addr.String()) + log.Error("balance in Apply", "b", account.Balance.String(), "address", addr.String()) } if account.State != nil && account.StateDiff != nil { return fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex()) diff --git a/api/simulate.go b/api/simulate.go index c1b7388ca..ec321c9f0 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -252,7 +252,7 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, if err := ctx.Context().Err(); err != nil { return nil, nil, nil, err } - log.Error(fmt.Sprintf("call %d, balance of %s is %s", i, call.from().Hex(), s.state.GetBalance(call.from()).String())) + log.Error(fmt.Sprintf("before setDefaults for call %d, balance of %s is %s", i, call.from().Hex(), s.state.GetBalance(call.from()).String())) if err := call.setDefaults(ctx, s.chainConfig, s.evmFn, s.state, header, vm.BlockNumberOrHashWithHash(header.Hash(), false)); err != nil { return nil, nil, nil, err } diff --git a/api/transaction_args.go b/api/transaction_args.go index 3a036b128..6bb14f9ca 100644 --- a/api/transaction_args.go +++ b/api/transaction_args.go @@ -202,6 +202,7 @@ func (args *TransactionArgs) setDefaults(ctx *rpc.CallContext, chainConfig *para } } if args.Gas == nil { + log.Error("in setDefaults", "address", args.From.Hex(), "balance", db.GetBalance(*args.From).String()) gas, _, err := DoEstimateGas(ctx, getEVM, *args, &PreviousState{db.ALCalcCopy(), header}, blockNrOrHash, header.GasLimit, true, chainConfig) if err != nil { return err diff --git a/state/state_object.go b/state/state_object.go index f87b03f38..ac0b1191b 100644 --- a/state/state_object.go +++ b/state/state_object.go @@ -111,6 +111,7 @@ func (s *stateObject) equal(b *stateObject) bool { } func (s *stateObject) copy() *stateObject { + log.Error("copy() called", "addr", s.address.Hex(), "fakeBalance", s.fakeBalance) state := &stateObject{ address: s.address, @@ -130,6 +131,7 @@ func (s *stateObject) copy() *stateObject { } if s.fakeBalance != nil { state.fakeBalance = new(big.Int).Set(s.fakeBalance) + log.Error("copied fakeBalance", "addr", s.address.Hex(), "value", state.fakeBalance) } return state } @@ -231,7 +233,7 @@ func (s *stateObject) getBalance() *big.Int { return new(big.Int).Add(delta, balance) } func (s *stateObject) setBalance(balance *big.Int) journalEntry { - log.Error("balance inside setBalance", "b", balance.String(), "address", s.address.String()) + log.Error("balance inside setBalance", "b", balance, "address", s.address.String()) old := s.fakeBalance s.fakeBalance = balance return journalEntry{&s.address, func(sdb *stateDB) { sdb.getAccount(s.address).fakeBalance = old }} diff --git a/state/statedb.go b/state/statedb.go index 51be86263..20a9ed1e3 100644 --- a/state/statedb.go +++ b/state/statedb.go @@ -248,7 +248,7 @@ func (sdb *stateDB) SetStorage(addr common.Address, storage map[ctypes.Hash]ctyp func (sdb *stateDB) SetBalance(addr common.Address, balance *big.Int) { sobj := sdb.getAccount(addr) sdb.journal = append(sdb.journal, sobj.setBalance(balance)) - log.Error("balance inside setBalance", "b", balance.String(), "address", addr.String()) + log.Error("balance inside SetBalance", "b", balance, "address", addr.Hex()) } func (sdb *stateDB) Suicide(addr common.Address) bool { From cb6da8698d6b7a1ca168dad04f9eae028aeaf2a7 Mon Sep 17 00:00:00 2001 From: Jesse Date: Tue, 14 Oct 2025 16:34:30 +0100 Subject: [PATCH 59/72] return balance if fake balance is set --- api/simulate.go | 1 + state/statedb.go | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/api/simulate.go b/api/simulate.go index ec321c9f0..5e56429e3 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -223,6 +223,7 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, if err := block.StateOverrides.Apply(s.state); err != nil { return nil, nil, nil, err } + log.Error("after Apply, checking balance", "addr", "0xC000000000000000000000000000000000000000", "balance", s.state.GetBalance(common.HexToAddress("0xC000000000000000000000000000000000000000"))) } var ( diff --git a/state/statedb.go b/state/statedb.go index 20a9ed1e3..da311978e 100644 --- a/state/statedb.go +++ b/state/statedb.go @@ -27,6 +27,7 @@ import ( "github.com/openrelayxyz/cardinal-storage/db/mem" ctypes "github.com/openrelayxyz/cardinal-types" "math/big" + "fmt" ) type journalEntry struct { @@ -168,7 +169,11 @@ func (sdb *stateDB) AddBalance(addr common.Address, amount *big.Int) { sdb.journal = append(sdb.journal, sobj.addBalance(amount)) } func (sdb *stateDB) GetBalance(addr common.Address) *big.Int { + log.Error("GetBalance pointer", "statedb", fmt.Sprintf("%p", sdb), "stateobj", fmt.Sprintf("%p", sdb.getAccount(addr))) sobj := sdb.getAccount(addr) + if sobj.fakeBalance != nil { + return sobj.getBalance() + } if !sobj.loadAccount(sdb.tx, sdb.chainid) { return common.Big0 } @@ -246,6 +251,7 @@ func (sdb *stateDB) SetStorage(addr common.Address, storage map[ctypes.Hash]ctyp sdb.journal = append(sdb.journal, sobj.setStorage(storage)) } func (sdb *stateDB) SetBalance(addr common.Address, balance *big.Int) { + log.Error("SetBalance pointer", "statedb", fmt.Sprintf("%p", sdb), "stateobj", fmt.Sprintf("%p", sdb.getAccount(addr))) sobj := sdb.getAccount(addr) sdb.journal = append(sdb.journal, sobj.setBalance(balance)) log.Error("balance inside SetBalance", "b", balance, "address", addr.Hex()) From 95c66726d3f467588d38735099f3ad3576d6eb0c Mon Sep 17 00:00:00 2001 From: Jesse Date: Tue, 14 Oct 2025 18:57:50 +0100 Subject: [PATCH 60/72] fix maxFeePerGas error --- api/eth.go | 1 - api/simulate.go | 3 +-- api/state_transition.go | 2 +- api/transaction_args.go | 1 - state/state_object.go | 4 ---- state/statedb.go | 6 +----- 6 files changed, 3 insertions(+), 14 deletions(-) diff --git a/api/eth.go b/api/eth.go index 83ddb4e38..c2f519b40 100644 --- a/api/eth.go +++ b/api/eth.go @@ -177,7 +177,6 @@ func (diff *StateOverride) Apply(state state.StateDB) error { // Override account balance. if account.Balance != nil { state.SetBalance(addr, (*big.Int)(account.Balance)) - log.Error("balance in Apply", "b", account.Balance.String(), "address", addr.String()) } if account.State != nil && account.StateDiff != nil { return fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex()) diff --git a/api/simulate.go b/api/simulate.go index 5e56429e3..a9069827c 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -223,7 +223,6 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, if err := block.StateOverrides.Apply(s.state); err != nil { return nil, nil, nil, err } - log.Error("after Apply, checking balance", "addr", "0xC000000000000000000000000000000000000000", "balance", s.state.GetBalance(common.HexToAddress("0xC000000000000000000000000000000000000000"))) } var ( @@ -253,7 +252,6 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, if err := ctx.Context().Err(); err != nil { return nil, nil, nil, err } - log.Error(fmt.Sprintf("before setDefaults for call %d, balance of %s is %s", i, call.from().Hex(), s.state.GetBalance(call.from()).String())) if err := call.setDefaults(ctx, s.chainConfig, s.evmFn, s.state, header, vm.BlockNumberOrHashWithHash(header.Hash(), false)); err != nil { return nil, nil, nil, err } @@ -275,6 +273,7 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, }, call.from(), call.GasPrice.ToInt()) tx := call.ToTransaction(types.DynamicFeeTxType) + log.Error(fmt.Sprintf("TX: nonce=%d gas=%d gasFeeCap=%s gasTipCap=%s chainID=%s to=%s value=%s data=%s hash=%s\n", tx.Nonce(), tx.Gas(), tx.GasFeeCap(), tx.GasTipCap(), tx.ChainId(), tx.To().Hex(), tx.Value(), hexutil.Encode(tx.Data()), tx.Hash().Hex())) txes[i] = tx senders[tx.Hash()] = call.from() diff --git a/api/state_transition.go b/api/state_transition.go index 0536ae092..e82ff9ea2 100644 --- a/api/state_transition.go +++ b/api/state_transition.go @@ -262,7 +262,7 @@ func (st *StateTransition) preCheck() error { // Make sure that transaction gasFeeCap is greater than the baseFee (post london) if st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) && st.evm.Context.BaseFee != nil { // Skip the checks if gas fields are zero and baseFee was explicitly disabled (eth_call) - if !st.evm.Config.NoBaseFee || st.gasFeeCap.BitLen() > 0 || st.gasTipCap.BitLen() > 0 { + if !st.evm.Config.NoBaseFee && st.gasFeeCap.BitLen() == 0 && st.gasTipCap.BitLen() == 0 { if l := st.gasFeeCap.BitLen(); l > 256 { return fmt.Errorf("%w: address %v, maxFeePerGas bit length: %d", ErrFeeCapVeryHigh, st.msg.From().Hex(), l) diff --git a/api/transaction_args.go b/api/transaction_args.go index 6bb14f9ca..3a036b128 100644 --- a/api/transaction_args.go +++ b/api/transaction_args.go @@ -202,7 +202,6 @@ func (args *TransactionArgs) setDefaults(ctx *rpc.CallContext, chainConfig *para } } if args.Gas == nil { - log.Error("in setDefaults", "address", args.From.Hex(), "balance", db.GetBalance(*args.From).String()) gas, _, err := DoEstimateGas(ctx, getEVM, *args, &PreviousState{db.ALCalcCopy(), header}, blockNrOrHash, header.GasLimit, true, chainConfig) if err != nil { return err diff --git a/state/state_object.go b/state/state_object.go index ac0b1191b..893fbde29 100644 --- a/state/state_object.go +++ b/state/state_object.go @@ -111,8 +111,6 @@ func (s *stateObject) equal(b *stateObject) bool { } func (s *stateObject) copy() *stateObject { - log.Error("copy() called", "addr", s.address.Hex(), "fakeBalance", s.fakeBalance) - state := &stateObject{ address: s.address, account: s.account, @@ -131,7 +129,6 @@ func (s *stateObject) copy() *stateObject { } if s.fakeBalance != nil { state.fakeBalance = new(big.Int).Set(s.fakeBalance) - log.Error("copied fakeBalance", "addr", s.address.Hex(), "value", state.fakeBalance) } return state } @@ -233,7 +230,6 @@ func (s *stateObject) getBalance() *big.Int { return new(big.Int).Add(delta, balance) } func (s *stateObject) setBalance(balance *big.Int) journalEntry { - log.Error("balance inside setBalance", "b", balance, "address", s.address.String()) old := s.fakeBalance s.fakeBalance = balance return journalEntry{&s.address, func(sdb *stateDB) { sdb.getAccount(s.address).fakeBalance = old }} diff --git a/state/statedb.go b/state/statedb.go index da311978e..af18f6903 100644 --- a/state/statedb.go +++ b/state/statedb.go @@ -17,7 +17,7 @@ package state import ( - log "github.com/inconshreveable/log15" + // log "github.com/inconshreveable/log15" "github.com/openrelayxyz/cardinal-evm/common" "github.com/openrelayxyz/cardinal-evm/crypto" "github.com/openrelayxyz/cardinal-evm/params" @@ -27,7 +27,6 @@ import ( "github.com/openrelayxyz/cardinal-storage/db/mem" ctypes "github.com/openrelayxyz/cardinal-types" "math/big" - "fmt" ) type journalEntry struct { @@ -169,7 +168,6 @@ func (sdb *stateDB) AddBalance(addr common.Address, amount *big.Int) { sdb.journal = append(sdb.journal, sobj.addBalance(amount)) } func (sdb *stateDB) GetBalance(addr common.Address) *big.Int { - log.Error("GetBalance pointer", "statedb", fmt.Sprintf("%p", sdb), "stateobj", fmt.Sprintf("%p", sdb.getAccount(addr))) sobj := sdb.getAccount(addr) if sobj.fakeBalance != nil { return sobj.getBalance() @@ -251,10 +249,8 @@ func (sdb *stateDB) SetStorage(addr common.Address, storage map[ctypes.Hash]ctyp sdb.journal = append(sdb.journal, sobj.setStorage(storage)) } func (sdb *stateDB) SetBalance(addr common.Address, balance *big.Int) { - log.Error("SetBalance pointer", "statedb", fmt.Sprintf("%p", sdb), "stateobj", fmt.Sprintf("%p", sdb.getAccount(addr))) sobj := sdb.getAccount(addr) sdb.journal = append(sdb.journal, sobj.setBalance(balance)) - log.Error("balance inside SetBalance", "b", balance, "address", addr.Hex()) } func (sdb *stateDB) Suicide(addr common.Address) bool { From f7414cde2159d02528c651c8b9ee79135a13a206 Mon Sep 17 00:00:00 2001 From: Jesse Date: Tue, 14 Oct 2025 21:26:00 +0100 Subject: [PATCH 61/72] skip gas estimation --- api/simulate.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/simulate.go b/api/simulate.go index a9069827c..6cfbebf70 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -252,13 +252,13 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, if err := ctx.Context().Err(); err != nil { return nil, nil, nil, err } - if err := call.setDefaults(ctx, s.chainConfig, s.evmFn, s.state, header, vm.BlockNumberOrHashWithHash(header.Hash(), false)); err != nil { - return nil, nil, nil, err - } // Let the call run wild unless explicitly specified. if call.Gas == nil { remaining := header.GasLimit - gasUsed - call.Gas = (*hexutil.Uint64)(&remaining) + call.Gas = (*hexutil.Uint64)(&remaining) + } + if err := call.setDefaults(ctx, s.chainConfig, s.evmFn, s.state, header, vm.BlockNumberOrHashWithHash(header.Hash(), false)); err != nil { + return nil, nil, nil, err } if call.ChainID == nil { call.ChainID = (*hexutil.Big)(s.chainConfig.ChainID) From be3428b08a1079e03c71b6453348f75666d6ac19 Mon Sep 17 00:00:00 2001 From: Jesse Date: Wed, 15 Oct 2025 21:52:24 +0100 Subject: [PATCH 62/72] comment out setDefaults --- api/simulate.go | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/api/simulate.go b/api/simulate.go index 6cfbebf70..7eea78a24 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -257,16 +257,42 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, remaining := header.GasLimit - gasUsed call.Gas = (*hexutil.Uint64)(&remaining) } - if err := call.setDefaults(ctx, s.chainConfig, s.evmFn, s.state, header, vm.BlockNumberOrHashWithHash(header.Hash(), false)); err != nil { - return nil, nil, nil, err + if gasUsed+uint64(*call.Gas) > header.GasLimit { + return nil,nil,nil, &blockGasLimitReachedError{fmt.Sprintf("block gas limit reached: %d >= %d", gasUsed, header.GasLimit)} } if call.ChainID == nil { call.ChainID = (*hexutil.Big)(s.chainConfig.ChainID) } - - if gasUsed+uint64(*call.Gas) > header.GasLimit { - return nil,nil,nil, &blockGasLimitReachedError{fmt.Sprintf("block gas limit reached: %d >= %d", gasUsed, header.GasLimit)} + if call.From == nil { + call.From = &(common.Address{}) + } + if call.Value == nil { + call.Value = new(hexutil.Big) } + if call.Nonce == nil { + nonce := s.state.GetNonce(call.from()) + call.Nonce = (*hexutil.Uint64)(&nonce) + } + // if call.ChainID == nil { + // call.ChainID = (*hexutil.Big)(s.chainConfig.ChainID) + // } + // Gas price defaults + if header.BaseFee != nil { + if call.MaxFeePerGas == nil { + call.MaxFeePerGas = (*hexutil.Big)(header.BaseFee) + } + if call.MaxPriorityFeePerGas == nil { + call.MaxPriorityFeePerGas = new(hexutil.Big) + } + } else { + if call.GasPrice == nil { + call.GasPrice = new(hexutil.Big) + } + } + + // if err := call.setDefaults(ctx, s.chainConfig, s.evmFn, s.state, header, vm.BlockNumberOrHashWithHash(header.Hash(), false)); err != nil { + // return nil, nil, nil, err + // } evm := s.evmFn(s.state, &vm.Config{ NoBaseFee: !s.validate, Tracer: tracer, Debug: s.traceTransfers, From c1301f9459de83123855b9390ab2484efd9cc996 Mon Sep 17 00:00:00 2001 From: Jesse Date: Wed, 15 Oct 2025 22:24:53 +0100 Subject: [PATCH 63/72] update simulate.go --- api/simulate.go | 31 ++----------------------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/api/simulate.go b/api/simulate.go index 7eea78a24..073ef2058 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -263,37 +263,10 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, if call.ChainID == nil { call.ChainID = (*hexutil.Big)(s.chainConfig.ChainID) } - if call.From == nil { - call.From = &(common.Address{}) - } - if call.Value == nil { - call.Value = new(hexutil.Big) - } - if call.Nonce == nil { - nonce := s.state.GetNonce(call.from()) - call.Nonce = (*hexutil.Uint64)(&nonce) - } - // if call.ChainID == nil { - // call.ChainID = (*hexutil.Big)(s.chainConfig.ChainID) - // } - // Gas price defaults - if header.BaseFee != nil { - if call.MaxFeePerGas == nil { - call.MaxFeePerGas = (*hexutil.Big)(header.BaseFee) - } - if call.MaxPriorityFeePerGas == nil { - call.MaxPriorityFeePerGas = new(hexutil.Big) - } - } else { - if call.GasPrice == nil { - call.GasPrice = new(hexutil.Big) - } + if err := call.setDefaults(ctx, s.chainConfig, s.evmFn, s.state, header, vm.BlockNumberOrHashWithHash(header.Hash(), false)); err != nil { + return nil, nil, nil, err } - // if err := call.setDefaults(ctx, s.chainConfig, s.evmFn, s.state, header, vm.BlockNumberOrHashWithHash(header.Hash(), false)); err != nil { - // return nil, nil, nil, err - // } - evm := s.evmFn(s.state, &vm.Config{ NoBaseFee: !s.validate, Tracer: tracer, Debug: s.traceTransfers, }, call.from(), call.GasPrice.ToInt()) From 3281105f14cd26c6cfda2528c66d813c899d417a Mon Sep 17 00:00:00 2001 From: Jesse Date: Wed, 15 Oct 2025 22:57:25 +0100 Subject: [PATCH 64/72] cap gas at gasPool --- api/simulate.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/api/simulate.go b/api/simulate.go index 073ef2058..22886b89c 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -257,6 +257,13 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, remaining := header.GasLimit - gasUsed call.Gas = (*hexutil.Uint64)(&remaining) } + + // Cap at gas pool + gasCap := s.gp.Gas() + if gasCap > 0 && gasCap < uint64(*call.Gas) { + call.Gas = (*hexutil.Uint64)(&gasCap) + } + if gasUsed+uint64(*call.Gas) > header.GasLimit { return nil,nil,nil, &blockGasLimitReachedError{fmt.Sprintf("block gas limit reached: %d >= %d", gasUsed, header.GasLimit)} } From e8b98822684498cd2d8c20f6ece8f207e41ceadf Mon Sep 17 00:00:00 2001 From: Jesse Date: Thu, 16 Oct 2025 02:36:38 +0100 Subject: [PATCH 65/72] move tracer out of loop --- api/logtracer.go | 1 - api/simulate.go | 6 ++---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/api/logtracer.go b/api/logtracer.go index b4a1bc2f4..d2abc1fd4 100644 --- a/api/logtracer.go +++ b/api/logtracer.go @@ -75,7 +75,6 @@ func (t *tracer) CaptureEnd(output []byte, gasUsed uint64, time time.Duration, e // reset prepares the tracer for the next transaction. func (t *tracer) reset(txHash ctypes.Hash, txIdx uint) { t.logs = make([]*types.Log, 0) - t.count = 0 t.txHash = txHash t.txIdx = txIdx } diff --git a/api/simulate.go b/api/simulate.go index 22886b89c..c7f707732 100644 --- a/api/simulate.go +++ b/api/simulate.go @@ -20,7 +20,7 @@ import ( rpc "github.com/openrelayxyz/cardinal-rpc" ctypes "github.com/openrelayxyz/cardinal-types" "github.com/openrelayxyz/cardinal-types/hexutil" - log "github.com/inconshreveable/log15" + // log "github.com/inconshreveable/log15" ) const ( @@ -232,6 +232,7 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, callResults = make([]simCallResult, len(block.Calls)) receipts = make([]*types.Receipt, len(block.Calls)) senders = make(map[ctypes.Hash]common.Address) + tracer = newTracer(s.traceTransfers, header.Number.Uint64(), header.Hash(), ctypes.Hash{}, 0) ) getHashFn := func(n uint64) ctypes.Hash { @@ -247,8 +248,6 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, } for i, call := range block.Calls { - tracer := newTracer(s.traceTransfers, header.Number.Uint64(), header.Hash(), ctypes.Hash{}, uint(i)) - if err := ctx.Context().Err(); err != nil { return nil, nil, nil, err } @@ -279,7 +278,6 @@ func (s *simulator) processBlock(ctx *rpc.CallContext, block *simBlock, header, }, call.from(), call.GasPrice.ToInt()) tx := call.ToTransaction(types.DynamicFeeTxType) - log.Error(fmt.Sprintf("TX: nonce=%d gas=%d gasFeeCap=%s gasTipCap=%s chainID=%s to=%s value=%s data=%s hash=%s\n", tx.Nonce(), tx.Gas(), tx.GasFeeCap(), tx.GasTipCap(), tx.ChainId(), tx.To().Hex(), tx.Value(), hexutil.Encode(tx.Data()), tx.Hash().Hex())) txes[i] = tx senders[tx.Hash()] = call.from() From 0653c66ae4b2cb49661aa8dffc37863546037d9b Mon Sep 17 00:00:00 2001 From: Jesse Date: Thu, 16 Oct 2025 19:55:21 +0100 Subject: [PATCH 66/72] simulatev1 integration test --- hardfork-tests/.gitignore | 4 + hardfork-tests/requirements.txt | 10 ++ hardfork-tests/resources/01.json.gz | Bin 0 -> 928 bytes hardfork-tests/resources/config.yaml | 13 +++ hardfork-tests/simv1.py | 154 +++++++++++++++++++++++++++ 5 files changed, 181 insertions(+) create mode 100644 hardfork-tests/.gitignore create mode 100644 hardfork-tests/requirements.txt create mode 100644 hardfork-tests/resources/01.json.gz create mode 100644 hardfork-tests/resources/config.yaml create mode 100644 hardfork-tests/simv1.py diff --git a/hardfork-tests/.gitignore b/hardfork-tests/.gitignore new file mode 100644 index 000000000..64c7eed01 --- /dev/null +++ b/hardfork-tests/.gitignore @@ -0,0 +1,4 @@ +fd5-data/ +.venv +venv +__pycache__ \ No newline at end of file diff --git a/hardfork-tests/requirements.txt b/hardfork-tests/requirements.txt new file mode 100644 index 000000000..3dbbf4ec6 --- /dev/null +++ b/hardfork-tests/requirements.txt @@ -0,0 +1,10 @@ +certifi==2025.10.5 +charset-normalizer==3.4.4 +idna==3.11 +iniconfig==2.1.0 +packaging==25.0 +pluggy==1.6.0 +Pygments==2.19.2 +pytest==8.4.2 +requests==2.32.5 +urllib3==2.5.0 diff --git a/hardfork-tests/resources/01.json.gz b/hardfork-tests/resources/01.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..aa650e0205e07664b22566afcd0bedc4e336309c GIT binary patch literal 928 zcmV;R17G|fiwFpC`S54}128czYIARH0PR*wZyYxazV~O$*(gNnNgs_OZGoaF3gi^z zKv7gY?LOR{*~UfS|6b1Sc4Id+U=&*f1)`foiZ6Zqs1L(0({{7Fli_;6uibDpjBU7v zt6`_pel-u*Z$1o_Cw-;*lkUFd$@JVknOEd%~%oMS5?vT6$21rar>>GHxLB%P#HzkRNxll34`r#@)l!>F_Nu<+=pk|hZQZwGuS$GG&r=UOU2lJ1 zpx1dd>4TN=-6JqsLdBG(ndkixX)ruoeSzf*jo{~mrD4I)6~(|8#kWYgQ1cd4(VCpA z9@(l_K-7mWq||bXE@bb#2W-#568>{o!oP#%QtGkyQH*c@IOALI#n5kC9+jI7wK?KEjRE2Js^}sF!N})!`jXb%OvH{d_ z%svEe0#*O!3Fm-Rni28i{zIW>5vtWb<^qSMILWkdjOCG?{lG7x>iA!0hTlTLvveFaHHIZOZHI*D&W;V4NQ?5y*1p|gLcd+y+ z6wU#BcMJi03$6w8kQY)P*A^tN?+yl&CCSpuQ?@_N;S2n1E-;7QJ`L^sVu=c+LOFP% z?xli<;!7&Ld>zo`lzI6;FqKeT>((ZmS_14EMU1p9y8WrLZ_LO0alUOke$T5<56(Vg zQp+APR1stJ)O+zEY9bWZOVU_FQD^n1B*ybLv=gF%NxcokfXkG6`07tt5>0R{3;+NH COw4fr literal 0 HcmV?d00001 diff --git a/hardfork-tests/resources/config.yaml b/hardfork-tests/resources/config.yaml new file mode 100644 index 000000000..bb5e85985 --- /dev/null +++ b/hardfork-tests/resources/config.yaml @@ -0,0 +1,13 @@ +http.port: 8000 +chainid: 7092415936 +log.level: 2 +datadir: /Users/jesseakoh/Desktop/work/code/openrelay/cardinal-evm/hardfork-tests/fd5-data +reorg.threshold: 128 +cloudwatch: + namespace: cardinal + dimensions: + network: fd5 +#topic.transactions: hoodi-tx +block.wait.ms: 2000 +brokers: + - url: "null://" \ No newline at end of file diff --git a/hardfork-tests/simv1.py b/hardfork-tests/simv1.py new file mode 100644 index 000000000..041c89da8 --- /dev/null +++ b/hardfork-tests/simv1.py @@ -0,0 +1,154 @@ +import subprocess +import requests +import pytest, logging +import argparse +import os, time, json, gzip, shutil + +logging.basicConfig(level=logging.INFO, format="%(levelname)s - %(message)s") + +node = None + +# Payload = [ +# {"jsonrpc":"2.0","method":"eth_simulateV1","params":[{"blockStateCalls":[{"blockOverrides":{"baseFeePerGas":"0x9","gasLimit":"0x1c9c380"},"stateOverrides":{"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045":{"balance":"0x4a817c420"}},"calls":[{"from":"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045","to":"0x014d023e954bAae7F21E56ed8a5d81b12902684D","gas":"0x5208","maxFeePerGas":"0xf","value":"0x1"}]}],"validation":True,"traceTransfers":True}],"id":1} +# ] + +ignored_keys = ['blockHash', 'hash', 'stateRoot', 'timestamp', 'logsBloom'] + +def start_node(bin_path): + global node + bin_path = os.path.abspath(bin_path) + node = subprocess.Popen([ + bin_path, "--debug", os.path.join(os.path.dirname(__file__), "config.yaml") + ]) + time.sleep(3) + +def gather_data(endpoint=None): + logging.info("running method") + url = endpoint if endpoint else "http://localhost:8000" + rpc = { + "jsonrpc": "2.0", + "id": 1, + "method": "eth_simulateV1", + "params": [{ + "blockStateCalls": [{ + "blockOverrides": {"baseFeePerGas": "0x9"}, + "stateOverrides": { + "0xc000000000000000000000000000000000000000": {"balance": "0x4a817c800"} + }, + "calls": [ + { + "from": "0xc000000000000000000000000000000000000000", + "to": "0xc000000000000000000000000000000000000001", + "maxFeePerGas": "0xf", + "value": "0x1", + "gas": "0x5208" + }, + { + "from": "0xc000000000000000000000000000000000000000", + "to": "0xc000000000000000000000000000000000000002", + "maxFeePerGas": "0xf", + "value": "0x1", + "gas": "0x5208" + } + ] + }], + "validation": True, + "traceTransfers": True + }] + } + response = requests.post(url, json=rpc, timeout=3) + response.raise_for_status() + + output_file = os.path.join(os.path.dirname(__file__), "./resources/cardinal_test.json") + with open(output_file, "w") as f: + json.dump(response.json(), f) + + return output_file + +def compare_dicts(control, test, path=""): + control_keys = set(k for k in control.keys() if k not in ignored_keys) + test_keys = set(k for k in test.keys() if k not in ignored_keys) + + if control_keys != test_keys: + missing = control_keys - test_keys + extra = test_keys - control_keys + if missing: + pytest.fail(f"Missing keys in test at {path}: {missing}") + if extra: + pytest.fail(f"Extra keys in test at {path}: {extra}") + + for key in control.keys(): + if key in ignored_keys: + continue + + new_path = f"{path}.{key}" if path else key + + if key not in test: + pytest.fail(f"Missing key '{key}' in test at {new_path}") + + compare_values(control[key], test[key], new_path) + +def compare_lists(control, test, path=""): + if len(control) != len(test): + pytest.fail(f"List length mismatch at {path}: expected {len(control)}, got {len(test)}") + + for i, (control_item, test_item) in enumerate(zip(control, test)): + new_path = f"{path}[{i}]" + compare_values(control_item, test_item, new_path) + +def compare_values(control, test, path=""): + if type(control) != type(test): + pytest.fail(f"Type mismatch at {path}: expected {type(control).__name__}, got {type(test).__name__}") + elif isinstance(control, dict): + compare_dicts(control, test, path) + elif isinstance(control, list): + compare_lists(control, test, path) + elif control != test: + pytest.fail(f"Value mismatch at {path}: expected {control}, got {test}") + + +def compare_results(): + with gzip.open('./resources/01.json.gz', "rb") as f: + with open('./resources/cardinal_control.json', "wb") as f_o: + shutil.copyfileobj(f, f_o) + + with open('./resources/cardinal_test.json', 'r') as cf: + test_data = json.load(cf) + + with open('./resources/cardinal_control.json', 'r') as cf: + control_data = json.load(cf) + + compare_values(control_data['result'], test_data['result'], path="result") + + +def run_test(args): + if args.binary_path: + start_node(args.binary_path) + try: + gather_data(args.endpoint) + compare_results() + logging.info("test passed") + finally: + if args.binary_path and node: + node.terminate() + node.wait() + print("node terminated") + cleanup() + +def cleanup(): + files_to_remove = [ + './resources/cardinal_control.json', + './resources/cardinal_test.json' + ] + + for path in files_to_remove: + if os.path.exists(path): + os.remove(path) + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument("-p", "--binary_path") + parser.add_argument("-e", "--endpoint", default='http://localhost:8000') + args = parser.parse_args() + + run_test(args) From fcd9f6af034bad33f3903888803770c7c7472224 Mon Sep 17 00:00:00 2001 From: Philip Morlier Date: Fri, 17 Oct 2025 10:59:24 -0700 Subject: [PATCH 67/72] Added integration test chain config --- params/config.go | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/params/config.go b/params/config.go index 40e7628d6..36e65f2d7 100644 --- a/params/config.go +++ b/params/config.go @@ -467,8 +467,8 @@ var ( }, } - Fd5ChainConfig = &ChainConfig{ - ChainID: big.NewInt(7092415936), + IntegrationTestChainConfig = &ChainConfig{ + ChainID: big.NewInt(70722), HomesteadBlock: big.NewInt(0), DAOForkBlock: nil, DAOForkSupport: true, @@ -485,7 +485,7 @@ var ( ShanghaiTime: big.NewInt(0), CancunTime: big.NewInt(0), PragueTime: big.NewInt(0), - OsakaTime: big.NewInt(1757611104), + OsakaTime: big.NewInt(0), Ethash: new(EthashConfig), Engine: ETHashEngine, BlobSchedule: []*BlobConfig{ @@ -503,6 +503,27 @@ var ( Max : 9, UpdateFraction: 5007716, }, + // Osaka + &BlobConfig{ + ActivationTime: 0, + Target: 6, + Max: 9, + UpdateFraction: 5007716, + }, + // BP01 + &BlobConfig{ + ActivationTime: 0, + Target: 10, + Max: 15, + UpdateFraction: 8346193, + }, + // BP02 + &BlobConfig{ + ActivationTime: 0, + Target: 14, + Max: 21, + UpdateFraction: 11684671, + }, }, } @@ -537,7 +558,7 @@ var ChainLookup = map[int64]*ChainConfig{ 1337802: KilnChainConfig, 11155111: SepoliaChainConfig, 560048: HoodiChainConfig, - 7092415936: Fd5ChainConfig, + 70722: IntegrationTestChainConfig, } // ChainConfig is the core config which determines the blockchain settings. From d00dab0db4627138387f337773651476c4389a96 Mon Sep 17 00:00:00 2001 From: Jesse Date: Mon, 20 Oct 2025 16:26:31 +0100 Subject: [PATCH 68/72] add aggregation logic --- hardfork-tests/collect.py | 125 ++++++++++++++++++ hardfork-tests/resources/01.json.gz | Bin 928 -> 0 bytes hardfork-tests/resources/control-data.json.gz | Bin 0 -> 1859 bytes hardfork-tests/simv1.py | 51 ++----- 4 files changed, 136 insertions(+), 40 deletions(-) create mode 100644 hardfork-tests/collect.py delete mode 100644 hardfork-tests/resources/01.json.gz create mode 100644 hardfork-tests/resources/control-data.json.gz diff --git a/hardfork-tests/collect.py b/hardfork-tests/collect.py new file mode 100644 index 000000000..cf7506502 --- /dev/null +++ b/hardfork-tests/collect.py @@ -0,0 +1,125 @@ +import requests,json, gzip + +Payloads = [ + { + # Simple transfer + "jsonrpc": "2.0", + "id": 1, + "method": "eth_simulateV1", + "params": [{ + "blockStateCalls": [{ + "blockOverrides": {"baseFeePerGas": "0x9"}, + "stateOverrides": { + "0xc000000000000000000000000000000000000000": {"balance": "0x4a817c800"} + }, + "calls": [ + { + "from": "0xc000000000000000000000000000000000000000", + "to": "0xc000000000000000000000000000000000000001", + "maxFeePerGas": "0xf", + "value": "0x1", + "gas": "0x5208" + }, + { + "from": "0xc000000000000000000000000000000000000000", + "to": "0xc000000000000000000000000000000000000002", + "maxFeePerGas": "0xf", + "value": "0x1", + "gas": "0x5208" + } + ] + }], + "validation": True, + "traceTransfers": True + }], + }, + # complete test payload + { + "jsonrpc": "2.0", + "id": 1, + "method": "eth_simulateV1", + "params": [{ + "blockStateCalls": [{ + "blockOverrides": {"baseFeePerGas": "0x9", "gasLimit": "0x1c9c380"}, + "stateOverrides": { + "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045": {"balance": "0x4a817c420"} + }, + "calls": [{ + "from": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", + "to": "0x014d023e954bAae7F21E56ed8a5d81b12902684D", + "gas": "0x5208", + "maxFeePerGas": "0xf", + "value": "0x1" + }] + }], + "validation": True, + "traceTransfers": True + }] + }, + # precompile ecrecover + { + "jsonrpc": "2.0", + "id": 1, + "method": "eth_simulateV1", + "params": [{ + "blockStateCalls": [{ + "stateOverrides": { + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266": {"balance": "0x56bc75e2d63100000"} + }, + "calls": [{ + "from": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "to": "0x0000000000000000000000000000000000000001", + "data": "0x456e9aea5e197a1f1af7a3e85a3212fa4049a3ba34c2289b4c860fc0b0c64ef3000000000000000000000000000000000000000000000000000000000000001c9242685bf161793cc25603c231bc2f568eb630ea16aa137d2664ac8038825608f90e5c4da9e4c60030b6d02a2ae8c5be86f0088b8c5ccbb82ba1e2e3f0c3ab5c4b", + "gas": "0x10000" + }] + }] + }] + }, + # trace multi calls + { + "jsonrpc": "2.0", + "id": 1, + "method": "eth_simulateV1", + "params": [{ + "blockStateCalls": [{ + "stateOverrides": { + "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266": {"balance": "0x56bc75e2d630e00000"} + }, + "calls": [ + { + "from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "to": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "value": "0xde0b6b3a7640000" + }, + { + "from": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "to": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "value": "0x6f05b59d3b20000" + } + ] + }], + "traceTransfers": True + }] + }, + ] + +def gather_data(): + results = [] + + for i in Payloads: + res= requests.post("http://localhost:8000", json=i) + if res.status_code == 200: + results.append(res.json().get("result")) + else: + print(f"error calling {i["method"]}: {res.text}") + + return results + +if __name__ == "__main__": + data = gather_data() + if data: + with gzip.open("./resources/control-data.json.gz", "wt", compresslevel=5) as fo: + json.dump(data, fo) + + + \ No newline at end of file diff --git a/hardfork-tests/resources/01.json.gz b/hardfork-tests/resources/01.json.gz deleted file mode 100644 index aa650e0205e07664b22566afcd0bedc4e336309c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 928 zcmV;R17G|fiwFpC`S54}128czYIARH0PR*wZyYxazV~O$*(gNnNgs_OZGoaF3gi^z zKv7gY?LOR{*~UfS|6b1Sc4Id+U=&*f1)`foiZ6Zqs1L(0({{7Fli_;6uibDpjBU7v zt6`_pel-u*Z$1o_Cw-;*lkUFd$@JVknOEd%~%oMS5?vT6$21rar>>GHxLB%P#HzkRNxll34`r#@)l!>F_Nu<+=pk|hZQZwGuS$GG&r=UOU2lJ1 zpx1dd>4TN=-6JqsLdBG(ndkixX)ruoeSzf*jo{~mrD4I)6~(|8#kWYgQ1cd4(VCpA z9@(l_K-7mWq||bXE@bb#2W-#568>{o!oP#%QtGkyQH*c@IOALI#n5kC9+jI7wK?KEjRE2Js^}sF!N})!`jXb%OvH{d_ z%svEe0#*O!3Fm-Rni28i{zIW>5vtWb<^qSMILWkdjOCG?{lG7x>iA!0hTlTLvveFaHHIZOZHI*D&W;V4NQ?5y*1p|gLcd+y+ z6wU#BcMJi03$6w8kQY)P*A^tN?+yl&CCSpuQ?@_N;S2n1E-;7QJ`L^sVu=c+LOFP% z?xli<;!7&Ld>zo`lzI6;FqKeT>((ZmS_14EMU1p9y8WrLZ_LO0alUOke$T5<56(Vg zQp+APR1stJ)O+zEY9bWZOVU_FQD^n1B*ybLv=gF%NxcokfXkG6`07tt5>0R{3;+NH COw4fr diff --git a/hardfork-tests/resources/control-data.json.gz b/hardfork-tests/resources/control-data.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..a0557efe457f93fa06c687bffc5dbf53a387faea GIT binary patch literal 1859 zcmV-J2fX+niwFoAQ}$>8|6^}%baHQOEo5PIVJ>QOZ*BnWS<7x*$qoG#L+e=->&+~U z!6X5KAP6uk!wVM48VjR`qi$s!1oQ7Xw;ySZtd=~mY{yO)AV7T|EEemK=dkXpSN}d6 zvhp9?|K>M;l-1cEUYvz@eD>XovvF}faOKC9+xBW$uBu#IEKlLZ8(-hvTzxNV+1_7n zUtIsRyDMflzSx`}X!L)xU8Gz(FU zeU>8GW{GJepNm0weq`ve8~4VuSfR7mFk}A>*Hh0wF=)3(`tx6JFNfdk0$oDc1o1|$ zR-)_k>#IM>>do$ERPmU2XecpQh-wm3>@=&$oKj+qS-3LQF+&?A%@I9EW9ubZxM%p! zSLW~TKx5;~VYca7Ifv2*UXH`lpkC8Z!8|THS{Qv;sMz6Ttt2W^$ZaO;*kP6(obJzZ zJe8fT)e>?`Atj1-9I*#yJ|$x5V-ULu{`s8edV8_{*PRsb zcM6=o+}7vq_0-ju7;kT5$-f0Tru)nJ<@tIy-z-ksXMf{|WZGPObBIV1QjMBsbf*bS zCz6me*E|X}sE#)kxgk4Ax&ohU8g2C78%Vz=6X?*2lfyCwc)js_vH>Zw@ zRDu^7p~Xlf7By1xQN$gkky75i_U@3ZPtEhxSPWiWU#UNAZgcyAGWAw&{A&FY<>312 zofK-D z+xnw^{&u}Oc#7H;dgpGXk;@!Kt*d#Dt&dqyaVcq;)it!tu^KAj=vwDfEHm`E$+PUS#Jd+Me)Kjlr7S$0$t4$W|A>4BhHjzD2tx}v` zR8h|t2XFTweznZ=%lZ0^-N-L;@zABi%%wWQK#hhfN`9TQr(C>p)G&MXGIIBT&ETpa z0x*7!5$N&+7R_->Y5(6HhrtAJv*c-(iA{Gm_K>`!OvMrq&NbxnxRpQ|z)a314-s!O zl4ElW7_d%*B8OOewlA*VSli6A)afZ$D#4U(0=+(G6P{g_>O`5yjsii#KCO|J)IHBY z#>e&BLmfwkPBBF?w8_wHW%Q+D6h>Z-SY43Pxj`mbi&c<{yzq_ynYp&8%BkLox+ znSLW>1rxld-}ZhS^jq`hGfHf_XjZOLgY>~czzsi~OJKE<1OHDduRW3e zKIL^R2OvEgpi`WDi9RTY^90|>NSZX-gmCF;rn}1RJwC0ugwM&R$Ie6cejNC;CO7Es zMaCh^1pBK~G{=bl9oblo6iI`%0>L4>(gL=csrP7>_6p;~r>C(WCq6wkJ|mx^=MFrD z(B`4fi?O4jv#!v>Ih2;sOATzu7&Z4wp&I&XT8=nNhCh9L%X});dgnrO^eRbtU`3!x zLW@<1N-T=bVhI{5Xs3d%%A5h+D)1|bKf0LhjI!-cX;%SH!i2xw80@;o|2hp|ISm#_Y9&3HvN(;=5 zkdJbV5 z)B@IU9I+36JAHP~*y1!V*@%ldi009600{}BK;(V?t008F3l63$8 literal 0 HcmV?d00001 diff --git a/hardfork-tests/simv1.py b/hardfork-tests/simv1.py index 041c89da8..56decfe5c 100644 --- a/hardfork-tests/simv1.py +++ b/hardfork-tests/simv1.py @@ -3,15 +3,12 @@ import pytest, logging import argparse import os, time, json, gzip, shutil +from collect import Payloads logging.basicConfig(level=logging.INFO, format="%(levelname)s - %(message)s") node = None -# Payload = [ -# {"jsonrpc":"2.0","method":"eth_simulateV1","params":[{"blockStateCalls":[{"blockOverrides":{"baseFeePerGas":"0x9","gasLimit":"0x1c9c380"},"stateOverrides":{"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045":{"balance":"0x4a817c420"}},"calls":[{"from":"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045","to":"0x014d023e954bAae7F21E56ed8a5d81b12902684D","gas":"0x5208","maxFeePerGas":"0xf","value":"0x1"}]}],"validation":True,"traceTransfers":True}],"id":1} -# ] - ignored_keys = ['blockHash', 'hash', 'stateRoot', 'timestamp', 'logsBloom'] def start_node(bin_path): @@ -25,43 +22,17 @@ def start_node(bin_path): def gather_data(endpoint=None): logging.info("running method") url = endpoint if endpoint else "http://localhost:8000" - rpc = { - "jsonrpc": "2.0", - "id": 1, - "method": "eth_simulateV1", - "params": [{ - "blockStateCalls": [{ - "blockOverrides": {"baseFeePerGas": "0x9"}, - "stateOverrides": { - "0xc000000000000000000000000000000000000000": {"balance": "0x4a817c800"} - }, - "calls": [ - { - "from": "0xc000000000000000000000000000000000000000", - "to": "0xc000000000000000000000000000000000000001", - "maxFeePerGas": "0xf", - "value": "0x1", - "gas": "0x5208" - }, - { - "from": "0xc000000000000000000000000000000000000000", - "to": "0xc000000000000000000000000000000000000002", - "maxFeePerGas": "0xf", - "value": "0x1", - "gas": "0x5208" - } - ] - }], - "validation": True, - "traceTransfers": True - }] - } - response = requests.post(url, json=rpc, timeout=3) - response.raise_for_status() + results = [] + for i,j in enumerate(Payloads): + response = requests.post(url, json=j) + if response.status_code == 200: + results.append(response.json().get("result")) + else: + raise Exception(f"error calling method {i} :{response.text}") output_file = os.path.join(os.path.dirname(__file__), "./resources/cardinal_test.json") with open(output_file, "w") as f: - json.dump(response.json(), f) + json.dump(results, f) return output_file @@ -108,7 +79,7 @@ def compare_values(control, test, path=""): def compare_results(): - with gzip.open('./resources/01.json.gz', "rb") as f: + with gzip.open('./resources/control-data.json.gz', "rb") as f: with open('./resources/cardinal_control.json', "wb") as f_o: shutil.copyfileobj(f, f_o) @@ -118,7 +89,7 @@ def compare_results(): with open('./resources/cardinal_control.json', 'r') as cf: control_data = json.load(cf) - compare_values(control_data['result'], test_data['result'], path="result") + compare_values(control_data, test_data, path="result") def run_test(args): From 240656b11e4b0edd5487c7e740351585953a8ff2 Mon Sep 17 00:00:00 2001 From: Philip Morlier Date: Mon, 20 Oct 2025 09:49:31 -0700 Subject: [PATCH 69/72] Added genesis and config for custom genesis data extraction --- cmd/cardinal-evm-rpc/genesis.json | 934 +++++++++++++++++++++ cmd/cardinal-evm-rpc/null-test-config.yaml | 13 + 2 files changed, 947 insertions(+) create mode 100644 cmd/cardinal-evm-rpc/genesis.json create mode 100644 cmd/cardinal-evm-rpc/null-test-config.yaml diff --git a/cmd/cardinal-evm-rpc/genesis.json b/cmd/cardinal-evm-rpc/genesis.json new file mode 100644 index 000000000..8857467cc --- /dev/null +++ b/cmd/cardinal-evm-rpc/genesis.json @@ -0,0 +1,934 @@ +{ + "hash": "0x482478a8edda693bcad4f504b8654165ec52382f8b04f5f66fc477452e3a4c25", + "config": { + "chainId": 70722, + "homesteadBlock": 0, + "eip150Block": 0, + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "mergeNetsplitBlock": 0, + "terminalTotalDifficulty": 0, + "terminalTotalDifficultyPassed": true, + "shanghaiTime": 0, + "cancunTime": 0, + "depositContractAddress": "0x00000000219ab540356cBB839Cbe05303d7705Fa", + "pragueTime": 0, + "osakaTime": 0, + "bpo1Time": 0, + "bpo2Time": 0, + "bpo3Time": 0, + "bpo4Time": 0, + "bpo5Time": 0 + }, + "alloc": { + "0x0000000000000000000000000000000000000000": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000001": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000002": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000003": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000004": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000005": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000006": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000007": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000008": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000009": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000000a": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000000b": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000000c": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000000d": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000000e": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000000f": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000010": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000011": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000012": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000013": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000014": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000015": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000016": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000017": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000018": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000019": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000001a": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000001b": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000001c": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000001d": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000001e": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000001f": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000020": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000021": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000022": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000023": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000024": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000025": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000026": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000027": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000028": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000029": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000002a": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000002b": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000002c": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000002d": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000002e": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000002f": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000030": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000031": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000032": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000033": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000034": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000035": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000036": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000037": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000038": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000039": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000003a": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000003b": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000003c": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000003d": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000003e": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000003f": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000040": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000041": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000042": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000043": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000044": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000045": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000046": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000047": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000048": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000049": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000004a": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000004b": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000004c": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000004d": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000004e": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000004f": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000050": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000051": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000052": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000053": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000054": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000055": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000056": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000057": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000058": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000059": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000005a": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000005b": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000005c": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000005d": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000005e": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000005f": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000060": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000061": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000062": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000063": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000064": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000065": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000066": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000067": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000068": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000069": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000006a": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000006b": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000006c": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000006d": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000006e": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000006f": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000070": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000071": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000072": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000073": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000074": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000075": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000076": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000077": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000078": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000079": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000007a": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000007b": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000007c": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000007d": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000007e": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000007f": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000080": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000081": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000082": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000083": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000084": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000085": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000086": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000087": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000088": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000089": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000008a": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000008b": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000008c": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000008d": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000008e": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000008f": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000090": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000091": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000092": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000093": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000094": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000095": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000096": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000097": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000098": { + "balance": "0x1" + }, + "0x0000000000000000000000000000000000000099": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000009a": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000009b": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000009c": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000009d": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000009e": { + "balance": "0x1" + }, + "0x000000000000000000000000000000000000009f": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000a0": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000a1": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000a2": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000a3": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000a4": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000a5": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000a6": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000a7": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000a8": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000a9": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000aa": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000ab": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000ac": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000ad": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000ae": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000af": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000b0": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000b1": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000b2": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000b3": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000b4": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000b5": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000b6": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000b7": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000b8": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000b9": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000ba": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000bb": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000bc": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000bd": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000be": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000bf": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000c0": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000c1": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000c2": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000c3": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000c4": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000c5": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000c6": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000c7": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000c8": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000c9": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000ca": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000cb": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000cc": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000cd": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000ce": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000cf": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000d0": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000d1": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000d2": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000d3": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000d4": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000d5": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000d6": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000d7": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000d8": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000d9": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000da": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000db": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000dc": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000dd": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000de": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000df": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000e0": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000e1": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000e2": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000e3": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000e4": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000e5": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000e6": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000e7": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000e8": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000e9": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000ea": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000eb": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000ec": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000ed": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000ee": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000ef": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000f0": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000f1": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000f2": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000f3": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000f4": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000f5": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000f6": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000f7": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000f8": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000f9": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000fa": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000fb": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000fc": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000fd": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000fe": { + "balance": "0x1" + }, + "0x00000000000000000000000000000000000000ff": { + "balance": "0x1" + }, + "0x00000000219ab540356cBB839Cbe05303d7705Fa": { + "balance": "0x0", + "code": "0x60806040526004361061003f5760003560e01c806301ffc9a71461004457806322895118146100a4578063621fd130146101ba578063c5f2892f14610244575b600080fd5b34801561005057600080fd5b506100906004803603602081101561006757600080fd5b50357fffffffff000000000000000000000000000000000000000000000000000000001661026b565b604080519115158252519081900360200190f35b6101b8600480360360808110156100ba57600080fd5b8101906020810181356401000000008111156100d557600080fd5b8201836020820111156100e757600080fd5b8035906020019184600183028401116401000000008311171561010957600080fd5b91939092909160208101903564010000000081111561012757600080fd5b82018360208201111561013957600080fd5b8035906020019184600183028401116401000000008311171561015b57600080fd5b91939092909160208101903564010000000081111561017957600080fd5b82018360208201111561018b57600080fd5b803590602001918460018302840111640100000000831117156101ad57600080fd5b919350915035610304565b005b3480156101c657600080fd5b506101cf6110b5565b6040805160208082528351818301528351919283929083019185019080838360005b838110156102095781810151838201526020016101f1565b50505050905090810190601f1680156102365780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561025057600080fd5b506102596110c7565b60408051918252519081900360200190f35b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f01ffc9a70000000000000000000000000000000000000000000000000000000014806102fe57507fffffffff0000000000000000000000000000000000000000000000000000000082167f8564090700000000000000000000000000000000000000000000000000000000145b92915050565b6030861461035d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118056026913960400191505060405180910390fd5b602084146103b6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603681526020018061179c6036913960400191505060405180910390fd5b6060821461040f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806118786029913960400191505060405180910390fd5b670de0b6b3a7640000341015610470576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118526026913960400191505060405180910390fd5b633b9aca003406156104cd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260338152602001806117d26033913960400191505060405180910390fd5b633b9aca00340467ffffffffffffffff811115610535576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602781526020018061182b6027913960400191505060405180910390fd5b6060610540826114ba565b90507f649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c589898989858a8a6105756020546114ba565b6040805160a0808252810189905290819060208201908201606083016080840160c085018e8e80828437600083820152601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690910187810386528c815260200190508c8c808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690920188810386528c5181528c51602091820193918e019250908190849084905b83811015610648578181015183820152602001610630565b50505050905090810190601f1680156106755780820380516001836020036101000a031916815260200191505b5086810383528881526020018989808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169092018881038452895181528951602091820193918b019250908190849084905b838110156106ef5781810151838201526020016106d7565b50505050905090810190601f16801561071c5780820380516001836020036101000a031916815260200191505b509d505050505050505050505050505060405180910390a1600060028a8a600060801b604051602001808484808284377fffffffffffffffffffffffffffffffff0000000000000000000000000000000090941691909301908152604080517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0818403018152601090920190819052815191955093508392506020850191508083835b602083106107fc57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016107bf565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610859573d6000803e3d6000fd5b5050506040513d602081101561086e57600080fd5b5051905060006002806108846040848a8c6116fe565b6040516020018083838082843780830192505050925050506040516020818303038152906040526040518082805190602001908083835b602083106108f857805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016108bb565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610955573d6000803e3d6000fd5b5050506040513d602081101561096a57600080fd5b5051600261097b896040818d6116fe565b60405160009060200180848480828437919091019283525050604080518083038152602092830191829052805190945090925082918401908083835b602083106109f457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016109b7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610a51573d6000803e3d6000fd5b5050506040513d6020811015610a6657600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610ada57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610a9d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610b37573d6000803e3d6000fd5b5050506040513d6020811015610b4c57600080fd5b50516040805160208101858152929350600092600292839287928f928f92018383808284378083019250505093505050506040516020818303038152906040526040518082805190602001908083835b60208310610bd957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610b9c565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610c36573d6000803e3d6000fd5b5050506040513d6020811015610c4b57600080fd5b50516040518651600291889160009188916020918201918291908601908083835b60208310610ca957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610c6c565b6001836020036101000a0380198251168184511680821785525050505050509050018367ffffffffffffffff191667ffffffffffffffff1916815260180182815260200193505050506040516020818303038152906040526040518082805190602001908083835b60208310610d4e57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610d11565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610dab573d6000803e3d6000fd5b5050506040513d6020811015610dc057600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610e3457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610df7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610e91573d6000803e3d6000fd5b5050506040513d6020811015610ea657600080fd5b50519050858114610f02576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260548152602001806117486054913960600191505060405180910390fd5b60205463ffffffff11610f60576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806117276021913960400191505060405180910390fd5b602080546001019081905560005b60208110156110a9578160011660011415610fa0578260008260208110610f9157fe5b0155506110ac95505050505050565b600260008260208110610faf57fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061102557805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610fe8565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015611082573d6000803e3d6000fd5b5050506040513d602081101561109757600080fd5b50519250600282049150600101610f6e565b50fe5b50505050505050565b60606110c26020546114ba565b905090565b6020546000908190815b60208110156112f05781600116600114156111e6576002600082602081106110f557fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061116b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161112e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156111c8573d6000803e3d6000fd5b5050506040513d60208110156111dd57600080fd5b505192506112e2565b600283602183602081106111f657fe5b015460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061126b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161122e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156112c8573d6000803e3d6000fd5b5050506040513d60208110156112dd57600080fd5b505192505b6002820491506001016110d1565b506002826112ff6020546114ba565b600060401b6040516020018084815260200183805190602001908083835b6020831061135a57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161131d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790527fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000095909516920191825250604080518083037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8018152601890920190819052815191955093508392850191508083835b6020831061143f57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101611402565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa15801561149c573d6000803e3d6000fd5b5050506040513d60208110156114b157600080fd5b50519250505090565b60408051600880825281830190925260609160208201818036833701905050905060c082901b8060071a60f81b826000815181106114f457fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060061a60f81b8260018151811061153757fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060051a60f81b8260028151811061157a57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060041a60f81b826003815181106115bd57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060031a60f81b8260048151811061160057fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060021a60f81b8260058151811061164357fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060011a60f81b8260068151811061168657fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060001a60f81b826007815181106116c957fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a90535050919050565b6000808585111561170d578182fd5b83861115611719578182fd5b505082019391909203915056fe4465706f736974436f6e74726163743a206d65726b6c6520747265652066756c6c4465706f736974436f6e74726163743a207265636f6e7374727563746564204465706f7369744461746120646f6573206e6f74206d6174636820737570706c696564206465706f7369745f646174615f726f6f744465706f736974436f6e74726163743a20696e76616c6964207769746864726177616c5f63726564656e7469616c73206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c7565206e6f74206d756c7469706c65206f6620677765694465706f736974436f6e74726163743a20696e76616c6964207075626b6579206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f20686967684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f206c6f774465706f736974436f6e74726163743a20696e76616c6964207369676e6174757265206c656e677468a2646970667358221220dceca8706b29e917dacf25fceef95acac8d90d765ac926663ce4096195952b6164736f6c634300060b0033", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000022": "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b", + "0x0000000000000000000000000000000000000000000000000000000000000023": "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71", + "0x0000000000000000000000000000000000000000000000000000000000000024": "0xc78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c", + "0x0000000000000000000000000000000000000000000000000000000000000025": "0x536d98837f2dd165a55d5eeae91485954472d56f246df256bf3cae19352a123c", + "0x0000000000000000000000000000000000000000000000000000000000000026": "0x9efde052aa15429fae05bad4d0b1d7c64da64d03d7a1854a588c2cb8430c0d30", + "0x0000000000000000000000000000000000000000000000000000000000000027": "0xd88ddfeed400a8755596b21942c1497e114c302e6118290f91e6772976041fa1", + "0x0000000000000000000000000000000000000000000000000000000000000028": "0x87eb0ddba57e35f6d286673802a4af5975e22506c7cf4c64bb6be5ee11527f2c", + "0x0000000000000000000000000000000000000000000000000000000000000029": "0x26846476fd5fc54a5d43385167c95144f2643f533cc85bb9d16b782f8d7db193", + "0x000000000000000000000000000000000000000000000000000000000000002a": "0x506d86582d252405b840018792cad2bf1259f1ef5aa5f887e13cb2f0094f51e1", + "0x000000000000000000000000000000000000000000000000000000000000002b": "0xffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b", + "0x000000000000000000000000000000000000000000000000000000000000002c": "0x6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220", + "0x000000000000000000000000000000000000000000000000000000000000002d": "0xb7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f", + "0x000000000000000000000000000000000000000000000000000000000000002e": "0xdf6af5f5bbdb6be9ef8aa618e4bf8073960867171e29676f8b284dea6a08a85e", + "0x000000000000000000000000000000000000000000000000000000000000002f": "0xb58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784", + "0x0000000000000000000000000000000000000000000000000000000000000030": "0xd49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb", + "0x0000000000000000000000000000000000000000000000000000000000000031": "0x8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb", + "0x0000000000000000000000000000000000000000000000000000000000000032": "0x8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab", + "0x0000000000000000000000000000000000000000000000000000000000000033": "0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4", + "0x0000000000000000000000000000000000000000000000000000000000000034": "0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f", + "0x0000000000000000000000000000000000000000000000000000000000000035": "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa", + "0x0000000000000000000000000000000000000000000000000000000000000036": "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c", + "0x0000000000000000000000000000000000000000000000000000000000000037": "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167", + "0x0000000000000000000000000000000000000000000000000000000000000038": "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7", + "0x0000000000000000000000000000000000000000000000000000000000000039": "0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0", + "0x000000000000000000000000000000000000000000000000000000000000003a": "0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544", + "0x000000000000000000000000000000000000000000000000000000000000003b": "0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765", + "0x000000000000000000000000000000000000000000000000000000000000003c": "0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4", + "0x000000000000000000000000000000000000000000000000000000000000003d": "0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1", + "0x000000000000000000000000000000000000000000000000000000000000003e": "0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636", + "0x000000000000000000000000000000000000000000000000000000000000003f": "0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c", + "0x0000000000000000000000000000000000000000000000000000000000000040": "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7" + } + }, + "0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02": { + "balance": "0x0", + "nonce": "0x1", + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500" + }, + "0x0000F90827F1C53a10cb7A02335B175320002935": { + "balance": "0x0", + "nonce": "0x1", + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604657602036036042575f35600143038111604257611fff81430311604257611fff9006545f5260205ff35b5f5ffd5b5f35611fff60014303065500" + }, + "0x00000961Ef480Eb55e80D19ad83579A64c007002": { + "balance": "0x0", + "nonce": "0x1", + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe1460cb5760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff146101f457600182026001905f5b5f82111560685781019083028483029004916001019190604d565b909390049250505036603814608857366101f457346101f4575f5260205ff35b34106101f457600154600101600155600354806003026004013381556001015f35815560010160203590553360601b5f5260385f601437604c5fa0600101600355005b6003546002548082038060101160df575060105b5f5b8181146101835782810160030260040181604c02815460601b8152601401816001015481526020019060020154807fffffffffffffffffffffffffffffffff00000000000000000000000000000000168252906010019060401c908160381c81600701538160301c81600601538160281c81600501538160201c81600401538160181c81600301538160101c81600201538160081c81600101535360010160e1565b910180921461019557906002556101a0565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff14156101cd57505f5b6001546002828201116101e25750505f6101e8565b01600290035b5f555f600155604c025ff35b5f5ffd", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + } + }, + "0x0000BBdDc7CE488642fb579F8B00f3a590007251": { + "balance": "0x0", + "nonce": "0x1", + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe1460d35760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461019a57600182026001905f5b5f82111560685781019083028483029004916001019190604d565b9093900492505050366060146088573661019a573461019a575f5260205ff35b341061019a57600154600101600155600354806004026004013381556001015f358155600101602035815560010160403590553360601b5f5260605f60143760745fa0600101600355005b6003546002548082038060021160e7575060025b5f5b8181146101295782810160040260040181607402815460601b815260140181600101548152602001816002015481526020019060030154905260010160e9565b910180921461013b5790600255610146565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561017357505f5b6001546001828201116101885750505f61018e565b01600190035b5f555f6001556074025ff35b5f5ffd", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + } + }, + "0x454b0EA7d8aD3C56D0CF2e44Ed97b2Feab4D7AF2": { + "balance": "0x33b2e3c9fd0803ce8000000" + }, + "0xd3248BA3E5492D767F8e427Cb9C7B9D5C3972D7B": { + "balance": "0x33b2e3c9fd0803ce8000000" + }, + "0xAD01b55d7c3448B8899862eb335FBb17075d8DE2": { + "balance": "0x33b2e3c9fd0803ce8000000" + }, + "0x7e454a14B8e7528465eeF86f0DC1da4f235d9D79": { + "balance": "0x33b2e3c9fd0803ce8000000" + }, + "0x7a40026A3b9A41754a95EeC8c92C6B99886f440C": { + "balance": "0x33b2e3c9fd0803ce8000000" + }, + "0x8c4D8CDD1f474510Dd70D66F2785a3a38a29AC1A": { + "balance": "0x33b2e3c9fd0803ce8000000" + }, + "0xfC7360b3b28cf4204268A8354dbEc60720d155D2": { + "balance": "0x33b2e3c9fd0803ce8000000" + }, + "0x2F7626bBDb8c0f9071bC98046Ef6fDed2167F97F": { + "balance": "0x33b2e3c9fd0803ce8000000" + }, + "0x752CE31Dec0dde7D1563CdF6438d892De2D4FBee": { + "balance": "0x33b2e3c9fd0803ce8000000" + }, + "0x455f42d91096c4Aa708D7Cbcb2DC499dE89C402c": { + "balance": "0x33b2e3c9fd0803ce8000000" + }, + "0x85154341488732D57a97F54AB9706Bc4B71B8636": { + "balance": "0x33b2e3c9fd0803ce8000000" + }, + "0x6a9CcA73d4Ff3a249fa778C7651f4Df8B9fFa0Df": { + "balance": "0x33b2e3c9fd0803ce8000000" + }, + "0xee2d0567AAe8080CA269b7908F4aF8BBb59A6804": { + "balance": "0x33b2e3c9fd0803ce8000000" + }, + "0xDd8D4027078a471816e4Ef7F69aFc0A5d2947dDc": { + "balance": "0x33b2e3c9fd0803ce8000000" + }, + "0x20466E9A67f299F6056bE52A50ea324FA6Bd05D5": { + "balance": "0x33b2e3c9fd0803ce8000000" + }, + "0x03F24BB0C9cfb30217Ff992A36ae9230F2A1697f": { + "balance": "0x33b2e3c9fd0803ce8000000" + }, + "0x032d8372C519c3927b87BDe4479E846a81EF2d10": { + "balance": "0x33b2e3c9fd0803ce8000000" + }, + "0xF863DF14954df73804b3150F3754a8F98CBB1D0d": { + "balance": "0x33b2e3c9fd0803ce8000000" + }, + "0xbe918A6aef1920F3706E23d153146aA6C5982620": { + "balance": "0x33b2e3c9fd0803ce8000000" + }, + "0xA0c7edA3CE474BC670A11EA9537cBEfd36331123": { + "balance": "0x33b2e3c9fd0803ce8000000" + }, + "0xF03b43BeB861044492Eb43E247bEE2AC6C80c651": { + "balance": "0x33b2e3c9fd0803ce8000000" + } + }, + "coinbase": "0x0000000000000000000000000000000000000000", + "difficulty": "0x0", + "extraData": "", + "gasLimit": "0x2aea540", + "nonce": "0x4d2", + "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "timestamp": "1757512500" +} + diff --git a/cmd/cardinal-evm-rpc/null-test-config.yaml b/cmd/cardinal-evm-rpc/null-test-config.yaml new file mode 100644 index 000000000..e541f14fc --- /dev/null +++ b/cmd/cardinal-evm-rpc/null-test-config.yaml @@ -0,0 +1,13 @@ +http.port: 8000 +chainid: 70722 +log.level: 2 +datadir: ./data +reorg.threshold: 128 +cloudwatch: + namespace: cardinal + dimensions: + network: IntegrationTest +#topic.transactions: hoodi-tx +block.wait.ms: 2000 +brokers: + - url: "null://" From 7c1f4930578199d6fefa81fb60b37f602e975250 Mon Sep 17 00:00:00 2001 From: Jesse Date: Tue, 21 Oct 2025 12:21:40 +0100 Subject: [PATCH 70/72] store genesis block header during init-genesis --- cmd/cardinal-evm-rpc/genesisinit.go | 34 ++++++++++++++---- hardfork-tests/cardinal | Bin 0 -> 36959538 bytes hardfork-tests/resources/control-data.json.gz | Bin 1859 -> 1840 bytes .../resources}/genesis.json | 2 +- .../resources}/null-test-config.yaml | 0 hardfork-tests/simv1.py | 7 +++- 6 files changed, 35 insertions(+), 8 deletions(-) create mode 100755 hardfork-tests/cardinal rename {cmd/cardinal-evm-rpc => hardfork-tests/resources}/genesis.json (99%) rename {cmd/cardinal-evm-rpc => hardfork-tests/resources}/null-test-config.yaml (100%) diff --git a/cmd/cardinal-evm-rpc/genesisinit.go b/cmd/cardinal-evm-rpc/genesisinit.go index 0a2f8a201..70fdb325b 100644 --- a/cmd/cardinal-evm-rpc/genesisinit.go +++ b/cmd/cardinal-evm-rpc/genesisinit.go @@ -9,7 +9,8 @@ import ( "errors" "github.com/openrelayxyz/cardinal-storage/resolver" - "github.com/openrelayxyz/cardinal-types" + ctypes "github.com/openrelayxyz/cardinal-types" + "github.com/openrelayxyz/cardinal-evm/types" "github.com/openrelayxyz/cardinal-types/hexutil" "github.com/openrelayxyz/cardinal-evm/common" "github.com/openrelayxyz/cardinal-evm/crypto" @@ -21,11 +22,17 @@ import ( type genesisBlock struct { // init.SetBlockData(r.Hash, r.ParentHash, r.Number, r.Weight.ToInt()) Config params.ChainConfig `json:"config"` - Hash types.Hash `json:"hash"` - ParentHash types.Hash `json:"parentHash"` + Hash ctypes.Hash `json:"hash"` + ParentHash ctypes.Hash `json:"parentHash"` Number hexutil.Uint64 `json:"number"` Weight hexutil.Uint64 `json:"difficulty"` Alloc GenesisAlloc `json:"alloc"` + GasLimit hexutil.Uint64 `json:"gasLimit"` + Coinbase common.Address `json:"coinbase"` + Timestamp uint64 `json:"timestamp"` + ExtraData hexutil.Bytes `json:"extraData"` + Nonce hexutil.Uint64 `json:"nonce"` + MixHash ctypes.Hash `json:"mixhash"` } type GenesisAlloc map[string]GenesisAccount @@ -33,14 +40,15 @@ type GenesisAlloc map[string]GenesisAccount // GenesisAccount is an account in the state of the genesis block. type GenesisAccount struct { Code hexutil.Bytes `json:"code,omitempty"` - Storage map[types.Hash]types.Hash `json:"storage,omitempty"` + Storage map[ctypes.Hash]ctypes.Hash `json:"storage,omitempty"` Balance *hexutil.Big `json:"balance"` Nonce hexutil.Uint64 `json:"nonce,omitempty"` } func genesisInit(dbpath, genesispath string, archival bool) error { - gfile, err := os.Open(genesispath) + gfile, err := os.Open(genesispath) + if err != nil {return err} decoder := json.NewDecoder(gfile) var gb genesisBlock if err := decoder.Decode(&gb); err != nil { @@ -49,11 +57,25 @@ func genesisInit(dbpath, genesispath string, archival bool) error { gfile.Close() init, err := resolver.ResolveInitializer(dbpath, archival) if err != nil { return err } - if gb.Hash == (types.Hash{}) { + if gb.Hash == (ctypes.Hash{}) { return errors.New("hash must be set in genesis file") } var emptyAccount *state.Account init.SetBlockData(gb.Hash, gb.ParentHash, uint64(gb.Number), new(big.Int).SetInt64(int64(gb.Weight))) + header:= &types.Header{ + Number: big.NewInt(int64(gb.Number)), + ParentHash: ctypes.Hash(gb.ParentHash), + Difficulty: big.NewInt(int64(gb.Weight)), + GasLimit: uint64(gb.GasLimit), + Time: uint64(gb.Timestamp), + Extra: gb.ExtraData, + MixDigest: ctypes.Hash(gb.MixHash), + Nonce: types.EncodeNonce(uint64(gb.Nonce)), + } + rawHeader,_ := rlp.EncodeToBytes(header) + headerKey := fmt.Sprintf("c/%x/b/%x/h", gb.Config.ChainID, gb.Hash.Bytes()) + init.AddData([]byte(headerKey), rawHeader) + for addrString, alloc := range gb.Alloc { addr := common.HexToAddress(addrString) if len(alloc.Code) != 0 { diff --git a/hardfork-tests/cardinal b/hardfork-tests/cardinal new file mode 100755 index 0000000000000000000000000000000000000000..76f6233e057be3ebbf923d7ca61f5615b58dabdf GIT binary patch literal 36959538 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%0AFn5VJc9(|j^#oBozt(rCX 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%0D&TrX&1D2zX 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