github.com/0xsequence/ethkit@v1.25.0/cmd/chain-receipts/main.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"log"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/0xsequence/ethkit"
    12  	"github.com/0xsequence/ethkit/ethmonitor"
    13  	"github.com/0xsequence/ethkit/ethreceipts"
    14  	"github.com/0xsequence/ethkit/ethrpc"
    15  	"github.com/0xsequence/ethkit/go-ethereum/common"
    16  	"github.com/0xsequence/ethkit/go-ethereum/core/types"
    17  	"github.com/0xsequence/ethkit/go-ethereum/crypto"
    18  	"github.com/0xsequence/ethkit/util"
    19  	"github.com/goware/logger"
    20  )
    21  
    22  var ETH_NODE_URL = "http://localhost:8545"
    23  var ETH_NODE_WSS_URL = ""
    24  
    25  func init() {
    26  	testConfig, err := util.ReadTestConfig("../../ethkit-test.json")
    27  	if err != nil {
    28  		panic(err)
    29  	}
    30  
    31  	if testConfig["POLYGON_MAINNET_URL"] != "" {
    32  		ETH_NODE_URL = testConfig["POLYGON_MAINNET_URL"]
    33  		ETH_NODE_WSS_URL = testConfig["POLYGON_MAINNET_WSS_URL"]
    34  	}
    35  	// if testConfig["MAINNET_URL"] != "" {
    36  	// 	ETH_NODE_URL = testConfig["MAINNET_URL"]
    37  	// 	ETH_NODE_WSS_URL = testConfig["MAINNET_WSS_URL"]
    38  	// }
    39  }
    40  
    41  func main() {
    42  	fmt.Println("chain-receipts start")
    43  
    44  	// Provider
    45  	provider, err := ethrpc.NewProvider(ETH_NODE_URL, ethrpc.WithStreaming(ETH_NODE_WSS_URL))
    46  	if err != nil {
    47  		log.Fatal(err)
    48  	}
    49  
    50  	// Monitor options
    51  	monitorOptions := ethmonitor.DefaultOptions
    52  	monitorOptions.PollingInterval = time.Duration(1000 * time.Millisecond)
    53  	// monitorOptions.DebugLogging = true
    54  	monitorOptions.WithLogs = true
    55  	monitorOptions.BlockRetentionLimit = 400
    56  	monitorOptions.StartBlockNumber = nil // track the head
    57  
    58  	receiptListenerOptions := ethreceipts.DefaultOptions
    59  	receiptListenerOptions.NumBlocksToFinality = 20
    60  	receiptListenerOptions.FilterMaxWaitNumBlocks = 5
    61  
    62  	err = listener(provider, monitorOptions, receiptListenerOptions)
    63  	if err != nil {
    64  		log.Fatal(err)
    65  	}
    66  }
    67  
    68  func listener(provider *ethrpc.Provider, monitorOptions ethmonitor.Options, receiptListenerOptions ethreceipts.Options) error {
    69  	ctx := context.Background()
    70  
    71  	monitor, err := ethmonitor.NewMonitor(provider, monitorOptions)
    72  	if err != nil {
    73  		log.Fatal(err)
    74  	}
    75  
    76  	go func() {
    77  		err = monitor.Run(ctx)
    78  		if err != nil {
    79  			panic(err)
    80  		}
    81  	}()
    82  	defer monitor.Stop()
    83  
    84  	// monitorSub := monitor.Subscribe()
    85  	// defer monitorSub.Unsubscribe()
    86  
    87  	receiptListener, err := ethreceipts.NewReceiptsListener(logger.NewLogger(logger.LogLevel_INFO), provider, monitor, receiptListenerOptions)
    88  	if err != nil {
    89  		log.Fatal(err)
    90  	}
    91  
    92  	go func() {
    93  		err := receiptListener.Run(ctx)
    94  		if err != nil {
    95  			log.Fatal(err)
    96  		}
    97  	}()
    98  	defer receiptListener.Stop()
    99  
   100  	// Find specific meta transaction -- note: this is not the "transaction hash",
   101  	// this is a sub-transaction where the id is emitted as an event.
   102  	FilterMetaTransactionID := func(metaTxnID ethkit.Hash) ethreceipts.FilterQuery {
   103  		return ethreceipts.FilterLogs(func(logs []*types.Log) bool {
   104  			for _, log := range logs {
   105  				isTxExecuted := IsTxExecutedEvent(log, metaTxnID)
   106  				isTxFailed := IsTxFailedEvent(log, metaTxnID)
   107  				if isTxExecuted || isTxFailed {
   108  					// found the sequence meta txn
   109  					return true
   110  				}
   111  			}
   112  			return false
   113  		})
   114  	}
   115  	_ = FilterMetaTransactionID
   116  
   117  	// Find any Sequence meta txns
   118  	FilterMetaTransactionAny := func() ethreceipts.FilterQuery {
   119  		return ethreceipts.FilterLogs(func(logs []*types.Log) bool {
   120  			foundNonceEvent := false
   121  			for _, log := range logs {
   122  				if len(log.Topics) > 0 && log.Topics[0] == NonceChangeEventSig {
   123  					foundNonceEvent = true
   124  					break
   125  				}
   126  			}
   127  			if !foundNonceEvent {
   128  				return false
   129  			}
   130  
   131  			for _, log := range logs {
   132  				if len(log.Topics) == 1 && log.Topics[0] == TxFailedEventSig {
   133  					// failed sequence txn
   134  					return true
   135  				} else if len(log.Topics) == 0 && len(log.Data) == 32 {
   136  					// possibly a successful sequence txn -- but not for certain
   137  					return true
   138  				}
   139  			}
   140  
   141  			return false
   142  		})
   143  	}
   144  	_ = FilterMetaTransactionAny
   145  
   146  	sub := receiptListener.Subscribe(
   147  		// FilterMetaTransactionID(common.HexToHash("2d5174e4f5ff20a19c34b63e90818c9ced7854675a679373be92b87f718118d4")).LimitOne(true),
   148  		FilterMetaTransactionAny().MaxWait(0), // listen on all sequence txns
   149  	)
   150  
   151  	var wg sync.WaitGroup
   152  	wg.Add(1)
   153  	go func() {
   154  		defer wg.Done()
   155  		for {
   156  			select {
   157  			case receipt := <-sub.TransactionReceipt():
   158  
   159  				fmt.Println("=> sequence txn receipt:", receipt.TransactionHash())
   160  
   161  				// This block of code will search for the same metaTxnID in 10 seconds,
   162  				// so that we can ensure we can find it easily from our cache.
   163  				// go func(txn common.Hash, receipt ethreceipts.Receipt) {
   164  				// 	time.Sleep(10 * time.Second)
   165  
   166  				// 	var metaTxnID common.Hash
   167  				// 	for _, log := range receipt.Logs() {
   168  				// 		if len(log.Topics) == 0 && len(log.Data) == 32 {
   169  				// 			metaTxnID = common.BytesToHash(log.Data)
   170  				// 		}
   171  				// 	}
   172  				// 	fmt.Println("-> search for metaTxnID", metaTxnID)
   173  
   174  				// 	r, _, err := receiptListener.FetchTransactionReceiptWithFilter(context.Background(), FilterMetaTransactionID(metaTxnID).LimitOne(true).SearchCache(true))
   175  				// 	if err != nil {
   176  				// 		panic(err)
   177  				// 	}
   178  				// 	fmt.Println("===> found the meta txn! txnHash:", r.TransactionHash())
   179  				// }(receipt.TransactionHash(), receipt)
   180  
   181  			case <-sub.Done():
   182  				return
   183  			}
   184  		}
   185  	}()
   186  
   187  	wg.Wait()
   188  
   189  	return nil
   190  }
   191  
   192  // Transaction events as defined in wallet-contracts IModuleCalls.sol
   193  var (
   194  	// NonceChangeEventSig is the signature event emitted as the first event on the batch execution
   195  	// 0x1f180c27086c7a39ea2a7b25239d1ab92348f07ca7bb59d1438fcf527568f881
   196  	NonceChangeEventSig = MustEncodeSig("NonceChange(uint256,uint256)")
   197  
   198  	// TxFailedEventSig is the signature event emitted in a failed smart-wallet meta-transaction batch
   199  	// 0x3dbd1590ea96dd3253a91f24e64e3a502e1225d602a5731357bc12643070ccd7
   200  	TxFailedEventSig = MustEncodeSig("TxFailed(bytes32,bytes)")
   201  
   202  	// TxExecutedEventSig is the signature of the event emitted in a successful transaction
   203  	// 0x0639b0b186d373976f8bb98f9f7226ba8070f10cb6c7f9bd5086d3933f169a25
   204  	TxExecutedEventSig = MustEncodeSig("TxExecuted(bytes32)")
   205  )
   206  
   207  func MustEncodeSig(str string) common.Hash {
   208  	return crypto.Keccak256Hash([]byte(str))
   209  }
   210  
   211  func IsTxExecutedEvent(log *types.Log, hash common.Hash) bool {
   212  	return len(log.Topics) == 0 &&
   213  		len(log.Data) == 32 &&
   214  		bytes.Equal(log.Data, hash[:])
   215  }
   216  
   217  func IsTxFailedEvent(log *types.Log, hash common.Hash) bool {
   218  	return len(log.Topics) == 1 &&
   219  		log.Topics[0] == TxFailedEventSig &&
   220  		bytes.HasPrefix(log.Data, hash[:])
   221  }