1 package wallet
2 3 import (
4 "bytes"
5 "github.com/p9c/p9/pkg/amt"
6 "github.com/p9c/p9/pkg/btcaddr"
7 "sync"
8 9 "github.com/p9c/p9/pkg/chainhash"
10 "github.com/p9c/p9/pkg/txscript"
11 "github.com/p9c/p9/pkg/waddrmgr"
12 "github.com/p9c/p9/pkg/walletdb"
13 "github.com/p9c/p9/pkg/wtxmgr"
14 )
15 16 // AccountBalance associates a total (zero confirmation) balance with an account. Balances for other minimum
17 // confirmation counts require more expensive logic and it is not clear which minimums a client is interested in, so
18 // they are not included.
19 type AccountBalance struct {
20 Account uint32
21 TotalBalance amt.Amount
22 }
23 24 // AccountNotification contains properties regarding an account, such as its name and the number of derived and imported
25 // keys. When any of these properties change, the notification is fired.
26 type AccountNotification struct {
27 AccountNumber uint32
28 AccountName string
29 ExternalKeyCount uint32
30 InternalKeyCount uint32
31 ImportedKeyCount uint32
32 }
33 34 // AccountNotificationsClient receives AccountNotifications over the channel C.
35 type AccountNotificationsClient struct {
36 C chan *AccountNotification
37 server *NotificationServer
38 }
39 40 // Block contains the properties and all relevant transactions of an attached
41 // block.
42 type Block struct {
43 Hash *chainhash.Hash
44 Height int32
45 Timestamp int64
46 Transactions []TransactionSummary
47 }
48 49 // TODO: It would be good to send errors during notification creation to the rpc server instead of just logging them
50 // here so the client is aware that wallet isn't working correctly and notifications are missing.
51 //
52 // TODO: Anything dealing with accounts here is expensive because the database is not organized correctly for true
53 // account support, but do the slow thing instead of the easy thing since the db can be fixed later, and we want the api
54 // correct now.
55 56 // NotificationServer is a server that interested clients may hook into to receive notifications of changes
57 // in a wallet. A client is created for each registered notification. Clients are guaranteed to receive messages in the
58 // order wallet created them, but there is no guaranteed synchronization between different clients.
59 type NotificationServer struct {
60 transactions []chan *TransactionNotifications
61 currentTxNtfn *TransactionNotifications // coalesce this since wallet does not add mined txs together
62 spentness map[uint32][]chan *SpentnessNotifications
63 accountClients []chan *AccountNotification
64 mu sync.Mutex // Only protects registered client channels
65 wallet *Wallet // smells like hacks
66 }
67 68 // SpentnessNotifications is a notification that is fired for transaction outputs controlled by some account's keys. The
69 // notification may be about a newly added unspent transaction output or that a previously unspent output is now spent.
70 // When spent, the notification includes the spending transaction's hash and input index.
71 type SpentnessNotifications struct {
72 hash *chainhash.Hash
73 spenderHash *chainhash.Hash
74 index uint32
75 spenderIndex uint32
76 }
77 78 // SpentnessNotificationsClient receives SpentnessNotifications from the NotificationServer over the channel C.
79 type SpentnessNotificationsClient struct {
80 C <-chan *SpentnessNotifications
81 account uint32
82 server *NotificationServer
83 }
84 85 // TransactionNotifications is a notification of changes to the wallet's transaction set and the current chain tip that
86 // wallet is considered to be synced with. All transactions added to the blockchain are organized by the block they were
87 // mined in.
88 //
89 // During a chain switch, all removed block hashes are included. Detached blocks are sorted in the reverse order they
90 // were mined. Attached blocks are sorted in the order mined.
91 //
92 // All newly added unmined transactions are included. Removed unmined transactions are not explicitly included. Instead,
93 // the hashes of all transactions still unmined are included.
94 //
95 // If any transactions were involved, each affected account's new total balance is included.
96 //
97 // TODO: Because this includes stuff about blocks and can be fired without any changes to transactions, it needs a
98 // better name.
99 type TransactionNotifications struct {
100 AttachedBlocks []Block
101 DetachedBlocks []*chainhash.Hash
102 UnminedTransactions []TransactionSummary
103 UnminedTransactionHashes []*chainhash.Hash
104 NewBalances []AccountBalance
105 }
106 107 // TransactionNotificationsClient receives TransactionNotifications from the NotificationServer over the channel C.
108 type TransactionNotificationsClient struct {
109 C <-chan *TransactionNotifications
110 server *NotificationServer
111 }
112 113 // TransactionSummary contains a transaction relevant to the wallet and marks which inputs and outputs were relevant.
114 type TransactionSummary struct {
115 Hash *chainhash.Hash
116 Transaction []byte
117 MyInputs []TransactionSummaryInput
118 MyOutputs []TransactionSummaryOutput
119 Fee amt.Amount
120 Timestamp int64
121 }
122 123 // TransactionSummaryInput describes a transaction input that is relevant to the wallet. The Index field marks the
124 // transaction input index of the transaction (not included here). The PreviousAccount and PreviousAmount fields
125 // describe how much this input debits from a wallet account.
126 type TransactionSummaryInput struct {
127 Index uint32
128 PreviousAccount uint32
129 PreviousAmount amt.Amount
130 }
131 132 // TransactionSummaryOutput describes wallet properties of a transaction output controlled by the wallet. The Index
133 // field marks the transaction output index of the transaction (not included here).
134 type TransactionSummaryOutput struct {
135 Index uint32
136 Account uint32
137 Internal bool
138 }
139 140 // Done unregisters the client from the server and drains any remaining messages. It must be called exactly once when
141 // the client is finished receiving notifications.
142 func (c *AccountNotificationsClient) Done() {
143 go func() {
144 for range c.C {
145 }
146 }()
147 go func() {
148 s := c.server
149 s.mu.Lock()
150 clients := s.accountClients
151 for i, ch := range clients {
152 if c.C == ch {
153 clients[i] = clients[len(clients)-1]
154 s.accountClients = clients[:len(clients)-1]
155 close(ch)
156 break
157 }
158 }
159 s.mu.Unlock()
160 }()
161 }
162 163 // AccountNotifications returns a client for receiving AccountNotifications over a channel. The channel is unbuffered.
164 // When finished, the client's Done method should be called to disassociate the client from the server.
165 func (s *NotificationServer) AccountNotifications() AccountNotificationsClient {
166 c := make(chan *AccountNotification)
167 s.mu.Lock()
168 s.accountClients = append(s.accountClients, c)
169 s.mu.Unlock()
170 return AccountNotificationsClient{
171 C: c,
172 server: s,
173 }
174 }
175 176 // AccountSpentnessNotifications registers a client for spentness changes of outputs controlled by the account.
177 func (s *NotificationServer) AccountSpentnessNotifications(account uint32) SpentnessNotificationsClient {
178 c := make(chan *SpentnessNotifications)
179 s.mu.Lock()
180 s.spentness[account] = append(s.spentness[account], c)
181 s.mu.Unlock()
182 return SpentnessNotificationsClient{
183 C: c,
184 account: account,
185 server: s,
186 }
187 }
188 189 // TransactionNotifications returns a client for receiving TransactionNotifications notifications over a channel. The
190 // channel is unbuffered.
191 //
192 // When finished, the Done method should be called on the client to disassociate it from the server.
193 func (s *NotificationServer) TransactionNotifications() TransactionNotificationsClient {
194 c := make(chan *TransactionNotifications)
195 s.mu.Lock()
196 s.transactions = append(s.transactions, c)
197 s.mu.Unlock()
198 return TransactionNotificationsClient{
199 C: c,
200 server: s,
201 }
202 }
203 func (s *NotificationServer) notifyAccountProperties(props *waddrmgr.AccountProperties) {
204 defer s.mu.Unlock()
205 s.mu.Lock()
206 clients := s.accountClients
207 if len(clients) == 0 {
208 return
209 }
210 n := &AccountNotification{
211 AccountNumber: props.AccountNumber,
212 AccountName: props.AccountName,
213 ExternalKeyCount: props.ExternalKeyCount,
214 InternalKeyCount: props.InternalKeyCount,
215 ImportedKeyCount: props.ImportedKeyCount,
216 }
217 for _, c := range clients {
218 c <- n
219 }
220 }
221 func (s *NotificationServer) notifyAttachedBlock(dbtx walletdb.ReadTx, block *wtxmgr.BlockMeta) {
222 if s.currentTxNtfn == nil {
223 s.currentTxNtfn = &TransactionNotifications{}
224 }
225 // Add block details if it wasn't already included for previously notified mined transactions.
226 n := len(s.currentTxNtfn.AttachedBlocks)
227 if n == 0 || *s.currentTxNtfn.AttachedBlocks[n-1].Hash != block.Hash {
228 s.currentTxNtfn.AttachedBlocks = append(
229 s.currentTxNtfn.AttachedBlocks, Block{
230 Hash: &block.Hash,
231 Height: block.Height,
232 Timestamp: block.Time.Unix(),
233 },
234 )
235 }
236 // For now (until notification coalescing isn't necessary) just use chain length to determine if this is the new
237 // best block.
238 if s.wallet.ChainSynced() {
239 if len(s.currentTxNtfn.DetachedBlocks) >= len(s.currentTxNtfn.AttachedBlocks) {
240 return
241 }
242 }
243 defer s.mu.Unlock()
244 s.mu.Lock()
245 clients := s.transactions
246 if len(clients) == 0 {
247 s.currentTxNtfn = nil
248 return
249 }
250 // The UnminedTransactions field is intentionally not set. Since the hashes of all detached blocks are reported, and
251 // all transactions moved from a mined block back to unconfirmed are either in the UnminedTransactionHashes slice or
252 // don't exist due to conflicting with a mined transaction in the new best chain, there is no possiblity of a new,
253 // previously unseen transaction appearing in unconfirmed.
254 txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey)
255 unminedHashes, e := s.wallet.TxStore.UnminedTxHashes(txmgrNs)
256 if e != nil {
257 E.Ln(
258 "cannot fetch unmined transaction hashes:", e,
259 )
260 return
261 }
262 s.currentTxNtfn.UnminedTransactionHashes = unminedHashes
263 bals := make(map[uint32]amt.Amount)
264 for _, b := range s.currentTxNtfn.AttachedBlocks {
265 relevantAccounts(s.wallet, bals, b.Transactions)
266 }
267 e = totalBalances(dbtx, s.wallet, bals)
268 if e != nil {
269 E.Ln(
270 "cannot determine balances for relevant accounts:", e,
271 )
272 return
273 }
274 s.currentTxNtfn.NewBalances = flattenBalanceMap(bals)
275 for _, c := range clients {
276 c <- s.currentTxNtfn
277 }
278 s.currentTxNtfn = nil
279 }
280 func (s *NotificationServer) notifyDetachedBlock(hash *chainhash.Hash) {
281 if s.currentTxNtfn == nil {
282 s.currentTxNtfn = &TransactionNotifications{}
283 }
284 s.currentTxNtfn.DetachedBlocks = append(s.currentTxNtfn.DetachedBlocks, hash)
285 }
286 func (s *NotificationServer) notifyMinedTransaction(
287 dbtx walletdb.ReadTx,
288 details *wtxmgr.TxDetails,
289 block *wtxmgr.BlockMeta,
290 ) {
291 if s.currentTxNtfn == nil {
292 s.currentTxNtfn = &TransactionNotifications{}
293 }
294 n := len(s.currentTxNtfn.AttachedBlocks)
295 if n == 0 || *s.currentTxNtfn.AttachedBlocks[n-1].Hash != block.Hash {
296 s.currentTxNtfn.AttachedBlocks = append(
297 s.currentTxNtfn.AttachedBlocks, Block{
298 Hash: &block.Hash,
299 Height: block.Height,
300 Timestamp: block.Time.Unix(),
301 },
302 )
303 n++
304 }
305 txs := s.currentTxNtfn.AttachedBlocks[n-1].Transactions
306 s.currentTxNtfn.AttachedBlocks[n-1].Transactions =
307 append(txs, makeTxSummary(dbtx, s.wallet, details))
308 }
309 310 // // notifySpentOutput notifies registered clients that a previously-unspent
311 // // output is now spent, and includes the spender hash and input index in the
312 // // notification.
313 // func (s *NotificationServer) notifySpentOutput(account uint32, op *wire.OutPoint, spenderHash *chainhash.Hash, spenderIndex uint32) {
314 // defer s.mu.Unlock()
315 // s.mu.Lock()
316 // clients := s.spentness[account]
317 // if len(clients) == 0 {
318 // return
319 // }
320 // n := &SpentnessNotifications{
321 // hash: &op.Hash,
322 // index: op.Index,
323 // spenderHash: spenderHash,
324 // spenderIndex: spenderIndex,
325 // }
326 // for _, c := range clients {
327 // c <- n
328 // }
329 // }
330 func (s *NotificationServer) notifyUnminedTransaction(dbtx walletdb.ReadTx, details *wtxmgr.TxDetails) {
331 // Sanity check: should not be currently coalescing a notification for mined transactions at the same time that an
332 // unmined tx is notified.
333 if s.currentTxNtfn != nil {
334 E.Ln(
335 "notifying unmined tx notification (",
336 details.Hash.String(),
337 ") while creating notification for blocks",
338 )
339 }
340 defer s.mu.Unlock()
341 s.mu.Lock()
342 clients := s.transactions
343 if len(clients) == 0 {
344 return
345 }
346 unminedTxs := []TransactionSummary{makeTxSummary(dbtx, s.wallet, details)}
347 unminedHashes, e := s.wallet.TxStore.UnminedTxHashes(dbtx.ReadBucket(wtxmgrNamespaceKey))
348 if e != nil {
349 E.Ln(
350 "cannot fetch unmined transaction hashes:", e,
351 )
352 return
353 }
354 bals := make(map[uint32]amt.Amount)
355 relevantAccounts(s.wallet, bals, unminedTxs)
356 e = totalBalances(dbtx, s.wallet, bals)
357 if e != nil {
358 E.Ln(
359 "cannot determine balances for relevant accounts:", e,
360 )
361 return
362 }
363 n := &TransactionNotifications{
364 UnminedTransactions: unminedTxs,
365 UnminedTransactionHashes: unminedHashes,
366 NewBalances: flattenBalanceMap(bals),
367 }
368 for _, c := range clients {
369 c <- n
370 }
371 }
372 373 // notifyUnspentOutput notifies registered clients of a new unspent output that is controlled by the wallet.
374 func (s *NotificationServer) notifyUnspentOutput(account uint32, hash *chainhash.Hash, index uint32) {
375 defer s.mu.Unlock()
376 s.mu.Lock()
377 clients := s.spentness[account]
378 if len(clients) == 0 {
379 return
380 }
381 n := &SpentnessNotifications{
382 hash: hash,
383 index: index,
384 }
385 for _, c := range clients {
386 c <- n
387 }
388 }
389 390 // Hash returns the transaction hash of the spent output.
391 func (n *SpentnessNotifications) Hash() *chainhash.Hash {
392 return n.hash
393 }
394 395 // Index returns the transaction output index of the spent output.
396 func (n *SpentnessNotifications) Index() uint32 {
397 return n.index
398 }
399 400 // Spender returns the spending transaction's hash and input index, if any. If the output is unspent, the final bool
401 // return is false.
402 func (n *SpentnessNotifications) Spender() (*chainhash.Hash, uint32, bool) {
403 return n.spenderHash, n.spenderIndex, n.spenderHash != nil
404 }
405 406 // Done unregisters the client from the server and drains any remaining messages. It must be called exactly once when
407 // the client is finished receiving notifications.
408 func (c *SpentnessNotificationsClient) Done() {
409 go func() {
410 // Drain notifications until the client channel is removed from the server and closed.
411 for range c.C {
412 }
413 }()
414 go func() {
415 s := c.server
416 s.mu.Lock()
417 clients := s.spentness[c.account]
418 for i, ch := range clients {
419 if c.C == ch {
420 clients[i] = clients[len(clients)-1]
421 s.spentness[c.account] = clients[:len(clients)-1]
422 close(ch)
423 break
424 }
425 }
426 s.mu.Unlock()
427 }()
428 }
429 430 // Done unregisters the client from the server and drains any remaining messages. It must be called exactly once when
431 // the client is finished receiving notifications.
432 func (c *TransactionNotificationsClient) Done() {
433 go func() {
434 // Drain notifications until the client channel is removed from the server and closed.
435 for range c.C {
436 }
437 }()
438 go func() {
439 s := c.server
440 s.mu.Lock()
441 clients := s.transactions
442 for i, ch := range clients {
443 if c.C == ch {
444 clients[i] = clients[len(clients)-1]
445 s.transactions = clients[:len(clients)-1]
446 close(ch)
447 break
448 }
449 }
450 s.mu.Unlock()
451 }()
452 }
453 func flattenBalanceMap(m map[uint32]amt.Amount) []AccountBalance {
454 s := make([]AccountBalance, 0, len(m))
455 for k, v := range m {
456 s = append(s, AccountBalance{Account: k, TotalBalance: v})
457 }
458 return s
459 }
460 func lookupInputAccount(dbtx walletdb.ReadTx, w *Wallet, details *wtxmgr.TxDetails, deb wtxmgr.DebitRecord) uint32 {
461 addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey)
462 txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey)
463 // TODO: Debits should record which account(s?) they
464 // debit from so this doesn't need to be looked up.
465 prevOP := &details.MsgTx.TxIn[deb.Index].PreviousOutPoint
466 prev, e := w.TxStore.TxDetails(txmgrNs, &prevOP.Hash)
467 if e != nil {
468 E.F(
469 "cannot query previous transaction details for %v: %v",
470 prevOP.Hash, e,
471 )
472 return 0
473 }
474 if prev == nil {
475 E.Ln(
476 "missing previous transaction", prevOP.Hash,
477 )
478 return 0
479 }
480 prevOut := prev.MsgTx.TxOut[prevOP.Index]
481 var addrs []btcaddr.Address
482 _, addrs, _, e = txscript.ExtractPkScriptAddrs(prevOut.PkScript, w.chainParams)
483 var inputAcct uint32
484 if e == nil && len(addrs) > 0 {
485 _, inputAcct, e = w.Manager.AddrAccount(addrmgrNs, addrs[0])
486 }
487 if e != nil {
488 E.F(
489 "cannot fetch account for previous output %v: %v", prevOP, e,
490 )
491 inputAcct = 0
492 }
493 return inputAcct
494 }
495 func lookupOutputChain(
496 dbtx walletdb.ReadTx, w *Wallet, details *wtxmgr.TxDetails,
497 cred wtxmgr.CreditRecord,
498 ) (account uint32, internal bool) {
499 addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey)
500 output := details.MsgTx.TxOut[cred.Index]
501 var addrs []btcaddr.Address
502 var e error
503 _, addrs, _, e = txscript.ExtractPkScriptAddrs(output.PkScript, w.chainParams)
504 var ma waddrmgr.ManagedAddress
505 if e == nil && len(addrs) > 0 {
506 ma, e = w.Manager.Address(addrmgrNs, addrs[0])
507 }
508 if e != nil {
509 E.Ln(
510 "cannot fetch account for wallet output:", e,
511 )
512 } else {
513 account = ma.Account()
514 internal = ma.Internal()
515 }
516 return
517 }
518 func makeTxSummary(dbtx walletdb.ReadTx, w *Wallet, details *wtxmgr.TxDetails) TransactionSummary {
519 serializedTx := details.SerializedTx
520 if serializedTx == nil {
521 var buf bytes.Buffer
522 e := details.MsgTx.Serialize(&buf)
523 if e != nil {
524 E.Ln("transaction serialization:", e)
525 }
526 serializedTx = buf.Bytes()
527 }
528 var fee amt.Amount
529 if len(details.Debits) == len(details.MsgTx.TxIn) {
530 for _, deb := range details.Debits {
531 fee += deb.Amount
532 }
533 for _, txOut := range details.MsgTx.TxOut {
534 fee -= amt.Amount(txOut.Value)
535 }
536 }
537 var inputs []TransactionSummaryInput
538 if len(details.Debits) != 0 {
539 inputs = make([]TransactionSummaryInput, len(details.Debits))
540 for i, d := range details.Debits {
541 inputs[i] = TransactionSummaryInput{
542 Index: d.Index,
543 PreviousAccount: lookupInputAccount(dbtx, w, details, d),
544 PreviousAmount: d.Amount,
545 }
546 }
547 }
548 outputs := make([]TransactionSummaryOutput, 0, len(details.MsgTx.TxOut))
549 for i := range details.MsgTx.TxOut {
550 credIndex := len(outputs)
551 mine := len(details.Credits) > credIndex && details.Credits[credIndex].Index == uint32(i)
552 if !mine {
553 continue
554 }
555 acct, internal := lookupOutputChain(dbtx, w, details, details.Credits[credIndex])
556 output := TransactionSummaryOutput{
557 Index: uint32(i),
558 Account: acct,
559 Internal: internal,
560 }
561 outputs = append(outputs, output)
562 }
563 return TransactionSummary{
564 Hash: &details.Hash,
565 Transaction: serializedTx,
566 MyInputs: inputs,
567 MyOutputs: outputs,
568 Fee: fee,
569 Timestamp: details.Received.Unix(),
570 }
571 }
572 func newNotificationServer(wallet *Wallet) *NotificationServer {
573 return &NotificationServer{
574 spentness: make(map[uint32][]chan *SpentnessNotifications),
575 wallet: wallet,
576 }
577 }
578 func relevantAccounts(w *Wallet, m map[uint32]amt.Amount, txs []TransactionSummary) {
579 for _, tx := range txs {
580 for _, d := range tx.MyInputs {
581 m[d.PreviousAccount] = 0
582 }
583 for _, c := range tx.MyOutputs {
584 m[c.Account] = 0
585 }
586 }
587 }
588 func totalBalances(dbtx walletdb.ReadTx, w *Wallet, m map[uint32]amt.Amount) (e error) {
589 addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey)
590 unspent, e := w.TxStore.UnspentOutputs(dbtx.ReadBucket(wtxmgrNamespaceKey))
591 if e != nil {
592 return e
593 }
594 for i := range unspent {
595 output := &unspent[i]
596 var outputAcct uint32
597 var addrs []btcaddr.Address
598 _, addrs, _, e = txscript.ExtractPkScriptAddrs(
599 output.PkScript, w.chainParams,
600 )
601 if e == nil && len(addrs) > 0 {
602 _, outputAcct, e = w.Manager.AddrAccount(addrmgrNs, addrs[0])
603 }
604 if e == nil {
605 _, ok := m[outputAcct]
606 if ok {
607 m[outputAcct] += output.Amount
608 }
609 }
610 }
611 return nil
612 }
613