erigon-pulse/cmd/devnet/contracts/steps/subscriber.go
Mark Holt cc74e74a60
Devnet contract utils (#7928)
This request is extending the devnet functionality to more fully handle
contract processing by adding support for the following calls:
 * trace_call,
* trace_transaction
* debug_accountAt,
* eth_getCode
* eth_estimateGas
* eth_gasPrice

It also contains an initial rationalization of the devnet subscription
code to use the erigon client code directly rather than using its own
intermediate subscription management.

This is used to implement a general purpose block waiter - which can be
used in any scenario step - rather than being specific to transaction
processing.

This pull also contains an end to end tested sync processor for bor and
associated support services:
* Heimdall (supports sync event transfer)
* Faucet - allows the creation and funding of arbitary test specific
accounts (cross chain)

Notes and Caveats:
 
* Code generation for contracts requires `--evm-version paris`. For
chains which don't support push0 for solc over 0.8.19
* The bor log processing post the application of sync events causes a
panic - this will be the subject of a seperate smaller push as it is not
devnet specific
* The bor code seems to make repeated calls for the same sync events and
also reverts requests - this needs further investigation. This is the
behaviour of the current implementation and may be required - although
it does seem to generate repeat processing - which could be avoided.
2023-07-28 14:03:32 +01:00

147 lines
4.7 KiB
Go

package contracts_steps
import (
"context"
"fmt"
"math/big"
ethereum "github.com/ledgerwatch/erigon"
libcommon "github.com/ledgerwatch/erigon-lib/common"
"github.com/ledgerwatch/erigon-lib/common/hexutility"
"github.com/ledgerwatch/log/v3"
"github.com/ledgerwatch/erigon/accounts/abi/bind"
"github.com/ledgerwatch/erigon/cmd/devnet/accounts"
"github.com/ledgerwatch/erigon/cmd/devnet/contracts"
"github.com/ledgerwatch/erigon/cmd/devnet/devnet"
"github.com/ledgerwatch/erigon/cmd/devnet/devnetutils"
"github.com/ledgerwatch/erigon/cmd/devnet/requests"
"github.com/ledgerwatch/erigon/cmd/devnet/scenarios"
"github.com/ledgerwatch/erigon/cmd/devnet/transactions"
"github.com/ledgerwatch/erigon/common/hexutil"
"github.com/ledgerwatch/erigon/core/types"
)
func init() {
scenarios.MustRegisterStepHandlers(
scenarios.StepHandler(DeployAndCallLogSubscriber),
)
}
func DeployAndCallLogSubscriber(ctx context.Context, deployer string) (*libcommon.Hash, error) {
logger := devnet.Logger(ctx)
node := devnet.SelectNode(ctx)
deployerAddress := libcommon.HexToAddress(deployer)
// subscriptionContract is the handler to the contract for further operations
tx, address, subscriptionContract, transactOpts, err := DeploySubsriptionContract(node, deployerAddress)
if err != nil {
logger.Error("failed to create transaction", "error", err)
return nil, err
}
hash := tx.Hash()
eventHash, err := EmitFallbackEvent(node, subscriptionContract, transactOpts, logger)
if err != nil {
logger.Error("failed to emit events", "error", err)
return nil, err
}
txToBlockMap, err := transactions.AwaitTransactions(ctx, hash, eventHash)
if err != nil {
return nil, fmt.Errorf("failed to call contract tx: %v", err)
}
blockNum := txToBlockMap[eventHash]
block, err := node.GetBlockByNumber(blockNum, true)
if err != nil {
return nil, err
}
logs, err := node.FilterLogs(ctx, ethereum.FilterQuery{
FromBlock: big.NewInt(0),
ToBlock: big.NewInt(int64(blockNum)),
Addresses: []libcommon.Address{address}})
if err != nil || len(logs) == 0 {
return nil, fmt.Errorf("failed to get logs: %v", err)
}
// compare the log events
errs, ok := requests.Compare(requests.NewLog(eventHash, blockNum, address,
devnetutils.GenerateTopic("SubscriptionEvent()"), hexutility.Bytes{}, 1,
block.Hash, hexutil.Uint(0), false), logs[0])
if !ok {
logger.Error("Log result is incorrect", "errors", errs)
return nil, fmt.Errorf("incorrect logs: %v", errs)
}
logger.Info("SUCCESS => Logs compared successfully, no discrepancies")
return &hash, nil
}
// DeploySubsriptionContract creates and signs a transaction using the developer address, returns the contract and the signed transaction
func DeploySubsriptionContract(node devnet.Node, deployer libcommon.Address) (types.Transaction, libcommon.Address, *contracts.Subscription, *bind.TransactOpts, error) {
// initialize transactOpts
transactOpts, err := initializeTransactOps(node, deployer)
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, tx, subscriptionContract, err := contracts.DeploySubscription(transactOpts, contracts.NewBackend(node))
if err != nil {
return nil, libcommon.Address{}, nil, nil, fmt.Errorf("failed to deploy subscription: %v", err)
}
return tx, address, subscriptionContract, transactOpts, nil
}
// EmitFallbackEvent emits an event from the contract using the fallback method
func EmitFallbackEvent(node devnet.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 libcommon.Hash{}, fmt.Errorf("failed to emit event from fallback: %v", err)
}
return tx.Hash(), nil
}
// initializeTransactOps initializes the transactOpts object for a contract transaction
func initializeTransactOps(node devnet.Node, transactor libcommon.Address) (*bind.TransactOpts, error) {
count, err := node.GetTransactionCount(transactor, requests.BlockNumbers.Latest)
if err != nil {
return nil, fmt.Errorf("failed to get transaction count for address 0x%x: %v", transactor, err)
}
transactOpts, err := bind.NewKeyedTransactorWithChainID(accounts.SigKey(transactor), node.ChainID())
if err != nil {
return nil, fmt.Errorf("cannot create transactor with chainID %d, error: %v", node.ChainID(), err)
}
transactOpts.GasLimit = uint64(200_000)
transactOpts.GasPrice = big.NewInt(880_000_000)
transactOpts.Nonce = count
return transactOpts, nil
}