Skip to content

Commit

Permalink
add TxFeeCap config (#3611) (#3673)
Browse files Browse the repository at this point in the history
  • Loading branch information
tclemos authored Jun 5, 2024
1 parent d34c824 commit 09227d1
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 16 deletions.
4 changes: 4 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,10 @@ func Test_Defaults(t *testing.T) {
path: "Pool.GlobalQueue",
expectedValue: uint64(1024),
},
{
path: "Pool.TxFeeCap",
expectedValue: float64(1),
},
{
path: "Pool.EffectiveGasPrice.Enabled",
expectedValue: false,
Expand Down
1 change: 1 addition & 0 deletions config/default.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ MinAllowedGasPriceInterval = "5m"
PollMinAllowedGasPriceInterval = "15s"
AccountQueue = 64
GlobalQueue = 1024
TxFeeCap = 1.0
[Pool.EffectiveGasPrice]
Enabled = false
L1GasPriceFactor = 0.25
Expand Down
2 changes: 1 addition & 1 deletion docs/config-file/node-config-doc.html

Large diffs are not rendered by default.

44 changes: 30 additions & 14 deletions docs/config-file/node-config-doc.md
Original file line number Diff line number Diff line change
Expand Up @@ -427,20 +427,21 @@ MaxGasPriceLimit=0
**Type:** : `object`
**Description:** Pool service configuration

| Property | Pattern | Type | Deprecated | Definition | Title/Description |
| ------------------------------------------------------------------------------- | ------- | ------- | ---------- | ---------- | ---------------------------------------------------------------------------------------------------- |
| - [IntervalToRefreshBlockedAddresses](#Pool_IntervalToRefreshBlockedAddresses ) | No | string | No | - | Duration |
| - [IntervalToRefreshGasPrices](#Pool_IntervalToRefreshGasPrices ) | No | string | No | - | Duration |
| - [MaxTxBytesSize](#Pool_MaxTxBytesSize ) | No | integer | No | - | MaxTxBytesSize is the max size of a transaction in bytes |
| - [MaxTxDataBytesSize](#Pool_MaxTxDataBytesSize ) | No | integer | No | - | MaxTxDataBytesSize is the max size of the data field of a transaction in bytes |
| - [DB](#Pool_DB ) | No | object | No | - | DB is the database configuration |
| - [DefaultMinGasPriceAllowed](#Pool_DefaultMinGasPriceAllowed ) | No | integer | No | - | DefaultMinGasPriceAllowed is the default min gas price to suggest |
| - [MinAllowedGasPriceInterval](#Pool_MinAllowedGasPriceInterval ) | No | string | No | - | Duration |
| - [PollMinAllowedGasPriceInterval](#Pool_PollMinAllowedGasPriceInterval ) | No | string | No | - | Duration |
| - [AccountQueue](#Pool_AccountQueue ) | No | integer | No | - | AccountQueue represents the maximum number of non-executable transaction slots permitted per account |
| - [GlobalQueue](#Pool_GlobalQueue ) | No | integer | No | - | GlobalQueue represents the maximum number of non-executable transaction slots for all accounts |
| - [EffectiveGasPrice](#Pool_EffectiveGasPrice ) | No | object | No | - | EffectiveGasPrice is the config for the effective gas price calculation |
| - [ForkID](#Pool_ForkID ) | No | integer | No | - | ForkID is the current fork ID of the chain |
| Property | Pattern | Type | Deprecated | Definition | Title/Description |
| ------------------------------------------------------------------------------- | ------- | ------- | ---------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| - [IntervalToRefreshBlockedAddresses](#Pool_IntervalToRefreshBlockedAddresses ) | No | string | No | - | Duration |
| - [IntervalToRefreshGasPrices](#Pool_IntervalToRefreshGasPrices ) | No | string | No | - | Duration |
| - [MaxTxBytesSize](#Pool_MaxTxBytesSize ) | No | integer | No | - | MaxTxBytesSize is the max size of a transaction in bytes |
| - [MaxTxDataBytesSize](#Pool_MaxTxDataBytesSize ) | No | integer | No | - | MaxTxDataBytesSize is the max size of the data field of a transaction in bytes |
| - [DB](#Pool_DB ) | No | object | No | - | DB is the database configuration |
| - [DefaultMinGasPriceAllowed](#Pool_DefaultMinGasPriceAllowed ) | No | integer | No | - | DefaultMinGasPriceAllowed is the default min gas price to suggest |
| - [MinAllowedGasPriceInterval](#Pool_MinAllowedGasPriceInterval ) | No | string | No | - | Duration |
| - [PollMinAllowedGasPriceInterval](#Pool_PollMinAllowedGasPriceInterval ) | No | string | No | - | Duration |
| - [AccountQueue](#Pool_AccountQueue ) | No | integer | No | - | AccountQueue represents the maximum number of non-executable transaction slots permitted per account |
| - [GlobalQueue](#Pool_GlobalQueue ) | No | integer | No | - | GlobalQueue represents the maximum number of non-executable transaction slots for all accounts |
| - [EffectiveGasPrice](#Pool_EffectiveGasPrice ) | No | object | No | - | EffectiveGasPrice is the config for the effective gas price calculation |
| - [ForkID](#Pool_ForkID ) | No | integer | No | - | ForkID is the current fork ID of the chain |
| - [TxFeeCap](#Pool_TxFeeCap ) | No | number | No | - | TxFeeCap is the global transaction fee(price * gaslimit) cap for<br />send-transaction variants. The unit is ether. 0 means no cap. |

### <a name="Pool_IntervalToRefreshBlockedAddresses"></a>7.1. `Pool.IntervalToRefreshBlockedAddresses`

Expand Down Expand Up @@ -905,6 +906,21 @@ L2GasPriceSuggesterFactor=0.5
ForkID=0
```

### <a name="Pool_TxFeeCap"></a>7.13. `Pool.TxFeeCap`

**Type:** : `number`

**Default:** `1`

**Description:** TxFeeCap is the global transaction fee(price * gaslimit) cap for
send-transaction variants. The unit is ether. 0 means no cap.

**Example setting the default value** (1):
```
[Pool]
TxFeeCap=1
```

## <a name="RPC"></a>8. `[RPC]`

**Type:** : `object`
Expand Down
5 changes: 5 additions & 0 deletions docs/config-file/node-config-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,11 @@
"type": "integer",
"description": "ForkID is the current fork ID of the chain",
"default": 0
},
"TxFeeCap": {
"type": "number",
"description": "TxFeeCap is the global transaction fee(price * gaslimit) cap for\nsend-transaction variants. The unit is ether. 0 means no cap.",
"default": 1
}
},
"additionalProperties": false,
Expand Down
4 changes: 4 additions & 0 deletions pool/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ type Config struct {

// ForkID is the current fork ID of the chain
ForkID uint64 `mapstructure:"ForkID"`

// TxFeeCap is the global transaction fee(price * gaslimit) cap for
// send-transaction variants. The unit is ether. 0 means no cap.
TxFeeCap float64 `mapstructure:"TxFeeCap"`
}

// EffectiveGasPriceCfg contains the configuration properties for the effective gas price
Expand Down
22 changes: 22 additions & 0 deletions pool/pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"math/big"
"strconv"
"sync"
"time"

Expand All @@ -16,6 +17,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
)

var (
Expand Down Expand Up @@ -457,6 +459,10 @@ func (p *Pool) validateTx(ctx context.Context, poolTx Transaction) error {
return ErrNegativeValue
}

if err := checkTxFee(poolTx.GasPrice(), poolTx.Gas(), p.cfg.TxFeeCap); err != nil {
return err
}

// check if sender is blocked
_, blocked := p.blockedAddresses.Load(from.String())
if blocked {
Expand Down Expand Up @@ -729,3 +735,19 @@ func IntrinsicGas(tx types.Transaction) (uint64, error) {
}
return gas, nil
}

// checkTxFee is an internal function used to check whether the fee of
// the given transaction is _reasonable_(under the cap).
func checkTxFee(gasPrice *big.Int, gas uint64, cap float64) error {
// Short circuit if there is no cap for transaction fee at all.
if cap == 0 {
return nil
}
feeEth := new(big.Float).Quo(new(big.Float).SetInt(new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(gas))), new(big.Float).SetInt(big.NewInt(params.Ether)))
feeFloat, _ := feeEth.Float64()
if feeFloat > cap {
feeFloatTruncated := strconv.FormatFloat(feeFloat, 'f', -1, 64)
return fmt.Errorf("tx fee (%s ether) exceeds the configured cap (%.2f ether)", feeFloatTruncated, cap)
}
return nil
}
114 changes: 113 additions & 1 deletion pool/pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ var (
IntervalToRefreshGasPrices: cfgTypes.NewDuration(5 * time.Second),
AccountQueue: 15,
GlobalQueue: 20,
TxFeeCap: 1,
EffectiveGasPrice: pool.EffectiveGasPriceCfg{
Enabled: true,
L1GasPriceFactor: 0.25,
Expand Down Expand Up @@ -1086,10 +1087,12 @@ func Test_TryAddIncompatibleTxs(t *testing.T) {
expectedError: fmt.Errorf("chain id higher than allowed, max allowed is %v", uint64(math.MaxUint64)),
},
}
c := cfg
c.TxFeeCap = 0
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
incompatibleTx := testCase.createIncompatibleTx()
p := setupPool(t, cfg, bc, s, st, incompatibleTx.ChainId().Uint64(), ctx, eventLog)
p := setupPool(t, c, bc, s, st, incompatibleTx.ChainId().Uint64(), ctx, eventLog)
err = p.AddTx(ctx, incompatibleTx, ip)
assert.Equal(t, testCase.expectedError, err)
})
Expand Down Expand Up @@ -1965,6 +1968,115 @@ func Test_AddTx_IPValidation(t *testing.T) {
}
}

func Test_AddTx_TxFeeCap(t *testing.T) {
eventStorage, err := nileventstorage.NewNilEventStorage()
if err != nil {
log.Fatal(err)
}
eventLog := event.NewEventLog(event.Config{}, eventStorage)

initOrResetDB(t)

stateSqlDB, err := db.NewSQLDB(stateDBCfg)
if err != nil {
panic(err)
}
defer stateSqlDB.Close() //nolint:gosec,errcheck

poolSqlDB, err := db.NewSQLDB(poolDBCfg)
require.NoError(t, err)
defer poolSqlDB.Close() //nolint:gosec,errcheck

st := newState(stateSqlDB, eventLog)

genesisBlock := state.Block{
BlockNumber: 0,
BlockHash: state.ZeroHash,
ParentHash: state.ZeroHash,
ReceivedAt: time.Now(),
}
genesis := state.Genesis{
Actions: []*state.GenesisAction{
{
Address: senderAddress,
Type: int(merkletree.LeafTypeBalance),
Value: "1000000000000000000000",
},
},
}
ctx := context.Background()
dbTx, err := st.BeginStateTransaction(ctx)
require.NoError(t, err)
_, err = st.SetGenesis(ctx, genesisBlock, genesis, metrics.SynchronizerCallerLabel, dbTx)
require.NoError(t, err)
require.NoError(t, dbTx.Commit(ctx))

s, err := pgpoolstorage.NewPostgresPoolStorage(poolDBCfg)
require.NoError(t, err)

p := setupPool(t, cfg, bc, s, st, chainID.Uint64(), ctx, eventLog)

privateKey, err := crypto.HexToECDSA(strings.TrimPrefix(senderPrivateKey, "0x"))
require.NoError(t, err)

auth, err := bind.NewKeyedTransactorWithChainID(privateKey, chainID)
require.NoError(t, err)

type testCase struct {
name string
nonce uint64
gas uint64
gasPrice string
expectedError error
}

testCases := []testCase{
{
name: "add tx with fee under cap",
nonce: 0,
gas: uint64(100000),
gasPrice: "9999999999999",
expectedError: nil,
},
{
name: "add tx with fee exactly as cap",
nonce: 0,
gas: uint64(100000),
gasPrice: "10000000000000",
expectedError: nil,
},
{
name: "add tx with fee over the cap",
nonce: 0,
gas: uint64(100000),
gasPrice: "10000000000001",
expectedError: fmt.Errorf("tx fee (1.0000000000001 ether) exceeds the configured cap (1.00 ether)"),
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
gasPrice, ok := big.NewInt(0).SetString(tc.gasPrice, encoding.Base10)
require.True(t, ok)
tx := ethTypes.NewTx(&ethTypes.LegacyTx{
Nonce: tc.nonce,
Gas: tc.gas,
GasPrice: gasPrice,
})

signedTx, err := auth.Signer(auth.From, tx)
require.NoError(t, err)

err = p.AddTx(ctx, *signedTx, ip)
if tc.expectedError != nil {
require.Equal(t, err.Error(), tc.expectedError.Error())
} else {
require.Nil(t, err)
}
})
}
}

func setupPool(t *testing.T, cfg pool.Config, constraintsCfg state.BatchConstraintsCfg, s *pgpoolstorage.PostgresPoolStorage, st *state.State, chainID uint64, ctx context.Context, eventLog *event.EventLog) *pool.Pool {
err := s.SetGasPrices(ctx, gasPrice.Uint64(), l1GasPrice.Uint64())
require.NoError(t, err)
Expand Down

0 comments on commit 09227d1

Please sign in to comment.