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, &currentBlock, "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, &currentBlock, "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