erigon-pulse/cmd/devnet/transactions/tx.go
battlmonstr 3698e7f476
devnet: configuration fixes (#8592)
* fix "genesis hash does not match" when dev nodes connect  
The "dev" nodes need to have the same --miner.etherbase in order to
generate the same genesis ExtraData by DeveloperGenesisBlock(). Override
DevnetEtherbase global var that's used if --miner.etherbase is not
passed. (for NonBlockProducer case)

* fix missing private key for the hardcoded DevnetEtherbase  
Fixes panic if SigKey is not found. Bor non-producers will use a default
`DevnetEtherbase` while Dev nodes modify it. Save hardcoded
DevnetEtherbase/DevnetSignPrivateKey into accounts so that SigKey can
recover it.

* refactor devnet.node to contain Node config  
This avoids interface{} type casts and fixes an error with
Heimdall.validatorSet == nil

* add connection retries to rpcCall and Subscribe of requestGenerator  
Fixes "connection refused" errors due to node not ready to handle early
RPC requests.

* fix deadlock in Heimdall.NodeStarted

* fix GetBlockByNumber
Fixes "cannot unmarshal string into Go struct field body.transactions of
type jsonrpc.RPCTransaction"

* demote "no of blocks on childchain is less than confirmations
required" to Info (#8626)

* demote "mismatched pending subpool size" to Debug (#8615)

* revert wiggle testing code
2023-11-01 11:08:47 +01:00

372 lines
12 KiB
Go

package transactions
import (
"context"
"fmt"
"strings"
"time"
libcommon "github.com/ledgerwatch/erigon-lib/common"
"github.com/ledgerwatch/log/v3"
"github.com/ledgerwatch/erigon/cmd/devnet/accounts"
"github.com/ledgerwatch/erigon/cmd/devnet/blocks"
"github.com/ledgerwatch/erigon/cmd/devnet/devnet"
"github.com/ledgerwatch/erigon/cmd/devnet/devnetutils"
"github.com/ledgerwatch/erigon/cmd/devnet/scenarios"
"github.com/ledgerwatch/erigon/rpc"
"github.com/holiman/uint256"
"github.com/ledgerwatch/erigon/core/types"
"github.com/ledgerwatch/erigon/params"
)
func init() {
scenarios.MustRegisterStepHandlers(
scenarios.StepHandler(CheckTxPoolContent),
scenarios.StepHandler(SendTxWithDynamicFee),
scenarios.StepHandler(AwaitBlocks),
)
}
func CheckTxPoolContent(ctx context.Context, expectedPendingSize, expectedQueuedSize, expectedBaseFeeSize int) {
pendingSize, queuedSize, baseFeeSize, err := devnet.SelectNode(ctx).TxpoolContent()
logger := devnet.Logger(ctx)
if err != nil {
logger.Error("FAILURE getting txpool content", "error", err)
return
}
if expectedPendingSize >= 0 && pendingSize != expectedPendingSize {
logger.Debug("FAILURE mismatched pending subpool size", "expected", expectedPendingSize, "got", pendingSize)
return
}
if expectedQueuedSize >= 0 && queuedSize != expectedQueuedSize {
logger.Error("FAILURE mismatched queued subpool size", "expected", expectedQueuedSize, "got", queuedSize)
return
}
if expectedBaseFeeSize >= 0 && baseFeeSize != expectedBaseFeeSize {
logger.Debug("FAILURE mismatched basefee subpool size", "expected", expectedBaseFeeSize, "got", baseFeeSize)
}
logger.Info("Subpool sizes", "pending", pendingSize, "queued", queuedSize, "basefee", baseFeeSize)
}
func Transfer(ctx context.Context, toAddr, fromAddr string, value uint64, wait bool) (libcommon.Hash, error) {
logger := devnet.Logger(ctx)
node := devnet.SelectNode(ctx)
// create a non-contract transaction and sign it
signedTx, _, err := CreateTransaction(node, toAddr, fromAddr, value)
if err != nil {
logger.Error("failed to create a transaction", "error", err)
return libcommon.Hash{}, err
}
logger.Info("Sending tx", "value", value, "to", toAddr, "from", fromAddr, "tx", signedTx.Hash())
// send the signed transaction
hash, err := node.SendTransaction(signedTx)
if err != nil {
logger.Error("failed to send transaction", "error", err)
return libcommon.Hash{}, err
}
if wait {
if _, err = AwaitTransactions(ctx, hash); err != nil {
return libcommon.Hash{}, fmt.Errorf("failed to call contract tx: %v", err)
}
}
return hash, nil
}
func SendTxWithDynamicFee(ctx context.Context, to, from string, amount uint64) ([]libcommon.Hash, error) {
// get the latest nonce for the next transaction
logger := devnet.Logger(ctx)
lowerThanBaseFeeTxs, higherThanBaseFeeTxs, err := CreateManyEIP1559TransactionsRefWithBaseFee2(ctx, to, from)
if err != nil {
logger.Error("failed CreateManyEIP1559TransactionsRefWithBaseFee", "error", err)
return nil, err
}
higherThanBaseFeeHashlist, err := SendManyTransactions(ctx, higherThanBaseFeeTxs)
if err != nil {
logger.Error("failed SendManyTransactions(higherThanBaseFeeTxs)", "error", err)
return nil, err
}
lowerThanBaseFeeHashlist, err := SendManyTransactions(ctx, lowerThanBaseFeeTxs)
if err != nil {
logger.Error("failed SendManyTransactions(lowerThanBaseFeeTxs)", "error", err)
return nil, err
}
CheckTxPoolContent(ctx, 100, 0, 100)
CheckTxPoolContent(ctx, -1, -1, -1)
if _, err = AwaitTransactions(ctx, higherThanBaseFeeHashlist...); err != nil {
return nil, fmt.Errorf("failed to call contract tx: %v", err)
}
logger.Info("SUCCESS: All transactions in pending pool included in blocks")
return append(lowerThanBaseFeeHashlist, higherThanBaseFeeHashlist...), nil
}
func AwaitBlocks(ctx context.Context, sleepTime time.Duration) error {
logger := devnet.Logger(ctx)
for i := 1; i <= 20; i++ {
node := devnet.SelectNode(ctx)
blockNumber, err := node.BlockNumber()
if err != nil {
logger.Error("FAILURE => error getting block number", "error", err)
} else {
logger.Info("Got block number", "blockNum", blockNumber)
}
pendingSize, queuedSize, baseFeeSize, err := node.TxpoolContent()
if err != nil {
logger.Error("FAILURE getting txpool content", "error", err)
} else {
logger.Info("Txpool subpool sizes", "pending", pendingSize, "queued", queuedSize, "basefee", baseFeeSize)
}
time.Sleep(sleepTime)
}
return nil
}
const gasPrice = 912_345_678
const gasAmount = 875_000_000
func CreateManyEIP1559TransactionsRefWithBaseFee(ctx context.Context, to, from string, logger log.Logger) ([]types.Transaction, []types.Transaction, error) {
toAddress := libcommon.HexToAddress(to)
fromAddress := libcommon.HexToAddress(from)
baseFeePerGas, err := blocks.BaseFeeFromBlock(ctx)
if err != nil {
return nil, nil, fmt.Errorf("failed BaseFeeFromBlock: %v", err)
}
devnet.Logger(ctx).Info("BaseFeePerGas", "val", baseFeePerGas)
lowerBaseFeeTransactions, higherBaseFeeTransactions, err := signEIP1559TxsLowerAndHigherThanBaseFee2(ctx, 1, 1, baseFeePerGas, toAddress, fromAddress)
if err != nil {
return nil, nil, fmt.Errorf("failed signEIP1559TxsLowerAndHigherThanBaseFee2: %v", err)
}
return lowerBaseFeeTransactions, higherBaseFeeTransactions, nil
}
func CreateManyEIP1559TransactionsRefWithBaseFee2(ctx context.Context, to, from string) ([]types.Transaction, []types.Transaction, error) {
toAddress := libcommon.HexToAddress(to)
fromAddress := libcommon.HexToAddress(from)
baseFeePerGas, err := blocks.BaseFeeFromBlock(ctx)
if err != nil {
return nil, nil, fmt.Errorf("failed BaseFeeFromBlock: %v", err)
}
devnet.Logger(ctx).Info("BaseFeePerGas2", "val", baseFeePerGas)
lowerBaseFeeTransactions, higherBaseFeeTransactions, err := signEIP1559TxsLowerAndHigherThanBaseFee2(ctx, 100, 100, baseFeePerGas, toAddress, fromAddress)
if err != nil {
return nil, nil, fmt.Errorf("failed signEIP1559TxsLowerAndHigherThanBaseFee2: %v", err)
}
return lowerBaseFeeTransactions, higherBaseFeeTransactions, nil
}
// createNonContractTx returns a signed transaction and the recipient address
func CreateTransaction(node devnet.Node, to, from string, value uint64) (types.Transaction, libcommon.Address, error) {
toAccount := accounts.GetAccount(to)
var toAddress libcommon.Address
if toAccount == nil {
if strings.HasPrefix(to, "0x") {
toAddress = libcommon.HexToAddress(from)
} else {
return nil, libcommon.Address{}, fmt.Errorf("Unknown to account: %s", to)
}
} else {
toAddress = toAccount.Address
}
fromAccount := accounts.GetAccount(from)
if fromAccount == nil {
return nil, libcommon.Address{}, fmt.Errorf("Unknown from account: %s", from)
}
res, err := node.GetTransactionCount(fromAccount.Address, rpc.PendingBlock)
if err != nil {
return nil, libcommon.Address{}, fmt.Errorf("failed to get transaction count for address 0x%x: %v", fromAccount.Address, err)
}
// create a new transaction using the parameters to send
transaction := types.NewTransaction(res.Uint64(), toAddress, uint256.NewInt(value), params.TxGas, uint256.NewInt(gasPrice), nil)
// sign the transaction using the developer 0signed private key
signedTx, err := types.SignTx(transaction, *types.LatestSignerForChainID(node.ChainID()), fromAccount.SigKey())
if err != nil {
return nil, libcommon.Address{}, fmt.Errorf("failed to sign non-contract transaction: %v", err)
}
return signedTx, toAddress, nil
}
func signEIP1559TxsLowerAndHigherThanBaseFee2(ctx context.Context, amountLower, amountHigher int, baseFeePerGas uint64, toAddress libcommon.Address, fromAddress libcommon.Address) ([]types.Transaction, []types.Transaction, error) {
node := devnet.SelectNode(ctx)
res, err := node.GetTransactionCount(fromAddress, rpc.PendingBlock)
if err != nil {
return nil, nil, fmt.Errorf("failed to get transaction count for address 0x%x: %v", fromAddress, err)
}
nonce := res.Uint64()
higherBaseFeeTransactions, err := signEIP1559TxsHigherThanBaseFee(ctx, amountHigher, baseFeePerGas, &nonce, toAddress, fromAddress)
if err != nil {
return nil, nil, fmt.Errorf("failed signEIP1559TxsHigherThanBaseFee: %v", err)
}
lowerBaseFeeTransactions, err := signEIP1559TxsLowerThanBaseFee(ctx, amountLower, baseFeePerGas, &nonce, toAddress, fromAddress)
if err != nil {
return nil, nil, fmt.Errorf("failed signEIP1559TxsLowerThanBaseFee: %v", err)
}
return lowerBaseFeeTransactions, higherBaseFeeTransactions, nil
}
// signEIP1559TxsLowerThanBaseFee creates n number of transactions with gasFeeCap lower than baseFeePerGas
func signEIP1559TxsLowerThanBaseFee(ctx context.Context, n int, baseFeePerGas uint64, nonce *uint64, toAddress, fromAddress libcommon.Address) ([]types.Transaction, error) {
var signedTransactions []types.Transaction
var (
minFeeCap = baseFeePerGas - 300_000_000
maxFeeCap = (baseFeePerGas - 100_000_000) + 1 // we want the value to be inclusive in the random number generation, hence the addition of 1
)
node := devnet.SelectNode(ctx)
signer := *types.LatestSignerForChainID(node.ChainID())
chainId := *uint256.NewInt(node.ChainID().Uint64())
for i := 0; i < n; i++ {
gasFeeCap, err := devnetutils.RandomNumberInRange(minFeeCap, maxFeeCap)
if err != nil {
return nil, err
}
value, err := devnetutils.RandomNumberInRange(0, 100_000)
if err != nil {
return nil, err
}
transaction := types.NewEIP1559Transaction(chainId, *nonce, toAddress, uint256.NewInt(value), uint64(210_000), uint256.NewInt(gasPrice), new(uint256.Int), uint256.NewInt(gasFeeCap), nil)
devnet.Logger(ctx).Info("LOWER", "transaction", i, "nonce", transaction.Nonce, "value", transaction.Value, "feecap", transaction.FeeCap)
signedTransaction, err := types.SignTx(transaction, signer, accounts.SigKey(fromAddress))
if err != nil {
return nil, err
}
signedTransactions = append(signedTransactions, signedTransaction)
*nonce++
}
return signedTransactions, nil
}
// signEIP1559TxsHigherThanBaseFee creates amount number of transactions with gasFeeCap higher than baseFeePerGas
func signEIP1559TxsHigherThanBaseFee(ctx context.Context, n int, baseFeePerGas uint64, nonce *uint64, toAddress, fromAddress libcommon.Address) ([]types.Transaction, error) {
var signedTransactions []types.Transaction
var (
minFeeCap = baseFeePerGas
maxFeeCap = (baseFeePerGas + 100_000_000) + 1 // we want the value to be inclusive in the random number generation, hence the addition of 1
)
node := devnet.SelectNode(ctx)
signer := *types.LatestSignerForChainID(node.ChainID())
chainId := *uint256.NewInt(node.ChainID().Uint64())
for i := 0; i < n; i++ {
gasFeeCap, err := devnetutils.RandomNumberInRange(minFeeCap, maxFeeCap)
if err != nil {
return nil, err
}
value, err := devnetutils.RandomNumberInRange(0, 100_000)
if err != nil {
return nil, err
}
transaction := types.NewEIP1559Transaction(chainId, *nonce, toAddress, uint256.NewInt(value), uint64(210_000), uint256.NewInt(gasPrice), new(uint256.Int), uint256.NewInt(gasFeeCap), nil)
devnet.Logger(ctx).Info("HIGHER", "transaction", i, "nonce", transaction.Nonce, "value", transaction.Value, "feecap", transaction.FeeCap)
signerKey := accounts.SigKey(fromAddress)
if signerKey == nil {
return nil, fmt.Errorf("devnet.signEIP1559TxsHigherThanBaseFee failed to SignTx: private key not found for address %s", fromAddress)
}
signedTransaction, err := types.SignTx(transaction, signer, signerKey)
if err != nil {
return nil, err
}
signedTransactions = append(signedTransactions, signedTransaction)
*nonce++
}
return signedTransactions, nil
}
func SendManyTransactions(ctx context.Context, signedTransactions []types.Transaction) ([]libcommon.Hash, error) {
logger := devnet.Logger(ctx)
logger.Info("Sending multiple transactions to the txpool...")
hashes := make([]libcommon.Hash, len(signedTransactions))
for idx, tx := range signedTransactions {
hash, err := devnet.SelectNode(ctx).SendTransaction(tx)
if err != nil {
logger.Error("failed SendTransaction", "error", err)
//return nil, err
}
hashes[idx] = hash
}
return hashes, nil
}