Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ACP-176 e2e tests #3749

Open
wants to merge 25 commits into
base: master
Choose a base branch
from
Open
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
19 changes: 19 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,25 @@ jobs:
prometheus_password: ${{ secrets.PROMETHEUS_PASSWORD || '' }}
loki_username: ${{ secrets.LOKI_ID || '' }}
loki_password: ${{ secrets.LOKI_PASSWORD || '' }}
e2e_post_fortuna:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-go-for-project
- name: Build AvalancheGo Binary
shell: bash
run: ./scripts/build.sh -r
- name: Run e2e tests
uses: ./.github/actions/run-monitored-tmpnet-cmd
with:
run: ./scripts/tests.e2e.sh --activate-fortuna
run_env: E2E_SERIAL=1
artifact_prefix: e2e-post-fortuna
filter_by_owner: avalanchego-e2e
prometheus_username: ${{ secrets.PROMETHEUS_ID || '' }}
prometheus_password: ${{ secrets.PROMETHEUS_PASSWORD || '' }}
loki_username: ${{ secrets.LOKI_ID || '' }}
loki_password: ${{ secrets.LOKI_PASSWORD || '' }}
e2e_existing_network:
runs-on: ubuntu-latest
steps:
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ require (
github.com/DataDog/zstd v1.5.2
github.com/NYTimes/gziphandler v1.1.1
github.com/antithesishq/antithesis-sdk-go v0.3.8
github.com/ava-labs/coreth v0.14.1-rc.1.0.20250219185827-6a9db205a450
github.com/ava-labs/coreth v0.14.1-acp-176.1
github.com/ava-labs/ledger-avalanche/go v0.0.0-20241009183145-e6f90a8a1a60
github.com/btcsuite/btcd/btcutil v1.1.3
github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ github.com/antithesishq/antithesis-sdk-go v0.3.8/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/ava-labs/coreth v0.14.1-rc.1.0.20250219185827-6a9db205a450 h1:xSoL6plkPrb2w21v4JRnQg3BIJSg2svBHXyil09RMAg=
github.com/ava-labs/coreth v0.14.1-rc.1.0.20250219185827-6a9db205a450/go.mod h1:AEQcF8MWrKH0sS114wEZlkSfeGF5F66xTbQYqqimNLI=
github.com/ava-labs/coreth v0.14.1-acp-176.1 h1:44iXYYeLR4LzTM+5DB7ohGqysDiTFPIvxiuFY9vZJAI=
github.com/ava-labs/coreth v0.14.1-acp-176.1/go.mod h1:AEQcF8MWrKH0sS114wEZlkSfeGF5F66xTbQYqqimNLI=
github.com/ava-labs/ledger-avalanche/go v0.0.0-20241009183145-e6f90a8a1a60 h1:EL66gtXOAwR/4KYBjOV03LTWgkEXvLePribLlJNu4g0=
github.com/ava-labs/ledger-avalanche/go v0.0.0-20241009183145-e6f90a8a1a60/go.mod h1:/7qKobTfbzBu7eSTVaXMTr56yTYk4j2Px6/8G+idxHo=
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
Expand Down
14 changes: 14 additions & 0 deletions tests/e2e/c/consume_gas.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package c

const (
// consumeGasCompiledContract is the compiled bytecode of the contract
// defined in consume_gas.sol.
consumeGasCompiledContract = "6080604052348015600f57600080fd5b5060788061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063c040622614602d575b600080fd5b60336035565b005b5b6001156040576036565b56fea264697066735822122070cfeeb0992270b4ff725a1264654534853d25ea6bb28c85d986beccfdbc997164736f6c63430008060033"
// consumeGasABIJson is the ABI of the contract defined in consume_gas.sol.
consumeGasABIJson = `[{"inputs":[],"name":"run","outputs":[],"stateMutability":"nonpayable","type":"function"}]`
// consumeGasFunction is the name of the function to call to consume gas.
consumeGasFunction = "run"
)
9 changes: 9 additions & 0 deletions tests/e2e/c/consume_gas.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: MIT

pragma solidity = 0.8.6;

contract ConsumeGas {
function run() public {
while (true){}
}
}
212 changes: 145 additions & 67 deletions tests/e2e/c/dynamic_fees.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,37 +6,66 @@ package c
import (
"math/big"
"strings"
"time"

"github.com/ava-labs/coreth/core/types"
"github.com/ava-labs/coreth/params"
"github.com/ava-labs/coreth/plugin/evm/upgrade/acp176"
"github.com/ava-labs/coreth/plugin/evm/upgrade/cortina"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/onsi/ginkgo/v2"
"github.com/stretchr/testify/require"
"go.uber.org/zap"

"github.com/ava-labs/avalanchego/api/info"
"github.com/ava-labs/avalanchego/tests/fixture/e2e"
"github.com/ava-labs/avalanchego/tests/fixture/tmpnet"
)

// This test uses the compiled bin for `hashing.sol` as
// well as its ABI contained in `hashing_contract.go`.
// This test uses the compiled bytecode for `consume_gas.sol` as well as its ABI
// contained in `consume_gas.go`.

var _ = e2e.DescribeCChain("[Dynamic Fees]", func() {
tc := e2e.NewTestContext()
require := require.New(tc)

// Need a gas limit much larger than the standard 21_000 to enable
// the contract to induce a gas price increase
const largeGasLimit = uint64(8_000_000)

// TODO(marun) What is the significance of this value?
gasTip := big.NewInt(1000 * params.GWei)
const (
// maxFeePerGas is the maximum fee that transactions issued by this test
// will be willing to pay. The actual value doesn't really matter, it
// just needs to be higher than the `targetGasPrice` calculated below.
maxFeePerGas = 1000 * params.GWei
// minFeePerGas is the minimum fee that transactions issued by this test
// will pay. The mempool enforces that this value is non-zero.
minFeePerGas = 1 * params.Wei

// expectedGasPriceIncreaseNumerator/expectedGasPriceIncreaseDenominator
// is the multiplier that the gas price is attempted to reach. So if the
// initial gas price is 5 GWei, the test will attempt to increase the
// gas price to 6 GWei.
expectedGasPriceIncreaseNumerator = 6
expectedGasPriceIncreaseDenominator = 5
)
var (
bigExpectedGasPriceIncreaseNumerator = big.NewInt(expectedGasPriceIncreaseNumerator)
bigExpectedGasPriceIncreaseDenominator = big.NewInt(expectedGasPriceIncreaseDenominator)
bigExpectedGasPriceIncreaseDenominatorMinus1 = big.NewInt(expectedGasPriceIncreaseDenominator - 1)

gasFeeCap = big.NewInt(maxFeePerGas)
gasTipCap = big.NewInt(minFeePerGas)
)

ginkgo.It("should ensure that the gas price is affected by load", func() {
tc.By("creating a new private network to ensure isolation from other tests")
env := e2e.GetEnv(tc)
publicNetwork := env.GetNetwork()

privateNetwork := tmpnet.NewDefaultNetwork("avalanchego-e2e-dynamic-fees")
e2e.GetEnv(tc).StartPrivateNetwork(privateNetwork)
// Copy over the defaults from the normal test suite to include settings
// like the upgrade config.
privateNetwork.DefaultFlags = tmpnet.FlagsMap{}
privateNetwork.DefaultFlags.SetDefaults(publicNetwork.DefaultFlags)
env.StartPrivateNetwork(privateNetwork)

// Avoid emitting a spec-scoped metrics link for the shared
// network since the link emitted by the start of the private
Expand All @@ -60,71 +89,109 @@ var _ = e2e.DescribeCChain("[Dynamic Fees]", func() {
tc.By("initializing a transaction signer")
cChainID, err := ethClient.ChainID(tc.DefaultContext())
require.NoError(err)
signer := types.NewEIP155Signer(cChainID)
signer := types.NewLondonSigner(cChainID)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The london signer supports the DynamicFeeTx

ecdsaKey := key.ToECDSA()
sign := func(tx *types.Transaction) *types.Transaction {
signedTx, err := types.SignTx(tx, signer, ecdsaKey)
require.NoError(err)
return signedTx
}

// gasLimit is the maximum amount of gas that can be used in a block.
var gasLimit uint64
tc.By("checking if Fortuna is activated", func() {
infoClient := info.NewClient(nodeURI.URI)
upgrades, err := infoClient.Upgrades(tc.DefaultContext())
require.NoError(err)

now := time.Now()
if upgrades.IsFUpgradeActivated(now) {
gasLimit = acp176.MinMaxCapacity
} else {
gasLimit = cortina.GasLimit
}
})
tc.Log().Info("set gas limit",
zap.Uint64("gasLimit", gasLimit),
)

var contractAddress common.Address
tc.By("deploying an expensive contract", func() {
// Create transaction
nonce, err := ethClient.AcceptedNonceAt(tc.DefaultContext(), ethAddress)
require.NoError(err)
compiledContract := common.Hex2Bytes(hashingCompiledContract)
tx := types.NewTx(&types.LegacyTx{
Nonce: nonce,
GasPrice: gasTip,
Gas: largeGasLimit,
Value: common.Big0,
Data: compiledContract,
compiledContract := common.Hex2Bytes(consumeGasCompiledContract)
tx := types.NewTx(&types.DynamicFeeTx{
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DynamicFeeTxs should realistically always be used where possible. They enable transactions to pay the minimum price, rather than the price they specify.

Specifically, previously the LegacyTx always paid gasTip. The DynamicFeeTx will always pay the gasTipCap for a total less than or equal to the gasFeeCap.

This allows this test to specify such a high gasFeeCap without burning a ton of funds unnecessarily.

ChainID: cChainID,
Nonce: nonce,
GasTipCap: gasTipCap,
GasFeeCap: gasFeeCap,
Gas: gasLimit,
To: nil, // contract creation
Data: compiledContract,
})

// Send the transaction and wait for acceptance
signedTx := sign(tx)
receipt := e2e.SendEthTransaction(tc, ethClient, signedTx)
require.Equal(types.ReceiptStatusSuccessful, receipt.Status)

contractAddress = receipt.ContractAddress
})

var gasPrice *big.Int
tc.By("calling the expensive contract repeatedly until a gas price increase is detected", func() {
latest, err := ethClient.HeaderByNumber(tc.DefaultContext(), nil)
require.NoError(err)

initialGasPrice := latest.BaseFee
targetGasPrice := new(big.Int).Set(initialGasPrice)
targetGasPrice.Mul(targetGasPrice, bigExpectedGasPriceIncreaseNumerator)
targetGasPrice.Add(targetGasPrice, bigExpectedGasPriceIncreaseDenominatorMinus1)
targetGasPrice.Div(targetGasPrice, bigExpectedGasPriceIncreaseDenominator)

tc.Log().Info("initializing gas prices",
zap.Stringer("initialPrice", initialGasPrice),
zap.Stringer("targetPrice", targetGasPrice),
)

tc.By("calling the contract repeatedly until a sufficient gas price increase is detected", func() {
// Evaluate the bytes representation of the contract
hashingABI, err := abi.JSON(strings.NewReader(hashingABIJson))
hashingABI, err := abi.JSON(strings.NewReader(consumeGasABIJson))
require.NoError(err)
contractData, err := hashingABI.Pack("hashIt")
contractData, err := hashingABI.Pack(consumeGasFunction)
require.NoError(err)

var initialGasPrice *big.Int
tc.Eventually(func() bool {
// Check the gas price
var err error
gasPrice, err = ethClient.SuggestGasPrice(tc.DefaultContext())
latest, err := ethClient.HeaderByNumber(tc.DefaultContext(), nil)
require.NoError(err)
if initialGasPrice == nil {
initialGasPrice = gasPrice
tc.Log().Info("initial gas price",
zap.Uint64("price", initialGasPrice.Uint64()),
)
} else if gasPrice.Cmp(initialGasPrice) > 0 {

// If the gas price has increased, stop the loop.
if latest.BaseFee.Cmp(targetGasPrice) >= 0 {
tc.Log().Info("gas price has increased",
zap.Uint64("price", gasPrice.Uint64()),
zap.Stringer("initialPrice", initialGasPrice),
zap.Stringer("targetPrice", targetGasPrice),
zap.Stringer("newPrice", latest.BaseFee),
)
return true
}

tc.Log().Info("gas price hasn't sufficiently increased",
zap.Stringer("initialPrice", initialGasPrice),
zap.Stringer("newPrice", latest.BaseFee),
zap.Stringer("targetPrice", targetGasPrice),
)

// Create the transaction
nonce, err := ethClient.AcceptedNonceAt(tc.DefaultContext(), ethAddress)
require.NoError(err)
tx := types.NewTx(&types.LegacyTx{
Nonce: nonce,
GasPrice: gasTip,
Gas: largeGasLimit,
To: &contractAddress,
Value: common.Big0,
Data: contractData,
tx := types.NewTx(&types.DynamicFeeTx{
ChainID: cChainID,
Nonce: nonce,
GasTipCap: gasTipCap,
GasFeeCap: gasFeeCap,
Gas: gasLimit,
To: &contractAddress,
Data: contractData,
})

// Send the transaction and wait for acceptance
Expand All @@ -136,40 +203,51 @@ var _ = e2e.DescribeCChain("[Dynamic Fees]", func() {
}, e2e.DefaultTimeout, e2e.DefaultPollingInterval, "failed to see gas price increase before timeout")
})

tc.By("waiting for the gas price to decrease...", func() {
initialGasPrice := gasPrice
// Create a recipient address
var (
recipientKey = e2e.NewPrivateKey(tc)
recipientEthAddress = recipientKey.EthAddress()
)
tc.By("sending small transactions until a sufficient gas price decrease is detected", func() {
tc.Eventually(func() bool {
var err error
gasPrice, err = ethClient.SuggestGasPrice(tc.DefaultContext())
// Check the gas price
latest, err := ethClient.HeaderByNumber(tc.DefaultContext(), nil)
require.NoError(err)
return initialGasPrice.Cmp(gasPrice) > 0
}, e2e.DefaultTimeout, e2e.DefaultPollingInterval, "failed to see gas price decrease before timeout")
tc.Log().Info("gas price has decreased",
zap.Uint64("price", gasPrice.Uint64()),
)
})

tc.By("sending funds at the current gas price", func() {
// Create a recipient address
var (
recipientKey = e2e.NewPrivateKey(tc)
recipientEthAddress = recipientKey.EthAddress()
)
// If the gas price has decreased, stop the loop.
if latest.BaseFee.Cmp(initialGasPrice) <= 0 {
tc.Log().Info("gas price has decreased",
zap.Stringer("initialPrice", initialGasPrice),
zap.Stringer("newPrice", latest.BaseFee),
)
return true
}

tc.Log().Info("gas price hasn't sufficiently decreased",
zap.Stringer("initialPrice", initialGasPrice),
zap.Stringer("newPrice", latest.BaseFee),
)

// Create transaction
nonce, err := ethClient.AcceptedNonceAt(tc.DefaultContext(), ethAddress)
require.NoError(err)
tx := types.NewTx(&types.LegacyTx{
Nonce: nonce,
GasPrice: gasPrice,
Gas: e2e.DefaultGasLimit,
To: &recipientEthAddress,
Value: common.Big0,
})
// Create the transaction
nonce, err := ethClient.AcceptedNonceAt(tc.DefaultContext(), ethAddress)
require.NoError(err)
tx := types.NewTx(&types.DynamicFeeTx{
ChainID: cChainID,
Nonce: nonce,
GasTipCap: gasTipCap,
GasFeeCap: gasFeeCap,
Gas: e2e.DefaultGasLimit,
To: &recipientEthAddress,
})

// Send the transaction and wait for acceptance
signedTx := sign(tx)
_ = e2e.SendEthTransaction(tc, ethClient, signedTx)
// Send the transaction and wait for acceptance
signedTx := sign(tx)
receipt := e2e.SendEthTransaction(tc, ethClient, signedTx)
require.Equal(types.ReceiptStatusSuccessful, receipt.Status)

// The gas price will be checked at the start of the next iteration
return false
}, e2e.DefaultTimeout, e2e.DefaultPollingInterval, "failed to see gas price decrease before timeout")
})

_ = e2e.CheckBootstrapIsPossible(tc, privateNetwork)
Expand Down
15 changes: 0 additions & 15 deletions tests/e2e/c/hashing.sol

This file was deleted.

11 changes: 0 additions & 11 deletions tests/e2e/c/hashing_contract.go

This file was deleted.

Loading
Loading