From d074a403c0d88296a8676d207e8be9cd2ee61c10 Mon Sep 17 00:00:00 2001 From: leonardchinonso <36096513+leonardchinonso@users.noreply.github.com> Date: Thu, 17 Mar 2022 21:16:02 +0100 Subject: [PATCH] improved error handling and segmented packages and functions (#3728) --- cmd/devnettest/commands/account.go | 13 +- cmd/devnettest/commands/block.go | 211 ++-------------------- cmd/devnettest/commands/parity.go | 8 +- cmd/devnettest/commands/requests.go | 5 +- cmd/devnettest/commands/root.go | 6 - cmd/devnettest/commands/tx.go | 8 +- cmd/devnettest/requests/mock_requests.go | 9 +- cmd/devnettest/requests/requests.go | 100 +++++++---- cmd/devnettest/requests/utils.go | 33 ++-- cmd/devnettest/services/block.go | 218 +++++++++++++++++++++++ 10 files changed, 339 insertions(+), 272 deletions(-) create mode 100644 cmd/devnettest/services/block.go diff --git a/cmd/devnettest/commands/account.go b/cmd/devnettest/commands/account.go index 932bc7b9a6..9c7248f64f 100644 --- a/cmd/devnettest/commands/account.go +++ b/cmd/devnettest/commands/account.go @@ -2,6 +2,7 @@ package commands import ( "fmt" + "github.com/ledgerwatch/erigon/cmd/devnettest/services" "github.com/ledgerwatch/erigon/cmd/devnettest/requests" "github.com/ledgerwatch/erigon/common" @@ -25,22 +26,20 @@ var getBalanceCmd = &cobra.Command{ Use: "get-balance", Short: "Checks balance by address", Args: func(cmd *cobra.Command, args []string) error { - var err error switch blockNum { case "pending", "latest", "earliest": default: - err = fmt.Errorf("block number must be 'pending', 'latest' or 'earliest'") - } - if err != nil { - return err + return fmt.Errorf("block number must be 'pending', 'latest' or 'earliest'") } return nil }, Run: func(cmd *cobra.Command, args []string) { if clearDev { - defer clearDevDB() + defer services.ClearDevDB() } toAddress := common.HexToAddress(addr) - requests.GetBalance(reqId, toAddress, blockNum) + if err := requests.GetBalance(reqId, toAddress, blockNum); err != nil { + fmt.Printf("could not get balance: %v", err) + } }, } diff --git a/cmd/devnettest/commands/block.go b/cmd/devnettest/commands/block.go index d2e8d1cb02..037675582d 100644 --- a/cmd/devnettest/commands/block.go +++ b/cmd/devnettest/commands/block.go @@ -1,33 +1,13 @@ package commands import ( - "context" "fmt" - "github.com/ledgerwatch/erigon/accounts/abi/bind" - "github.com/ledgerwatch/erigon/accounts/abi/bind/backends" - "github.com/ledgerwatch/erigon/cmd/devnettest/contracts" - "github.com/ledgerwatch/erigon/common/hexutil" - "github.com/ledgerwatch/erigon/core" - "math/big" - "strings" - "time" - "github.com/ledgerwatch/erigon/rpc" - - "github.com/holiman/uint256" "github.com/ledgerwatch/erigon/cmd/devnettest/requests" - "github.com/ledgerwatch/erigon/common" - "github.com/ledgerwatch/erigon/core/types" - "github.com/ledgerwatch/erigon/crypto" - "github.com/ledgerwatch/erigon/params" + "github.com/ledgerwatch/erigon/cmd/devnettest/services" "github.com/spf13/cobra" ) -var ( - devnetSignPrivateKey, _ = crypto.HexToECDSA("26e86e45f6fc45ec6e2ecd128cec80fa1d1505e5507dcd2ae58c3130a7a97b48") - signer = types.LatestSigner(params.AllCliqueProtocolChanges) -) - var ( sendAddr string sendValue uint64 @@ -36,10 +16,6 @@ var ( txType string ) -const ( - gasPrice = 912345678 -) - func init() { sendTxCmd.Flags().StringVar(&txType, "tx-type", "", "type of transaction, specify 'contract' or 'regular'") sendTxCmd.MarkFlagRequired("tx-type") @@ -71,188 +47,35 @@ var sendTxCmd = &cobra.Command{ }, Run: func(cmd *cobra.Command, args []string) { if clearDev { - defer clearDevDB() + defer services.ClearDevDB() } // subscriptionContract is the handler to the contract for further operations - signedTx, address, subscriptionContract, transactOpts, err := createTransaction(txType) + signedTx, address, subscriptionContract, transactOpts, err := services.CreateTransaction(txType, sendAddr, sendValue, nonce, searchBlock) if err != nil { - panic(err) + fmt.Printf("failed to deploy subscription: %v", err) + return } hash, err := requests.SendTx(reqId, signedTx) if err != nil { - panic(err) + fmt.Printf("failed to send transaction: %v", err) + return } if searchBlock { - _ = searchBlockForTx(*hash) - } - - if err := emitEventAndGetLogs(subscriptionContract, transactOpts, address); err != nil { - panic(err) - } - }, -} - -func createTransaction(transactionType string) (*types.Transaction, common.Address, *contracts.Subscription, *bind.TransactOpts, error) { - if transactionType == "regular" { - tx, address, err := createNonContractTx(signer) - return tx, address, nil, nil, err - } - return createContractTx(signer) -} - -// createNonContractTx takes in a signer and returns the signed transaction and the address receiving the sent value -func createNonContractTx(signer *types.Signer) (*types.Transaction, common.Address, error) { - toAddress := common.HexToAddress(sendAddr) - signedTx, err := types.SignTx(types.NewTransaction(nonce, toAddress, uint256.NewInt(sendValue), - params.TxGas, uint256.NewInt(gasPrice), nil), *signer, devnetSignPrivateKey) - if err != nil { - return nil, toAddress, err - } - return &signedTx, toAddress, nil -} - -func createContractTx(signer *types.Signer) (*types.Transaction, common.Address, *contracts.Subscription, *bind.TransactOpts, error) { - const txGas uint64 = 200_000 - gspec := core.DeveloperGenesisBlock(uint64(0), common.HexToAddress("67b1d87101671b127f5f8714789C7192f7ad340e")) - contractBackend := backends.NewSimulatedBackendWithConfig(gspec.Alloc, gspec.Config, 1_000_000) - transactOpts, err := bind.NewKeyedTransactorWithChainID(devnetSignPrivateKey, big.NewInt(1337)) - if err != nil { - return nil, common.Address{}, nil, nil, err - } - - transactOpts.GasLimit = txGas - transactOpts.GasPrice = big.NewInt(880_000_000) - // TODO: Get Nonce from account automatically - transactOpts.Nonce = big.NewInt(int64(nonce)) - - // get transaction to sign and contract handler - address, txToSign, subscriptionContract, err := contracts.DeploySubscription(transactOpts, contractBackend) - if err != nil { - return nil, common.Address{}, nil, nil, err - } - - // sign the transaction with the private key - signedTx, err := types.SignTx(txToSign, *signer, devnetSignPrivateKey) - if err != nil { - return nil, common.Address{}, nil, nil, err - } - - return &signedTx, address, subscriptionContract, transactOpts, nil -} - -func searchBlockForTx(txnHash common.Hash) uint64 { - url := "ws://127.0.0.1:8545" - client, clientErr := rpc.DialWebsocket(context.Background(), url, "") - if clientErr != nil { - panic(clientErr) - } - fmt.Println() - fmt.Println("Connected to web socket successfully") - - blockN, err := subscribe(client, "eth_newHeads", txnHash) - if err != nil { - panic(err) - } - - return blockN -} - -func subscribe(client *rpc.Client, method string, hash common.Hash) (uint64, error) { - parts := strings.SplitN(method, "_", 2) - namespace := parts[0] - method = parts[1] - ch := make(chan interface{}) - sub, err := client.Subscribe(context.Background(), namespace, ch, []interface{}{method}...) - if err != nil { - return uint64(0), err - } - defer sub.Unsubscribe() - - var ( - blockCount int - blockN uint64 - ) -ForLoop: - for { - select { - case v := <-ch: - blockCount++ - blockNumber := v.(map[string]interface{})["number"] - fmt.Printf("Searching for the transaction in block with number: %+v, type: %[1]T\n", blockNumber.(string)) - num, foundTx, err := blockHasHash(client, hash, blockNumber.(string)) - if err != nil { - return uint64(0), err + if _, err := services.SearchBlockForTx(*hash); err != nil { + fmt.Printf("error searching block for tx: %v", err) + return } - if foundTx || blockCount == 128 { - blockN = num - break ForLoop - } - case err := <-sub.Err(): - return uint64(0), err } - } - - return blockN, nil - -} - -type Block struct { - Number *hexutil.Big - Transactions []common.Hash -} -func blockHasHash(client *rpc.Client, hash common.Hash, blockNumber string) (uint64, bool, error) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - var currentBlock Block - err := client.CallContext(ctx, ¤tBlock, "eth_getBlockByNumber", blockNumber, false) - if err != nil { - return uint64(0), false, err - } - - for _, txnHash := range currentBlock.Transactions { - if txnHash == hash { - fmt.Println() - fmt.Printf("Block with number: %v was mined and included transaction with hash: %v ==> %+v\n", blockNumber, hash, currentBlock) - fmt.Println() - return requests.HexToInt(blockNumber), true, nil + // if the contract is not nil, then the initial transaction created a contract. Emit an event + if subscriptionContract != nil { + if err := services.EmitEventAndGetLogs(reqId, subscriptionContract, transactOpts, address); err != nil { + fmt.Printf("failed to emit events: %v", err) + return + } } - } - - return uint64(0), false, nil -} - -func emitEventAndGetLogs(subContract *contracts.Subscription, opts *bind.TransactOpts, address common.Address) error { - if subContract == nil { - return nil - } - - opts.Nonce.Add(opts.Nonce, big.NewInt(1)) - - tx, err := subContract.Fallback(opts, []byte{}) - if err != nil { - panic(err) - } - - signedTx, err := types.SignTx(tx, *signer, devnetSignPrivateKey) - if err != nil { - return err - } - - hash, err := requests.SendTx(reqId, &signedTx) - if err != nil { - panic(err) - } - - blockN := searchBlockForTx(*hash) - - if err = requests.GetLogs(reqId, blockN, blockN, address); err != nil { - panic(err) - } - - return nil + }, } diff --git a/cmd/devnettest/commands/parity.go b/cmd/devnettest/commands/parity.go index ef7b61b20c..85f0edcde6 100644 --- a/cmd/devnettest/commands/parity.go +++ b/cmd/devnettest/commands/parity.go @@ -1,6 +1,8 @@ package commands import ( + "fmt" + "github.com/ledgerwatch/erigon/cmd/devnettest/services" "strings" "github.com/ledgerwatch/erigon/cmd/devnettest/requests" @@ -28,11 +30,13 @@ var listStorageKeysCmd = &cobra.Command{ Short: "Returns all storage keys of the given address", RunE: func(cmd *cobra.Command, args []string) error { if clearDev { - defer clearDevDB() + defer services.ClearDevDB() } toAddress := common.HexToAddress(addr) offset := common.Hex2Bytes(strings.TrimSuffix(offsetAddr, "0x")) - requests.ParityList(reqId, toAddress, quantity, offset, blockNum) + if err := requests.ParityList(reqId, toAddress, quantity, offset, blockNum); err != nil { + fmt.Printf("error getting parity list: %v", err) + } return nil }, } diff --git a/cmd/devnettest/commands/requests.go b/cmd/devnettest/commands/requests.go index a8d3a7f9aa..077263e249 100644 --- a/cmd/devnettest/commands/requests.go +++ b/cmd/devnettest/commands/requests.go @@ -1,6 +1,7 @@ package commands import ( + "fmt" "github.com/ledgerwatch/erigon/cmd/devnettest/requests" "github.com/spf13/cobra" ) @@ -13,6 +14,8 @@ var mockRequestCmd = &cobra.Command{ Use: "mock", Short: "Mocks a request on the devnet", Run: func(cmd *cobra.Command, args []string) { - requests.MockGetRequest(reqId) + if err := requests.MockGetRequest(reqId); err != nil { + fmt.Printf("error mocking get request: %v", err) + } }, } diff --git a/cmd/devnettest/commands/root.go b/cmd/devnettest/commands/root.go index 22dc56cd9c..cec5304586 100644 --- a/cmd/devnettest/commands/root.go +++ b/cmd/devnettest/commands/root.go @@ -1,8 +1,6 @@ package commands import ( - "fmt" - "github.com/spf13/cobra" ) @@ -25,7 +23,3 @@ var rootCmd = &cobra.Command{ func Execute() error { return rootCmd.Execute() } - -func clearDevDB() { - fmt.Printf("Clearing ~/dev\n") -} diff --git a/cmd/devnettest/commands/tx.go b/cmd/devnettest/commands/tx.go index bd27b6dfe9..8eeaf95bc5 100644 --- a/cmd/devnettest/commands/tx.go +++ b/cmd/devnettest/commands/tx.go @@ -1,7 +1,9 @@ package commands import ( + "fmt" "github.com/ledgerwatch/erigon/cmd/devnettest/requests" + "github.com/ledgerwatch/erigon/cmd/devnettest/services" "github.com/spf13/cobra" ) @@ -14,8 +16,10 @@ var txPoolCmd = &cobra.Command{ Short: "Gets content of txpool", Run: func(cmd *cobra.Command, args []string) { if clearDev { - defer clearDevDB() + defer services.ClearDevDB() + } + if err := requests.TxpoolContent(reqId); err != nil { + fmt.Printf("error getting txpool content: %v", err) } - requests.TxpoolContent(reqId) }, } diff --git a/cmd/devnettest/requests/mock_requests.go b/cmd/devnettest/requests/mock_requests.go index 834d6ab247..7b998f2b2a 100644 --- a/cmd/devnettest/requests/mock_requests.go +++ b/cmd/devnettest/requests/mock_requests.go @@ -2,15 +2,12 @@ package requests import "fmt" -func MockGetRequest(reqId int) { +func MockGetRequest(reqId int) error { reqGen := initialiseRequestGenerator(reqId) - res := reqGen.Get() - if res.Err != nil { - fmt.Printf("error: %v\n", res.Err) - return + return fmt.Errorf("failed to make get request: %v", res.Err) } - fmt.Printf("OK\n") + return nil } diff --git a/cmd/devnettest/requests/requests.go b/cmd/devnettest/requests/requests.go index 640dad534b..87e5586e55 100644 --- a/cmd/devnettest/requests/requests.go +++ b/cmd/devnettest/requests/requests.go @@ -4,31 +4,53 @@ import ( "bytes" "encoding/json" "fmt" + "net/http" + "strings" + "time" + "github.com/ledgerwatch/erigon/cmd/rpctest/rpctest" "github.com/ledgerwatch/erigon/common" "github.com/ledgerwatch/erigon/core/types" + "github.com/ledgerwatch/log/v3" ) -func parseResponse(resp interface{}) string { - result, err := json.Marshal(resp) +func post(client *http.Client, url, request string, response interface{}) error { + start := time.Now() + r, err := client.Post(url, "application/json", strings.NewReader(request)) if err != nil { - panic(err) + return fmt.Errorf("client failed to make post request: %v", err) + } + defer r.Body.Close() + + if r.StatusCode != 200 { + return fmt.Errorf("status %s", r.Status) } - return string(result) + decoder := json.NewDecoder(r.Body) + err = decoder.Decode(response) + if err != nil { + return fmt.Errorf("failed to decode response: %v", err) + } + + log.Info("Got in", "time", time.Since(start).Seconds()) + return nil } -func GetBalance(reqId int, address common.Address, blockNum string) { +func GetBalance(reqId int, address common.Address, blockNum string) error { reqGen := initialiseRequestGenerator(reqId) var b rpctest.EthBalance - res := reqGen.Erigon("eth_getBalance", reqGen.getBalance(address, blockNum), &b) - if res.Err != nil { - fmt.Printf("Error getting balance: %v\n", res.Err) - return + if res := reqGen.Erigon("eth_getBalance", reqGen.getBalance(address, blockNum), &b); res.Err != nil { + return fmt.Errorf("failed to get balance: %v", res.Err) } - fmt.Printf("Balance retrieved: %v\n", parseResponse(b)) + s, err := parseResponse(b) + if err != nil { + return fmt.Errorf("error parsing resonse: %v", err) + } + + fmt.Printf("Balance retrieved: %v\n", s) + return nil } func SendTx(reqId int, signedTx *types.Transaction) (*common.Hash, error) { @@ -36,56 +58,70 @@ func SendTx(reqId int, signedTx *types.Transaction) (*common.Hash, error) { var b rpctest.EthSendRawTransaction var buf bytes.Buffer - err := (*signedTx).MarshalBinary(&buf) - if err != nil { - return nil, err + if err := (*signedTx).MarshalBinary(&buf); err != nil { + return nil, fmt.Errorf("failed to marshal binary: %v", err) } - res := reqGen.Erigon("eth_sendRawTransaction", reqGen.sendRawTransaction(buf.Bytes()), &b) - if res.Err != nil { - return nil, err + if res := reqGen.Erigon("eth_sendRawTransaction", reqGen.sendRawTransaction(buf.Bytes()), &b); res.Err != nil { + return nil, fmt.Errorf("could not make request to eth_sendRawTransaction: %v", res.Err) } - fmt.Printf("Submitted transaction successfully: %v\n", parseResponse(b)) + s, err := parseResponse(b) + if err != nil { + return nil, fmt.Errorf("error parsing resonse: %v", err) + } + + fmt.Printf("Submitted transaction successfully: %v\n", s) return &b.TxnHash, nil } -func TxpoolContent(reqId int) { +func TxpoolContent(reqId int) error { reqGen := initialiseRequestGenerator(reqId) var b rpctest.EthTxPool - res := reqGen.Erigon("txpool_content", reqGen.txpoolContent(), &b) - if res.Err != nil { - fmt.Printf("Error fetching txpool: %v\n", res.Err) - return + if res := reqGen.Erigon("txpool_content", reqGen.txpoolContent(), &b); res.Err != nil { + return fmt.Errorf("failed to fetch txpool content: %v", res.Err) + } + + s, err := parseResponse(b) + if err != nil { + return fmt.Errorf("error parsing resonse: %v", err) } - fmt.Printf("Txpool content: %v\n", parseResponse(b)) + fmt.Printf("Txpool content: %v\n", s) + return nil } -func ParityList(reqId int, account common.Address, quantity int, offset []byte, blockNum string) { +func ParityList(reqId int, account common.Address, quantity int, offset []byte, blockNum string) error { reqGen := initialiseRequestGenerator(reqId) var b rpctest.ParityListStorageKeysResult - res := reqGen.Erigon("parity_listStorageKeys", reqGen.parityStorageKeyListContent(account, quantity, offset, blockNum), &b) - if res.Err != nil { - fmt.Printf("Error fetching storage keys: %v\n", res.Err) - return + if res := reqGen.Erigon("parity_listStorageKeys", reqGen.parityStorageKeyListContent(account, quantity, offset, blockNum), &b); res.Err != nil { + return fmt.Errorf("failed to fetch storage keys: %v", res.Err) } - fmt.Printf("Storage keys: %v\n", parseResponse(b)) + s, err := parseResponse(b) + if err != nil { + return fmt.Errorf("error parsing resonse: %v", err) + } + fmt.Printf("Storage keys: %v\n", s) + return nil } func GetLogs(reqId int, fromBlock, toBlock uint64, address common.Address) error { reqGen := initialiseRequestGenerator(reqId) var b rpctest.EthGetLogs - res := reqGen.Erigon("eth_getLogs", reqGen.getLogs(fromBlock, toBlock, address), &b) - if res.Err != nil { + if res := reqGen.Erigon("eth_getLogs", reqGen.getLogs(fromBlock, toBlock, address), &b); res.Err != nil { return fmt.Errorf("Error fetching logs: %v\n", res.Err) } - fmt.Printf("Logs: %v\n", parseResponse(b)) + s, err := parseResponse(b) + if err != nil { + return fmt.Errorf("error parsing resonse: %v", err) + } + + fmt.Printf("Logs: %v\n", s) return nil } diff --git a/cmd/devnettest/requests/utils.go b/cmd/devnettest/requests/utils.go index 7401798bce..ef49310a00 100644 --- a/cmd/devnettest/requests/utils.go +++ b/cmd/devnettest/requests/utils.go @@ -3,32 +3,11 @@ package requests import ( "encoding/json" "fmt" - "net/http" "strconv" "strings" - "time" - - "github.com/ledgerwatch/log/v3" ) -func post(client *http.Client, url, request string, response interface{}) error { - start := time.Now() - r, err := client.Post(url, "application/json", strings.NewReader(request)) - if err != nil { - return err - } - defer r.Body.Close() - - if r.StatusCode != 200 { - return fmt.Errorf("status %s", r.Status) - } - - decoder := json.NewDecoder(r.Body) - err = decoder.Decode(response) - log.Info("Got in", "time", time.Since(start).Seconds()) - return err -} - +// HexToInt converts a hex string to a type uint64 func HexToInt(hexStr string) uint64 { // Remove the 0x prefix cleaned := strings.ReplaceAll(hexStr, "0x", "") @@ -36,3 +15,13 @@ func HexToInt(hexStr string) uint64 { result, _ := strconv.ParseUint(cleaned, 16, 64) return result } + +// parseResponse converts any of the rpctest interfaces to a string for readability +func parseResponse(resp interface{}) (string, error) { + result, err := json.Marshal(resp) + if err != nil { + return "", fmt.Errorf("error trying to marshal response: %v", err) + } + + return string(result), nil +} diff --git a/cmd/devnettest/services/block.go b/cmd/devnettest/services/block.go new file mode 100644 index 0000000000..bd86b5d393 --- /dev/null +++ b/cmd/devnettest/services/block.go @@ -0,0 +1,218 @@ +package services + +import ( + "context" + "fmt" + "math/big" + "strings" + "time" + + "github.com/holiman/uint256" + "github.com/ledgerwatch/erigon/accounts/abi/bind" + "github.com/ledgerwatch/erigon/accounts/abi/bind/backends" + "github.com/ledgerwatch/erigon/cmd/devnettest/contracts" + "github.com/ledgerwatch/erigon/cmd/devnettest/requests" + "github.com/ledgerwatch/erigon/common" + "github.com/ledgerwatch/erigon/common/hexutil" + "github.com/ledgerwatch/erigon/core" + "github.com/ledgerwatch/erigon/core/types" + "github.com/ledgerwatch/erigon/crypto" + "github.com/ledgerwatch/erigon/params" + "github.com/ledgerwatch/erigon/rpc" +) + +const ( + gasPrice = 912345678 +) + +var ( + devnetSignPrivateKey, _ = crypto.HexToECDSA("26e86e45f6fc45ec6e2ecd128cec80fa1d1505e5507dcd2ae58c3130a7a97b48") + signer = types.LatestSigner(params.AllCliqueProtocolChanges) +) + +type Block struct { + Number *hexutil.Big + Transactions []common.Hash +} + +// CreateTransaction returns transaction details depending on what transaction type is given +func CreateTransaction(transactionType, addr string, value, nonce uint64, searchBlock bool) (*types.Transaction, common.Address, *contracts.Subscription, *bind.TransactOpts, error) { + if transactionType == "regular" { + tx, address, err := createNonContractTx(addr, value, nonce) + if err != nil { + return nil, common.Address{}, nil, nil, fmt.Errorf("failed to create non-contract transaction: %v", err) + } + return tx, address, nil, nil, nil + } + return createContractTx(nonce) +} + +// createNonContractTx takes in a signer and returns the signed transaction and the address receiving the sent value +func createNonContractTx(addr string, value, nonce uint64) (*types.Transaction, common.Address, error) { + toAddress := common.HexToAddress(addr) + signedTx, err := types.SignTx(types.NewTransaction(nonce, toAddress, uint256.NewInt(value), + params.TxGas, uint256.NewInt(gasPrice), nil), *signer, devnetSignPrivateKey) + if err != nil { + return nil, toAddress, fmt.Errorf("failed to sign transaction: %v", err) + } + return &signedTx, toAddress, nil +} + +// createContractTx creates and signs a transaction using the developer address, returns the contract and the signed transaction +func createContractTx(nonce uint64) (*types.Transaction, common.Address, *contracts.Subscription, *bind.TransactOpts, error) { + gspec := core.DeveloperGenesisBlock(uint64(0), common.HexToAddress("67b1d87101671b127f5f8714789C7192f7ad340e")) + contractBackend := backends.NewSimulatedBackendWithConfig(gspec.Alloc, gspec.Config, 1_000_000) + + // initialize transactOpts + transactOpts, err := initializeTransactOps(nonce) + if err != nil { + return nil, common.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, contractBackend) + if err != nil { + return nil, common.Address{}, nil, nil, fmt.Errorf("failed to deploy subscription: %v", err) + } + + // sign the transaction with the private key + signedTx, err := types.SignTx(txToSign, *signer, devnetSignPrivateKey) + if err != nil { + return nil, common.Address{}, nil, nil, fmt.Errorf("failed to sign tx: %v", err) + } + + return &signedTx, address, subscriptionContract, transactOpts, nil +} + +func initializeTransactOps(nonce uint64) (*bind.TransactOpts, error) { + const txGas uint64 = 200_000 + var chainID = big.NewInt(1337) + + transactOpts, err := bind.NewKeyedTransactorWithChainID(devnetSignPrivateKey, chainID) + if err != nil { + return nil, fmt.Errorf("cannot create transactor with chainID %s, error: %v", chainID, err) + } + + transactOpts.GasLimit = txGas + transactOpts.GasPrice = big.NewInt(880_000_000) + // TODO: Get Nonce from account automatically + transactOpts.Nonce = big.NewInt(int64(nonce)) + + return transactOpts, nil +} + +// SearchBlockForTx connects the client to a websocket and listens for new heads to search the blocks for a tx hash +func SearchBlockForTx(txnHash common.Hash) (uint64, error) { + client, clientErr := rpc.DialWebsocket(context.Background(), "ws://127.0.0.1:8545", "") + if clientErr != nil { + return 0, fmt.Errorf("failed to dial websocket: %v", clientErr) + } + fmt.Println() + fmt.Println("Connected to web socket successfully") + + blockN, err := subscribe(client, "eth_newHeads", txnHash) + if err != nil { + return 0, fmt.Errorf("failed to subscribe to ws: %v", err) + } + + return blockN, nil +} + +// subscribe makes a ws subscription using eth_newHeads +func subscribe(client *rpc.Client, method string, hash common.Hash) (uint64, error) { + parts := strings.SplitN(method, "_", 2) + namespace := parts[0] + method = parts[1] + ch := make(chan interface{}) + sub, err := client.Subscribe(context.Background(), namespace, ch, []interface{}{method}...) + if err != nil { + return uint64(0), fmt.Errorf("client failed to subscribe: %v", err) + } + defer sub.Unsubscribe() + + var ( + blockCount int + blockN uint64 + ) +ForLoop: + for { + select { + case v := <-ch: + blockCount++ + blockNumber := v.(map[string]interface{})["number"] + fmt.Printf("Searching for the transaction in block with number: %+v, type: %[1]T\n", blockNumber.(string)) + num, foundTx, err := blockHasHash(client, hash, blockNumber.(string)) + if err != nil { + return uint64(0), fmt.Errorf("could not verify if current block contains the tx hash: %v", err) + } + if foundTx || blockCount == 128 { + blockN = num + break ForLoop + } + case err := <-sub.Err(): + return uint64(0), fmt.Errorf("subscription error from client: %v", err) + } + } + + return blockN, nil + +} + +// blockHasHash checks if the current block has the transaction hash in its list of transactions +func blockHasHash(client *rpc.Client, hash common.Hash, blockNumber string) (uint64, bool, error) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + var currentBlock Block + err := client.CallContext(ctx, ¤tBlock, "eth_getBlockByNumber", blockNumber, false) + if err != nil { + return uint64(0), false, fmt.Errorf("failed to get block by number: %v", err) + } + + for _, txnHash := range currentBlock.Transactions { + if txnHash == hash { + fmt.Println() + fmt.Printf("Block with number: %v was mined and included transaction with hash: %v ==> %+v\n", blockNumber, hash, currentBlock) + fmt.Println() + return requests.HexToInt(blockNumber), true, nil + } + } + + return uint64(0), false, nil +} + +// EmitEventAndGetLogs emits an event from the contract using the fallback method +func EmitEventAndGetLogs(reqId int, subContract *contracts.Subscription, opts *bind.TransactOpts, address common.Address) error { + opts.Nonce.Add(opts.Nonce, big.NewInt(1)) + + tx, err := subContract.Fallback(opts, []byte{}) + if err != nil { + return fmt.Errorf("failed to emit event from fallback: %v", err) + } + + signedTx, err := types.SignTx(tx, *signer, devnetSignPrivateKey) + if err != nil { + return fmt.Errorf("failed to sign transaction: %v", err) + } + + hash, err := requests.SendTx(reqId, &signedTx) + if err != nil { + return fmt.Errorf("failed to send transaction: %v", err) + } + + blockN, err := SearchBlockForTx(*hash) + if err != nil { + return fmt.Errorf("error searching block for tx: %v", err) + } + + if err = requests.GetLogs(reqId, blockN, blockN, address); err != nil { + return fmt.Errorf("failed to get logs: %v", err) + } + + return nil +} + +// ClearDevDB cleans up the dev folder used for the operations +func ClearDevDB() { + fmt.Printf("Clearing ~/dev\n") +} -- GitLab