diff --git a/tests/e2e/c/dynamic_fees.go b/tests/e2e/c/dynamic_fees.go new file mode 100644 index 000000000000..f3a1daaf3d2c --- /dev/null +++ b/tests/e2e/c/dynamic_fees.go @@ -0,0 +1,169 @@ +// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package c + +import ( + "math/big" + "strings" + + ginkgo "github.com/onsi/ginkgo/v2" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + + "github.com/ava-labs/coreth/core/types" + "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/plugin/evm" + + "github.com/ava-labs/avalanchego/tests" + "github.com/ava-labs/avalanchego/tests/e2e" + "github.com/ava-labs/avalanchego/tests/fixture/testnet" + "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" +) + +// This test uses the compiled bin for `hashing.sol` as +// well as its ABI contained in `hashing_contract.go`. + +var _ = e2e.DescribeCChain("[Dynamic Fees]", func() { + require := require.New(ginkgo.GinkgoT()) + + // 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) + + ginkgo.It("should ensure that the gas price is affected by load", func() { + ginkgo.By("creating a new private network to ensure isolation from other tests") + privateNetwork := e2e.Env.NewPrivateNetwork() + + ginkgo.By("allocating a pre-funded key") + key := privateNetwork.GetConfig().FundedKeys[0] + ethAddress := evm.GetEthAddress(key) + + ginkgo.By("initializing a coreth client") + node := privateNetwork.GetNodes()[0] + nodeURI := testnet.NodeURI{ + NodeID: node.GetID(), + URI: node.GetProcessContext().URI, + } + ethClient := e2e.Env.NewEthClient(nodeURI) + + ginkgo.By("initializing a transaction signer") + cChainID, err := ethClient.ChainID(e2e.DefaultContext()) + require.NoError(err) + signer := types.NewEIP155Signer(cChainID) + ecdsaKey := key.ToECDSA() + sign := func(tx *types.Transaction) *types.Transaction { + signedTx, err := types.SignTx(tx, signer, ecdsaKey) + require.NoError(err) + return signedTx + } + + var contractAddress common.Address + ginkgo.By("deploying an expensive contract", func() { + // Create transaction + nonce, err := ethClient.AcceptedNonceAt(e2e.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, + }) + + // Send the transaction and wait for acceptance + signedTx := sign(tx) + receipt := e2e.SendEthTransaction(ethClient, signedTx) + + contractAddress = receipt.ContractAddress + }) + + var gasPrice *big.Int + ginkgo.By("calling the expensive contract repeatedly until a gas price increase is detected", func() { + // Evaluate the bytes representation of the contract + hashingABI, err := abi.JSON(strings.NewReader(hashingABIJson)) + require.NoError(err) + contractData, err := hashingABI.Pack("hashIt") + require.NoError(err) + + var initialGasPrice *big.Int + e2e.Eventually(func() bool { + // Check the gas price + var err error + gasPrice, err = ethClient.SuggestGasPrice(e2e.DefaultContext()) + require.NoError(err) + if initialGasPrice == nil { + initialGasPrice = gasPrice + tests.Outf("{{blue}}initial gas price is %v{{/}}\n", initialGasPrice) + } else if gasPrice.Cmp(initialGasPrice) > 0 { + // Gas price has increased + tests.Outf("{{blue}}gas price has increased to %v{{/}}\n", gasPrice) + return true + } + + // Create the transaction + nonce, err := ethClient.AcceptedNonceAt(e2e.DefaultContext(), ethAddress) + require.NoError(err) + tx := types.NewTx(&types.LegacyTx{ + Nonce: nonce, + GasPrice: gasTip, + Gas: largeGasLimit, + To: &contractAddress, + Value: common.Big0, + Data: contractData, + }) + + // Send the transaction and wait for acceptance + signedTx := sign(tx) + _ = e2e.SendEthTransaction(ethClient, signedTx) + + // The gas price will be checked at the start of the next iteration + return false + }, e2e.DefaultTimeout, e2e.DefaultPollingInterval, "failed to see gas price increase before timeout") + }) + + ginkgo.By("waiting for the gas price to decrease...", func() { + initialGasPrice := gasPrice + e2e.Eventually(func() bool { + var err error + gasPrice, err = ethClient.SuggestGasPrice(e2e.DefaultContext()) + require.NoError(err) + tests.Outf("{{blue}}.{{/}}") + return initialGasPrice.Cmp(gasPrice) > 0 + }, e2e.DefaultTimeout, e2e.DefaultPollingInterval, "failed to see gas price decrease before timeout") + tests.Outf("\n{{blue}}gas price has decreased to %v{{/}}\n", gasPrice) + }) + + ginkgo.By("sending funds at the current gas price", func() { + // Create a recipient address + factory := secp256k1.Factory{} + recipientKey, err := factory.NewPrivateKey() + require.NoError(err) + recipientEthAddress := evm.GetEthAddress(recipientKey) + + // Create transaction + nonce, err := ethClient.AcceptedNonceAt(e2e.DefaultContext(), ethAddress) + require.NoError(err) + tx := types.NewTx(&types.LegacyTx{ + Nonce: nonce, + GasPrice: gasPrice, + Gas: e2e.DefaultGasLimit, + To: &recipientEthAddress, + Value: common.Big0, + }) + + // Send the transaction and wait for acceptance + signedTx := sign(tx) + _ = e2e.SendEthTransaction(ethClient, signedTx) + }) + + e2e.CheckBootstrapIsPossible(privateNetwork) + }) +}) diff --git a/tests/e2e/c/hashing.sol b/tests/e2e/c/hashing.sol new file mode 100644 index 000000000000..0457ac428e01 --- /dev/null +++ b/tests/e2e/c/hashing.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT + +pragma solidity = 0.8.6; + +contract ConsumeGas { + + bytes hashVar = bytes("This is the hashing text for the test"); + + function hashIt() public { + for (uint i=0; i<3700; i++) { + ripemd160(hashVar); + } + } + +} diff --git a/tests/e2e/c/hashing_contract.go b/tests/e2e/c/hashing_contract.go new file mode 100644 index 000000000000..af5e81eb9057 --- /dev/null +++ b/tests/e2e/c/hashing_contract.go @@ -0,0 +1,11 @@ +// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +// AUTOMATICALLY GENERATED. DO NOT EDIT! +// Generated from hashing.sol by compile-contract.sh +package c + +const ( + hashingCompiledContract = "60806040526040518060600160405280602581526020016103ba6025913960009080519060200190610032929190610045565b5034801561003f57600080fd5b50610149565b828054610051906100e8565b90600052602060002090601f01602090048101928261007357600085556100ba565b82601f1061008c57805160ff19168380011785556100ba565b828001600101855582156100ba579182015b828111156100b957825182559160200191906001019061009e565b5b5090506100c791906100cb565b5090565b5b808211156100e45760008160009055506001016100cc565b5090565b6000600282049050600182168061010057607f821691505b602082108114156101145761011361011a565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b610262806101586000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80636f37ecea14610030575b600080fd5b61003861003a565b005b60005b610e7481101561009057600360006040516100589190610112565b602060405180830381855afa158015610075573d6000803e3d6000fd5b5050506040515150808061008890610185565b91505061003d565b50565b600081546100a081610153565b6100aa818661013e565b945060018216600081146100c557600181146100d657610109565b60ff19831686528186019350610109565b6100df85610129565b60005b83811015610101578154818901526001820191506020810190506100e2565b838801955050505b50505092915050565b600061011e8284610093565b915081905092915050565b60008190508160005260206000209050919050565b600081905092915050565b6000819050919050565b6000600282049050600182168061016b57607f821691505b6020821081141561017f5761017e6101fd565b5b50919050565b600061019082610149565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8214156101c3576101c26101ce565b5b600182019050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fdfea2646970667358221220cc5de5cd3c7aa5bda60e63e0f3156691253f7a78191eb336ec6699b38a8a777c64736f6c6343000806003354686973206973207468652068617368696e67207465787420666f72207468652074657374" + hashingABIJson = `[{"inputs":[],"name":"hashIt","outputs":[],"stateMutability":"nonpayable","type":"function"}]` +) diff --git a/tests/e2e/c/interchain_workflow.go b/tests/e2e/c/interchain_workflow.go index 4d24f38f9652..47c430d4c12a 100644 --- a/tests/e2e/c/interchain_workflow.go +++ b/tests/e2e/c/interchain_workflow.go @@ -26,10 +26,7 @@ import ( var _ = e2e.DescribeCChain("[Interchain Workflow]", func() { require := require.New(ginkgo.GinkgoT()) - const ( - txAmount = 10 * units.Avax // Arbitrary amount to send and transfer - gasLimit = uint64(21000) // Standard gas limit - ) + const txAmount = 10 * units.Avax // Arbitrary amount to send and transfer ginkgo.It("should ensure that funds can be transferred from the C-Chain to the X-Chain and the P-Chain", func() { ginkgo.By("initializing a new eth client") @@ -56,7 +53,7 @@ var _ = e2e.DescribeCChain("[Interchain Workflow]", func() { acceptedNonce, recipientEthAddress, big.NewInt(int64(txAmount)), - gasLimit, + e2e.DefaultGasLimit, gasPrice, nil, ) diff --git a/tests/e2e/e2e.go b/tests/e2e/e2e.go index ce5e99096595..2cbea2d6290c 100644 --- a/tests/e2e/e2e.go +++ b/tests/e2e/e2e.go @@ -12,6 +12,7 @@ import ( "math/big" "math/rand" "os" + "path/filepath" "strings" "time" @@ -29,6 +30,7 @@ import ( "github.com/ava-labs/avalanchego/tests/fixture/testnet" "github.com/ava-labs/avalanchego/tests/fixture/testnet/local" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" + "github.com/ava-labs/avalanchego/utils/perms" "github.com/ava-labs/avalanchego/vms/platformvm/txs/executor" "github.com/ava-labs/avalanchego/vms/secp256k1fx" "github.com/ava-labs/avalanchego/wallet/subnet/primary" @@ -54,6 +56,16 @@ const ( // current time for validator addition to succeed, and adding 20 // seconds provides a buffer in case of any delay in processing. DefaultValidatorStartTimeDiff = executor.SyncBound + 20*time.Second + + DefaultGasLimit = uint64(21000) // Standard gas limit + + // An empty string prompts the use of the default path which ensures a + // predictable target for github's upload-artifact action. + DefaultNetworkDir = "" + + // Directory used to store private networks (specific to a single test) + // under the shared network dir. + PrivateNetworksDirName = "private_networks" ) // Env is used to access shared test fixture. Intended to be @@ -116,6 +128,7 @@ func (te *TestEnvironment) NewKeychain(count int) *secp256k1fx.Keychain { } // Create a new wallet for the provided keychain against the specified node URI. +// TODO(marun) Make this a regular function. func (te *TestEnvironment) NewWallet(keychain *secp256k1fx.Keychain, nodeURI testnet.NodeURI) primary.Wallet { tests.Outf("{{blue}} initializing a new wallet for node %s with URI: %s {{/}}\n", nodeURI.NodeID, nodeURI.URI) baseWallet, err := primary.MakeWallet(DefaultContext(), &primary.WalletConfig{ @@ -135,6 +148,7 @@ func (te *TestEnvironment) NewWallet(keychain *secp256k1fx.Keychain, nodeURI tes } // Create a new eth client targeting the specified node URI. +// TODO(marun) Make this a regular function. func (te *TestEnvironment) NewEthClient(nodeURI testnet.NodeURI) ethclient.Client { tests.Outf("{{blue}} initializing a new eth client for node %s with URI: %s {{/}}\n", nodeURI.NodeID, nodeURI.URI) nodeAddress := strings.Split(nodeURI.URI, "//")[1] @@ -144,6 +158,20 @@ func (te *TestEnvironment) NewEthClient(nodeURI testnet.NodeURI) ethclient.Clien return client } +// Create a new private network that is not shared with other tests. +func (te *TestEnvironment) NewPrivateNetwork() testnet.Network { + // Load the shared network to retrieve its path and exec path + sharedNetwork, err := local.ReadNetwork(te.NetworkDir) + te.require.NoError(err) + + // The private networks dir is under the shared network dir to ensure it + // will be included in the artifact uploaded in CI. + privateNetworksDir := filepath.Join(sharedNetwork.Dir, PrivateNetworksDirName) + te.require.NoError(os.MkdirAll(privateNetworksDir, perms.ReadWriteExecute)) + + return StartLocalNetwork(sharedNetwork.ExecPath, privateNetworksDir) +} + // Helper simplifying use of a timed context by canceling the context on ginkgo teardown. func ContextWithTimeout(duration time.Duration) context.Context { ctx, cancel := context.WithTimeout(context.Background(), duration) @@ -259,3 +287,30 @@ func CheckBootstrapIsPossible(network testnet.Network) { node := AddEphemeralNode(network, testnet.FlagsMap{}) WaitForHealthy(node) } + +// Start a local test-managed network with the provided avalanchego binary. +func StartLocalNetwork(avalancheGoExecPath string, networkDir string) *local.LocalNetwork { + require := require.New(ginkgo.GinkgoT()) + + network, err := local.StartNetwork( + DefaultContext(), + ginkgo.GinkgoWriter, + networkDir, + &local.LocalNetwork{ + LocalConfig: local.LocalConfig{ + ExecPath: avalancheGoExecPath, + }, + }, + testnet.DefaultNodeCount, + testnet.DefaultFundedKeyCount, + ) + require.NoError(err) + ginkgo.DeferCleanup(func() { + tests.Outf("Shutting down network\n") + require.NoError(network.Stop()) + }) + + tests.Outf("{{green}}Successfully started network{{/}}\n") + + return network +} diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go index 5373082f94a0..2e9a86684df0 100644 --- a/tests/e2e/e2e_test.go +++ b/tests/e2e/e2e_test.go @@ -4,7 +4,6 @@ package e2e_test import ( - "context" "encoding/json" "flag" "fmt" @@ -20,7 +19,6 @@ import ( "github.com/ava-labs/avalanchego/tests" "github.com/ava-labs/avalanchego/tests/e2e" "github.com/ava-labs/avalanchego/tests/fixture" - "github.com/ava-labs/avalanchego/tests/fixture/testnet" "github.com/ava-labs/avalanchego/tests/fixture/testnet/local" // ensure test packages are scanned by ginkgo @@ -83,28 +81,7 @@ var _ = ginkgo.SynchronizedBeforeSuite(func() []byte { network, err = local.ReadNetwork(persistentNetworkDir) require.NoError(err) } else { - ctx, cancel := context.WithTimeout(context.Background(), local.DefaultNetworkStartTimeout) - defer cancel() - var err error - network, err = local.StartNetwork( - ctx, - ginkgo.GinkgoWriter, - "", // Use the default path to ensure a predictable target for github's upload-artifact action - &local.LocalNetwork{ - LocalConfig: local.LocalConfig{ - ExecPath: avalancheGoExecPath, - }, - }, - testnet.DefaultNodeCount, - testnet.DefaultFundedKeyCount, - ) - require.NoError(err) - ginkgo.DeferCleanup(func() { - tests.Outf("Shutting down network\n") - require.NoError(network.Stop()) - }) - - tests.Outf("{{green}}Successfully started network{{/}}\n") + network = e2e.StartLocalNetwork(avalancheGoExecPath, e2e.DefaultNetworkDir) } uris := network.GetURIs()