Skip to content

Commit

Permalink
Merge pull request METADIUM#40 from cp-yoonjin/dev_feeDelegation
Browse files Browse the repository at this point in the history
Added functions required to support fee delegation transaction
  • Loading branch information
trident90 authored Apr 20, 2023
2 parents 5d99937 + b64e4c2 commit 98e8bc5
Show file tree
Hide file tree
Showing 21 changed files with 1,323 additions and 30 deletions.
487 changes: 487 additions & 0 deletions FEEDELEGATION.md

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions accounts/abi/bind/backends/simulated.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
11 changes: 11 additions & 0 deletions accounts/keystore/keystore.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
12 changes: 12 additions & 0 deletions accounts/scwallet/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -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[:])
Expand Down
12 changes: 12 additions & 0 deletions core/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -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("fee delegation: invalid 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: insufficient feePayer's 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: insufficient sender's funds for value")
)
76 changes: 58 additions & 18 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 supported", 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
}

Expand Down Expand Up @@ -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)
Expand Down
73 changes: 71 additions & 2 deletions core/tx_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
6 changes: 6 additions & 0 deletions core/types/access_list_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
6 changes: 6 additions & 0 deletions core/types/dynamic_fee_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
Loading

0 comments on commit 98e8bc5

Please sign in to comment.