Skip to content

Commit 05b05ca

Browse files
authored
Deprecate gateway fee (#2120)
* Discard tx with gateway fee in state processor * Reject txs with gateway fee in tx pools * Deprecate gateway fee APIs in JS after hardfork * Fix failing LES tests * cleanup: Use available field * Move `light.gatewayfee` flag to deprecated section * Don't set gateway fee fields in tx after g fork * Fix tx_pool tests * Fix e2e transfer tests * Pull test changes from monorepo
1 parent 84da6f3 commit 05b05ca

14 files changed

+314
-15
lines changed

cmd/geth/usage.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ var AppHelpFlagGroups = []flags.FlagGroup{
6161
utils.LightIngressFlag,
6262
utils.LightEgressFlag,
6363
utils.LightMaxPeersFlag,
64-
utils.LightGatewayFeeFlag,
6564
utils.UltraLightServersFlag,
6665
utils.UltraLightFractionFlag,
6766
utils.UltraLightOnlyAnnounceFlag,
@@ -231,6 +230,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{
231230
utils.LegacyWSApiFlag,
232231
utils.LegacyGraphQLListenAddrFlag,
233232
utils.LegacyGraphQLPortFlag,
233+
utils.LightGatewayFeeFlag,
234234
},
235235
},
236236
{

core/error.go

+4
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,8 @@ var (
113113
// ErrUnprotectedTransaction is returned if replay protection is required (post-Donut) but the transaction doesn't
114114
// use it.
115115
ErrUnprotectedTransaction = errors.New("replay protection is required")
116+
117+
// ErrGatewayFeeDeprecated is returned when a transaction containing a gateway fee is encountered after the
118+
// G hardfork
119+
ErrGatewayFeeDeprecated = errors.New("gateway fee is deprecated")
116120
)

core/state_processor.go

+12-3
Original file line numberDiff line numberDiff line change
@@ -85,17 +85,17 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
8585
baseFee *big.Int
8686
sysCtx *SysContractCallCtx
8787
)
88-
if p.bc.Config().IsEspresso(blockNumber) {
88+
if p.config.IsEspresso(blockNumber) {
8989
sysCtx = NewSysContractCallCtx(header, statedb, p.bc)
90-
if p.bc.Config().FakeBaseFee != nil {
90+
if p.config.FakeBaseFee != nil {
9191
sysCtx = MockSysContractCallCtx(p.bc.Config().FakeBaseFee)
9292
}
9393
}
9494
blockContext := NewEVMBlockContext(header, p.bc, nil)
9595
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg)
9696
// Iterate over and process the individual transactions
9797
for i, tx := range block.Transactions() {
98-
if p.bc.chainConfig.IsEspresso(header.Number) {
98+
if p.config.IsEspresso(header.Number) {
9999
baseFee = sysCtx.GetGasPriceMinimum(tx.FeeCurrency())
100100
}
101101
msg, err := tx.AsMessage(types.MakeSigner(p.config, header.Number), baseFee)
@@ -124,6 +124,15 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, gp *GasPool
124124
return nil, ErrUnprotectedTransaction
125125
}
126126

127+
// CIP 57 deprecates full node incentives
128+
// Check that neither `GatewayFeeRecipient` nor `GatewayFee` are set, otherwise reject the transaction
129+
if config.IsGFork(blockNumber) {
130+
gatewayFeeSet := !(msg.GatewayFee() == nil || msg.GatewayFee().Cmp(common.Big0) == 0)
131+
if msg.GatewayFeeRecipient() != nil || gatewayFeeSet {
132+
return nil, ErrGatewayFeeDeprecated
133+
}
134+
}
135+
127136
// Create a new context to be used in the EVM environment
128137
txContext := NewEVMTxContext(msg)
129138
evm.Reset(txContext, statedb)

core/tx_pool.go

+8
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ type TxPool struct {
276276
istanbul bool // Fork indicator whether we are in the istanbul stage.
277277
donut bool // Fork indicator for the Donut fork.
278278
espresso bool // Fork indicator for the Espresso fork.
279+
gfork bool // Fork indicator for the G fork.
279280

280281
currentState *state.StateDB // Current state in the blockchain head
281282
currentVMRunner vm.EVMRunner // Current EVMRunner
@@ -659,6 +660,12 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
659660
return err
660661
}
661662

663+
// CIP 57 deprecates full node incentives
664+
gatewayFeeSet := !(tx.GatewayFee() == nil || tx.GatewayFee().Cmp(common.Big0) == 0)
665+
if pool.gfork && (tx.GatewayFeeRecipient() != nil || gatewayFeeSet) {
666+
return ErrGatewayFeeDeprecated
667+
}
668+
662669
// Accept only legacy transactions until EIP-2718/2930 activates.
663670
if !pool.espresso && tx.Type() != types.LegacyTxType {
664671
return ErrTxTypeNotSupported
@@ -1412,6 +1419,7 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) {
14121419
pool.istanbul = pool.chainconfig.IsIstanbul(next)
14131420
pool.donut = pool.chainconfig.IsDonut(next)
14141421
pool.espresso = pool.chainconfig.IsEspresso(next)
1422+
pool.gfork = pool.chainconfig.IsGFork(next)
14151423
}
14161424

14171425
// promoteExecutables moves transactions that have become processable from the

core/tx_pool_test.go

+51-1
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,57 @@ func TestInvalidTransactions(t *testing.T) {
341341
t.Error("expected", ErrIntrinsicGas, "got", err)
342342
}
343343

344-
// TODO(joshua): Convert this to testAddGatewayFee
344+
// Adding a gateway fee should result in deprecation error.
345+
tx = lesTransaction(0, 100, big.NewInt(50), key)
346+
if err := pool.AddRemote(tx); err != ErrGatewayFeeDeprecated {
347+
t.Error("expected", ErrGatewayFeeDeprecated, "got", err)
348+
}
349+
350+
// Should still return a deprecation error.
351+
pool.currentState.AddBalance(from, tx.GatewayFee())
352+
if err := pool.AddRemote(tx); err != ErrGatewayFeeDeprecated {
353+
t.Error("expected", ErrGatewayFeeDeprecated, "got", err)
354+
}
355+
356+
testSetNonce(pool, from, 1)
357+
testAddBalance(pool, from, big.NewInt(0xffffffffffffff))
358+
359+
tx = transaction(0, 100000, key)
360+
if err := pool.AddRemote(tx); !errors.Is(err, ErrNonceTooLow) {
361+
t.Error("expected", ErrNonceTooLow)
362+
}
363+
364+
tx = transaction(1, 100000, key)
365+
pool.gasPrice = big.NewInt(1000)
366+
if err := pool.AddRemote(tx); err != ErrUnderpriced {
367+
t.Error("expected", ErrUnderpriced, "got", err)
368+
}
369+
if err := pool.AddLocal(tx); err != nil {
370+
t.Error("expected", nil, "got", err)
371+
}
372+
}
373+
374+
func TestInvalidTransactionsPreGFork(t *testing.T) {
375+
t.Parallel()
376+
377+
pool, key := setupTxPool()
378+
defer pool.Stop()
379+
pool.gfork = false
380+
381+
tx := transaction(0, 100, key)
382+
from, _ := deriveSender(tx)
383+
384+
testAddBalance(pool, from, big.NewInt(1))
385+
if err := pool.AddRemote(tx); !errors.Is(err, ErrInsufficientFunds) {
386+
t.Error("expected", ErrInsufficientFunds)
387+
}
388+
389+
balance := new(big.Int).Add(tx.Value(), new(big.Int).Mul(new(big.Int).SetUint64(tx.Gas()), tx.GasPrice()))
390+
testAddBalance(pool, from, balance)
391+
if err := pool.AddRemote(tx); !errors.Is(err, ErrIntrinsicGas) {
392+
t.Error("expected", ErrIntrinsicGas, "got", err)
393+
}
394+
345395
// Adding a gateway fee should result in insufficient funds again.
346396
tx = lesTransaction(0, 100, big.NewInt(50), key)
347397
if err := pool.AddRemote(tx); err != ErrInsufficientFunds {

e2e_test/e2e_transfer_test.go

+186-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
ethereum "github.com/celo-org/celo-blockchain"
1212
"github.com/celo-org/celo-blockchain/common"
1313
"github.com/celo-org/celo-blockchain/common/hexutil"
14+
"github.com/celo-org/celo-blockchain/core"
1415
"github.com/celo-org/celo-blockchain/core/types"
1516
"github.com/celo-org/celo-blockchain/ethclient"
1617
"github.com/celo-org/celo-blockchain/internal/ethapi"
@@ -31,7 +32,191 @@ const (
3132
func TestTransferCELO(t *testing.T) {
3233
ac := test.AccountConfig(1, 3)
3334
gc, ec, err := test.BuildConfig(ac)
34-
gc.Hardforks.EspressoBlock = big.NewInt(0)
35+
require.NoError(t, err)
36+
network, shutdown, err := test.NewNetwork(ac, gc, ec)
37+
require.NoError(t, err)
38+
defer shutdown()
39+
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
40+
defer cancel()
41+
42+
node := network[0] // validator node
43+
client := node.WsClient
44+
devAccounts := test.Accounts(ac.DeveloperAccounts(), gc.ChainConfig())
45+
sender := devAccounts[0]
46+
recipient := devAccounts[1]
47+
gateWayFeeRecipient := devAccounts[2]
48+
49+
// Get datum to set GasPrice/MaxFeePerGas/MaxPriorityFeePerGas to sensible values
50+
header, err := network[0].WsClient.HeaderByNumber(ctx, common.Big0)
51+
require.NoError(t, err)
52+
datum, err := network[0].Eth.APIBackend.GasPriceMinimumForHeader(ctx, nil, header)
53+
require.NoError(t, err)
54+
55+
testCases := []struct {
56+
name string
57+
txArgs *ethapi.TransactionArgs
58+
expectedErr error
59+
}{
60+
{
61+
name: "eth compatible LegacyTxType",
62+
txArgs: &ethapi.TransactionArgs{
63+
To: &recipient.Address,
64+
Value: (*hexutil.Big)(new(big.Int).SetInt64(oneCelo)),
65+
GasPrice: (*hexutil.Big)(datum.Mul(datum, new(big.Int).SetInt64(4))),
66+
},
67+
expectedErr: nil,
68+
},
69+
{
70+
name: "eth incompatible LegacyTxType",
71+
txArgs: &ethapi.TransactionArgs{
72+
To: &recipient.Address,
73+
Value: (*hexutil.Big)(new(big.Int).SetInt64(oneCelo)),
74+
GasPrice: (*hexutil.Big)(datum.Mul(datum, new(big.Int).SetInt64(4))),
75+
GatewayFee: (*hexutil.Big)(new(big.Int).SetInt64(oneCelo / 10)),
76+
GatewayFeeRecipient: &gateWayFeeRecipient.Address,
77+
},
78+
expectedErr: core.ErrGatewayFeeDeprecated,
79+
},
80+
{
81+
name: "AccessListTxType",
82+
txArgs: &ethapi.TransactionArgs{
83+
To: &recipient.Address,
84+
Value: (*hexutil.Big)(new(big.Int).SetInt64(oneCelo)),
85+
GasPrice: (*hexutil.Big)(datum.Mul(datum, new(big.Int).SetInt64(4))),
86+
AccessList: &types.AccessList{},
87+
},
88+
expectedErr: nil,
89+
},
90+
{
91+
name: "DynamicFeeTxType - tip = MaxFeePerGas - BaseFee",
92+
txArgs: &ethapi.TransactionArgs{
93+
To: &recipient.Address,
94+
Value: (*hexutil.Big)(new(big.Int).SetInt64(oneCelo)),
95+
MaxFeePerGas: (*hexutil.Big)(datum.Mul(datum, new(big.Int).SetInt64(4))),
96+
MaxPriorityFeePerGas: (*hexutil.Big)(datum.Mul(datum, new(big.Int).SetInt64(4))),
97+
},
98+
expectedErr: nil,
99+
},
100+
{
101+
name: "DynamicFeeTxType - tip = MaxPriorityFeePerGas",
102+
txArgs: &ethapi.TransactionArgs{
103+
To: &recipient.Address,
104+
Value: (*hexutil.Big)(new(big.Int).SetInt64(oneCelo)),
105+
MaxFeePerGas: (*hexutil.Big)(datum.Mul(datum, new(big.Int).SetInt64(4))),
106+
MaxPriorityFeePerGas: (*hexutil.Big)(datum),
107+
},
108+
expectedErr: nil,
109+
},
110+
{
111+
name: "CeloDynamicFeeTxType - gas = MaxFeePerGas - BaseFee",
112+
txArgs: &ethapi.TransactionArgs{
113+
To: &recipient.Address,
114+
Value: (*hexutil.Big)(new(big.Int).SetInt64(oneCelo)),
115+
MaxFeePerGas: (*hexutil.Big)(datum.Mul(datum, new(big.Int).SetInt64(4))),
116+
MaxPriorityFeePerGas: (*hexutil.Big)(datum.Mul(datum, new(big.Int).SetInt64(4))),
117+
GatewayFee: (*hexutil.Big)(new(big.Int).SetInt64(oneCelo / 10)),
118+
GatewayFeeRecipient: &gateWayFeeRecipient.Address,
119+
},
120+
expectedErr: core.ErrGatewayFeeDeprecated,
121+
},
122+
{
123+
name: "CeloDynamicFeeTxType - MaxPriorityFeePerGas",
124+
txArgs: &ethapi.TransactionArgs{
125+
To: &recipient.Address,
126+
Value: (*hexutil.Big)(new(big.Int).SetInt64(oneCelo)),
127+
MaxFeePerGas: (*hexutil.Big)(datum.Mul(datum, new(big.Int).SetInt64(4))),
128+
MaxPriorityFeePerGas: (*hexutil.Big)(datum),
129+
GatewayFee: (*hexutil.Big)(new(big.Int).SetInt64(oneCelo / 10)),
130+
GatewayFeeRecipient: &gateWayFeeRecipient.Address,
131+
},
132+
expectedErr: core.ErrGatewayFeeDeprecated,
133+
},
134+
}
135+
136+
for _, tc := range testCases {
137+
t.Run(tc.name, func(t *testing.T) {
138+
watcher := test.NewBalanceWatcher(client, []common.Address{sender.Address, recipient.Address, gateWayFeeRecipient.Address, node.Address})
139+
blockNum, err := client.BlockNumber(ctx)
140+
require.NoError(t, err)
141+
signer := types.MakeSigner(devAccounts[0].ChainConfig, new(big.Int).SetUint64(blockNum))
142+
tx, err := prepareTransaction(*tc.txArgs, sender.Key, sender.Address, signer, client)
143+
require.NoError(t, err)
144+
err = client.SendTransaction(ctx, tx)
145+
if tc.expectedErr != nil {
146+
// Once the error is checked, there's nothing more to do
147+
if err.Error() != tc.expectedErr.Error() {
148+
t.Error("Expected error", tc.expectedErr, "got", err)
149+
}
150+
return
151+
}
152+
153+
require.NoError(t, err, "SendTransaction failed", "tx", *tx)
154+
err = network.AwaitTransactions(ctx, tx)
155+
require.NoError(t, err)
156+
watcher.Update()
157+
receipt, err := client.TransactionReceipt(ctx, tx.Hash())
158+
require.NoError(t, err)
159+
160+
// check value goes to recipient
161+
expected := tx.Value()
162+
actual := watcher.Delta(recipient.Address)
163+
assert.Equal(t, expected, actual, "Recipient's balance increase unexpected", "expected", expected.Int64(), "actual", actual.Int64())
164+
165+
// Check tip goes to validator
166+
header, err := network[0].WsClient.HeaderByNumber(ctx, receipt.BlockNumber)
167+
require.NoError(t, err)
168+
gpm, err := network[0].Eth.APIBackend.GasPriceMinimumForHeader(ctx, nil, header)
169+
require.NoError(t, err)
170+
baseFee := new(big.Int).Mul(gpm, new(big.Int).SetUint64(receipt.GasUsed))
171+
switch tx.Type() {
172+
case types.LegacyTxType, types.AccessListTxType:
173+
fee := new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(receipt.GasUsed))
174+
expected = new(big.Int).Sub(fee, baseFee)
175+
case types.DynamicFeeTxType, types.CeloDynamicFeeTxType:
176+
expected = tx.EffectiveGasTipValue(gpm)
177+
expected.Mul(expected, new(big.Int).SetUint64(receipt.GasUsed))
178+
}
179+
actual = watcher.Delta(node.Address)
180+
assert.Equal(t, expected, actual, "Validator's balance increase unexpected", "expected", expected.Int64(), "actual", actual.Int64())
181+
182+
// check value + tx fee + gateway fee are subtracted from sender
183+
var fee *big.Int
184+
switch tx.Type() {
185+
case types.LegacyTxType, types.AccessListTxType:
186+
fee = new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(receipt.GasUsed))
187+
case types.DynamicFeeTxType, types.CeloDynamicFeeTxType:
188+
tip := tx.EffectiveGasTipValue(gpm)
189+
tip.Mul(tip, new(big.Int).SetUint64(receipt.GasUsed))
190+
fee = new(big.Int).Add(tip, baseFee)
191+
}
192+
consumed := new(big.Int).Add(tx.Value(), fee)
193+
if tx.GatewayFeeRecipient() != nil && tx.GatewayFee() != nil {
194+
consumed.Add(consumed, tx.GatewayFee())
195+
}
196+
expected = new(big.Int).Neg(consumed)
197+
actual = watcher.Delta(sender.Address)
198+
assert.Equal(t, expected, actual, "Sender's balance decrease unexpected", "expected", expected.Int64(), "actual", expected.Int64())
199+
200+
// Check gateway fee
201+
if tx.GatewayFeeRecipient() != nil && tx.GatewayFee() != nil {
202+
expected = tx.GatewayFee()
203+
actual = watcher.Delta(gateWayFeeRecipient.Address)
204+
assert.Equal(t, expected, actual, "gateWayFeeRecipient's balance increase unexpected", "expected", expected.Int64(), "actual", actual.Int64())
205+
}
206+
})
207+
}
208+
}
209+
210+
// TestTransferCELO checks following accounts:
211+
// - Sender account has transfer value, transaction fee deducted
212+
// - Receiver account has transfer value added.
213+
// - Governance account has base fee added.
214+
// - validator account has tip fee added.
215+
func TestTransferCELOPreGFork(t *testing.T) {
216+
ac := test.AccountConfig(1, 3)
217+
gc, ec, err := test.BuildConfig(ac)
218+
gc.Hardforks.GForkBlock = nil
219+
35220
require.NoError(t, err)
36221
network, shutdown, err := test.NewNetwork(ac, gc, ec)
37222
require.NoError(t, err)

internal/ethapi/transaction_args.go

+6-4
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,14 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error {
8686
return errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
8787
}
8888
// After london, default to 1559 unless gasPrice is set
89-
head := b.CurrentHeader()
89+
currentBlockNumber := b.CurrentHeader().Number
90+
isGFork := b.ChainConfig().IsGFork(currentBlockNumber)
91+
9092
// If user specifies both maxPriorityfee and maxFee, then we do not
9193
// need to consult the chain for defaults. It's definitely a London tx.
9294
if args.MaxPriorityFeePerGas == nil || args.MaxFeePerGas == nil {
9395
// In this clause, user left some fields unspecified.
94-
if b.ChainConfig().IsEspresso(head.Number) {
96+
if b.ChainConfig().IsEspresso(currentBlockNumber) {
9597
if args.GasPrice == nil || args.GasPrice.ToInt().Cmp(big.NewInt(0)) == 0 {
9698
if args.MaxPriorityFeePerGas == nil {
9799
tip, err := b.SuggestGasTipCap(ctx, args.FeeCurrency)
@@ -149,7 +151,7 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error {
149151
if args.To == nil && len(args.data()) == 0 {
150152
return errors.New(`contract creation without any data provided`)
151153
}
152-
if args.GatewayFeeRecipient == nil && !args.EthCompatible {
154+
if args.GatewayFeeRecipient == nil && !args.EthCompatible && !isGFork {
153155
recipient := b.GatewayFeeRecipient()
154156
if (recipient != common.Address{}) {
155157
args.GatewayFeeRecipient = &recipient
@@ -181,7 +183,7 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error {
181183
args.Gas = &estimated
182184
log.Trace("Estimate gas usage automatically", "gas", args.Gas)
183185
}
184-
if args.GatewayFeeRecipient != nil && args.GatewayFee == nil {
186+
if args.GatewayFeeRecipient != nil && args.GatewayFee == nil && !isGFork {
185187
args.GatewayFee = (*hexutil.Big)(b.GatewayFee())
186188
}
187189
if args.ChainID == nil {

0 commit comments

Comments
 (0)