// marshalAndSendPost marshals the passed command to JSON-RPC and sends it to // the server by issuing an HTTP POST request and returns a response channel // on which the reply will be delivered. Typically a new connection is opened // and closed for each command when using this method, however, the underlying // HTTP client might coalesce multiple commands depending on several factors // including the remote server configuration. func (c *Client) marshalAndSendPost(cmd btcjson.Cmd, responseChan chan *futureResult) { marshalledJSON, err := json.Marshal(cmd) if err != nil { responseChan <- &futureResult{reply: nil, err: err} return } // Generate a request to the configured RPC server. protocol := "http" if !c.config.DisableTLS { protocol = "https" } url := protocol + "://" + c.config.Host req, err := http.NewRequest("POST", url, bytes.NewReader(marshalledJSON)) if err != nil { responseChan <- &futureResult{reply: nil, err: err} return } req.Close = true req.Header.Set("Content-Type", "application/json") // Configure basic access authorization. req.SetBasicAuth(c.config.User, c.config.Pass) log.Tracef("Sending command [%s] with id %d", cmd.Method(), cmd.Id()) c.sendPostRequest(req, cmd, responseChan) }
// standardCmdReply checks that a parsed command is a standard // Bitcoin JSON-RPC command and runs the proper handler to reply to the // command. func standardCmdReply(cmd btcjson.Cmd, s *rpcServer) (reply btcjson.Reply) { id := cmd.Id() reply.Id = &id handler, ok := rpcHandlers[cmd.Method()] if !ok { reply.Error = &btcjson.ErrMethodNotFound return reply } result, err := handler(s, cmd) if err != nil { jsonErr, ok := err.(btcjson.Error) if !ok { // In the case where we did not have a btcjson // error to begin with, make a new one to send, // but this really should not happen. jsonErr = btcjson.Error{ Code: btcjson.ErrInternal.Code, Message: err.Error(), } } reply.Error = &jsonErr } else { reply.Result = result } return reply }
// respondToAnyCmd checks that a parsed command is a standard or // extension JSON-RPC command and runs the proper handler to reply to // the command. Any and all responses are sent to the wallet from // this function. func respondToAnyCmd(cmd btcjson.Cmd, s *rpcServer, wallet walletChan) { // Lookup the websocket extension for the command and if it doesn't // exist fallback to handling the command as a standard command. wsHandler, ok := wsHandlers[cmd.Method()] if !ok { reply := standardCmdReply(cmd, s) mreply, _ := json.Marshal(reply) wallet <- mreply return } // Call the appropriate handler which responds unless there was an // error in which case the error is marshalled and sent here. if err := wsHandler(s, cmd, wallet); err != nil { var reply btcjson.Reply jsonErr, ok := err.(btcjson.Error) if ok { reply.Error = &jsonErr mreply, _ := json.Marshal(reply) wallet <- mreply return } // In the case where we did not have a btcjson // error to begin with, make a new one to send, // but this really should not happen. jsonErr = btcjson.Error{ Code: btcjson.ErrInternal.Code, Message: err.Error(), } reply.Error = &jsonErr mreply, _ := json.Marshal(reply) wallet <- mreply } }
// handleBtcdConnectedNtfn handles btcwallet btcdconnected notifications, // updating the GUI with info about whether btcd is connected to btcwallet // or not. func handleBtcdConnectedNtfn(n btcjson.Cmd) { bcn, ok := n.(*btcws.BtcdConnectedNtfn) if !ok { log.Printf("[ERR] %v handler: unexpected type", n.Method()) return } updateChans.btcdConnected <- bcn.Connected }
// marshalAndSend marshals the passed command to JSON-RPC and sends it to the // server. It returns a response channel on which the reply will be delivered. func (c *Client) marshalAndSend(cmd btcjson.Cmd, responseChan chan *futureResult) { marshalledJSON, err := json.Marshal(cmd) if err != nil { responseChan <- &futureResult{reply: nil, err: err} return } log.Tracef("Sending command [%s] with id %d", cmd.Method(), cmd.Id()) c.sendMessage(marshalledJSON) }
// handleBlockDisconnectedNtfn handles btcd/btcwallet blockdisconnected // notifications resulting from blocks disconnected from the main chain. // // TODO(jrick): handle this by rolling back tx history and balances. func handleBlockDisconnectedNtfn(n btcjson.Cmd) { bdn, ok := n.(*btcws.BlockDisconnectedNtfn) if !ok { log.Printf("[ERR] %v handler: unexpected type", n.Method()) return } // TODO _ = bdn }
// NtfnRescanProgress handles btcd rescanprogress notifications resulting // from a partially completed rescan. func NtfnRescanProgress(n btcjson.Cmd) error { cn, ok := n.(*btcws.RescanProgressNtfn) if !ok { return fmt.Errorf("%v handler: unexpected type", n.Method()) } // Notify the rescan manager of the completed partial progress for // the current rescan. AcctMgr.rm.MarkProgress(cn.LastProcessed) return nil }
// handleWalletLockStateNtfn handles btcwallet walletlockstate notifications // by updating the GUI with whether the wallet is locked or not, setting // the sensitivity of various widgets for locking or unlocking the wallet. func handleWalletLockStateNtfn(n btcjson.Cmd) { wlsn, ok := n.(*btcws.WalletLockStateNtfn) if !ok { log.Printf("[ERR] %v handler: unexpected type", n.Method()) return } // TODO(jrick): do proper filtering and display all // account balances somewhere if wlsn.Account == "" { updateChans.lockState <- wlsn.Locked } }
// NtfnTxMined handles btcd notifications resulting from newly // mined transactions that originated from this wallet. func NtfnTxMined(n btcjson.Cmd, marshaled []byte) { tmn, ok := n.(*btcws.TxMinedNtfn) if !ok { log.Errorf("%v handler: unexpected type", n.Method()) return } txid, err := btcwire.NewShaHashFromStr(tmn.TxID) if err != nil { log.Errorf("%v handler: invalid hash string", n.Method()) return } blockhash, err := btcwire.NewShaHashFromStr(tmn.BlockHash) if err != nil { log.Errorf("%v handler: invalid block hash string", n.Method()) return } err = accountstore.RecordMinedTx(txid, blockhash, tmn.BlockHeight, tmn.Index, tmn.BlockTime) if err != nil { log.Errorf("%v handler: %v", n.Method(), err) return } // Remove mined transaction from pool. UnminedTxs.Lock() delete(UnminedTxs.m, TXID(*txid)) UnminedTxs.Unlock() }
// handleAccountBalanceNtfn handles btcwallet accountbalance notifications by // updating the GUI with either a new confirmed or unconfirmed balance. func handleAccountBalanceNtfn(n btcjson.Cmd) { abn, ok := n.(*btcws.AccountBalanceNtfn) if !ok { log.Printf("[ERR] %v handler: unexpected type", n.Method()) return } // TODO(jrick): do proper filtering and display all // account balances somewhere if abn.Account == "" { if abn.Confirmed { updateChans.balance <- abn.Balance } else { updateChans.unconfirmed <- abn.Balance } } }
// NtfnBlockDisconnected handles btcd notifications resulting from // blocks disconnected from the main chain in the event of a chain // switch and notifies frontends of the new blockchain height. func NtfnBlockDisconnected(n btcjson.Cmd, marshaled []byte) { bdn, ok := n.(*btcws.BlockDisconnectedNtfn) if !ok { log.Errorf("%v handler: unexpected type", n.Method()) return } hash, err := btcwire.NewShaHashFromStr(bdn.Hash) if err != nil { log.Errorf("%v handler: invalid hash string", n.Method()) return } // Rollback Utxo and Tx data stores. accountstore.Rollback(bdn.Height, hash) // Pass notification to frontends too. frontendNotificationMaster <- marshaled }
// NtfnBlockDisconnected handles btcd notifications resulting from // blocks disconnected from the main chain in the event of a chain // switch and notifies frontends of the new blockchain height. func NtfnBlockDisconnected(n btcjson.Cmd) error { bdn, ok := n.(*btcws.BlockDisconnectedNtfn) if !ok { return fmt.Errorf("%v handler: unexpected type", n.Method()) } hash, err := btcwire.NewShaHashFromStr(bdn.Hash) if err != nil { return fmt.Errorf("%v handler: invalid hash string", n.Method()) } // Rollback Utxo and Tx data stores. AcctMgr.Rollback(bdn.Height, hash) // Pass notification to frontends too. marshaled, _ := n.MarshalJSON() allClients <- marshaled return nil }
// respondToAnyCmd checks that a parsed command is a standard or // extension JSON-RPC command and runs the proper handler to reply to // the command. Any and all responses are sent to the wallet from // this function. func respondToAnyCmd(cmd btcjson.Cmd, s *rpcServer, c handlerChans) *btcjson.Reply { // Lookup the websocket extension for the command and if it doesn't // exist fallback to handling the command as a standard command. wsHandler, ok := wsHandlers[cmd.Method()] if !ok { // No websocket-specific handler so handle like a legacy // RPC connection. response := standardCmdReply(cmd, s) return &response } result, jsonErr := wsHandler(s, cmd, c) id := cmd.Id() response := btcjson.Reply{ Id: &id, Result: result, Error: jsonErr, } return &response }
// handleTxNtfn handles btcwallet newtx notifications by updating the GUI // with details about a new tx to or from wallet addresses. func handleTxNtfn(n btcjson.Cmd) { tn, ok := n.(*btcws.TxNtfn) if !ok { log.Printf("[ERR] %v handler: unexpected type", n.Method()) return } // TODO(jrick): do proper filtering and display // tx details for all accounts. if tn.Account == "" { attr, err := parseTxDetails(tn.Details) if err != nil { log.Printf("[ERR] %v handler: bad details: %v", n.Method(), err) return } updateChans.prependOverviewTx <- attr updateChans.prependTx <- attr } }
// NtfnBlockConnected handles btcd notifications resulting from newly // connected blocks to the main blockchain. // // TODO(jrick): Send block time with notification. This will be used // to mark wallet files with a possibly-better earliest block height, // and will greatly reduce rescan times for wallets created with an // out of sync btcd. func NtfnBlockConnected(n btcjson.Cmd) error { bcn, ok := n.(*btcws.BlockConnectedNtfn) if !ok { return fmt.Errorf("%v handler: unexpected type", n.Method()) } hash, err := btcwire.NewShaHashFromStr(bcn.Hash) if err != nil { return fmt.Errorf("%v handler: invalid hash string", n.Method()) } // Update the blockstamp for the newly-connected block. bs := &wallet.BlockStamp{ Height: bcn.Height, Hash: *hash, } curBlock.Lock() curBlock.BlockStamp = *bs curBlock.Unlock() // btcd notifies btcwallet about transactions first, and then sends // the new block notification. New balance notifications for txs // in blocks are therefore sent here after all tx notifications // have arrived and finished being processed by the handlers. workers := NotifyBalanceRequest{ block: *hash, wg: make(chan *sync.WaitGroup), } NotifyBalanceSyncerChans.access <- workers if wg := <-workers.wg; wg != nil { wg.Wait() NotifyBalanceSyncerChans.remove <- *hash } AcctMgr.BlockNotify(bs) // Pass notification to frontends too. marshaled, _ := n.MarshalJSON() allClients <- marshaled return nil }
// processNotification checks for a handler for a notification, and sends func processNotification(n btcjson.Cmd, s string) { // Message is a btcd notification. Check the method and dispatch // correct handler, or if no handler, pass up to each wallet. if ntfnHandler, ok := notificationHandlers[n.Method()]; ok { log.Debugf("Running notification handler for method %v", n.Method()) ntfnHandler(n, []byte(s)) } else { // No handler; send to all wallets. log.Debugf("Sending notification with method %v to all wallets", n.Method()) frontendNotificationMaster <- []byte(s) } }
// NtfnRedeemingTx handles btcd redeemingtx notifications resulting from a // transaction spending a watched outpoint. func NtfnRedeemingTx(n btcjson.Cmd) error { cn, ok := n.(*btcws.RedeemingTxNtfn) if !ok { return fmt.Errorf("%v handler: unexpected type", n.Method()) } rawTx, err := hex.DecodeString(cn.HexTx) if err != nil { return fmt.Errorf("%v handler: bad hexstring: %v", n.Method(), err) } tx, err := btcutil.NewTxFromBytes(rawTx) if err != nil { return fmt.Errorf("%v handler: bad transaction bytes: %v", n.Method(), err) } block, txIdx, err := parseBlock(cn.Block) if err != nil { return fmt.Errorf("%v handler: bad block: %v", n.Method(), err) } tx.SetIndex(txIdx) return AcctMgr.RecordSpendingTx(tx, block) }
// NtfnRecvTx handles the btcws.RecvTxNtfn notification. func NtfnRecvTx(n btcjson.Cmd) error { rtx, ok := n.(*btcws.RecvTxNtfn) if !ok { return fmt.Errorf("%v handler: unexpected type", n.Method()) } bs, err := GetCurBlock() if err != nil { return fmt.Errorf("%v handler: cannot get current block: %v", n.Method(), err) } rawTx, err := hex.DecodeString(rtx.HexTx) if err != nil { return fmt.Errorf("%v handler: bad hexstring: %v", n.Method(), err) } tx, err := btcutil.NewTxFromBytes(rawTx) if err != nil { return fmt.Errorf("%v handler: bad transaction bytes: %v", n.Method(), err) } block, txIdx, err := parseBlock(rtx.Block) if err != nil { return fmt.Errorf("%v handler: bad block: %v", n.Method(), err) } tx.SetIndex(txIdx) // For transactions originating from this wallet, the sent tx history should // be recorded before the received history. If wallet created this tx, wait // for the sent history to finish being recorded before continuing. // // TODO(jrick) this is wrong due to tx malleability. Cannot safely use the // txsha as an identifier. req := SendTxHistSyncRequest{ txsha: *tx.Sha(), response: make(chan SendTxHistSyncResponse), } SendTxHistSyncChans.access <- req resp := <-req.response if resp.ok { // Wait until send history has been recorded. <-resp.c SendTxHistSyncChans.remove <- *tx.Sha() } // For every output, find all accounts handling that output address (if any) // and record the received txout. for outIdx, txout := range tx.MsgTx().TxOut { var accounts []*Account _, addrs, _, _ := btcscript.ExtractPkScriptAddrs(txout.PkScript, cfg.Net()) for _, addr := range addrs { a, err := AcctMgr.AccountByAddress(addr) if err != nil { continue } accounts = append(accounts, a) } for _, a := range accounts { txr, err := a.TxStore.InsertTx(tx, block) if err != nil { return err } cred, err := txr.AddCredit(uint32(outIdx), false) if err != nil { return err } AcctMgr.ds.ScheduleTxStoreWrite(a) // Notify frontends of tx. If the tx is unconfirmed, it is always // notified and the outpoint is marked as notified. If the outpoint // has already been notified and is now in a block, a txmined notifiction // should be sent once to let frontends that all previous send/recvs // for this unconfirmed tx are now confirmed. op := *cred.OutPoint() previouslyNotifiedReq := NotifiedRecvTxRequest{ op: op, response: make(chan NotifiedRecvTxResponse), } NotifiedRecvTxChans.access <- previouslyNotifiedReq if <-previouslyNotifiedReq.response { NotifiedRecvTxChans.remove <- op } else { // Notify frontends of new recv tx and mark as notified. NotifiedRecvTxChans.add <- op ltr, err := cred.ToJSON(a.Name(), bs.Height, a.Wallet.Net()) if err != nil { return err } NotifyNewTxDetails(allClients, a.Name(), ltr) } // Notify frontends of new account balance. confirmed := a.CalculateBalance(1) unconfirmed := a.CalculateBalance(0) - confirmed NotifyWalletBalance(allClients, a.name, confirmed) NotifyWalletBalanceUnconfirmed(allClients, a.name, unconfirmed) } } return nil }
// NtfnProcessedTx handles the btcws.ProcessedTxNtfn notification. func NtfnProcessedTx(n btcjson.Cmd, marshaled []byte) { ptn, ok := n.(*btcws.ProcessedTxNtfn) if !ok { log.Errorf("%v handler: unexpected type", n.Method()) return } // Create useful types from the JSON strings. receiver, err := btcutil.DecodeAddr(ptn.Receiver) if err != nil { log.Errorf("%v handler: error parsing receiver: %v", n.Method(), err) return } txID, err := btcwire.NewShaHashFromStr(ptn.TxID) if err != nil { log.Errorf("%v handler: error parsing txid: %v", n.Method(), err) return } blockHash, err := btcwire.NewShaHashFromStr(ptn.BlockHash) if err != nil { log.Errorf("%v handler: error parsing block hash: %v", n.Method(), err) return } pkscript, err := hex.DecodeString(ptn.PkScript) if err != nil { log.Errorf("%v handler: error parsing pkscript: %v", n.Method(), err) return } // Lookup account for address in result. aname, err := LookupAccountByAddress(ptn.Receiver) if err == ErrNotFound { log.Warnf("Received rescan result for unknown address %v", ptn.Receiver) return } a, err := accountstore.Account(aname) if err == ErrAcctNotExist { log.Errorf("Missing account for rescaned address %v", ptn.Receiver) } // Create RecvTx to add to tx history. t := &tx.RecvTx{ TxID: *txID, TxOutIdx: ptn.TxOutIndex, TimeReceived: time.Now().Unix(), BlockHeight: ptn.BlockHeight, BlockHash: *blockHash, BlockIndex: int32(ptn.BlockIndex), BlockTime: ptn.BlockTime, Amount: ptn.Amount, ReceiverHash: receiver.ScriptAddress(), } // For transactions originating from this wallet, the sent tx history should // be recorded before the received history. If wallet created this tx, wait // for the sent history to finish being recorded before continuing. req := SendTxHistSyncRequest{ txid: *txID, response: make(chan SendTxHistSyncResponse), } SendTxHistSyncChans.access <- req resp := <-req.response if resp.ok { // Wait until send history has been recorded. <-resp.c SendTxHistSyncChans.remove <- *txID } // Record the tx history. a.TxStore.Lock() a.TxStore.s.InsertRecvTx(t) a.TxStore.Unlock() a.ScheduleTxStoreWrite() // Notify frontends of tx. If the tx is unconfirmed, it is always // notified and the outpoint is marked as notified. If the outpoint // has already been notified and is now in a block, a txmined notifiction // should be sent once to let frontends that all previous send/recvs // for this unconfirmed tx are now confirmed. recvTxOP := btcwire.NewOutPoint(txID, ptn.TxOutIndex) previouslyNotifiedReq := NotifiedRecvTxRequest{ op: *recvTxOP, response: make(chan NotifiedRecvTxResponse), } NotifiedRecvTxChans.access <- previouslyNotifiedReq if <-previouslyNotifiedReq.response { NotifyMinedTx <- t NotifiedRecvTxChans.remove <- *recvTxOP } else { // Notify frontends of new recv tx and mark as notified. NotifiedRecvTxChans.add <- *recvTxOP NotifyNewTxDetails(frontendNotificationMaster, a.Name(), t.TxInfo(a.Name(), ptn.BlockHeight, a.Wallet.Net())[0]) } if !ptn.Spent { u := &tx.Utxo{ Amt: uint64(ptn.Amount), Height: ptn.BlockHeight, Subscript: pkscript, } copy(u.Out.Hash[:], txID[:]) u.Out.Index = uint32(ptn.TxOutIndex) copy(u.AddrHash[:], receiver.ScriptAddress()) copy(u.BlockHash[:], blockHash[:]) a.UtxoStore.Lock() a.UtxoStore.s.Insert(u) a.UtxoStore.Unlock() a.ScheduleUtxoStoreWrite() // If this notification came from mempool, notify frontends of // the new unconfirmed balance immediately. Otherwise, wait until // the blockconnected notifiation is processed. if u.Height == -1 { bal := a.CalculateBalance(0) - a.CalculateBalance(1) NotifyWalletBalanceUnconfirmed(frontendNotificationMaster, a.name, bal) } } // Notify frontends of new account balance. confirmed := a.CalculateBalance(1) unconfirmed := a.CalculateBalance(0) - confirmed NotifyWalletBalance(frontendNotificationMaster, a.name, confirmed) NotifyWalletBalanceUnconfirmed(frontendNotificationMaster, a.name, unconfirmed) }