// handleNotifyNewTXs implements the notifynewtxs command extension for // websocket connections. func handleNotifyNewTXs(s *rpcServer, cmd btcjson.Cmd, wallet walletChan) error { id := cmd.Id() reply := &btcjson.Reply{Id: &id} notifyCmd, ok := cmd.(*btcws.NotifyNewTXsCmd) if !ok { return btcjson.ErrInternal } for _, addr := range notifyCmd.Addresses { addr, err := btcutil.DecodeAddr(addr) if err != nil { return fmt.Errorf("cannot decode address: %v", err) } // TODO(jrick) Notifing for non-P2PKH addresses is currently // unsuported. if _, ok := addr.(*btcutil.AddressPubKeyHash); !ok { return fmt.Errorf("address is not P2PKH: %v", addr.EncodeAddress()) } s.ws.AddTxRequest(wallet, addr.EncodeAddress()) } mreply, _ := json.Marshal(reply) wallet <- mreply return nil }
// sendCmd sends the passed command to the associated server and returns a // response channel on which the reply will be deliver at some point in the // future. It handles both websocket and HTTP POST mode depending on the // configuration of the client. func (c *Client) sendCmd(cmd btcjson.Cmd) chan *response { // Choose which marshal and send function to use depending on whether // the client running in HTTP POST mode or not. When running in HTTP // POST mode, the command is issued via an HTTP client. Otherwise, // the command is issued via the asynchronous websocket channels. responseChan := make(chan *response, 1) if c.config.HttpPostMode { c.marshalAndSendPost(cmd, responseChan) return responseChan } // Check whether the websocket connection has never been established, // in which case the handler goroutines are not running. select { case <-c.connEstablished: default: responseChan <- &response{err: ErrClientNotConnected} return responseChan } err := c.addRequest(cmd.Id().(uint64), &jsonRequest{ cmd: cmd, responseChan: responseChan, }) if err != nil { responseChan <- &response{err: err} return responseChan } c.marshalAndSend(cmd, responseChan) return 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 } }
// 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) }
// 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 }
// 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 }
// 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) }
// DeferToBtcd sends a marshaled JSON-RPC request to btcd and returns // the reply. func DeferToBtcd(cmd btcjson.Cmd) (interface{}, *btcjson.Error) { // Update cmd with a new ID so replies can be handled without frontend // IDs clashing with requests originating in btcwallet. The original // request ID is always used in the frontend's response. cmd.SetId(<-NewJSONID) request := NewRPCRequest(cmd, nil) response := <-CurrentRPCConn().SendRequest(request) return response.Result, response.Err }
// 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() }
// handleNotifySpent implements the notifyspent command extension for // websocket connections. func handleNotifySpent(s *rpcServer, cmd btcjson.Cmd, wallet walletChan) error { id := cmd.Id() reply := &btcjson.Reply{Id: &id} notifyCmd, ok := cmd.(*btcws.NotifySpentCmd) if !ok { return btcjson.ErrInternal } s.ws.AddSpentRequest(wallet, notifyCmd.OutPoint) mreply, _ := json.Marshal(reply) wallet <- mreply return nil }
// handleGetCurrentNet implements the getcurrentnet command extension // for websocket connections. func handleGetCurrentNet(s *rpcServer, cmd btcjson.Cmd, wallet walletChan) error { id := cmd.Id() reply := &btcjson.Reply{Id: &id} var net btcwire.BitcoinNet if cfg.TestNet3 { net = btcwire.TestNet3 } else { net = btcwire.MainNet } reply.Result = float64(net) mreply, _ := json.Marshal(reply) wallet <- mreply return nil }
// 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 }
// sendCmd sends the passed command to the associated server and returns a // response channel on which the reply will be deliver at some point in the // future. It handles both websocket and HTTP POST mode depending on the // configuration of the client. func (c *Client) sendCmd(cmd btcjson.Cmd) chan *futureResult { // Choose which marshal and send function to use depending on whether // the client running in HTTP POST mode or not. When running in HTTP // POST mode, the command is issued via an HTTP client. Otherwise, // the command is issued via the asynchronous websocket channels. responseChan := make(chan *futureResult, 1) if c.config.HttpPostMode { c.marshalAndSendPost(cmd, responseChan) return responseChan } c.addRequest(cmd.Id().(uint64), &jsonRequest{ cmd: cmd, responseChan: responseChan, }) c.marshalAndSend(cmd, responseChan) return responseChan }
// 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 } }
// handleGetBestBlock implements the getbestblock command extension // for websocket connections. func handleGetBestBlock(s *rpcServer, cmd btcjson.Cmd, wallet walletChan) error { id := cmd.Id() reply := &btcjson.Reply{Id: &id} // All other "get block" commands give either the height, the // hash, or both but require the block SHA. This gets both for // the best block. sha, height, err := s.server.db.NewestSha() if err != nil { return btcjson.ErrBestBlockHash } reply.Result = map[string]interface{}{ "hash": sha.String(), "height": height, } mreply, _ := json.Marshal(reply) wallet <- mreply return nil }
// 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, marshaled []byte) { bcn, ok := n.(*btcws.BlockConnectedNtfn) if !ok { log.Errorf("%v handler: unexpected type", n.Method()) return } hash, err := btcwire.NewShaHashFromStr(bcn.Hash) if err != nil { log.Errorf("%v handler: invalid hash string", n.Method()) return } // 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 } accountstore.BlockNotify(bs) // Pass notification to frontends too. frontendNotificationMaster <- marshaled }
// 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) }
// 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 }
// handleRescan implements the rescan command extension for websocket // connections. func handleRescan(s *rpcServer, cmd btcjson.Cmd, wallet walletChan) error { rescanCmd, ok := cmd.(*btcws.RescanCmd) if !ok { return btcjson.ErrInternal } if len(rescanCmd.Addresses) == 1 { rpcsLog.Info("Beginning rescan for 1 address.") } else { rpcsLog.Infof("Beginning rescan for %v addresses.", len(rescanCmd.Addresses)) } minblock := int64(rescanCmd.BeginBlock) maxblock := int64(rescanCmd.EndBlock) // FetchHeightRange may not return a complete list of block shas for // the given range, so fetch range as many times as necessary. for { blkshalist, err := s.server.db.FetchHeightRange(minblock, maxblock) if err != nil { return err } if len(blkshalist) == 0 { break } for i := range blkshalist { blk, err := s.server.db.FetchBlockBySha(&blkshalist[i]) if err != nil { rpcsLog.Errorf("Error looking up block sha: %v", err) return err } for _, tx := range blk.Transactions() { var txReply *btcdb.TxListReply txouts: for txOutIdx, txout := range tx.MsgTx().TxOut { _, addrs, _, err := btcscript.ExtractPkScriptAddrs( txout.PkScript, s.server.btcnet) if err != nil { continue txouts } for i, addr := range addrs { encodedAddr := addr.EncodeAddress() if _, ok := rescanCmd.Addresses[encodedAddr]; ok { // TODO(jrick): This lookup is expensive and can be avoided // if the wallet is sent the previous outpoints for all inputs // of the tx, so any can removed from the utxo set (since // they are, as of this tx, now spent). if txReply == nil { txReplyList, err := s.server.db.FetchTxBySha(tx.Sha()) if err != nil { rpcsLog.Errorf("Tx Sha %v not found by db.", tx.Sha()) continue txouts } for i := range txReplyList { if txReplyList[i].Height == blk.Height() { txReply = txReplyList[i] break } } } ntfn := &btcws.ProcessedTxNtfn{ Receiver: encodedAddr, Amount: txout.Value, TxID: tx.Sha().String(), TxOutIndex: uint32(txOutIdx), PkScript: hex.EncodeToString(txout.PkScript), BlockHash: blkshalist[i].String(), BlockHeight: int32(blk.Height()), BlockIndex: tx.Index(), BlockTime: blk.MsgBlock().Header.Timestamp.Unix(), Spent: txReply.TxSpent[txOutIdx], } mntfn, _ := ntfn.MarshalJSON() wallet <- mntfn } } } } } if maxblock-minblock > int64(len(blkshalist)) { minblock += int64(len(blkshalist)) } else { break } } rpcsLog.Info("Finished rescan") id := cmd.Id() response := &btcjson.Reply{ Id: &id, Result: nil, Error: nil, } mresponse, _ := json.Marshal(response) wallet <- mresponse return nil }
// 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) }