// connectRPCClient attempts to establish an RPC connection to the created // dcrd process belonging to this Harness instance. If the initial connection // attempt fails, this function will retry h.maxConnRetries times, backing off // the time between subsequent attempts. If after h.maxConnRetries attempts, // we're not able to establish a connection, this function returns with an error. func (h *Harness) connectRPCClient() error { var client *rpc.Client var err error rpcConf := h.node.config.rpcConnConfig() for i := 0; i < h.maxConnRetries; i++ { if client, err = rpc.New(&rpcConf, h.handlers); err != nil { time.Sleep(time.Duration(math.Log(float64(i+3))) * 50 * time.Millisecond) continue } break } if client == nil { return fmt.Errorf("connection timed out: %v", err) } err = client.NotifyBlocks() if err != nil { return err } h.Node = client return nil }
func main() { // Only override the handlers for notifications you care about. // Also note most of these handlers will only be called if you register // for notifications. See the documentation of the dcrrpcclient // NotificationHandlers type for more details about each handler. ntfnHandlers := dcrrpcclient.NotificationHandlers{ OnBlockConnected: func(hash *chainhash.Hash, height int32, time time.Time, vb uint16) { log.Printf("Block connected: %v (%d) %v %v", hash, height, time, vb) }, OnBlockDisconnected: func(hash *chainhash.Hash, height int32, time time.Time, vb uint16) { log.Printf("Block disconnected: %v (%d) %v %v", hash, height, time, vb) }, } // Connect to local dcrd RPC server using websockets. dcrdHomeDir := dcrutil.AppDataDir("dcrd", false) certs, err := ioutil.ReadFile(filepath.Join(dcrdHomeDir, "rpc.cert")) if err != nil { log.Fatal(err) } connCfg := &dcrrpcclient.ConnConfig{ Host: "localhost:8334", Endpoint: "ws", User: "******", Pass: "******", Certificates: certs, } client, err := dcrrpcclient.New(connCfg, &ntfnHandlers) if err != nil { log.Fatal(err) } // Register for block connect and disconnect notifications. if err := client.NotifyBlocks(); err != nil { log.Fatal(err) } log.Println("NotifyBlocks: Registration Complete") // Get the current block count. blockCount, err := client.GetBlockCount() if err != nil { log.Fatal(err) } log.Printf("Block count: %d", blockCount) // For this example gracefully shutdown the client after 10 seconds. // Ordinarily when to shutdown the client is highly application // specific. log.Println("Client shutdown in 10 seconds...") time.AfterFunc(time.Second*10, func() { log.Println("Client shutting down...") client.Shutdown() log.Println("Client shutdown complete.") }) // Wait until the client either shuts down gracefully (or the user // terminates the process with Ctrl+C). client.WaitForShutdown() }
func main() { // Only override the handlers for notifications you care about. // Also note most of the handlers will only be called if you register // for notifications. See the documentation of the dcrrpcclient // NotificationHandlers type for more details about each handler. ntfnHandlers := dcrrpcclient.NotificationHandlers{ OnAccountBalance: func(account string, balance dcrutil.Amount, confirmed bool) { log.Printf("New balance for account %s: %v", account, balance) }, } // Connect to local dcrwallet RPC server using websockets. certHomeDir := dcrutil.AppDataDir("dcrwallet", false) certs, err := ioutil.ReadFile(filepath.Join(certHomeDir, "rpc.cert")) if err != nil { log.Fatal(err) } connCfg := &dcrrpcclient.ConnConfig{ Host: "localhost:18332", Endpoint: "ws", User: "******", Pass: "******", Certificates: certs, } client, err := dcrrpcclient.New(connCfg, &ntfnHandlers) if err != nil { log.Fatal(err) } // Get the list of unspent transaction outputs (utxos) that the // connected wallet has at least one private key for. unspent, err := client.ListUnspent() if err != nil { log.Fatal(err) } log.Printf("Num unspent outputs (utxos): %d", len(unspent)) if len(unspent) > 0 { log.Printf("First utxo:\n%v", spew.Sdump(unspent[0])) } // For this example gracefully shutdown the client after 10 seconds. // Ordinarily when to shutdown the client is highly application // specific. log.Println("Client shutdown in 10 seconds...") time.AfterFunc(time.Second*10, func() { log.Println("Client shutting down...") client.Shutdown() log.Println("Client shutdown complete.") }) // Wait until the client either shuts down gracefully (or the user // terminates the process with Ctrl+C). client.WaitForShutdown() }
// NewClient creates a client connection to the server described by the connect // string. If disableTLS is false, the remote RPC certificate must be provided // in the certs slice. The connection is not established immediately, but must // be done using the Start method. If the remote server does not operate on // the same decred network as described by the passed chain parameters, the // connection will be disconnected. func NewClient(chainParams *chaincfg.Params, connect, user, pass string, certs []byte, disableTLS bool) (*Client, error) { client := Client{ reorganizeToHash: chainhash.Hash{}, reorganizing: false, chainParams: chainParams, enqueueNotification: make(chan interface{}), dequeueNotification: make(chan interface{}), enqueueVotingNotification: make(chan interface{}), dequeueVotingNotification: make(chan interface{}), currentBlock: make(chan *waddrmgr.BlockStamp), quit: make(chan struct{}), } ntfnCallbacks := dcrrpcclient.NotificationHandlers{ OnClientConnected: client.onClientConnect, OnBlockConnected: client.onBlockConnected, OnBlockDisconnected: client.onBlockDisconnected, OnReorganization: client.onReorganization, OnWinningTickets: client.onWinningTickets, OnSpentAndMissedTickets: client.onSpentAndMissedTickets, OnStakeDifficulty: client.onStakeDifficulty, OnRecvTx: client.onRecvTx, OnRedeemingTx: client.onRedeemingTx, OnRescanFinished: client.onRescanFinished, OnRescanProgress: client.onRescanProgress, } conf := dcrrpcclient.ConnConfig{ Host: connect, Endpoint: "ws", User: user, Pass: pass, Certificates: certs, DisableAutoReconnect: true, DisableConnectOnNew: true, DisableTLS: disableTLS, } c, err := dcrrpcclient.New(&conf, &ntfnCallbacks) if err != nil { return nil, err } client.Client = c return &client, nil }
// NewRPCClient creates a client connection to the server described by the // connect string. If disableTLS is false, the remote RPC certificate must be // provided in the certs slice. The connection is not established immediately, // but must be done using the Start method. If the remote server does not // operate on the same bitcoin network as described by the passed chain // parameters, the connection will be disconnected. func NewRPCClient(chainParams *chaincfg.Params, connect, user, pass string, certs []byte, disableTLS bool, reconnectAttempts int) (*RPCClient, error) { if reconnectAttempts < 0 { return nil, errors.New("reconnectAttempts must be positive") } client := &RPCClient{ connConfig: &dcrrpcclient.ConnConfig{ Host: connect, Endpoint: "ws", User: user, Pass: pass, Certificates: certs, DisableAutoReconnect: true, DisableConnectOnNew: true, DisableTLS: disableTLS, }, chainParams: chainParams, reconnectAttempts: reconnectAttempts, enqueueNotification: make(chan interface{}), dequeueNotification: make(chan interface{}), enqueueVotingNotification: make(chan interface{}), dequeueVotingNotification: make(chan interface{}), quit: make(chan struct{}), } ntfnCallbacks := &dcrrpcclient.NotificationHandlers{ OnClientConnected: client.onClientConnect, OnBlockConnected: client.onBlockConnected, OnBlockDisconnected: client.onBlockDisconnected, OnRelevantTxAccepted: client.onRelevantTxAccepted, OnReorganization: client.onReorganization, OnWinningTickets: client.onWinningTickets, OnSpentAndMissedTickets: client.onSpentAndMissedTickets, OnStakeDifficulty: client.onStakeDifficulty, } rpcClient, err := dcrrpcclient.New(client.connConfig, ntfnCallbacks) if err != nil { return nil, err } client.Client = rpcClient return client, nil }
// SetUp initializes the rpc test state. Initialization includes: starting up a // simnet node, creating a websocket client and connecting to the started node, // and finally: optionally generating and submitting a testchain with a configurable // number of mature coinbase outputs coinbase outputs. func (h *Harness) SetUp(createTestChain bool, numMatureOutputs uint32) error { var err error // Start the dcrd node itself. This spawns a new process which will be // managed if err = h.node.Start(); err != nil { return err } time.Sleep(200 * time.Millisecond) if err = h.connectRPCClient(); err != nil { return err } fmt.Println("Node RPC client connected.") // Start dcrwallet. This spawns a new process which will be managed if err = h.wallet.Start(); err != nil { return err } time.Sleep(1 * time.Second) // Connect walletClient so we can get the mining address var walletClient *rpc.Client walletRPCConf := h.wallet.config.rpcConnConfig() for i := 0; i < 400; i++ { if walletClient, err = rpc.New(&walletRPCConf, nil); err != nil { time.Sleep(time.Duration(math.Log(float64(i+3))) * 50 * time.Millisecond) continue } break } if walletClient == nil { return fmt.Errorf("walletClient connection timedout") } fmt.Println("Wallet RPC client connected.") h.WalletRPC = walletClient // Get a new address from the wallet to be set with dcrd's --miningaddr time.Sleep(5 * time.Second) var miningAddr dcrutil.Address for i := 0; i < 100; i++ { if miningAddr, err = walletClient.GetNewAddress("default"); err != nil { time.Sleep(time.Duration(math.Log(float64(i+3))) * 50 * time.Millisecond) continue } break } if miningAddr == nil { return fmt.Errorf("RPC not up for mining addr %v %v", h.testNodeDir, h.testWalletDir) } h.miningAddr = miningAddr var extraArgs []string miningArg := fmt.Sprintf("--miningaddr=%s", miningAddr) extraArgs = append(extraArgs, miningArg) // Stop node so we can restart it with --miningaddr if err = h.node.Stop(); err != nil { return err } config, err := newConfig(h.node.config.appDataDir, h.node.config.certFile, h.node.config.keyFile, extraArgs) if err != nil { return err } config.listen = h.node.config.listen config.rpcListen = h.node.config.rpcListen // Create the testing node bounded to the simnet. node, err := newNode(config, h.testNodeDir) if err != nil { return err } h.node = node // Restart node with mining address set if err = h.node.Start(); err != nil { return err } time.Sleep(1 * time.Second) if err := h.connectRPCClient(); err != nil { return err } fmt.Printf("Node RPC client connected, miningaddr: %v.\n", miningAddr) // Create a test chain with the desired number of mature coinbase outputs if createTestChain { numToGenerate := uint32(h.ActiveNet.CoinbaseMaturity) + numMatureOutputs fmt.Printf("Generating %v blocks...\n", numToGenerate) _, err := h.Node.Generate(numToGenerate) if err != nil { return err } fmt.Println("Block generation complete.") } // Wait for the wallet to sync up to the current height. // TODO: Figure out why this is the longest wait, about 60 sec, when it // should be almost immediate. fmt.Println("Waiting for wallet to sync to current height.") ticker := time.NewTicker(time.Millisecond * 500) desiredHeight := int64(numMatureOutputs + uint32(h.ActiveNet.CoinbaseMaturity)) out: for range ticker.C { count, err := h.WalletRPC.GetBlockCount() if err != nil { return err } if count == desiredHeight { break out } } ticker.Stop() fmt.Println("Wallet sync complete.") return nil }
// POSTClient creates the equivalent HTTP POST dcrrpcclient.Client. func (c *RPCClient) POSTClient() (*dcrrpcclient.Client, error) { configCopy := *c.connConfig configCopy.HTTPPostMode = true return dcrrpcclient.New(&configCopy, nil) }
func sweep() error { rpcPassword, err := promptSecret("Wallet RPC password") if err != nil { return errContext(err, "failed to read RPC password") } // Open RPC client. rpcCertificate, err := ioutil.ReadFile(opts.RPCCertificateFile) if err != nil { return errContext(err, "failed to read RPC certificate") } rpcClient, err := dcrrpcclient.New(&dcrrpcclient.ConnConfig{ Host: opts.RPCConnect, User: opts.RPCUsername, Pass: rpcPassword, Certificates: rpcCertificate, HTTPPostMode: true, }, nil) if err != nil { return errContext(err, "failed to create RPC client") } defer rpcClient.Shutdown() // Fetch all unspent outputs, ignore those not from the source // account, and group by their destination address. Each grouping of // outputs will be used as inputs for a single transaction sending to a // new destination account address. unspentOutputs, err := rpcClient.ListUnspent() if err != nil { return errContext(err, "failed to fetch unspent outputs") } sourceOutputs := make(map[string][]dcrjson.ListUnspentResult) for _, unspentOutput := range unspentOutputs { if !unspentOutput.Spendable { continue } if unspentOutput.Confirmations < opts.RequiredConfirmations { continue } if unspentOutput.Account != opts.SourceAccount { continue } sourceAddressOutputs := sourceOutputs[unspentOutput.Address] sourceOutputs[unspentOutput.Address] = append(sourceAddressOutputs, unspentOutput) } var privatePassphrase string if len(sourceOutputs) != 0 { privatePassphrase, err = promptSecret("Wallet private passphrase") if err != nil { return errContext(err, "failed to read private passphrase") } } var totalSwept dcrutil.Amount var numErrors int var reportError = func(format string, args ...interface{}) { fmt.Fprintf(os.Stderr, format, args...) os.Stderr.Write(newlineBytes) numErrors++ } for _, previousOutputs := range sourceOutputs { inputSource := makeInputSource(previousOutputs) destinationSource := makeDestinationScriptSource(rpcClient, opts.DestinationAccount) tx, err := txauthor.NewUnsignedTransaction(nil, opts.FeeRate.Amount, inputSource, destinationSource) if err != nil { if err != (noInputValue{}) { reportError("Failed to create unsigned transaction: %v", err) } continue } // Unlock the wallet, sign the transaction, and immediately lock. err = rpcClient.WalletPassphrase(privatePassphrase, 60) if err != nil { reportError("Failed to unlock wallet: %v", err) continue } signedTransaction, complete, err := rpcClient.SignRawTransaction(tx.Tx) _ = rpcClient.WalletLock() if err != nil { reportError("Failed to sign transaction: %v", err) continue } if !complete { reportError("Failed to sign every input") continue } // Publish the signed sweep transaction. txHash, err := rpcClient.SendRawTransaction(signedTransaction, false) if err != nil { reportError("Failed to publish transaction: %v", err) continue } outputAmount := dcrutil.Amount(tx.Tx.TxOut[0].Value) fmt.Printf("Swept %v to destination account with transaction %v\n", outputAmount, txHash) totalSwept += outputAmount } numPublished := len(sourceOutputs) - numErrors transactionNoun := pickNoun(numErrors, "transaction", "transactions") if numPublished != 0 { fmt.Printf("Swept %v to destination account across %d %s\n", totalSwept, numPublished, transactionNoun) } if numErrors > 0 { return fmt.Errorf("Failed to publish %d %s", numErrors, transactionNoun) } return nil }
// realMain is the real main function for the utility. It is necessary to work // around the fact that deferred functions do not run when os.Exit() is called. func realMain() int { // Load configuration and parse command line. cfg, args, err := loadConfig() if err != nil { return rcError } // Ensure the user specified a single argument. if len(args) < 1 { usage("Transaction hash not specified") return rcError } if len(args) > 1 { usage("Too many arguments specified") return rcError } // Read the argument from a stdin pipe when it is '-'. arg0 := args[0] if arg0 == "-" { bio := bufio.NewReader(os.Stdin) param, err := bio.ReadString('\n') if err != nil && err != io.EOF { fmt.Fprintf(os.Stderr, "Failed to read data from "+ "stdin: %v\n", err) return rcError } if err == io.EOF && len(param) == 0 { fmt.Fprintln(os.Stderr, "Not enough lines provided on "+ "stdin") return rcError } arg0 = param } arg0 = strings.TrimSpace(arg0) // Attempt to unmarshal the parameter as a JSON array of strings if it // looks like JSON input. This allows multiple transactions to be // specified via the argument. Treat the argument as a single hash if // it fails to unmarshal. var txHashes []*chainhash.Hash if strings.Contains(arg0, "[") && strings.Contains(arg0, "]") { var txHashStrs []string if err := json.Unmarshal([]byte(arg0), &txHashStrs); err != nil { fmt.Fprintf(os.Stderr, "Failed to unmarshal JSON "+ "string array of transaction hashes: %v\n", err) return rcError } for _, txHashStr := range txHashStrs { txHash, err := chainhash.NewHashFromStr(txHashStr) if err != nil { fmt.Fprintf(os.Stderr, "Unable to parse "+ "transaction hash %q: %v\n", txHashStr, err) return rcError } txHashes = append(txHashes, txHash) } } else { // Parse the provided transaction hash string. arg0 = strings.Trim(arg0, `"`) txHash, err := chainhash.NewHashFromStr(arg0) if err != nil { fmt.Fprintf(os.Stderr, "Unable to parse transaction "+ "hash %q: %v\n", arg0, err) return rcError } txHashes = append(txHashes, txHash) } // Connect to dcrd RPC server using websockets. certs, err := ioutil.ReadFile(cfg.RPCCert) if err != nil { fmt.Fprintf(os.Stderr, "Failed to read RPC server TLS cert: %v\n", err) return rcError } connCfg := &dcrrpcclient.ConnConfig{ Host: cfg.RPCServer, Endpoint: "ws", User: cfg.RPCUser, Pass: cfg.RPCPassword, Certificates: certs, } client, err := dcrrpcclient.New(connCfg, nil) if err != nil { fmt.Fprintf(os.Stderr, "Unable to connect to dcrd RPC server: "+ "%v\n", err) return rcError } defer client.Shutdown() // Check all of the provided transactions. var hasDevPremineOuts bool for _, txHash := range txHashes { // Get a list of all dev premine outpoints the are ancestors of // all inputs to the provided transaction. devPremineOuts, err := traceDevPremineOuts(client, txHash) if err != nil { fmt.Fprintln(os.Stderr, err) return rcError } // List outputs which are dev premine outputs. if len(devPremineOuts) > 0 { hasDevPremineOuts = true // Don't print anything in quiet mode. if cfg.Quiet { continue } fmt.Printf("Transaction %v contains inputs which "+ "trace back to the following original dev "+ "premine outpoints:\n", txHash) for _, out := range devPremineOuts { fmt.Println(out) } } } // Return the approriate code depending on whether or not any of the // inputs trace back to a dev premine outpoint. if hasDevPremineOuts { return rcDevPremineInputs } return rcNoDevPremineInputs }