diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index ac696f446be6..e26341d9df5d 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -813,6 +813,9 @@ func (m callMsg) Value() *big.Int { return m.CallMsg.Value } func (m callMsg) Data() []byte { return m.CallMsg.Data } func (m callMsg) AccessList() types.AccessList { return m.CallMsg.AccessList } +// fee delegation +func (m callMsg) FeePayer() *common.Address { return m.CallMsg.FeePayer } + // filterBackend implements filters.Backend to support filtering for logs without // taking bloom-bits acceleration structures into account. type filterBackend struct { diff --git a/accounts/keystore/keystore.go b/accounts/keystore/keystore.go index 88dcfbeb69e0..066fe4b02682 100644 --- a/accounts/keystore/keystore.go +++ b/accounts/keystore/keystore.go @@ -283,6 +283,11 @@ func (ks *KeyStore) SignTx(a accounts.Account, tx *types.Transaction, chainID *b if !found { return nil, ErrLocked } + // fee delegation + if tx.Type() == types.FeeDelegateDynamicFeeTxType { + signer := types.NewFeeDelegateSigner(chainID) + return types.SignTx(tx, signer, unlockedKey.PrivateKey) + } // Depending on the presence of the chain ID, sign with 2718 or homestead signer := types.LatestSignerForChainID(chainID) return types.SignTx(tx, signer, unlockedKey.PrivateKey) @@ -308,6 +313,12 @@ func (ks *KeyStore) SignTxWithPassphrase(a accounts.Account, passphrase string, return nil, err } defer zeroKey(key.PrivateKey) + // fee delegation + if tx.Type() == types.FeeDelegateDynamicFeeTxType { + signer := types.NewFeeDelegateSigner(chainID) + return types.SignTx(tx, signer, key.PrivateKey) + } + // Depending on the presence of the chain ID, sign with or without replay protection. signer := types.LatestSignerForChainID(chainID) return types.SignTx(tx, signer, key.PrivateKey) diff --git a/accounts/scwallet/wallet.go b/accounts/scwallet/wallet.go index 5e74e606e13f..9fd447c4c9ee 100644 --- a/accounts/scwallet/wallet.go +++ b/accounts/scwallet/wallet.go @@ -699,6 +699,18 @@ func (w *Wallet) signHash(account accounts.Account, hash []byte) ([]byte, error) // the needed details via SignTxWithPassphrase, or by other means (e.g. unlock // the account in a keystore). func (w *Wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { + + // fee delegation + if tx.Type() == types.FeeDelegateDynamicFeeTxType { + signer := types.NewFeeDelegateSigner(chainID) + hash := signer.Hash(tx) + sig, err := w.signHash(account, hash[:]) + if err != nil { + return nil, err + } + return tx.WithSignature(signer, sig) + } + signer := types.LatestSignerForChainID(chainID) hash := signer.Hash(tx) sig, err := w.signHash(account, hash[:]) diff --git a/core/error.go b/core/error.go index 51ebefc137bc..a611c7c23344 100644 --- a/core/error.go +++ b/core/error.go @@ -96,4 +96,16 @@ var ( // ErrSenderNoEOA is returned if the sender of a transaction is a contract. ErrSenderNoEOA = errors.New("sender not an eoa") + + // fee delegation + // ErrInvalidFeePayer is returned if the transaction contains an invalid feePayer's signature. + ErrInvalidFeePayer = errors.New("invalid fee delegation feePayer") + + // ErrFeePayerInsufficientFunds is returned if the fee cost of executing a transaction + // is higher than the balance of the feePayer's account. + ErrFeePayerInsufficientFunds = errors.New("fee delegation feePayer's insufficient funds for gas * price") + + // ErrSenderInsufficientFunds is returned if the value cost of executing a transaction + // is higher than the balance of the sender's account. + ErrSenderInsufficientFunds = errors.New("fee delegation sender's account insufficient funds for value") ) diff --git a/core/state_transition.go b/core/state_transition.go index 980a250ec495..b202b2619020 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -80,6 +80,8 @@ type Message interface { IsFake() bool Data() []byte AccessList() types.AccessList + // fee delegation + FeePayer() *common.Address } // ExecutionResult includes all output after executing given evm @@ -194,24 +196,57 @@ func (st *StateTransition) to() common.Address { } func (st *StateTransition) buyGas() error { - mgval := new(big.Int).SetUint64(st.msg.Gas()) - mgval = mgval.Mul(mgval, st.gasPrice) - balanceCheck := mgval - if st.gasFeeCap != nil { - balanceCheck = new(big.Int).SetUint64(st.msg.Gas()) - balanceCheck = balanceCheck.Mul(balanceCheck, st.gasFeeCap) - balanceCheck.Add(balanceCheck, st.value) - } - if have, want := st.state.GetBalance(st.msg.From()), balanceCheck; have.Cmp(want) < 0 { - return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From().Hex(), have, want) - } - if err := st.gp.SubGas(st.msg.Gas()); err != nil { - return err - } - st.gas += st.msg.Gas() + // fee delegation + if st.msg.FeePayer() != nil { + if !st.evm.ChainConfig().IsFeeDelegation(st.evm.Context.BlockNumber) { + return fmt.Errorf("%w: fee delegation type not supperted", ErrTxTypeNotSupported) + } + FDmgval := new(big.Int).SetUint64(st.msg.Gas()) + FDmgval = FDmgval.Mul(FDmgval, st.gasFeeCap) + feePayer := *st.msg.FeePayer() + if feePayer == st.msg.From() { + FDbalanceCheck := new(big.Int).SetUint64(st.msg.Gas()) + FDbalanceCheck = FDbalanceCheck.Mul(FDbalanceCheck, st.gasFeeCap) + FDbalanceCheck.Add(FDbalanceCheck, st.value) + if have, want := st.state.GetBalance(feePayer), FDbalanceCheck; have.Cmp(want) < 0 { + return ErrFeePayerInsufficientFunds + } + } else { + if have, want := st.state.GetBalance(feePayer), FDmgval; have.Cmp(want) < 0 { + return ErrFeePayerInsufficientFunds + } + if have, want := st.state.GetBalance(st.msg.From()), st.value; have.Cmp(want) < 0 { + return fmt.Errorf("%w: sender address %v have %v want %v", ErrInsufficientFunds, st.msg.From().Hex(), have, want) + } + } + if err := st.gp.SubGas(st.msg.Gas()); err != nil { + return err + } + st.gas += st.msg.Gas() - st.initialGas = st.msg.Gas() - st.state.SubBalance(st.msg.From(), mgval) + st.initialGas = st.msg.Gas() + st.state.SubBalance(feePayer, FDmgval) + } else { + mgval := new(big.Int).SetUint64(st.msg.Gas()) + mgval = mgval.Mul(mgval, st.gasPrice) + balanceCheck := mgval + if st.gasFeeCap != nil { + balanceCheck = new(big.Int).SetUint64(st.msg.Gas()) + balanceCheck = balanceCheck.Mul(balanceCheck, st.gasFeeCap) + balanceCheck.Add(balanceCheck, st.value) + } + + if have, want := st.state.GetBalance(st.msg.From()), balanceCheck; have.Cmp(want) < 0 { + return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From().Hex(), have, want) + } + if err := st.gp.SubGas(st.msg.Gas()); err != nil { + return err + } + st.gas += st.msg.Gas() + + st.initialGas = st.msg.Gas() + st.state.SubBalance(st.msg.From(), mgval) + } return nil } @@ -374,8 +409,13 @@ func (st *StateTransition) refundGas(refundQuotient uint64) { // Return ETH for remaining gas, exchanged at the original rate. remaining := new(big.Int).Mul(new(big.Int).SetUint64(st.gas), st.gasPrice) - st.state.AddBalance(st.msg.From(), remaining) + // fee delegation + if st.msg.FeePayer() != nil { + st.state.AddBalance(*st.msg.FeePayer(), remaining) + } else { + st.state.AddBalance(st.msg.From(), remaining) + } // Also return remaining gas to the block gas counter so it is // available for the next transaction. st.gp.AddGas(st.gas) diff --git a/core/tx_pool.go b/core/tx_pool.go index 8b638244b489..93fb9c0db4f2 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -248,6 +248,8 @@ type TxPool struct { istanbul bool // Fork indicator whether we are in the istanbul stage. eip2718 bool // Fork indicator whether we are using EIP-2718 type transactions. eip1559 bool // Fork indicator whether we are using EIP-1559 type transactions. + // fee delegation + feedelegation bool // Fork indicator whether we are using fee delegation type transactions. currentState *state.StateDB // Current state in the blockchain head pendingNonces *txNoncer // Pending state tracking virtual nonces @@ -393,6 +395,20 @@ func (pool *TxPool) loop() { case <-evict.C: pool.mu.Lock() for addr := range pool.queue { + // fee delegation + if pool.feedelegation { + list := pool.queue[addr].Flatten() + for _, tx := range list { + if tx.Type() == types.FeeDelegateDynamicFeeTxType && tx.FeePayer() != nil { + // check feePayer's balance + if pool.currentState.GetBalance(*tx.FeePayer()).Cmp(tx.FeePayerCost()) < 0 { + pool.removeTx(tx.Hash(), true) + queuedEvictionMeter.Mark(int64(1)) + } + } + } + } + // Skip local transactions from the eviction mechanism if pool.locals.contains(addr) { continue @@ -639,6 +655,11 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { if !pool.eip1559 && tx.Type() == types.DynamicFeeTxType { return ErrTxTypeNotSupported } + // fee delegation + // Reject fee delegation dynamic fee transactions until feedelegation activates. + if !pool.feedelegation && tx.Type() == types.FeeDelegateDynamicFeeTxType { + return ErrTxTypeNotSupported + } // Reject transactions over defined size to prevent DOS attacks if uint64(tx.Size()) > txMaxSize { return ErrOversizedData @@ -682,8 +703,24 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { } // Transactor should have enough funds to cover the costs // cost == V + GP * GL - if pool.currentState.GetBalance(from).Cmp(tx.Cost()) < 0 { - return ErrInsufficientFunds + + // fee delegation + if tx.Type() == types.FeeDelegateDynamicFeeTxType { + // Make sure the transaction is signed properly. + feePayer, err := types.FeePayer(types.NewFeeDelegateSigner(pool.chainconfig.ChainID), tx) + if *tx.FeePayer() != feePayer || err != nil { + return ErrInvalidFeePayer + } + if pool.currentState.GetBalance(feePayer).Cmp(tx.FeePayerCost()) < 0 { + return ErrFeePayerInsufficientFunds + } + if pool.currentState.GetBalance(from).Cmp(tx.Cost()) < 0 { + return ErrSenderInsufficientFunds + } + } else { + if pool.currentState.GetBalance(from).Cmp(tx.Cost()) < 0 { + return ErrInsufficientFunds + } } // Ensure the transaction has more gas than the basic tx fee. intrGas, err := IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, pool.istanbul) @@ -1358,6 +1395,8 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) { pool.istanbul = pool.chainconfig.IsIstanbul(next) pool.eip2718 = pool.chainconfig.IsBerlin(next) pool.eip1559 = pool.chainconfig.IsLondon(next) + // fee delegation + pool.feedelegation = pool.chainconfig.IsFeeDelegation(next) } // promoteExecutables moves transactions that have become processable from the @@ -1382,6 +1421,21 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) []*types.Trans log.Trace("Removed old queued transactions", "count", len(forwards)) // Drop all transactions that are too costly (low balance or out of gas) drops, _ := list.Filter(pool.currentState.GetBalance(addr), pool.currentMaxGas) + + // fee delegation + if pool.feedelegation { + for _, tx := range list.Flatten() { + if tx.Type() == types.FeeDelegateDynamicFeeTxType && tx.FeePayer() != nil { + feePayer := *tx.FeePayer() + if pool.currentState.GetBalance(feePayer).Cmp(tx.FeePayerCost()) < 0 { + log.Trace("promoteExecutables", "hash", tx.Hash().String()) + list.Remove(tx) + drops = append(drops, tx) + } + } + } + } + for _, tx := range drops { hash := tx.Hash() pool.all.Remove(hash) @@ -1579,6 +1633,21 @@ func (pool *TxPool) demoteUnexecutables() { } // Drop all transactions that are too costly (low balance or out of gas), and queue any invalids back for later drops, invalids := list.Filter(pool.currentState.GetBalance(addr), pool.currentMaxGas) + + // fee delegation + if pool.feedelegation { + for _, tx := range list.Flatten() { + if tx.Type() == types.FeeDelegateDynamicFeeTxType && tx.FeePayer() != nil { + feePayer := *tx.FeePayer() + if pool.currentState.GetBalance(feePayer).Cmp(tx.FeePayerCost()) < 0 { + log.Trace("demoteUnexecutables", "hash", tx.Hash().String()) + list.Remove(tx) + drops = append(drops, tx) + } + } + } + } + for _, tx := range drops { hash := tx.Hash() log.Trace("Removed unpayable pending transaction", "hash", hash) diff --git a/core/types/access_list_tx.go b/core/types/access_list_tx.go index 620848fe624a..572d110ad040 100644 --- a/core/types/access_list_tx.go +++ b/core/types/access_list_tx.go @@ -106,6 +106,12 @@ func (tx *AccessListTx) value() *big.Int { return tx.Value } func (tx *AccessListTx) nonce() uint64 { return tx.Nonce } func (tx *AccessListTx) to() *common.Address { return tx.To } +// fee delegation +func (tx *AccessListTx) feePayer() *common.Address { return nil } +func (tx *AccessListTx) rawFeePayerSignatureValues() (v, r, s *big.Int) { + return nil, nil, nil +} + func (tx *AccessListTx) rawSignatureValues() (v, r, s *big.Int) { return tx.V, tx.R, tx.S } diff --git a/core/types/dynamic_fee_tx.go b/core/types/dynamic_fee_tx.go index 53f246ea1fad..b1d242391b30 100644 --- a/core/types/dynamic_fee_tx.go +++ b/core/types/dynamic_fee_tx.go @@ -94,6 +94,12 @@ func (tx *DynamicFeeTx) value() *big.Int { return tx.Value } func (tx *DynamicFeeTx) nonce() uint64 { return tx.Nonce } func (tx *DynamicFeeTx) to() *common.Address { return tx.To } +// fee delegation +func (tx *DynamicFeeTx) feePayer() *common.Address { return nil } +func (tx *DynamicFeeTx) rawFeePayerSignatureValues() (v, r, s *big.Int) { + return nil, nil, nil +} + func (tx *DynamicFeeTx) rawSignatureValues() (v, r, s *big.Int) { return tx.V, tx.R, tx.S } diff --git a/core/types/feedelegate_dynamic_fee_tx.go b/core/types/feedelegate_dynamic_fee_tx.go new file mode 100644 index 000000000000..bec388c38368 --- /dev/null +++ b/core/types/feedelegate_dynamic_fee_tx.go @@ -0,0 +1,136 @@ +// 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 types + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" +) + +type FeeDelegateDynamicFeeTx struct { + SenderTx DynamicFeeTx + FeePayer *common.Address `rlp:"nil"` + // Signature values + FV *big.Int `json:"fv" gencodec:"required"` // feePayer V + FR *big.Int `json:"fr" gencodec:"required"` // feePayer R + FS *big.Int `json:"fs" gencodec:"required"` // feePayer S +} + +func (tx *FeeDelegateDynamicFeeTx) SetSenderTx(senderTx DynamicFeeTx) { + tx.SenderTx.ChainID = senderTx.ChainID + tx.SenderTx.Nonce = senderTx.Nonce + tx.SenderTx.GasFeeCap = senderTx.GasFeeCap + tx.SenderTx.GasTipCap = senderTx.GasTipCap + tx.SenderTx.Gas = senderTx.Gas + tx.SenderTx.To = senderTx.To + tx.SenderTx.Value = senderTx.Value + tx.SenderTx.Data = senderTx.Data + copy(tx.SenderTx.AccessList, senderTx.AccessList) + + v, r, s := senderTx.rawSignatureValues() + tx.SenderTx.V = v + tx.SenderTx.R = r + tx.SenderTx.S = s +} + +// copy creates a deep copy of the transaction data and initializes all fields. +func (tx *FeeDelegateDynamicFeeTx) copy() TxData { + cpy := &FeeDelegateDynamicFeeTx{ + SenderTx: tx.copySenderTx(), + FeePayer: copyAddressPtr(tx.FeePayer), + FV: new(big.Int), + FR: new(big.Int), + FS: new(big.Int), + } + if tx.FV != nil { + cpy.FV.Set(tx.FV) + } + if tx.FR != nil { + cpy.FR.Set(tx.FR) + } + if tx.FS != nil { + cpy.FS.Set(tx.FS) + } + return cpy +} + +func (tx *FeeDelegateDynamicFeeTx) copySenderTx() DynamicFeeTx { + cpy := DynamicFeeTx{ + Nonce: tx.SenderTx.Nonce, + To: copyAddressPtr(tx.SenderTx.To), + Data: common.CopyBytes(tx.SenderTx.Data), + Gas: tx.SenderTx.Gas, + // These are copied below. + AccessList: make(AccessList, len(tx.SenderTx.AccessList)), + Value: new(big.Int), + ChainID: new(big.Int), + GasTipCap: new(big.Int), + GasFeeCap: new(big.Int), + V: new(big.Int), + R: new(big.Int), + S: new(big.Int), + } + copy(cpy.AccessList, tx.SenderTx.accessList()) + if tx.SenderTx.Value != nil { + cpy.Value.Set(tx.SenderTx.Value) + } + if tx.SenderTx.ChainID != nil { + cpy.ChainID.Set(tx.SenderTx.ChainID) + } + if tx.SenderTx.GasTipCap != nil { + cpy.GasTipCap.Set(tx.SenderTx.GasTipCap) + } + if tx.SenderTx.GasFeeCap != nil { + cpy.GasFeeCap.Set(tx.SenderTx.GasFeeCap) + } + if tx.SenderTx.V != nil { + cpy.V.Set(tx.SenderTx.V) + } + if tx.SenderTx.R != nil { + cpy.R.Set(tx.SenderTx.R) + } + if tx.SenderTx.S != nil { + cpy.S.Set(tx.SenderTx.S) + } + return cpy +} + +// accessors for innerTx. +func (tx *FeeDelegateDynamicFeeTx) txType() byte { return FeeDelegateDynamicFeeTxType } +func (tx *FeeDelegateDynamicFeeTx) chainID() *big.Int { return tx.SenderTx.ChainID } +func (tx *FeeDelegateDynamicFeeTx) accessList() AccessList { return tx.SenderTx.AccessList } +func (tx *FeeDelegateDynamicFeeTx) data() []byte { return tx.SenderTx.Data } +func (tx *FeeDelegateDynamicFeeTx) gas() uint64 { return tx.SenderTx.Gas } +func (tx *FeeDelegateDynamicFeeTx) gasFeeCap() *big.Int { return tx.SenderTx.GasFeeCap } +func (tx *FeeDelegateDynamicFeeTx) gasTipCap() *big.Int { return tx.SenderTx.GasTipCap } +func (tx *FeeDelegateDynamicFeeTx) gasPrice() *big.Int { return tx.SenderTx.GasFeeCap } +func (tx *FeeDelegateDynamicFeeTx) value() *big.Int { return tx.SenderTx.Value } +func (tx *FeeDelegateDynamicFeeTx) nonce() uint64 { return tx.SenderTx.Nonce } +func (tx *FeeDelegateDynamicFeeTx) to() *common.Address { return tx.SenderTx.To } +func (tx *FeeDelegateDynamicFeeTx) feePayer() *common.Address { return tx.FeePayer } +func (tx *FeeDelegateDynamicFeeTx) rawFeePayerSignatureValues() (v, r, s *big.Int) { + return tx.FV, tx.FR, tx.FS +} + +func (tx *FeeDelegateDynamicFeeTx) rawSignatureValues() (v, r, s *big.Int) { + return tx.SenderTx.rawSignatureValues() +} + +func (tx *FeeDelegateDynamicFeeTx) setSignatureValues(chainID, v, r, s *big.Int) { + tx.FV, tx.FR, tx.FS = v, r, s +} diff --git a/core/types/legacy_tx.go b/core/types/legacy_tx.go index 14d307829cc9..0af63f35dd57 100644 --- a/core/types/legacy_tx.go +++ b/core/types/legacy_tx.go @@ -103,6 +103,11 @@ func (tx *LegacyTx) value() *big.Int { return tx.Value } func (tx *LegacyTx) nonce() uint64 { return tx.Nonce } func (tx *LegacyTx) to() *common.Address { return tx.To } +// fee delegation +func (tx *LegacyTx) feePayer() *common.Address { return nil } +func (tx *LegacyTx) rawFeePayerSignatureValues() (v, r, s *big.Int) { + return nil, nil, nil +} func (tx *LegacyTx) rawSignatureValues() (v, r, s *big.Int) { return tx.V, tx.R, tx.S } diff --git a/core/types/receipt.go b/core/types/receipt.go index a913cd0e83be..0692a43891ae 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -214,7 +214,7 @@ func (r *Receipt) decodeTyped(b []byte) error { return errShortTypedReceipt } switch b[0] { - case DynamicFeeTxType, AccessListTxType: + case DynamicFeeTxType, AccessListTxType, FeeDelegateDynamicFeeTxType: // fee delegation var data receiptRLP err := rlp.DecodeBytes(b[1:], &data) if err != nil { @@ -387,6 +387,10 @@ func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) { case DynamicFeeTxType: w.WriteByte(DynamicFeeTxType) rlp.Encode(w, data) + // fee delegation + case FeeDelegateDynamicFeeTxType: + w.WriteByte(FeeDelegateDynamicFeeTxType) + rlp.Encode(w, data) default: // For unsupported types, write nothing. Since this is for // DeriveSha, the error will be caught matching the derived hash diff --git a/core/types/transaction.go b/core/types/transaction.go index e19ce7eec56e..679953d31602 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -45,6 +45,7 @@ const ( LegacyTxType = iota AccessListTxType DynamicFeeTxType + FeeDelegateDynamicFeeTxType = 22 // fee delegation ) // Transaction is an Ethereum transaction. @@ -53,9 +54,10 @@ type Transaction struct { time time.Time // Time first seen locally (spam avoidance) // caches - hash atomic.Value - size atomic.Value - from atomic.Value + hash atomic.Value + size atomic.Value + from atomic.Value + feePayer atomic.Value // fee delegation } type TransactionEx struct { @@ -90,6 +92,9 @@ type TxData interface { rawSignatureValues() (v, r, s *big.Int) setSignatureValues(chainID, v, r, s *big.Int) + // fee delegation + feePayer() *common.Address + rawFeePayerSignatureValues() (v, r, s *big.Int) } // EncodeRLP implements rlp.Encoder @@ -189,6 +194,11 @@ func (tx *Transaction) decodeTyped(b []byte) (TxData, error) { var inner DynamicFeeTx err := rlp.DecodeBytes(b[1:], &inner) return &inner, err + // fee delegation + case FeeDelegateDynamicFeeTxType: + var inner FeeDelegateDynamicFeeTx + err := rlp.DecodeBytes(b[1:], &inner) + return &inner, err default: return nil, ErrTxTypeNotSupported } @@ -284,6 +294,16 @@ func (tx *Transaction) Value() *big.Int { return new(big.Int).Set(tx.inner.value // Nonce returns the sender account nonce of the transaction. func (tx *Transaction) Nonce() uint64 { return tx.inner.nonce() } +// fee delegation +// FeePayer returns the feePayer's address of the transaction. +func (tx *Transaction) FeePayer() *common.Address { return tx.inner.feePayer() } + +// RawFeePayerSignatureValues returns the feePayer's FV, FR, FS signature values of the transaction. +// The return values should not be modified by the caller. +func (tx *Transaction) RawFeePayerSignatureValues() (v, r, s *big.Int) { + return tx.inner.rawFeePayerSignatureValues() +} + // To returns the recipient address of the transaction. // For contract-creation transactions, To returns nil. func (tx *Transaction) To() *common.Address { @@ -292,11 +312,39 @@ func (tx *Transaction) To() *common.Address { // Cost returns gas * gasPrice + value. func (tx *Transaction) Cost() *big.Int { + // fee delegation + if tx.Type() == FeeDelegateDynamicFeeTxType { + signer := LatestSignerForChainID(tx.ChainId()) + from, _ := Sender(signer, tx) + if *tx.FeePayer() != from { + total := tx.Value() + return total + } else { + total := new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(tx.Gas())) + total.Add(total, tx.Value()) + return total + } + } total := new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(tx.Gas())) total.Add(total, tx.Value()) return total } +// fee delegation +// FeePayerCost returns feePayer's gas * gasPrice + value. +func (tx *Transaction) FeePayerCost() *big.Int { + signer := LatestSignerForChainID(tx.ChainId()) + from, _ := Sender(signer, tx) + if *tx.FeePayer() != from { + total := new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(tx.Gas())) + return total + } else { + total := new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(tx.Gas())) + total.Add(total, tx.Value()) + return total + } +} + // RawSignatureValues returns the V, R, S signature values of the transaction. // The return values should not be modified by the caller. func (tx *Transaction) RawSignatureValues() (v, r, s *big.Int) { @@ -627,6 +675,8 @@ type Message struct { data []byte accessList AccessList isFake bool + // fee delegation + feePayer *common.Address } func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *big.Int, gasLimit uint64, gasPrice, gasFeeCap, gasTipCap *big.Int, data []byte, accessList AccessList, isFake bool) Message { @@ -659,6 +709,10 @@ func (tx *Transaction) AsMessage(s Signer, baseFee *big.Int) (Message, error) { accessList: tx.AccessList(), isFake: false, } + // fee delegation + if tx.FeePayer() != nil { + msg.feePayer = tx.FeePayer() + } // If baseFee provided, set gasPrice to effectiveGasPrice. if baseFee != nil { msg.gasPrice = math.BigMin(msg.gasPrice.Add(msg.gasTipCap, baseFee), msg.gasFeeCap) @@ -680,6 +734,9 @@ func (m Message) Data() []byte { return m.data } func (m Message) AccessList() AccessList { return m.accessList } func (m Message) IsFake() bool { return m.isFake } +// fee delegation +func (m Message) FeePayer() *common.Address { return m.feePayer } + // copyAddressPtr copies an address. func copyAddressPtr(a *common.Address) *common.Address { if a == nil { diff --git a/core/types/transaction_marshalling.go b/core/types/transaction_marshalling.go index aad31a5a97e2..eb68e3db70e4 100644 --- a/core/types/transaction_marshalling.go +++ b/core/types/transaction_marshalling.go @@ -48,6 +48,11 @@ type txJSON struct { // Only used for encoding: Hash common.Hash `json:"hash"` + // fee delegation + FeePayer *common.Address `json:"feePayer,omitempty"` + FV *hexutil.Big `json:"fv,omitempty"` + FR *hexutil.Big `json:"fr,omitempty"` + FS *hexutil.Big `json:"fs,omitempty"` } // MarshalJSON marshals as JSON with a hash. @@ -94,6 +99,29 @@ func (t *Transaction) MarshalJSON() ([]byte, error) { enc.V = (*hexutil.Big)(tx.V) enc.R = (*hexutil.Big)(tx.R) enc.S = (*hexutil.Big)(tx.S) + // fee delegation + case *FeeDelegateDynamicFeeTx: + nonce := tx.SenderTx.Nonce + enc.Nonce = (*hexutil.Uint64)(&nonce) + gas := tx.SenderTx.Gas + enc.Gas = (*hexutil.Uint64)(&gas) + enc.ChainID = (*hexutil.Big)(tx.SenderTx.ChainID) + accessList := tx.SenderTx.AccessList + enc.AccessList = &accessList + enc.MaxFeePerGas = (*hexutil.Big)(tx.SenderTx.GasFeeCap) + enc.MaxPriorityFeePerGas = (*hexutil.Big)(tx.SenderTx.GasTipCap) + enc.Value = (*hexutil.Big)(tx.SenderTx.Value) + data := tx.SenderTx.Data + enc.Data = (*hexutil.Bytes)(&data) + enc.To = tx.SenderTx.to() + enc.V = (*hexutil.Big)(tx.SenderTx.V) + enc.R = (*hexutil.Big)(tx.SenderTx.R) + enc.S = (*hexutil.Big)(tx.SenderTx.S) + + enc.FeePayer = tx.FeePayer + enc.FV = (*hexutil.Big)(tx.FV) + enc.FR = (*hexutil.Big)(tx.FR) + enc.FS = (*hexutil.Big)(tx.FS) } return json.Marshal(&enc) } @@ -262,6 +290,85 @@ func (t *Transaction) UnmarshalJSON(input []byte) error { return err } } + // fee delegation + case FeeDelegateDynamicFeeTxType: + var itx FeeDelegateDynamicFeeTx + inner = &itx + // Access list is optional for now. + if dec.AccessList != nil { + itx.SenderTx.AccessList = *dec.AccessList + } + if dec.ChainID == nil { + return errors.New("missing required field 'chainId' in transaction") + } + itx.SenderTx.ChainID = (*big.Int)(dec.ChainID) + if dec.To != nil { + itx.SenderTx.To = dec.To + } + if dec.Nonce == nil { + return errors.New("missing required field 'nonce' in transaction") + } + itx.SenderTx.Nonce = uint64(*dec.Nonce) + if dec.MaxPriorityFeePerGas == nil { + return errors.New("missing required field 'maxPriorityFeePerGas' for txdata") + } + itx.SenderTx.GasTipCap = (*big.Int)(dec.MaxPriorityFeePerGas) + if dec.MaxFeePerGas == nil { + return errors.New("missing required field 'maxFeePerGas' for txdata") + } + itx.SenderTx.GasFeeCap = (*big.Int)(dec.MaxFeePerGas) + if dec.Gas == nil { + return errors.New("missing required field 'gas' for txdata") + } + itx.SenderTx.Gas = uint64(*dec.Gas) + if dec.Value == nil { + return errors.New("missing required field 'value' in transaction") + } + itx.SenderTx.Value = (*big.Int)(dec.Value) + if dec.Data == nil { + return errors.New("missing required field 'input' in transaction") + } + itx.SenderTx.Data = *dec.Data + if dec.V == nil { + return errors.New("missing required field 'v' in transaction") + } + itx.SenderTx.V = (*big.Int)(dec.V) + if dec.R == nil { + return errors.New("missing required field 'r' in transaction") + } + itx.SenderTx.R = (*big.Int)(dec.R) + if dec.S == nil { + return errors.New("missing required field 's' in transaction") + } + itx.SenderTx.S = (*big.Int)(dec.S) + withSignature := itx.SenderTx.V.Sign() != 0 || itx.SenderTx.R.Sign() != 0 || itx.SenderTx.S.Sign() != 0 + if withSignature { + if err := sanityCheckSignature(itx.SenderTx.V, itx.SenderTx.R, itx.SenderTx.S, false); err != nil { + return err + } + } + if dec.FeePayer == nil { + return errors.New("missing required field 'feePayer' in transaction") + } + itx.FeePayer = dec.FeePayer + if dec.FV == nil { + return errors.New("missing required field 'fv' in transaction") + } + itx.FV = (*big.Int)(dec.FV) + if dec.FR == nil { + return errors.New("missing required field 'fr' in transaction") + } + itx.FR = (*big.Int)(dec.FR) + if dec.FS == nil { + return errors.New("missing required field 'fs' in transaction") + } + itx.FS = (*big.Int)(dec.FS) + withSignature = itx.FV.Sign() != 0 || itx.FR.Sign() != 0 || itx.FS.Sign() != 0 + if withSignature { + if err := sanityCheckSignature(itx.FV, itx.FR, itx.FS, false); err != nil { + return err + } + } default: return ErrTxTypeNotSupported diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go index 672918035586..8f4daefac3d8 100644 --- a/core/types/transaction_signing.go +++ b/core/types/transaction_signing.go @@ -172,6 +172,27 @@ func GetSender(signer Signer, tx *Transaction) *common.Address { return nil } +// fee delegation +func FeePayer(signer Signer, tx *Transaction) (common.Address, error) { + if sc := tx.feePayer.Load(); sc != nil { + sigCache := sc.(sigCache) + // If the signer used to derive from in a previous + // call is not the same as used current, invalidate + // the cache. + if sigCache.signer.Equal(signer) { + return sigCache.from, nil + } + } + + addr, err := signer.Sender(tx) + + if err != nil { + return common.Address{}, err + } + tx.feePayer.Store(sigCache{signer: signer, from: addr}) + return addr, nil +} + // Signer encapsulates transaction signature handling. The name of this type is slightly // misleading because Signers don't actually sign, they're just for validating and // processing of signatures. @@ -195,6 +216,73 @@ type Signer interface { Equal(Signer) bool } +// fee delegation +type feeDelegateSigner struct{ londonSigner } + +func NewFeeDelegateSigner(chainId *big.Int) Signer { + return feeDelegateSigner{londonSigner{eip2930Signer{NewEIP155Signer(chainId)}}} +} + +func (s feeDelegateSigner) Sender(tx *Transaction) (common.Address, error) { + if tx.Type() != FeeDelegateDynamicFeeTxType { + return s.londonSigner.Sender(tx) + } + V, R, S := tx.RawFeePayerSignatureValues() + // DynamicFee txs are defined to use 0 and 1 as their recovery + // id, add 27 to become equivalent to unprotected Homestead signatures. + V = new(big.Int).Add(V, big.NewInt(27)) + if tx.ChainId().Cmp(s.chainId) != 0 { + return common.Address{}, ErrInvalidChainId + } + return recoverPlain(s.Hash(tx), R, S, V, true) +} + +func (s feeDelegateSigner) Equal(s2 Signer) bool { + x, ok := s2.(feeDelegateSigner) + return ok && x.chainId.Cmp(s.chainId) == 0 +} + +func (s feeDelegateSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) { + txdata, ok := tx.inner.(*FeeDelegateDynamicFeeTx) + + if !ok { + return s.londonSigner.SignatureValues(tx, sig) + } + // Check that chain ID of tx matches the signer. We also accept ID zero here, + // because it indicates that the chain ID was not specified in the tx. + if txdata.SenderTx.chainID().Sign() != 0 && txdata.SenderTx.chainID().Cmp(s.chainId) != 0 { + return nil, nil, nil, ErrInvalidChainId + } + R, S, _ = decodeSignature(sig) + V = big.NewInt(int64(sig[64])) + return R, S, V, nil +} + +// Hash returns the hash to be signed by the sender. +// It does not uniquely identify the transaction. +func (s feeDelegateSigner) Hash(tx *Transaction) common.Hash { + senderV, senderR, senderS := tx.RawSignatureValues() + return prefixedRlpHash( + tx.Type(), + []interface{}{ + []interface{}{ + s.chainId, + tx.Nonce(), + tx.GasTipCap(), + tx.GasFeeCap(), + tx.Gas(), + tx.To(), + tx.Value(), + tx.Data(), + tx.AccessList(), + senderV, + senderR, + senderS, + }, + tx.FeePayer(), + }) +} + type londonSigner struct{ eip2930Signer } // NewLondonSigner returns a signer that accepts @@ -207,7 +295,7 @@ func NewLondonSigner(chainId *big.Int) Signer { } func (s londonSigner) Sender(tx *Transaction) (common.Address, error) { - if tx.Type() != DynamicFeeTxType { + if tx.Type() != DynamicFeeTxType && tx.Type() != FeeDelegateDynamicFeeTxType { // fee delegation return s.eip2930Signer.Sender(tx) } V, R, S := tx.RawSignatureValues() @@ -243,11 +331,16 @@ func (s londonSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big // Hash returns the hash to be signed by the sender. // It does not uniquely identify the transaction. func (s londonSigner) Hash(tx *Transaction) common.Hash { - if tx.Type() != DynamicFeeTxType { + if tx.Type() != DynamicFeeTxType && tx.Type() != FeeDelegateDynamicFeeTxType { // fee delegation return s.eip2930Signer.Hash(tx) } + // fee delegation + txType := tx.Type() + if txType == FeeDelegateDynamicFeeTxType { + txType = DynamicFeeTxType + } return prefixedRlpHash( - tx.Type(), + txType, // fee delegation []interface{}{ s.chainId, tx.Nonce(), diff --git a/interfaces.go b/interfaces.go index 76c1ef6908f2..53d2df6df559 100644 --- a/interfaces.go +++ b/interfaces.go @@ -142,6 +142,8 @@ type CallMsg struct { Data []byte // input data, usually an ABI-encoded contract method invocation AccessList types.AccessList // EIP-2930 access list. + // fee delegation + FeePayer *common.Address } // A ContractCaller provides contract calls, essentially transactions that are executed by diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 43713ad50e7a..74e058d6ca38 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -447,6 +447,11 @@ func (s *PrivateAccountAPI) LockAccount(addr common.Address) bool { func (s *PrivateAccountAPI) signTransaction(ctx context.Context, args *TransactionArgs, passwd string) (*types.Transaction, error) { // Look up the wallet containing the requested signer account := accounts.Account{Address: args.from()} + // fee delegation + if args.FeePayer != nil { + account = accounts.Account{Address: *args.FeePayer} + } + wallet, err := s.am.Find(account) if err != nil { return nil, err @@ -1285,6 +1290,11 @@ type RPCTransaction struct { V *hexutil.Big `json:"v"` R *hexutil.Big `json:"r"` S *hexutil.Big `json:"s"` + // fee delegation + FeePayer *common.Address `json:"feePayer,omitempty"` + FV *hexutil.Big `json:"fv,omitempty"` + FR *hexutil.Big `json:"fr,omitempty"` + FS *hexutil.Big `json:"fs,omitempty"` } // newRPCTransaction returns a transaction that will serialize to the RPC @@ -1331,6 +1341,26 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber } else { result.GasPrice = (*hexutil.Big)(tx.GasFeeCap()) } + // fee delegation + case types.FeeDelegateDynamicFeeTxType: + al := tx.AccessList() + result.Accesses = &al + result.ChainID = (*hexutil.Big)(tx.ChainId()) + 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 != (common.Hash{}) { + // price = min(tip, gasFeeCap - baseFee) + baseFee + price := math.BigMin(new(big.Int).Add(tx.GasTipCap(), baseFee), tx.GasFeeCap()) + result.GasPrice = (*hexutil.Big)(price) + } else { + result.GasPrice = (*hexutil.Big)(tx.GasFeeCap()) + } + result.FeePayer = tx.FeePayer() + fv, fr, fs := tx.RawFeePayerSignatureValues() + result.FV = (*hexutil.Big)(fv) + result.FR = (*hexutil.Big)(fr) + result.FS = (*hexutil.Big)(fs) } return result } @@ -1693,6 +1723,16 @@ func SubmitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (c if err != nil { return common.Hash{}, err } + // fee delegation + if tx.Type() == types.FeeDelegateDynamicFeeTxType { + feePayer, err := types.FeePayer(types.NewFeeDelegateSigner(b.ChainConfig().ChainID), tx) + if err != nil { + return common.Hash{}, err + } + if feePayer != *tx.FeePayer() { + return common.Hash{}, errors.New("FeePayer Signature error") + } + } if tx.To() == nil { addr := crypto.CreateAddress(from, tx.Nonce()) @@ -1834,6 +1874,19 @@ func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args Tra if err := checkTxFee(tx.GasPrice(), tx.Gas(), s.b.RPCTxFeeCap()); err != nil { return nil, err } + // fee delegation + if args.FeePayer != nil { + log.Info("SignTransaction", "FeePayer", args.FeePayer) + signed, err := s.sign(*args.FeePayer, tx) + if err != nil { + return nil, err + } + data, err := signed.MarshalBinary() + if err != nil { + return nil, err + } + return &SignTransactionResult{data, signed}, nil + } signed, err := s.sign(args.from(), tx) if err != nil { return nil, err @@ -2085,3 +2138,118 @@ func toHexSlice(b [][]byte) []string { } return r } + +// fee delegation +// SignRawFeeDelegateTransaction will create a transaction from the given arguments and +// tries to sign it with the key associated with args.feePayer. If the given passwd isn't +// able to decrypt the key it fails. The transaction is returned in RLP-form, not broadcast +// to other nodes +func (s *PrivateAccountAPI) SignRawFeeDelegateTransaction(ctx context.Context, args TransactionArgs, input hexutil.Bytes, passwd string) (*SignTransactionResult, error) { + if args.FeePayer == nil { + return nil, fmt.Errorf("missing FeePayer") + } + + // Look up the wallet containing the requested signer + account := accounts.Account{Address: *args.FeePayer} + + wallet, err := s.am.Find(account) + if err != nil { + return nil, err + } + + rawTx := new(types.Transaction) + if err := rawTx.UnmarshalBinary(input); err != nil { + return nil, err + } + + V, R, S := rawTx.RawSignatureValues() + if rawTx.Type() == types.DynamicFeeTxType { + SenderTx := types.DynamicFeeTx{ + To: rawTx.To(), + ChainID: rawTx.ChainId(), + Nonce: rawTx.Nonce(), + Gas: rawTx.Gas(), + GasFeeCap: rawTx.GasFeeCap(), + GasTipCap: rawTx.GasTipCap(), + Value: rawTx.Value(), + Data: rawTx.Data(), + AccessList: rawTx.AccessList(), + V: V, + R: R, + S: S, + } + + FeeDelegateDynamicFeeTx := &types.FeeDelegateDynamicFeeTx{ + FeePayer: args.FeePayer, + } + + FeeDelegateDynamicFeeTx.SetSenderTx(SenderTx) + tx := types.NewTx(FeeDelegateDynamicFeeTx) + + signed, err := wallet.SignTxWithPassphrase(account, passwd, tx, s.b.ChainConfig().ChainID) + + if err != nil { + return nil, err + } + data, err := signed.MarshalBinary() + if err != nil { + return nil, err + } + + return &SignTransactionResult{data, signed}, nil + } + + return nil, fmt.Errorf("senderTx type error") +} + +// fee delegation +// SignRawFeeDelegateTransaction will sign the given feeDelegate transaction with the feePayer account. +// The node needs to have the private key of the account corresponding with +// the given from address and it needs to be unlocked. +func (s *PublicTransactionPoolAPI) SignRawFeeDelegateTransaction(ctx context.Context, args TransactionArgs, input hexutil.Bytes) (*SignTransactionResult, error) { + if args.FeePayer == nil { + return nil, fmt.Errorf("missing FeePayer") + } + + rawTx := new(types.Transaction) + if err := rawTx.UnmarshalBinary(input); err != nil { + return nil, err + } + + V, R, S := rawTx.RawSignatureValues() + if rawTx.Type() == types.DynamicFeeTxType { + SenderTx := types.DynamicFeeTx{ + To: rawTx.To(), + ChainID: rawTx.ChainId(), + Nonce: rawTx.Nonce(), + Gas: rawTx.Gas(), + GasFeeCap: rawTx.GasFeeCap(), + GasTipCap: rawTx.GasTipCap(), + Value: rawTx.Value(), + Data: rawTx.Data(), + AccessList: rawTx.AccessList(), + V: V, + R: R, + S: S, + } + + FeeDelegateDynamicFeeTx := &types.FeeDelegateDynamicFeeTx{ + FeePayer: args.FeePayer, + } + + FeeDelegateDynamicFeeTx.SetSenderTx(SenderTx) + tx := types.NewTx(FeeDelegateDynamicFeeTx) + + signed, err := s.sign(*args.FeePayer, tx) + if err != nil { + return nil, err + } + data, err := signed.MarshalBinary() + if err != nil { + return nil, err + } + + return &SignTransactionResult{data, signed}, nil + } + return nil, fmt.Errorf("senderTx type error") +} diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go index 9c5950af58fe..f03f6700e502 100644 --- a/internal/ethapi/transaction_args.go +++ b/internal/ethapi/transaction_args.go @@ -52,6 +52,13 @@ type TransactionArgs struct { // Introduced by AccessListTxType transaction. AccessList *types.AccessList `json:"accessList,omitempty"` ChainID *hexutil.Big `json:"chainId,omitempty"` + + // fee delegation + FeePayer *common.Address `json:"feePayer"` + // Signature values + V *hexutil.Big `json:"v"` + R *hexutil.Big `json:"r"` + S *hexutil.Big `json:"s"` } // from retrieves the transaction sender address. @@ -264,6 +271,29 @@ func (args *TransactionArgs) toTransaction() *types.Transaction { Data: args.data(), AccessList: al, } + // fee delegation + if args.FeePayer != nil && args.V != nil && args.R != nil && args.S != nil { + SenderTx := 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, + V: (*big.Int)(args.V), + R: (*big.Int)(args.R), + S: (*big.Int)(args.S), + } + FeeDelegateDynamicFeeTx := &types.FeeDelegateDynamicFeeTx{ + FeePayer: args.FeePayer, + } + + FeeDelegateDynamicFeeTx.SetSenderTx(SenderTx) + return types.NewTx(FeeDelegateDynamicFeeTx) + } case args.AccessList != nil: data = &types.AccessListTx{ To: args.To, diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 2d49de251fde..10a2ece31ff1 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -696,6 +696,12 @@ web3._extend({ call: 'eth_getLogs', params: 1, }), + // fee delegation + new web3._extend.Method({ + name: 'signRawFeeDelegateTransaction', + call: 'eth_signRawFeeDelegateTransaction', + params: 2, + }), ], properties: [ new web3._extend.Property({ @@ -837,7 +843,13 @@ web3._extend({ name: 'initializeWallet', call: 'personal_initializeWallet', params: 1 - }) + }), + // fee delegation + new web3._extend.Method({ + name: 'signRawFeeDelegateTransaction', + call: 'personal_signRawFeeDelegateTransaction', + params: 3, + }), ], properties: [ new web3._extend.Property({ diff --git a/light/txpool.go b/light/txpool.go index d12694d8f992..f890c534abd0 100644 --- a/light/txpool.go +++ b/light/txpool.go @@ -69,6 +69,8 @@ type TxPool struct { istanbul bool // Fork indicator whether we are in the istanbul stage. eip2718 bool // Fork indicator whether we are in the eip2718 stage. + // fee delegation + feedelegation bool // Fork indicator whether we are in the fee delegation stage. } // TxRelayBackend provides an interface to the mechanism that forwards transacions @@ -318,6 +320,8 @@ func (pool *TxPool) setNewHead(head *types.Header) { next := new(big.Int).Add(head.Number, big.NewInt(1)) pool.istanbul = pool.config.IsIstanbul(next) pool.eip2718 = pool.config.IsBerlin(next) + // fee delegation + pool.feedelegation = pool.config.IsFeeDelegation(next) } // Stop stops the light transaction pool @@ -380,8 +384,27 @@ func (pool *TxPool) validateTx(ctx context.Context, tx *types.Transaction) error // Transactor should have enough funds to cover the costs // cost == V + GP * GL - if b := currentState.GetBalance(from); b.Cmp(tx.Cost()) < 0 { - return core.ErrInsufficientFunds + + // fee delegation + if tx.Type() == types.FeeDelegateDynamicFeeTxType { + if !pool.feedelegation { + return core.ErrTxTypeNotSupported + } + // Make sure the transaction is signed properly. + feePayer, err := types.FeePayer(types.NewFeeDelegateSigner(pool.config.ChainID), tx) + if *tx.FeePayer() != feePayer || err != nil { + return core.ErrInvalidFeePayer + } + if currentState.GetBalance(feePayer).Cmp(tx.FeePayerCost()) < 0 { + return core.ErrFeePayerInsufficientFunds + } + if currentState.GetBalance(from).Cmp(tx.Cost()) < 0 { + return core.ErrSenderInsufficientFunds + } + } else { + if b := currentState.GetBalance(from); b.Cmp(tx.Cost()) < 0 { + return core.ErrInsufficientFunds + } } // Should supply enough intrinsic gas diff --git a/params/config.go b/params/config.go index cfcfbc15e70b..c5458df0b002 100644 --- a/params/config.go +++ b/params/config.go @@ -521,6 +521,16 @@ func (c *ChainConfig) IsPangyo(num *big.Int) bool { return isForked(c.PangyoBlock, num) } +// fee delegation +// IsFeeDelegation returns whether num is either equal to the fee delegation fork block or greater. +func (c *ChainConfig) IsFeeDelegation(num *big.Int) bool { + // TBD + // Test code + //FeeDelegateBlock := big.NewInt(277000) + //return isForked(FeeDelegateBlock, num) + return false +} + // IsArrowGlacier returns whether num is either equal to the Arrow Glacier (EIP-4345) fork block or greater. func (c *ChainConfig) IsArrowGlacier(num *big.Int) bool { return isForked(c.ArrowGlacierBlock, num)