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 }