github.com/decred/dcrlnd@v0.7.6/chainscan/examples/dcrwallethistorical/main.go (about)

     1  // Copyright (c) 2020 The Decred developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"context"
     9  	"errors"
    10  	"log"
    11  	"os"
    12  	"path/filepath"
    13  	"time"
    14  
    15  	"decred.org/dcrwallet/v4/rpc/walletrpc"
    16  	"github.com/decred/dcrd/chaincfg/v3"
    17  	"github.com/decred/dcrd/dcrutil/v4"
    18  	"github.com/decred/dcrd/txscript/v4/stdaddr"
    19  	"github.com/decred/dcrd/wire"
    20  	"github.com/decred/dcrlnd/chainscan"
    21  	"github.com/decred/dcrlnd/chainscan/csdrivers"
    22  	"github.com/jessevdk/go-flags"
    23  	"google.golang.org/grpc"
    24  	"google.golang.org/grpc/credentials"
    25  )
    26  
    27  type config struct {
    28  	RPCCert    string   `long:"rpccert" description:"File containing the certificate file"`
    29  	RPCConnect string   `short:"c" long:"rpcconnect" description:"Network address of dcrd RPC server"`
    30  	TestNet    bool     `long:"testnet" description:"Use the test network"`
    31  	Targets    []string `short:"t" long:"target" description:"Target address to search for. Can be multiple."`
    32  }
    33  
    34  type dcrwConfig struct {
    35  	RPCCert string `long:"rpccert" description:"File containing the certificate file"`
    36  	TestNet bool   `long:"testnet" description:"Use the test network"`
    37  }
    38  
    39  func main() {
    40  	dcrwHomeDir := dcrutil.AppDataDir("dcrwallet", false)
    41  	opts := &config{
    42  		RPCCert: filepath.Join(dcrwHomeDir, "rpc.cert"),
    43  	}
    44  	dcrwCfgFile := filepath.Join(dcrwHomeDir, "dcrwallet.conf")
    45  	if _, err := os.Stat(dcrwCfgFile); err == nil {
    46  		// ~/.dcrwallet/dcrwallet.conf exists. Read precfg data from
    47  		// it.
    48  		dcrwOpts := &dcrwConfig{}
    49  		parser := flags.NewParser(dcrwOpts, flags.Default)
    50  		err := flags.NewIniParser(parser).ParseFile(dcrwCfgFile)
    51  		if err == nil {
    52  			switch {
    53  			case dcrwOpts.TestNet:
    54  				opts.RPCConnect = "localhost:19111"
    55  				opts.TestNet = true
    56  			default:
    57  				opts.RPCConnect = "localhost:9111"
    58  			}
    59  		}
    60  	}
    61  
    62  	parser := flags.NewParser(opts, flags.Default)
    63  	_, err := parser.Parse()
    64  	if err != nil {
    65  		var e *flags.Error
    66  		if errors.As(err, &e) && e.Type == flags.ErrHelp {
    67  			os.Exit(0)
    68  		}
    69  		parser.WriteHelp(os.Stderr)
    70  		return
    71  	}
    72  
    73  	params := chaincfg.MainNetParams()
    74  	if opts.TestNet {
    75  		params = chaincfg.TestNet3Params()
    76  	}
    77  
    78  	if len(opts.Targets) == 0 {
    79  		log.Fatal("Specify at least one target address")
    80  	}
    81  
    82  	// Connect to local dcrwallet gRPC server using websockets.
    83  	creds, err := credentials.NewClientTLSFromFile(opts.RPCCert, "localhost")
    84  	if err != nil {
    85  		log.Fatalf("Error creating credentials: %v", err)
    86  
    87  	}
    88  	conn, err := grpc.Dial(opts.RPCConnect, grpc.WithTransportCredentials(creds))
    89  	if err != nil {
    90  		log.Fatalf("Error connecting to dcrwallet's gRPC: %v", err)
    91  	}
    92  	defer conn.Close()
    93  
    94  	w := walletrpc.NewWalletServiceClient(conn)
    95  	n := walletrpc.NewNetworkServiceClient(conn)
    96  	resp, err := w.BestBlock(context.Background(), &walletrpc.BestBlockRequest{})
    97  	if err != nil {
    98  		log.Fatal(err)
    99  	}
   100  	bestHeight := int32(resp.Height)
   101  	log.Printf("Searching up to height %d", bestHeight)
   102  	startTime := time.Now()
   103  
   104  	var targets []chainscan.TargetAndOptions
   105  	for _, t := range opts.Targets {
   106  		addr, err := stdaddr.DecodeAddress(t, params)
   107  		if err != nil {
   108  			log.Fatal(err)
   109  		}
   110  
   111  		scriptVer, script := addr.PaymentScript()
   112  
   113  		foundCb := func(e chainscan.Event, findMore chainscan.FindFunc) {
   114  			outp := wire.OutPoint{
   115  				Hash:  e.Tx.TxHash(),
   116  				Index: uint32(e.Index),
   117  				Tree:  e.Tree,
   118  			}
   119  			log.Printf("Found addr %s mined at block %d (output %s)",
   120  				t, e.BlockHeight, outp)
   121  
   122  			foundSpendCb := func(es chainscan.Event, _ chainscan.FindFunc) {
   123  				log.Printf("Found addr %s (outpoint %s) spent at block %d (input %s:%d)",
   124  					t, outp, es.BlockHeight,
   125  					es.Tx.TxHash(), es.Index)
   126  			}
   127  			findMore(
   128  				chainscan.SpentOutPoint(outp, scriptVer, script),
   129  				chainscan.WithFoundCallback(foundSpendCb),
   130  				chainscan.WithEndHeight(int32(bestHeight)),
   131  			)
   132  		}
   133  
   134  		target := chainscan.TargetAndOptions{
   135  			Target: chainscan.ConfirmedScript(0, script),
   136  			Options: []chainscan.Option{
   137  				chainscan.WithFoundCallback(foundCb),
   138  				chainscan.WithEndHeight(int32(bestHeight)),
   139  			},
   140  		}
   141  		targets = append(targets, target)
   142  	}
   143  
   144  	completeChan := make(chan struct{})
   145  	targets[0].Options = append(targets[0].Options, chainscan.WithCompleteChan(completeChan))
   146  
   147  	chainSrc := csdrivers.NewRemoteWalletCSDriver(w, n, nil)
   148  	hist := chainscan.NewHistorical(chainSrc)
   149  	histCtx, cancel := context.WithCancel(context.Background())
   150  	go func() {
   151  		err := hist.Run(histCtx)
   152  		select {
   153  		case <-histCtx.Done():
   154  		default:
   155  			log.Printf("Historical run errored: %v", err)
   156  			close(completeChan)
   157  		}
   158  	}()
   159  
   160  	hist.FindMany(targets)
   161  	<-completeChan
   162  
   163  	endTime := time.Now()
   164  	log.Printf("Completed search in %s", endTime.Sub(startTime))
   165  
   166  	cancel()
   167  }