From 4ee0ae161067a6cc68d85e4032d80fcf91f77fdb Mon Sep 17 00:00:00 2001
From: leonardchinonso <36096513+leonardchinonso@users.noreply.github.com>
Date: Mon, 14 Mar 2022 14:59:51 +0100
Subject: [PATCH] adding eth_getLogs functionality for contract events for the
 devnet tool (#3680)

* adding eth_getLogs functionality for contract events for the devnet tool

* Made changes to fix double hashing

* Fixed lint errors

* Changed strings.Replace to strings.ReplaceAll for replacing strings

Co-authored-by: Alexey Sharp <alexeysharp@Alexeys-iMac.local>
---
 cmd/devnettest/commands/block.go             | 111 +++++++++++++------
 cmd/devnettest/requests/request_generator.go |   5 +
 cmd/devnettest/requests/requests.go          |  15 ++-
 cmd/devnettest/requests/utils.go             |   9 ++
 cmd/rpctest/rpctest/type.go                  |   5 -
 5 files changed, 104 insertions(+), 41 deletions(-)

diff --git a/cmd/devnettest/commands/block.go b/cmd/devnettest/commands/block.go
index 0ed04b51ed..9007ab8792 100644
--- a/cmd/devnettest/commands/block.go
+++ b/cmd/devnettest/commands/block.go
@@ -23,7 +23,10 @@ import (
 	"github.com/spf13/cobra"
 )
 
-var devnetSignPrivateKey, _ = crypto.HexToECDSA("26e86e45f6fc45ec6e2ecd128cec80fa1d1505e5507dcd2ae58c3130a7a97b48")
+var (
+	devnetSignPrivateKey, _ = crypto.HexToECDSA("26e86e45f6fc45ec6e2ecd128cec80fa1d1505e5507dcd2ae58c3130a7a97b48")
+	signer                  = types.LatestSigner(params.AllCliqueProtocolChanges)
+)
 
 var (
 	sendAddr    string
@@ -74,7 +77,7 @@ var sendTxCmd = &cobra.Command{
 		}
 
 		// subscriptionContract is the handler to the contract for further operations
-		signedTx, subscriptionContract, transactOpts, err := createTransaction(txType)
+		signedTx, address, subscriptionContract, transactOpts, err := createTransaction(txType)
 		if err != nil {
 			panic(err)
 		}
@@ -85,46 +88,44 @@ var sendTxCmd = &cobra.Command{
 		}
 
 		if searchBlock {
-			searchBlockForTx(*hash)
+			_ = searchBlockForTx(*hash)
 		}
 
 		fmt.Printf("subscription contract, transactOps: %+v, %+v\n", subscriptionContract, transactOpts)
 
-		//_, err = subscriptionContract.Fallback(transactOpts, []byte{})
-		//if err != nil {
-		//	fmt.Printf("error 3: %+v\n", err)
-		//	panic(err)
-		//}
-
 		// TODO: call eth_getLogs on address and this block number
+		if err := emitEventAndGetLogs(subscriptionContract, transactOpts, address); err != nil {
+			panic(err)
+		}
 	},
 }
 
-func createTransaction(transactionType string) (*types.Transaction, *contracts.Subscription, *bind.TransactOpts, error) {
-	signer := types.LatestSigner(params.AllCliqueProtocolChanges)
+func createTransaction(transactionType string) (*types.Transaction, common.Address, *contracts.Subscription, *bind.TransactOpts, error) {
 	if transactionType == "regular" {
-		return createNonContractTx(signer)
+		tx, address, err := createNonContractTx(signer)
+		return tx, address, nil, nil, err
 	}
 	return createContractTx(signer)
 }
 
-func createNonContractTx(signer *types.Signer) (*types.Transaction, *contracts.Subscription, *bind.TransactOpts, error) {
+// 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, nil, nil, err
+		return nil, toAddress, err
 	}
-	return &signedTx, nil, nil, nil
+	return &signedTx, toAddress, nil
 }
 
-func createContractTx(signer *types.Signer) (*types.Transaction, *contracts.Subscription, *bind.TransactOpts, error) {
+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, nil, nil, err
+		return nil, common.Address{}, nil, nil, err
 	}
 
 	transactOpts.GasLimit = txGas
@@ -133,21 +134,21 @@ func createContractTx(signer *types.Signer) (*types.Transaction, *contracts.Subs
 	transactOpts.Nonce = big.NewInt(int64(nonce))
 
 	// get transaction to sign and contract handler
-	_, txToSign, subscriptionContract, err := contracts.DeploySubscription(transactOpts, contractBackend)
+	address, txToSign, subscriptionContract, err := contracts.DeploySubscription(transactOpts, contractBackend)
 	if err != nil {
-		return nil, nil, nil, err
+		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, nil, nil, err
+		return nil, common.Address{}, nil, nil, err
 	}
 
-	return &signedTx, subscriptionContract, transactOpts, nil
+	return &signedTx, address, subscriptionContract, transactOpts, nil
 }
 
-func searchBlockForTx(txnHash common.Hash) {
+func searchBlockForTx(txnHash common.Hash) uint64 {
 	url := "ws://127.0.0.1:8545"
 	client, clientErr := rpc.DialWebsocket(context.Background(), url, "")
 	if clientErr != nil {
@@ -157,44 +158,51 @@ func searchBlockForTx(txnHash common.Hash) {
 	fmt.Println()
 	fmt.Println("Connected to web socket successfully")
 
-	if err := subscribe(client, "eth_newHeads", txnHash); err != nil {
+	blockN, err := subscribe(client, "eth_newHeads", txnHash)
+	if err != nil {
 		fmt.Println("error occurred while subscribing", err)
 		panic(err)
 	}
+
+	return blockN
 }
 
-func subscribe(client *rpc.Client, method string, hash common.Hash) error {
+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 err
+		return uint64(0), err
 	}
 	defer sub.Unsubscribe()
 
-	blockCount := 0
+	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\n", blockNumber)
-			foundTx, err := blockHasHash(client, hash, blockNumber.(string))
+			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 err
+				return uint64(0), err
 			}
 			if foundTx || blockCount == 128 {
+				blockN = num
 				break ForLoop
 			}
 		case err := <-sub.Err():
-			return err
+			return uint64(0), err
 		}
 	}
 
-	return nil
+	return blockN, nil
 
 }
 
@@ -203,7 +211,7 @@ type Block struct {
 	Transactions []common.Hash
 }
 
-func blockHasHash(client *rpc.Client, hash common.Hash, blockNumber string) (bool, error) {
+func blockHasHash(client *rpc.Client, hash common.Hash, blockNumber string) (uint64, bool, error) {
 	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
 	defer cancel()
 
@@ -211,7 +219,7 @@ func blockHasHash(client *rpc.Client, hash common.Hash, blockNumber string) (boo
 	err := client.CallContext(ctx, &currentBlock, "eth_getBlockByNumber", blockNumber, false)
 	if err != nil {
 		fmt.Println("can't get latest block:", err)
-		return false, err
+		return uint64(0), false, err
 	}
 
 	for _, txnHash := range currentBlock.Transactions {
@@ -219,9 +227,42 @@ func blockHasHash(client *rpc.Client, hash common.Hash, blockNumber string) (boo
 			fmt.Println()
 			fmt.Printf("Block with number: %v was mined and included transaction with hash: %v ==> %+v\n", blockNumber, hash, currentBlock)
 			fmt.Println()
-			return true, nil
+			return requests.HexToInt(blockNumber), true, nil
 		}
 	}
 
-	return false, nil
+	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 {
+		fmt.Printf("error 3: %+v\n", err)
+		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 {
+		fmt.Printf("error 4: %+v\n", err)
+		panic(err)
+	}
+
+	return nil
 }
diff --git a/cmd/devnettest/requests/request_generator.go b/cmd/devnettest/requests/request_generator.go
index 9075eff2b0..1343316f9b 100644
--- a/cmd/devnettest/requests/request_generator.go
+++ b/cmd/devnettest/requests/request_generator.go
@@ -112,3 +112,8 @@ func (req *RequestGenerator) parityStorageKeyListContent(address common.Address,
 
 	return fmt.Sprintf(template, address, quantity, offsetString, blockNum, req.reqID)
 }
+
+func (req *RequestGenerator) getLogs(fromBlock, toBlock uint64, address common.Address) string {
+	const template = `{"jsonrpc":"2.0","method":"eth_getLogs","params":[{"fromBlock": "0x%x", "toBlock": "0x%x", "address": "0x%x"}],"id":%d}`
+	return fmt.Sprintf(template, fromBlock, toBlock, address, req.reqID)
+}
diff --git a/cmd/devnettest/requests/requests.go b/cmd/devnettest/requests/requests.go
index ba46234841..11444fc031 100644
--- a/cmd/devnettest/requests/requests.go
+++ b/cmd/devnettest/requests/requests.go
@@ -4,7 +4,6 @@ import (
 	"bytes"
 	"encoding/json"
 	"fmt"
-
 	"github.com/ledgerwatch/erigon/cmd/rpctest/rpctest"
 	"github.com/ledgerwatch/erigon/common"
 	"github.com/ledgerwatch/erigon/core/types"
@@ -77,3 +76,17 @@ func ParityList(reqId int, account common.Address, quantity int, offset []byte,
 	fmt.Printf("Storage keys: %v\n", parseResponse(b))
 
 }
+
+func GetLogs(reqId int, fromBlock, toBlock uint64, address common.Address) error {
+	reqGen := initialiseRequestGenerator(reqId)
+	var b rpctest.Log
+
+	reqq := reqGen.getLogs(fromBlock, toBlock, address)
+	res := reqGen.Erigon("eth_getLogs", reqq, &b)
+	if res.Err != nil {
+		return fmt.Errorf("Error fetching logs: %v\n", res.Err)
+	}
+
+	fmt.Printf("Logs and events: %v\n", parseResponse(b))
+	return nil
+}
diff --git a/cmd/devnettest/requests/utils.go b/cmd/devnettest/requests/utils.go
index 46c5054578..f751ca602d 100644
--- a/cmd/devnettest/requests/utils.go
+++ b/cmd/devnettest/requests/utils.go
@@ -4,6 +4,7 @@ import (
 	"encoding/json"
 	"fmt"
 	"net/http"
+	"strconv"
 	"strings"
 	"time"
 
@@ -27,3 +28,11 @@ func post(client *http.Client, url, request string, response interface{}) error
 	log.Info("Got in", "time", time.Since(start).Seconds())
 	return err
 }
+
+func HexToInt(hexStr string) uint64 {
+	// Remove the 0x prefix
+	cleaned := strings.ReplaceAll(hexStr, "0x", "")
+
+	result, _ := strconv.ParseUint(cleaned, 16, 64)
+	return result
+}
diff --git a/cmd/rpctest/rpctest/type.go b/cmd/rpctest/rpctest/type.go
index a36469e65c..ad3df795f9 100644
--- a/cmd/rpctest/rpctest/type.go
+++ b/cmd/rpctest/rpctest/type.go
@@ -229,11 +229,6 @@ type EthReceipt struct {
 	Result Receipt `json:"result"`
 }
 
-type EthLogs struct {
-	CommonResponse
-	Result []*Log `json:"result"`
-}
-
 type EthGetProof struct {
 	CommonResponse
 	Result AccountResult `json:"result"`
-- 
GitLab