Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions common/celo_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ var (
ZeroAddress = BytesToAddress([]byte{})
)

type AddressSet map[Address]struct{}

type ExchangeRates = map[Address]*big.Rat
type IntrinsicGasCosts = map[Address]uint64

Expand Down Expand Up @@ -51,6 +53,14 @@ func (fc *FeeCurrencyContext) UnmarshalJSON(data []byte) error {
return nil
}

func NewAddressSet(addresses ...Address) AddressSet {
as := AddressSet{}
for _, address := range addresses {
as[address] = struct{}{}
}
return as
}

func MaxAllowedIntrinsicGasCost(i IntrinsicGasCosts, feeCurrency *Address) (uint64, bool) {
intrinsicGas, ok := CurrencyIntrinsicGasCost(i, feeCurrency)
if !ok {
Expand All @@ -76,12 +86,10 @@ func CurrencyIntrinsicGasCost(i IntrinsicGasCosts, feeCurrency *Address) (uint64
return gasCost, true
}

func CurrencyAllowlist(exchangeRates ExchangeRates) []Address {
addrs := make([]Address, len(exchangeRates))
i := 0
func CurrencyAllowlist(exchangeRates ExchangeRates) AddressSet {
addrs := AddressSet{}
for k := range exchangeRates {
addrs[i] = k
i++
addrs[k] = struct{}{}
}
return addrs
}
Expand Down
47 changes: 38 additions & 9 deletions contracts/fee_currencies.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import (

var feeCurrencyABI *abi.ABI

var ErrFeeCurrencyEVMCall = errors.New("fee-currency contract error during internal EVM call")

func init() {
var err error
feeCurrencyABI, err = abigen.FeeCurrencyMetaData.GetAbi()
Expand Down Expand Up @@ -64,10 +66,21 @@ func DebitFees(evm *vm.EVM, feeCurrency *common.Address, address common.Address,
// debitGasFees(address from, uint256 value) parameters
address, amount,
)
if errors.Is(err, vm.ErrOutOfGas) {
// This basically is a configuration / contract error, since
// the contract itself used way more gas than was expected (including grace limit)
return 0, fmt.Errorf("surpassed maximum allowed intrinsic gas for fee currency: %w", err)
if err != nil {
if errors.Is(err, vm.ErrOutOfGas) {
// This basically is a configuration / contract error, since
// the contract itself used way more gas than was expected (including grace limit)
return 0, fmt.Errorf(
"%w: surpassed maximum allowed intrinsic gas for DebitFees() in fee-currency: %w",
ErrFeeCurrencyEVMCall,
err,
)
}
return 0, fmt.Errorf(
"%w: DebitFees() call error: %w",
ErrFeeCurrencyEVMCall,
err,
)
}

gasUsed := maxIntrinsicGasCost - leftoverGas
Expand Down Expand Up @@ -129,10 +142,21 @@ func CreditFees(
// )
txSender, tipReceiver, common.ZeroAddress, baseFeeReceiver, refund, feeTip, common.Big0, baseFee,
)
if errors.Is(err, vm.ErrOutOfGas) {
// This is a configuration / contract error, since
// the contract itself used way more gas than was expected (including grace limit)
return fmt.Errorf("surpassed maximum allowed intrinsic gas for fee currency: %w", err)
if err != nil {
if errors.Is(err, vm.ErrOutOfGas) {
// This is a configuration / contract error, since
// the contract itself used way more gas than was expected (including grace limit)
return fmt.Errorf(
"%w: surpassed maximum allowed intrinsic gas for CreditFees() in fee-currency: %w",
ErrFeeCurrencyEVMCall,
err,
)
}
return fmt.Errorf(
"%w: CreditFees() call error: %w",
ErrFeeCurrencyEVMCall,
err,
)
}

gasUsed := maxAllowedGasForCredit - leftoverGas
Expand All @@ -145,7 +169,12 @@ func CreditFees(
}
gasUsedForDebitAndCredit := gasUsedDebit + gasUsed
if gasUsedForDebitAndCredit > intrinsicGas {
log.Info("Gas usage for debit+credit exceeds intrinsic gas!", "gasUsed", gasUsedForDebitAndCredit, "intrinsicGas", intrinsicGas, "feeCurrency", feeCurrency)
log.Info(
"Gas usage for debit+credit exceeds intrinsic gas!",
"gasUsed", gasUsedForDebitAndCredit,
"intrinsicGas", intrinsicGas,
"feeCurrency", feeCurrency,
)
}
return err
}
Expand Down
5 changes: 2 additions & 3 deletions core/celo_multi_gaspool.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,13 @@ type FeeCurrencyLimitMapping = map[FeeCurrency]float64
// pool for CELO
func NewMultiGasPool(
blockGasLimit uint64,
allowlist []FeeCurrency,
allowlist common.AddressSet,
defaultLimit float64,
limitsMapping FeeCurrencyLimitMapping,
) *MultiGasPool {
pools := make(map[FeeCurrency]*GasPool, len(allowlist))

for i := range allowlist {
currency := allowlist[i]
for currency := range allowlist {
fraction, ok := limitsMapping[currency]
if !ok {
fraction = defaultLimit
Expand Down
47 changes: 18 additions & 29 deletions core/celo_multi_gaspool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func TestMultiCurrencyGasPool(t *testing.T) {
testCases := []struct {
name string
feeCurrency *FeeCurrency
allowlist []FeeCurrency
allowlist common.AddressSet
defaultLimit float64
limits FeeCurrencyLimitMapping
defaultPoolExpected bool
Expand All @@ -25,18 +25,16 @@ func TestMultiCurrencyGasPool(t *testing.T) {
{
name: "Empty allowlist, empty mapping, CELO uses default pool",
feeCurrency: nil,
allowlist: []FeeCurrency{},
allowlist: common.NewAddressSet(),
defaultLimit: 0.9,
limits: map[FeeCurrency]float64{},
defaultPoolExpected: true,
expectedValue: 900, // blockGasLimit - subGasAmount
},
{
name: "Non-empty allowlist, non-empty mapping, CELO uses default pool",
feeCurrency: nil,
allowlist: []FeeCurrency{
cUSDToken,
},
name: "Non-empty allowlist, non-empty mapping, CELO uses default pool",
feeCurrency: nil,
allowlist: common.NewAddressSet(cUSDToken),
defaultLimit: 0.9,
limits: map[FeeCurrency]float64{
cUSDToken: 0.5,
Expand All @@ -47,18 +45,16 @@ func TestMultiCurrencyGasPool(t *testing.T) {
{
name: "Empty allowlist, empty mapping, non-registered currency fallbacks to the default pool",
feeCurrency: &cUSDToken,
allowlist: []FeeCurrency{},
allowlist: common.NewAddressSet(),
defaultLimit: 0.9,
limits: map[FeeCurrency]float64{},
defaultPoolExpected: true,
expectedValue: 900, // blockGasLimit - subGasAmount
},
{
name: "Non-empty allowlist, non-empty mapping, non-registered currency uses default pool",
feeCurrency: &cEURToken,
allowlist: []FeeCurrency{
cUSDToken,
},
name: "Non-empty allowlist, non-empty mapping, non-registered currency uses default pool",
feeCurrency: &cEURToken,
allowlist: common.NewAddressSet(cUSDToken),
defaultLimit: 0.9,
limits: map[FeeCurrency]float64{
cUSDToken: 0.5,
Expand All @@ -67,22 +63,18 @@ func TestMultiCurrencyGasPool(t *testing.T) {
expectedValue: 900, // blockGasLimit - subGasAmount
},
{
name: "Non-empty allowlist, empty mapping, registered currency uses default limit",
feeCurrency: &cUSDToken,
allowlist: []FeeCurrency{
cUSDToken,
},
name: "Non-empty allowlist, empty mapping, registered currency uses default limit",
feeCurrency: &cUSDToken,
allowlist: common.NewAddressSet(cUSDToken),
defaultLimit: 0.9,
limits: map[FeeCurrency]float64{},
defaultPoolExpected: false,
expectedValue: 800, // blockGasLimit * defaultLimit - subGasAmount
},
{
name: "Non-empty allowlist, non-empty mapping, configured registered currency uses configured limits",
feeCurrency: &cUSDToken,
allowlist: []FeeCurrency{
cUSDToken,
},
name: "Non-empty allowlist, non-empty mapping, configured registered currency uses configured limits",
feeCurrency: &cUSDToken,
allowlist: common.NewAddressSet(cUSDToken),
defaultLimit: 0.9,
limits: map[FeeCurrency]float64{
cUSDToken: 0.5,
Expand All @@ -91,12 +83,9 @@ func TestMultiCurrencyGasPool(t *testing.T) {
expectedValue: 400, // blockGasLimit * 0.5 - subGasAmount
},
{
name: "Non-empty allowlist, non-empty mapping, unconfigured registered currency uses default limit",
feeCurrency: &cEURToken,
allowlist: []FeeCurrency{
cUSDToken,
cEURToken,
},
name: "Non-empty allowlist, non-empty mapping, unconfigured registered currency uses default limit",
feeCurrency: &cEURToken,
allowlist: common.NewAddressSet(cUSDToken, cEURToken),
defaultLimit: 0.9,
limits: map[FeeCurrency]float64{
cUSDToken: 0.5,
Expand Down
4 changes: 1 addition & 3 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -687,8 +687,7 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) {
}, nil
}

err = st.distributeTxFees()
if err != nil {
if err := st.distributeTxFees(); err != nil {
return nil, err
}

Expand Down Expand Up @@ -818,7 +817,6 @@ func (st *StateTransition) distributeTxFees() error {
l1Cost,
st.feeCurrencyGasUsed,
); err != nil {
err = fmt.Errorf("error crediting fee-currency: %w", err)
log.Error("Error crediting", "from", from, "coinbase", st.evm.Context.Coinbase, "feeHandler", feeHandlerAddress, "err", err)
return err
}
Expand Down
20 changes: 17 additions & 3 deletions core/txpool/legacypool/legacypool.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package legacypool

import (
"errors"
"fmt"
"math"
"math/big"
"sort"
Expand Down Expand Up @@ -725,9 +726,22 @@ func (pool *LegacyPool) validateTx(tx *types.Transaction, local bool) error {
return err
}
//NOTE: we only test the `debitFees` call here.
// If the `creditFees` reverts (or runs out of gas), the transaction will
// not be invalidated here and will be included in the block.
return contracts.TryDebitFees(tx, from, pool.celoBackend, pool.feeCurrencyContext)
// If the `creditFees` reverts (or runs out of gas),
// the transaction reverts within the STF. This results in the user's
// transaction being computed free of charge. In order to mitigate this,
// the txpool worker keeps track of fee-currencies where this happens and
// limits the impact this can have on resources by adding them to a blocklist
// for a limited time.
// Note however that the fee-currency blocklist is kept downstream,
// so the TryDebitFees call below will be executed even for blocklisted fee-currencies.
err = contracts.TryDebitFees(tx, from, pool.celoBackend, pool.feeCurrencyContext)
if errors.Is(err, contracts.ErrFeeCurrencyEVMCall) {
log.Error("executing debit fees in legacypool validation failed", "err", err)
// make the error message less specific / verbose,
// this will get returned by the `eth_sendRawTransaction` call
err = fmt.Errorf("fee-currency internal error")
}
return err
}
return nil
}
Expand Down
19 changes: 13 additions & 6 deletions e2e_test/run_all_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,22 @@ failures=0
tests=0
for f in test_*"$TEST_GLOB"*
do
echo -e "\nRun $f"
if "./$f"
# temporarily skip fee-currency related tests
if (echo "$f" | grep -q -e fee_currency )
then
tput setaf 2 || true
echo "PASS $f"
echo "SKIP $f"
else
tput setaf 1 || true
echo "FAIL $f ❌"
((failures++)) || true
echo -e "\nRun $f"
if "./$f"
then
tput setaf 2 || true
echo "PASS $f"
else
tput setaf 1 || true
echo "FAIL $f ❌"
((failures++)) || true
fi
fi
tput sgr0 || true
((tests++)) || true
Expand Down
3 changes: 1 addition & 2 deletions e2e_test/test_fee_currency_fails_intrinsic.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,4 @@ tail -f -n0 geth.log >debug-fee-currency/geth.intrinsic.log & # start log captur
(cd debug-fee-currency && ./deploy_and_send_tx.sh false false true)
sleep 0.5
kill %1 # stop log capture
grep "error crediting fee-currency: surpassed maximum allowed intrinsic gas for fee currency: out of gas" debug-fee-currency/geth.intrinsic.log
# echo $(grep "send_tx hash:" debug-fee-currency/send_tx.intrinsic.log)
grep "surpassed maximum allowed intrinsic gas for CreditFees() in fee-currency: out of gas" debug-fee-currency/geth.intrinsic.log
Loading