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 }