github.com/decred/dcrlnd@v0.7.6/chainscan/examples/dcrdhistorical/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  	"io/ioutil"
    11  	"log"
    12  	"os"
    13  	"path/filepath"
    14  	"time"
    15  
    16  	"github.com/decred/dcrd/chaincfg/chainhash"
    17  	"github.com/decred/dcrd/chaincfg/v3"
    18  	"github.com/decred/dcrd/dcrutil/v4"
    19  	"github.com/decred/dcrd/gcs/v4"
    20  	"github.com/decred/dcrd/rpcclient/v8"
    21  	"github.com/decred/dcrd/txscript/v4/stdaddr"
    22  	"github.com/decred/dcrd/wire"
    23  	"github.com/decred/dcrlnd/chainscan"
    24  	"github.com/jessevdk/go-flags"
    25  )
    26  
    27  type config struct {
    28  	RPCUser    string   `short:"u" long:"rpcuser" description:"Username for RPC connections"`
    29  	RPCPass    string   `short:"P" long:"rpcpass" description:"Password for RPC connections"`
    30  	RPCCert    string   `long:"rpccert" description:"File containing the certificate file"`
    31  	RPCConnect string   `short:"c" long:"rpcconnect" description:"Network address of dcrd RPC server"`
    32  	TestNet    bool     `long:"testnet" description:"Use the test network"`
    33  	Targets    []string `short:"t" long:"target" description:"Target address to search for. Can be multiple."`
    34  }
    35  
    36  type dcrdConfig struct {
    37  	RPCUser string `short:"u" long:"rpcuser" description:"Username for RPC connections"`
    38  	RPCPass string `short:"P" long:"rpcpass" description:"Password for RPC connections"`
    39  	RPCCert string `long:"rpccert" description:"File containing the certificate file"`
    40  	TestNet bool   `long:"testnet" description:"Use the test network"`
    41  }
    42  
    43  // dcrdChainSource implements a HistoricalChainSource that fetches data from an
    44  // rpc client connected to a dcrd instance.
    45  type dcrdChainSource struct {
    46  	c *rpcclient.Client
    47  }
    48  
    49  func (s *dcrdChainSource) GetCFilter(ctx context.Context, height int32) (*chainhash.Hash, [16]byte, *gcs.FilterV2, error) {
    50  	var cfkey [16]byte
    51  
    52  	bh, err := s.c.GetBlockHash(ctx, int64(height))
    53  	if err != nil {
    54  		return nil, cfkey, nil, err
    55  	}
    56  
    57  	head, err := s.c.GetBlockHeader(ctx, bh)
    58  	if err != nil {
    59  		return nil, cfkey, nil, err
    60  	}
    61  
    62  	// No need to check for proof inclusion of the cfilter in the header
    63  	// since we trust this dcrd instance, but on SPV clients this would be
    64  	// needed when fetching via the P2P network.
    65  	cf, err := s.c.GetCFilterV2(ctx, bh)
    66  	if err != nil {
    67  		return nil, cfkey, nil, err
    68  	}
    69  
    70  	if height%10000 == 0 {
    71  		log.Printf("Fetched CFilter for block height %d", height)
    72  	}
    73  
    74  	copy(cfkey[:], head.MerkleRoot[:])
    75  	return bh, cfkey, cf.Filter, nil
    76  }
    77  
    78  func (s *dcrdChainSource) GetBlock(ctx context.Context, bh *chainhash.Hash) (*wire.MsgBlock, error) {
    79  	bl, err := s.c.GetBlock(ctx, bh)
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  
    84  	log.Printf("Fetched block for height %d", bl.Header.Height)
    85  	return bl, err
    86  }
    87  
    88  func (s *dcrdChainSource) CurrentTip(ctx context.Context) (*chainhash.Hash, int32, error) {
    89  	bh, h, err := s.c.GetBestBlock(ctx)
    90  	return bh, int32(h), err
    91  }
    92  
    93  func main() {
    94  	dcrdHomeDir := dcrutil.AppDataDir("dcrd", false)
    95  	opts := &config{
    96  		RPCCert: filepath.Join(dcrdHomeDir, "rpc.cert"),
    97  	}
    98  	dcrdCfgFile := filepath.Join(dcrdHomeDir, "dcrd.conf")
    99  	if _, err := os.Stat(dcrdCfgFile); err == nil {
   100  		// ~/.dcrd/dcrd.conf exists. Read precfg data from it.
   101  		dcrdOpts := &dcrdConfig{}
   102  		parser := flags.NewParser(dcrdOpts, flags.Default)
   103  		err := flags.NewIniParser(parser).ParseFile(dcrdCfgFile)
   104  		if err == nil {
   105  			opts.RPCUser = dcrdOpts.RPCUser
   106  			opts.RPCPass = dcrdOpts.RPCPass
   107  			switch {
   108  			case dcrdOpts.TestNet:
   109  				opts.RPCConnect = "localhost:19109"
   110  				opts.TestNet = true
   111  			default:
   112  				opts.RPCConnect = "localhost:9109"
   113  			}
   114  		}
   115  	}
   116  
   117  	parser := flags.NewParser(opts, flags.Default)
   118  	_, err := parser.Parse()
   119  	if err != nil {
   120  		var e *flags.Error
   121  		if errors.As(err, &e) && e.Type == flags.ErrHelp {
   122  			os.Exit(0)
   123  		}
   124  		parser.WriteHelp(os.Stderr)
   125  		return
   126  	}
   127  
   128  	params := chaincfg.MainNetParams()
   129  	if opts.TestNet {
   130  		params = chaincfg.TestNet3Params()
   131  	}
   132  
   133  	if len(opts.Targets) == 0 {
   134  		log.Fatal("Specify at least one target address")
   135  	}
   136  
   137  	// Connect to local dcrd RPC server using websockets.
   138  	certs, err := ioutil.ReadFile(opts.RPCCert)
   139  	if err != nil {
   140  		log.Fatal(err)
   141  	}
   142  	connCfg := &rpcclient.ConnConfig{
   143  		Host:         opts.RPCConnect,
   144  		Endpoint:     "ws",
   145  		User:         opts.RPCUser,
   146  		Pass:         opts.RPCPass,
   147  		Certificates: certs,
   148  	}
   149  	client, err := rpcclient.New(connCfg, nil)
   150  	if err != nil {
   151  		log.Fatal(err)
   152  	}
   153  
   154  	_, bestHeight, err := client.GetBestBlock(context.Background())
   155  	if err != nil {
   156  		log.Fatal(err)
   157  	}
   158  	log.Printf("Searching up to height %d", bestHeight)
   159  	startTime := time.Now()
   160  
   161  	var targets []chainscan.TargetAndOptions
   162  	for _, t := range opts.Targets {
   163  		addr, err := stdaddr.DecodeAddress(t, params)
   164  		if err != nil {
   165  			log.Fatal(err)
   166  		}
   167  
   168  		scriptVer, script := addr.PaymentScript()
   169  
   170  		foundCb := func(e chainscan.Event, findMore chainscan.FindFunc) {
   171  			outp := wire.OutPoint{
   172  				Hash:  e.Tx.TxHash(),
   173  				Index: uint32(e.Index),
   174  				Tree:  e.Tree,
   175  			}
   176  			log.Printf("Found addr %s mined at block %d (output %s)",
   177  				t, e.BlockHeight, outp)
   178  
   179  			foundSpendCb := func(es chainscan.Event, _ chainscan.FindFunc) {
   180  				log.Printf("Found addr %s (outpoint %s) spent at block %d (input %s:%d)",
   181  					t, outp, es.BlockHeight,
   182  					es.Tx.TxHash(), es.Index)
   183  			}
   184  			findMore(
   185  				chainscan.SpentOutPoint(outp, scriptVer, script),
   186  				chainscan.WithFoundCallback(foundSpendCb),
   187  				chainscan.WithEndHeight(int32(bestHeight)),
   188  			)
   189  		}
   190  
   191  		target := chainscan.TargetAndOptions{
   192  			Target: chainscan.ConfirmedScript(0, script),
   193  			Options: []chainscan.Option{
   194  				chainscan.WithFoundCallback(foundCb),
   195  				chainscan.WithEndHeight(int32(bestHeight)),
   196  			},
   197  		}
   198  		targets = append(targets, target)
   199  	}
   200  
   201  	completeChan := make(chan struct{})
   202  	targets[0].Options = append(targets[0].Options, chainscan.WithCompleteChan(completeChan))
   203  
   204  	chainSrc := &dcrdChainSource{c: client}
   205  	hist := chainscan.NewHistorical(chainSrc)
   206  	histCtx, cancel := context.WithCancel(context.Background())
   207  	go hist.Run(histCtx)
   208  
   209  	hist.FindMany(targets)
   210  	<-completeChan
   211  
   212  	endTime := time.Now()
   213  	log.Printf("Completed search in %s", endTime.Sub(startTime))
   214  
   215  	cancel()
   216  	client.Shutdown()
   217  }