diff --git a/core/state/statedb.go b/core/state/statedb.go index 3a2d9c9ac2..17af38ddf2 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -556,6 +556,12 @@ func (s *StateDB) GetTransientState(addr common.Address, key common.Hash) common return s.transientStorage.Get(addr, key) } +// ClearTransientStorage resets all transient storage. +// todo(jvn): Impl it properly +func (s *StateDB) ClearTransientStorage() { + s.transientStorage = newTransientStorage() +} + // // Setting, updating & deleting state object methods. // diff --git a/core/state/statedb_hooked.go b/core/state/statedb_hooked.go index 48794a3f41..dcb4060841 100644 --- a/core/state/statedb_hooked.go +++ b/core/state/statedb_hooked.go @@ -110,6 +110,10 @@ func (s *hookedStateDB) SetTransientState(addr common.Address, key, value common s.inner.SetTransientState(addr, key, value) } +func (s *hookedStateDB) ClearTransientStorage() { + s.inner.ClearTransientStorage() +} + func (s *hookedStateDB) HasSelfDestructed(addr common.Address) bool { return s.inner.HasSelfDestructed(addr) } diff --git a/core/state_processor.go b/core/state_processor.go index 6eea74bdd8..f8ce1c9a6f 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -219,6 +219,29 @@ func MakeReceipt(evm *vm.EVM, result *ExecutionResult, statedb *state.StateDB, b receipt.BlockHash = blockHash receipt.BlockNumber = blockNumber receipt.TransactionIndex = uint(statedb.TxIndex()) + + // EIP-8141: populate frame transaction receipt fields. + if tx.Type() == types.FrameTxType { + receipt.Payer = result.Payer + if result.FrameStatuses != nil { + receipt.FrameReceipts = make([]*types.FrameReceipt, len(result.FrameStatuses)) + for i, status := range result.FrameStatuses { + var gasUsed uint64 + if i < len(result.FrameGasUsed) { + gasUsed = result.FrameGasUsed[i] + } + var logs []*types.Log + if i < len(result.FrameLogSets) { + logs = result.FrameLogSets[i] + } + receipt.FrameReceipts[i] = &types.FrameReceipt{ + Status: uint64(status), + GasUsed: gasUsed, + Logs: logs, + } + } + } + } return receipt } diff --git a/core/state_transition.go b/core/state_transition.go index bf5ac07636..921ed4a808 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" "github.com/holiman/uint256" ) @@ -38,6 +39,12 @@ type ExecutionResult struct { MaxUsedGas uint64 // Maximum gas consumed during execution, excluding gas refunds. Err error // Any error encountered during the execution(listed in core/vm/errors.go) ReturnData []byte // Returned data from evm(function result or data supplied with revert opcode) + + // EIP-8141 + Payer *common.Address // Address that paid fees + FrameStatuses []uint8 // Per-frame success (1) or failure (0) + FrameGasUsed []uint64 // Per-frame gas consumed + FrameLogSets [][]*types.Log // Per-frame logs } // Unwrap returns the internal evm error which allows us for further @@ -157,6 +164,12 @@ type Message struct { BlobHashes []common.Hash SetCodeAuthorizations []types.SetCodeAuthorization + // EIP-8141 + IsFrameTx bool // True if this is a frame transaction. + Frames []types.Frame // The frames from Frame Tx. + FrameSigHash common.Hash // pre-computed signing hash + TxFee *big.Int // total fee Todo(jvn): Check correctness + // When SkipNonceChecks is true, the message nonce is not checked against the // account nonce in state. // @@ -190,6 +203,44 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In BlobHashes: tx.BlobHashes(), BlobGasFeeCap: tx.BlobGasFeeCap(), } + + // Handle Frame Transactions + if tx.Type() == types.FrameTxType { + msg.IsFrameTx = true + msg.Frames = tx.Frames() + if sender, ok := tx.FrameSender(); ok { + msg.From = sender + } + + // Todo(jvn): Looks like unneccessary port it to here , prev method was clean + // Where you had abstraction on this , Come back later + + // Compute effective gas price for TxFee calculation. + effectiveGasPrice := new(big.Int).Set(msg.GasFeeCap) + if baseFee != nil { + tip := new(big.Int).Sub(msg.GasFeeCap, baseFee) + if tip.Cmp(msg.GasTipCap) > 0 { + tip.Set(msg.GasTipCap) + } + effectiveGasPrice = new(big.Int).Add(tip, baseFee) + } + msg.GasPrice = effectiveGasPrice + + // TxFee = gasLimit * effectiveGasPrice + blobFees + // Todo(jvn): Check blob fees calc + txFee := new(big.Int).SetUint64(msg.GasLimit) + txFee.Mul(txFee, effectiveGasPrice) + if len(msg.BlobHashes) > 0 && baseFee != nil { + blobGas := uint64(len(msg.BlobHashes) * params.BlobTxBlobGasPerBlob) + blobFee := new(big.Int).SetUint64(blobGas) + blobFee.Mul(blobFee, msg.BlobGasFeeCap) + txFee.Add(txFee, blobFee) + } + msg.TxFee = txFee + msg.FrameSigHash = tx.FrameSigHash() + + return msg, nil + } // If baseFee provided, set gasPrice to effectiveGasPrice. if baseFee != nil { msg.GasPrice = msg.GasPrice.Add(msg.GasTipCap, baseFee) @@ -331,10 +382,12 @@ func (st *stateTransition) preCheck() error { return fmt.Errorf("%w (cap: %d, tx: %d)", ErrGasLimitTooHigh, params.MaxTxGas, msg.GasLimit) } // Make sure the sender is an EOA - code := st.state.GetCode(msg.From) - _, delegated := types.ParseDelegation(code) - if len(code) > 0 && !delegated { - return fmt.Errorf("%w: address %v, len(code): %d", ErrSenderNoEOA, msg.From.Hex(), len(code)) + if !msg.IsFrameTx { + code := st.state.GetCode(msg.From) + _, delegated := types.ParseDelegation(code) + if len(code) > 0 && !delegated { + return fmt.Errorf("%w: address %v, len(code): %d", ErrSenderNoEOA, msg.From.Hex(), len(code)) + } } } // Make sure that transaction gasFeeCap is greater than the baseFee (post london) @@ -406,6 +459,19 @@ func (st *stateTransition) preCheck() error { return fmt.Errorf("%w (sender %v)", ErrEmptyAuthList, msg.From) } } + // Frame transactions collect fees after APPROVE opcode, not buyGas. + // Skip balance deduction but still reserve gas from block gas pool. + if msg.IsFrameTx { + if err := st.gp.SubGas(msg.GasLimit); err != nil { + return err + } + if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil { + st.evm.Config.Tracer.OnGasChange(0, msg.GasLimit, tracing.GasChangeTxInitialBalance) + } + st.gasRemaining = msg.GasLimit + st.initialGas = msg.GasLimit + return nil + } return st.buyGas() } @@ -443,10 +509,23 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { ) // Check clauses 4-5, subtract intrinsic gas if everything is correct - gas, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai) + txData := msg.Data + if msg.IsFrameTx { + txData, _ = rlp.EncodeToBytes(msg.Frames) + } + gas, err := IntrinsicGas(txData, msg.AccessList, msg.SetCodeAuthorizations, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai) if err != nil { return nil, err } + if msg.IsFrameTx { + // Frame tx uses 15000 base cost instead of 21000. + // IntrinsicGas adds 21000, so subtract 6000. + if gas < 6000 { + return nil, ErrGasUintOverflow + } + gas -= 6000 + } + if st.gasRemaining < gas { return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining, gas) } @@ -496,7 +575,9 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { ret []byte vmerr error // vm errors do not effect consensus and are therefore not assigned to err ) - if contractCreation { + if msg.IsFrameTx { + return st.executeFrameTx(rules) + } else if contractCreation { ret, _, st.gasRemaining, vmerr = st.evm.Create(msg.From, msg.Data, st.gasRemaining, value) } else { // Increment the nonce for the next transaction. @@ -675,3 +756,206 @@ func (st *stateTransition) gasUsed() uint64 { func (st *stateTransition) blobGasUsed() uint64 { return uint64(len(st.msg.BlobHashes) * params.BlobTxBlobGasPerBlob) } + +func (st *stateTransition) executeFrameTx(rules params.Rules) (*ExecutionResult, error) { + msg := st.msg + + // Validate frame count + if len(msg.Frames) == 0 || len(msg.Frames) > params.FrameTxMaxFrames { + return nil, ErrFrameTxInvalidFormat + } + + // Build FrameTxContext + sigHash := msg.FrameSigHash + + ftxCtx := &vm.FrameTxContext{ + Sender: msg.From, + Nonce: msg.Nonce, + GasTipCap: msg.GasTipCap, + GasFeeCap: msg.GasFeeCap, + BlobFeeCap: msg.BlobGasFeeCap, + BlobVersionedHashes: msg.BlobHashes, + SigHash: sigHash, + TxFee: msg.TxFee, + CurrentFrame: -1, // TODO(jvn) -> change to none? + FrameStatuses: []uint8{}, + } + + ftxCtx.Frames = make([]vm.FrameInfo, len(msg.Frames)) + for i, f := range msg.Frames { + ftxCtx.Frames[i] = vm.FrameInfo{ + Mode: f.Mode, + Target: f.Target, + GasLimit: f.GasLimit, + Data: f.Data, + } + } + + st.evm.FrameTxCtx = ftxCtx + entryPoint := params.FrameEntryPointAddress + st.state.Snapshot() // tx level snapshot + + var ( + totalGasUsed uint64 + allLogs []*types.Log + frameLogSets [][]*types.Log // per-frame logs for FrameReceipts + frameGasUsedList []uint64 // per-frame gas consumed + // logSizeBefore int // log boundary tracker for per-frame slicing + ) + + for i, frame := range msg.Frames { + ftxCtx.CurrentFrame = i + // TOdo (chk the bug here) + target := frame.Target + if target == (common.Address{}) { + target = msg.From + } + + // Todo(jvn): Look into Impl of clear transient storage , Placing stub gere + st.state.ClearTransientStorage() + + // todo (g1): Guess it says to add to accessed address and not to Access list + // add All accessed itens to Prefetcher + senderApprovedBefore := ftxCtx.SenderApproved + payerApprovedBefore := ftxCtx.PayerApproved + payerBefore := ftxCtx.Payer + ftxCtx.ApproveCalledInFrame = false + + var ( + ret []byte + gasLeft uint64 + vmerr error + caller common.Address + ) + frameGas := frame.GasLimit + + switch frame.Mode { + case types.FrameModeDefault: + caller = entryPoint + st.evm.TxContext.Origin = caller + ret, gasLeft, vmerr = st.evm.Call( + caller, target, frame.Data, + frameGas, new(uint256.Int), + ) + + case types.FrameModeVerify: + caller = entryPoint + st.evm.TxContext.Origin = caller + ret, gasLeft, vmerr = st.evm.StaticCall( + caller, target, frame.Data, frameGas, + ) + + case types.FrameModeSender: + if !ftxCtx.SenderApproved { + return nil, ErrFrameTxInvalidApproval + } + caller = msg.From + st.evm.TxContext.Origin = caller + ret, gasLeft, vmerr = st.evm.Call( + caller, target, frame.Data, + frameGas, new(uint256.Int), + ) + + default: + return nil, ErrFrameTxInvalidFormat + } + + // EELS: + // if frame_output.error is not None: + // tx_approval.sender_approved = sender_approved_before + // tx_approval.payer_approved = payer_approved_before + // tx_approval.payer_address = payer_address_before + // tx_approval.approve_called_in_frame = False + if vmerr != nil { + ftxCtx.SenderApproved = senderApprovedBefore + ftxCtx.PayerApproved = payerApprovedBefore + ftxCtx.Payer = payerBefore + ftxCtx.ApproveCalledInFrame = false + } + + frameGasUsed := frameGas - gasLeft + totalGasUsed += frameGasUsed + frameStatus := uint8(1) + if vmerr != nil { + frameStatus = 0 + } + // TODO(EELS) how approval will be true? + // Ok Approve works like return + if frame.Mode == types.FrameModeVerify && !ftxCtx.ApproveCalledInFrame { + return nil, ErrFrameTxInvalidFrameExecution + } + ftxCtx.FrameStatuses = append(ftxCtx.FrameStatuses, frameStatus) + + // Collect per-frame gas used for FrameReceipts + frameGasUsedList = append(frameGasUsedList, frameGasUsed) + var frameLogs []*types.Log + // Todo(jvn): handle Logs Properly + frameLogSets = append(frameLogSets, frameLogs) + allLogs = append(allLogs, frameLogs...) + + // jsonBytes, _ := json.MarshalIndent(ftxCtx, "", " ") + + // fmt.Printf("FrameCtx JSON after i = %v:", i) + // fmt.Println(string(jsonBytes)) + + _ = ret + } + + if !ftxCtx.PayerApproved { + // Rollback tx-level snapshot + st.state.RevertToSnapshot(0) + return nil, ErrFrameTxInvalidFrameExecution + } + + var frameGasSum uint64 + for _, f := range msg.Frames { + frameGasSum += f.GasLimit + } + gasLeft := frameGasSum - totalGasUsed + st.gasRemaining -= totalGasUsed + + // refund the UNUSED portion to payer + if gasLeft > 0 { + effectivePrice := st.evm.TxContext.GasPrice + refund := new(uint256.Int).SetUint64(gasLeft) + refund.Mul(refund, effectivePrice) + st.state.AddBalance( + ftxCtx.Payer, + refund, + tracing.BalanceIncreaseGasReturn, + ) + // Return gas to block gas pool + st.gp.AddGas(gasLeft) + } + + // Pay miner tip from gas used + effectiveTip := msg.GasPrice + if rules.IsLondon { + effectiveTip = new(big.Int).Sub(msg.GasPrice, st.evm.Context.BaseFee) + } + if !(st.evm.Config.NoBaseFee && msg.GasFeeCap.Sign() == 0 && msg.GasTipCap.Sign() == 0) { + tip := new(uint256.Int).SetUint64(totalGasUsed) + tipU256, _ := uint256.FromBig(effectiveTip) + tip.Mul(tip, tipU256) + st.state.AddBalance( + st.evm.Context.Coinbase, + tip, + tracing.BalanceIncreaseRewardTransactionFee, + ) + } + + // Clean up FrameTxCtx after execution completes + defer func() { st.evm.FrameTxCtx = nil }() + + payer := ftxCtx.Payer + return &ExecutionResult{ + UsedGas: st.gasUsed(), + MaxUsedGas: st.gasUsed(), + Err: nil, + ReturnData: nil, + Payer: &payer, + FrameStatuses: ftxCtx.FrameStatuses, + FrameGasUsed: frameGasUsedList, + FrameLogSets: frameLogSets, + }, nil +} diff --git a/core/txpool/validation.go b/core/txpool/validation.go index e0a333dfa5..86c838e387 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -92,6 +92,9 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types if rules.IsOsaka && tx.Gas() > params.MaxTxGas { return fmt.Errorf("%w (cap: %d, tx: %d)", core.ErrGasLimitTooHigh, params.MaxTxGas, tx.Gas()) } + if !rules.IsBogota && tx.Type() == types.FrameTxType { + return fmt.Errorf("%w: type %d rejected, pool not yet in Bogota", core.ErrTxTypeNotSupported, tx.Type()) + } // Transactions can't be negative. This may never happen using RLP decoded // transactions but may occur for transactions created using the RPC. if tx.Value().Sign() < 0 { diff --git a/core/types/receipt.go b/core/types/receipt.go index ba7d9900f0..f62f3f0496 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -67,6 +67,10 @@ type Receipt struct { BlobGasUsed uint64 `json:"blobGasUsed,omitempty"` BlobGasPrice *big.Int `json:"blobGasPrice,omitempty"` + // EIP-8141 + Payer *common.Address `json:"payer,omitempty"` + FrameReceipts []*FrameReceipt `json:"frameReceipts,omitempty"` + // Inclusion information: These fields provide information about the inclusion of the // transaction corresponding to this receipt. BlockHash common.Hash `json:"blockHash,omitempty"` @@ -74,6 +78,13 @@ type Receipt struct { TransactionIndex uint `json:"transactionIndex"` } +// FrameReceipt is the per-frame receipt for frame transactions. +type FrameReceipt struct { + Status uint64 `json:"status"` + GasUsed uint64 `json:"gasUsed"` + Logs []*Log `json:"logs"` +} + type receiptMarshaling struct { Type hexutil.Uint64 PostState hexutil.Bytes @@ -205,7 +216,7 @@ func (r *Receipt) decodeTyped(b []byte) error { return errShortTypedReceipt } switch b[0] { - case DynamicFeeTxType, AccessListTxType, BlobTxType, SetCodeTxType: + case DynamicFeeTxType, AccessListTxType, BlobTxType, SetCodeTxType, FrameTxType: var data receiptRLP err := rlp.DecodeBytes(b[1:], &data) if err != nil { @@ -368,7 +379,7 @@ func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) { } w.WriteByte(r.Type) switch r.Type { - case AccessListTxType, DynamicFeeTxType, BlobTxType, SetCodeTxType: + case AccessListTxType, DynamicFeeTxType, BlobTxType, SetCodeTxType, FrameTxType: rlp.Encode(w, data) default: // For unsupported types, write nothing. Since this is for diff --git a/core/types/transaction.go b/core/types/transaction.go index 6af960b8c3..7aba50eec5 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -50,6 +50,7 @@ const ( DynamicFeeTxType = 0x02 BlobTxType = 0x03 SetCodeTxType = 0x04 + FrameTxType = 0x06 ) // Transaction is an Ethereum transaction. @@ -212,6 +213,8 @@ func (tx *Transaction) decodeTyped(b []byte) (TxData, error) { inner = new(BlobTx) case SetCodeTxType: inner = new(SetCodeTx) + case FrameTxType: + inner = new(FrameTx) default: return nil, ErrTxTypeNotSupported } diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go index 5854af07c5..e178e6a9aa 100644 --- a/core/types/transaction_signing.go +++ b/core/types/transaction_signing.go @@ -41,6 +41,8 @@ type sigCache struct { func MakeSigner(config *params.ChainConfig, blockNumber *big.Int, blockTime uint64) Signer { var signer Signer switch { + case config.IsBogota(blockNumber, blockTime): + signer = NewBogotaSigner(config.ChainID) case config.IsPrague(blockNumber, blockTime): signer = NewPragueSigner(config.ChainID) case config.IsCancun(blockNumber, blockTime): @@ -70,6 +72,8 @@ func LatestSigner(config *params.ChainConfig) Signer { var signer Signer if config.ChainID != nil { switch { + case config.BogotaTime != nil: + signer = NewBogotaSigner(config.ChainID) case config.PragueTime != nil: signer = NewPragueSigner(config.ChainID) case config.CancunTime != nil: @@ -231,6 +235,9 @@ func newModernSigner(chainID *big.Int, fork forks.Fork) Signer { if fork >= forks.Prague { s.txtypes.set(SetCodeTxType) } + if fork >= forks.Bogota { + s.txtypes.set(FrameTxType) + } return s } @@ -262,6 +269,13 @@ func (s *modernSigner) Sender(tx *Transaction) (common.Address, error) { if tx.ChainId().Cmp(s.chainID) != 0 { return common.Address{}, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.ChainId(), s.chainID) } + // Frame transactions have an explicit sender. + if tt == FrameTxType { + if ftx, ok := tx.inner.(*FrameTx); ok { + return ftx.Sender, nil + } + return common.Address{}, ErrTxTypeNotSupported + } // 'modern' txs are defined to use 0 and 1 as their recovery // id, add 27 to become equivalent to unprotected Homestead signatures. V, R, S := tx.RawSignatureValues() @@ -271,6 +285,9 @@ func (s *modernSigner) Sender(tx *Transaction) (common.Address, error) { func (s *modernSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) { tt := tx.Type() + if tt == FrameTxType { + return nil, nil, nil, nil + } if !s.supportsType(tt) { return nil, nil, nil, ErrTxTypeNotSupported } @@ -290,6 +307,18 @@ func (s *modernSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *bi return R, S, V, nil } +// NewBogotaSigner returns a signer that accepts +// - EIP-8141 frame transactions +// - EIP-7702 set code transactions +// - EIP-4844 blob transactions +// - EIP-1559 dynamic fee transactions +// - EIP-2930 access list transactions, +// - EIP-155 replay protected transactions, and +// - legacy Homestead transactions. +func NewBogotaSigner(chainId *big.Int) Signer { + return newModernSigner(chainId, forks.Bogota) +} + // NewPragueSigner returns a signer that accepts // - EIP-7702 set code transactions // - EIP-4844 blob transactions diff --git a/core/types/tx_frame.go b/core/types/tx_frame.go new file mode 100644 index 0000000000..aca5dd9a6f --- /dev/null +++ b/core/types/tx_frame.go @@ -0,0 +1,235 @@ +// Copyright 2025 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" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" + "github.com/holiman/uint256" +) + +// Frame mode constants as defined by EIP-8141. +const ( + FrameModeDefault uint8 = 0 // Regular call with ENTRY_POINT as caller. + FrameModeVerify uint8 = 1 // Validation frame; must call APPROVE; STATICCALL. Data elided from sig hash. + FrameModeSender uint8 = 2 // Regular call with tx.sender as caller; requires sender_approved. +) + +// Frame represents a single call frame within a Frame Transaction +type Frame struct { + Mode uint8 + Target common.Address + GasLimit uint64 + Data []byte +} + +// FrameTx represents an EIP-8141 Frame Transaction. +type FrameTx struct { + ChainID *uint256.Int + Nonce uint64 + Sender common.Address + Frames []Frame + GasTipCap *uint256.Int // a.k.a. maxPriorityFeePerGas + GasFeeCap *uint256.Int // a.k.a. maxFeePerGas + + // Optional blob fields + BlobFeeCap *uint256.Int // a.k.a. maxFeePerBlobGas + BlobHashes []common.Hash // Blob versioned hashes +} + +// Todo(jvn): Just a quick version of each fn to fullfill requirements +// Must chk correctness later once impl completes + +// copy creates a deep copy of the transaction data. +func (tx *FrameTx) copy() TxData { + cpy := &FrameTx{ + Nonce: tx.Nonce, + Sender: tx.Sender, + Frames: make([]Frame, len(tx.Frames)), + ChainID: new(uint256.Int), + GasTipCap: new(uint256.Int), + GasFeeCap: new(uint256.Int), + } + // Deep copy frames. + for i, f := range tx.Frames { + cpy.Frames[i] = Frame{ + Mode: f.Mode, + Target: f.Target, + GasLimit: f.GasLimit, + Data: common.CopyBytes(f.Data), + } + } + if tx.ChainID != nil { + cpy.ChainID.Set(tx.ChainID) + } + if tx.GasTipCap != nil { + cpy.GasTipCap.Set(tx.GasTipCap) + } + if tx.GasFeeCap != nil { + cpy.GasFeeCap.Set(tx.GasFeeCap) + } + if tx.BlobFeeCap != nil { + cpy.BlobFeeCap = new(uint256.Int) + cpy.BlobFeeCap.Set(tx.BlobFeeCap) + } + if len(tx.BlobHashes) > 0 { + cpy.BlobHashes = make([]common.Hash, len(tx.BlobHashes)) + copy(cpy.BlobHashes, tx.BlobHashes) + } + return cpy +} + +// accessors for TxData interface. +func (tx *FrameTx) txType() byte { return FrameTxType } +func (tx *FrameTx) chainID() *big.Int { return tx.ChainID.ToBig() } +func (tx *FrameTx) accessList() AccessList { return nil } +func (tx *FrameTx) data() []byte { return nil } + +// todo(jvn) change this +func (tx *FrameTx) gasFeeCap() *big.Int { return tx.GasFeeCap.ToBig() } +func (tx *FrameTx) gasTipCap() *big.Int { return tx.GasTipCap.ToBig() } +func (tx *FrameTx) gasPrice() *big.Int { return tx.GasFeeCap.ToBig() } +func (tx *FrameTx) value() *big.Int { return new(big.Int) } // As Spec Frame txs have no value field Ig , confirm later +func (tx *FrameTx) nonce() uint64 { return tx.Nonce } +func (tx *FrameTx) to() *common.Address { return nil } // No single target; each frame has its own. + +// Todo: StreamLine Gas Calc , currently spreadout here and in State Processor +// Port correct Implementation here to keep it clean +func (tx *FrameTx) gas() uint64 { + total := uint64(params.FrameTxIntrinsicGas) + total += tx.CalldataCost() + for _, f := range tx.Frames { + total += f.GasLimit + } + return total +} + +func (tx *FrameTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int { + if baseFee == nil { + return dst.Set(tx.GasFeeCap.ToBig()) + } + tip := dst.Sub(tx.GasFeeCap.ToBig(), baseFee) + if tip.Cmp(tx.GasTipCap.ToBig()) > 0 { + tip.Set(tx.GasTipCap.ToBig()) + } + return tip.Add(tip, baseFee) +} + +func (tx *FrameTx) rawSignatureValues() (v, r, s *big.Int) { + // Frame transactions have no ECDSA signature. + return new(big.Int), new(big.Int), new(big.Int) +} + +func (tx *FrameTx) setSignatureValues(chainID, v, r, s *big.Int) { + // Frame transactions don't have signatures. + tx.ChainID = uint256.MustFromBig(chainID) +} + +func (tx *FrameTx) encode(b *bytes.Buffer) error { + return rlp.Encode(b, tx) +} + +func (tx *FrameTx) decode(input []byte) error { + return rlp.DecodeBytes(input, tx) +} + +// sigHash computes the signature hash for a frame transaction. +func (tx *FrameTx) sigHash(chainID *big.Int) common.Hash { + elidedFrames := make([]interface{}, len(tx.Frames)) + for i, f := range tx.Frames { + frameData := f.Data + if f.Mode == FrameModeVerify { + frameData = []byte{} // Elide data for VERIFY frames. + } + elidedFrames[i] = []interface{}{ + f.Mode, + f.Target, + f.GasLimit, + frameData, + } + } + return prefixedRlpHash( + FrameTxType, + []interface{}{ + chainID, + tx.Nonce, + tx.Sender, + elidedFrames, + tx.GasTipCap, + tx.GasFeeCap, + tx.BlobFeeCap, + tx.BlobHashes, + }) +} + +// ComputeSigHash returns the signature hash for external use. +func (tx *FrameTx) ComputeSigHash() common.Hash { + return tx.sigHash(tx.ChainID.ToBig()) +} + +// TotalFrameGas returns the sum of gas limits across all frames. +func (tx *FrameTx) TotalFrameGas() uint64 { + var total uint64 + for _, f := range tx.Frames { + total += f.GasLimit + } + return total +} + +// CalldataCost returns the gas cost for the calldata equivalent in the frame transaction. +func (tx *FrameTx) CalldataCost() uint64 { + encodedFrames, err := rlp.EncodeToBytes(tx.Frames) + if err != nil { + return 0 + } + return calldataCost(encodedFrames) +} + +// Check this dont think Currently using this anywhere +func calldataCost(data []byte) uint64 { + z := uint64(bytes.Count(data, []byte{0})) + nz := uint64(len(data)) - z + return z*params.TxDataZeroGas + nz*params.TxDataNonZeroGasEIP2028 +} + +// FrameSigHash returns the signature hash +func (tx *Transaction) FrameSigHash() common.Hash { + if ftx, ok := tx.inner.(*FrameTx); ok { + return ftx.ComputeSigHash() + } + return common.Hash{} +} + +// Frames returns the frame list for frame transactions, nil otherwise. +func (tx *Transaction) Frames() []Frame { + if ftx, ok := tx.inner.(*FrameTx); ok { + return ftx.Frames + } + return nil +} + +// FrameSender returns the explicit sender for frame transactions. +func (tx *Transaction) FrameSender() (common.Address, bool) { + if ftx, ok := tx.inner.(*FrameTx); ok { + return ftx.Sender, true + } + return common.Address{}, false +} diff --git a/miner/worker.go b/miner/worker.go index 45d7073ed7..42fda339be 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -296,6 +296,7 @@ func (miner *Miner) makeEnv(parent *types.Header, header *types.Header, coinbase } func (miner *Miner) commitTransaction(env *environment, tx *types.Transaction) error { + // Todo(jvn): Maybe add frame Txn handling here? if tx.Type() == types.BlobTxType { return miner.commitBlobTransaction(env, tx) }