erigon-pulse/cmd/devnet/services/block.go
Mark Holt 415cf86250
refactor to allow switchable consensus and multiple communicating nodes (#7646)
This branch is intended to allow the devnet to be used for testing
multiple consents types beyond the default clique. It is initially being
used to test Bor consensus for polygon.

It also has the following refactoring:

### 1.  Network configuration

The two node arg building functions miningNodeArgs and nonMiningNodeArgs
have been replaced with a configuration struct which is used to
configure:

```go
network := &node.Network{
		DataDir: dataDir,
		Chain:   networkname.DevChainName,
		//Chain:              networkname.BorDevnetChainName,
		Logger:             logger,
		BasePrivateApiAddr: "localhost:9090",
		BaseRPCAddr:        "localhost:8545",
		Nodes: []node.NetworkNode{
			&node.Miner{},
			&node.NonMiner{},
		},
	}
```
and start multiple nodes

```go
network.Start()
```
Network start will create a network of nodes ensuring that all nodes are
configured with non clashing network ports set via command line
arguments on start-up.

### 2. Request Routing

The `RequestRouter` has been updated to take a 'target' rather than
using a static dispatcher which routes to a single node on the network.
Each node in the network has its own request generator so command and
services have more flexibility in request routing and
`ExecuteAllMethods` currently takes the `node.Network` as an argument
and can pick which node (node 0 for the moment) to send requests to.
2023-06-04 20:53:05 +01:00

311 lines
11 KiB
Go

package services
import (
"context"
"fmt"
"math/big"
"time"
"github.com/holiman/uint256"
libcommon "github.com/ledgerwatch/erigon-lib/common"
"github.com/ledgerwatch/log/v3"
"github.com/ledgerwatch/erigon/accounts/abi/bind"
"github.com/ledgerwatch/erigon/cmd/devnet/contracts"
"github.com/ledgerwatch/erigon/cmd/devnet/devnetutils"
"github.com/ledgerwatch/erigon/cmd/devnet/models"
"github.com/ledgerwatch/erigon/cmd/devnet/node"
"github.com/ledgerwatch/erigon/cmd/devnet/requests"
"github.com/ledgerwatch/erigon/core/types"
"github.com/ledgerwatch/erigon/params"
"github.com/ledgerwatch/erigon/rpc"
)
const gasPrice = 912_345_678
const gasAmount = 875_000_000
var (
signer = types.LatestSigner(params.AllCliqueProtocolChanges)
)
func CreateManyEIP1559TransactionsRefWithBaseFee(node *node.Node, addr string, startingNonce *uint64, logger log.Logger) ([]*types.Transaction, []*types.Transaction, error) {
toAddress := libcommon.HexToAddress(addr)
baseFeePerGas, err := BaseFeeFromBlock(node, logger)
if err != nil {
return nil, nil, fmt.Errorf("failed BaseFeeFromBlock: %v", err)
}
logger.Info("BaseFeePerGas", "val", baseFeePerGas)
lowerBaseFeeTransactions, higherBaseFeeTransactions, err := signEIP1559TxsLowerAndHigherThanBaseFee2(1, 1, baseFeePerGas, startingNonce, toAddress, logger)
if err != nil {
return nil, nil, fmt.Errorf("failed signEIP1559TxsLowerAndHigherThanBaseFee2: %v", err)
}
return lowerBaseFeeTransactions, higherBaseFeeTransactions, nil
}
func CreateManyEIP1559TransactionsRefWithBaseFee2(node *node.Node, addr string, startingNonce *uint64, logger log.Logger) ([]*types.Transaction, []*types.Transaction, error) {
toAddress := libcommon.HexToAddress(addr)
baseFeePerGas, err := BaseFeeFromBlock(node, logger)
if err != nil {
return nil, nil, fmt.Errorf("failed BaseFeeFromBlock: %v", err)
}
logger.Info("BaseFeePerGas2", "val", baseFeePerGas)
lowerBaseFeeTransactions, higherBaseFeeTransactions, err := signEIP1559TxsLowerAndHigherThanBaseFee2(100, 100, baseFeePerGas, startingNonce, toAddress, logger)
if err != nil {
return nil, nil, fmt.Errorf("failed signEIP1559TxsLowerAndHigherThanBaseFee2: %v", err)
}
return lowerBaseFeeTransactions, higherBaseFeeTransactions, nil
}
// CreateTransaction creates a transaction depending on the type of transaction being passed
func CreateTransaction(txType models.TransactionType, addr string, value, nonce uint64) (*types.Transaction, libcommon.Address, *contracts.Subscription, *bind.TransactOpts, error) {
switch txType {
case models.NonContractTx:
tx, address, err := createNonContractTx(addr, value, nonce)
if err != nil {
return nil, libcommon.Address{}, nil, nil, fmt.Errorf("failed to create non-contract transaction: %v", err)
}
return tx, address, nil, nil, nil
case models.ContractTx:
return createContractTx(nonce)
default:
return nil, libcommon.Address{}, nil, nil, models.ErrInvalidTransactionType
}
}
// createNonContractTx returns a signed transaction and the recipient address
func createNonContractTx(addr string, value, nonce uint64) (*types.Transaction, libcommon.Address, error) {
toAddress := libcommon.HexToAddress(addr)
// create a new transaction using the parameters to send
transaction := types.NewTransaction(nonce, toAddress, uint256.NewInt(value), params.TxGas, uint256.NewInt(gasPrice), nil)
// sign the transaction using the developer 0signed private key
signedTx, err := types.SignTx(transaction, *signer, models.DevSignedPrivateKey)
if err != nil {
return nil, libcommon.Address{}, fmt.Errorf("failed to sign non-contract transaction: %v", err)
}
return &signedTx, toAddress, nil
}
func signEIP1559TxsLowerAndHigherThanBaseFee2(amountLower, amountHigher int, baseFeePerGas uint64, nonce *uint64, toAddress libcommon.Address, logger log.Logger) ([]*types.Transaction, []*types.Transaction, error) {
higherBaseFeeTransactions, err := signEIP1559TxsHigherThanBaseFee(amountHigher, baseFeePerGas, nonce, toAddress, logger)
if err != nil {
return nil, nil, fmt.Errorf("failed signEIP1559TxsHigherThanBaseFee: %v", err)
}
lowerBaseFeeTransactions, err := signEIP1559TxsLowerThanBaseFee(amountLower, baseFeePerGas, nonce, toAddress, logger)
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(n int, baseFeePerGas uint64, nonce *uint64, toAddress libcommon.Address, logger log.Logger) ([]*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
)
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(*signer.ChainID(), *nonce, toAddress, uint256.NewInt(value), uint64(210_000), uint256.NewInt(gasPrice), new(uint256.Int), uint256.NewInt(gasFeeCap), nil)
logger.Info("LOWER", "transaction", i, "nonce", transaction.Nonce, "value", transaction.Value, "feecap", transaction.FeeCap)
signedTransaction, err := types.SignTx(transaction, *signer, models.DevSignedPrivateKey)
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(n int, baseFeePerGas uint64, nonce *uint64, toAddress libcommon.Address, logger log.Logger) ([]*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
)
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(*signer.ChainID(), *nonce, toAddress, uint256.NewInt(value), uint64(210_000), uint256.NewInt(gasPrice), new(uint256.Int), uint256.NewInt(gasFeeCap), nil)
logger.Info("HIGHER", "transaction", i, "nonce", transaction.Nonce, "value", transaction.Value, "feecap", transaction.FeeCap)
signedTransaction, err := types.SignTx(transaction, *signer, models.DevSignedPrivateKey)
if err != nil {
return nil, err
}
signedTransactions = append(signedTransactions, &signedTransaction)
*nonce++
}
return signedTransactions, nil
}
// createContractTx creates and signs a transaction using the developer address, returns the contract and the signed transaction
func createContractTx(nonce uint64) (*types.Transaction, libcommon.Address, *contracts.Subscription, *bind.TransactOpts, error) {
// initialize transactOpts
transactOpts, err := initializeTransactOps(nonce)
if err != nil {
return nil, libcommon.Address{}, nil, nil, fmt.Errorf("failed to initialize transactOpts: %v", err)
}
// deploy the contract and get the contract handler
address, txToSign, subscriptionContract, err := contracts.DeploySubscription(transactOpts, models.ContractBackend)
if err != nil {
return nil, libcommon.Address{}, nil, nil, fmt.Errorf("failed to deploy subscription: %v", err)
}
// sign the transaction with the private key
signedTx, err := types.SignTx(txToSign, *signer, models.DevSignedPrivateKey)
if err != nil {
return nil, libcommon.Address{}, nil, nil, fmt.Errorf("failed to sign tx: %v", err)
}
return &signedTx, address, subscriptionContract, transactOpts, nil
}
// initializeTransactOps initializes the transactOpts object for a contract transaction
func initializeTransactOps(nonce uint64) (*bind.TransactOpts, error) {
var chainID = big.NewInt(1337)
transactOpts, err := bind.NewKeyedTransactorWithChainID(models.DevSignedPrivateKey, chainID)
if err != nil {
return nil, fmt.Errorf("cannot create transactor with chainID %d, error: %v", chainID, err)
}
transactOpts.GasLimit = uint64(200_000)
transactOpts.GasPrice = big.NewInt(880_000_000)
transactOpts.Nonce = big.NewInt(int64(nonce)) // TODO: Get Nonce from account automatically
return transactOpts, nil
}
// txHashInBlock checks if the block with block number has the transaction hash in its list of transactions
func txHashInBlock(client *rpc.Client, hashmap map[libcommon.Hash]bool, blockNumber string, txToBlockMap map[libcommon.Hash]string, logger log.Logger) (uint64, int, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel() // releases the resources held by the context
var (
currBlock models.Block
numFound int
)
err := client.CallContext(ctx, &currBlock, string(requests.Methods.ETHGetBlockByNumber), blockNumber, false)
if err != nil {
return uint64(0), 0, fmt.Errorf("failed to get block by number: %v", err)
}
for _, txnHash := range currBlock.Transactions {
// check if tx is in the hash set and remove it from the set if it is present
if _, ok := hashmap[txnHash]; ok {
numFound++
logger.Info("SUCCESS => Tx included into block", "txHash", txnHash, "blockNum", blockNumber)
// add the block number as an entry to the map
txToBlockMap[txnHash] = blockNumber
delete(hashmap, txnHash)
if len(hashmap) == 0 {
return requests.HexToInt(blockNumber), numFound, nil
}
}
}
return uint64(0), 0, nil
}
// EmitFallbackEvent emits an event from the contract using the fallback method
func EmitFallbackEvent(node *node.Node, subContract *contracts.Subscription, opts *bind.TransactOpts, logger log.Logger) (*libcommon.Hash, error) {
logger.Info("EMITTING EVENT FROM FALLBACK...")
// adding one to the nonce before initiating another transaction
opts.Nonce.Add(opts.Nonce, big.NewInt(1))
tx, err := subContract.Fallback(opts, []byte{})
if err != nil {
return nil, fmt.Errorf("failed to emit event from fallback: %v", err)
}
signedTx, err := types.SignTx(tx, *signer, models.DevSignedPrivateKey)
if err != nil {
return nil, fmt.Errorf("failed to sign fallback transaction: %v", err)
}
hash, err := node.SendTransaction(&signedTx)
if err != nil {
return nil, fmt.Errorf("failed to send fallback transaction: %v", err)
}
return hash, nil
}
func BaseFeeFromBlock(node *node.Node, logger log.Logger) (uint64, error) {
var val uint64
res, err := node.GetBlockByNumberDetails("latest", false)
if err != nil {
return 0, fmt.Errorf("failed to get base fee from block: %v\n", err)
}
if v, ok := res["baseFeePerGas"]; !ok {
return val, fmt.Errorf("baseFeePerGas field missing from response")
} else {
val = requests.HexToInt(v.(string))
}
return val, err
}
func SendManyTransactions(node *node.Node, signedTransactions []*types.Transaction, logger log.Logger) ([]*libcommon.Hash, error) {
logger.Info("Sending multiple transactions to the txpool...")
hashes := make([]*libcommon.Hash, len(signedTransactions))
for idx, tx := range signedTransactions {
hash, err := node.SendTransaction(tx)
if err != nil {
logger.Error("failed SendTransaction", "error", err)
return nil, err
}
hashes[idx] = hash
}
return hashes, nil
}