forked from decred/dcrwallet
-
Notifications
You must be signed in to change notification settings - Fork 0
/
utxos.go
163 lines (143 loc) · 4.95 KB
/
utxos.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
package wallet
import (
"fmt"
"time"
"github.com/decred/dcrd/blockchain"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/txscript"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrwallet/apperrors"
"github.com/decred/dcrwallet/wallet/udb"
"github.com/decred/dcrwallet/walletdb"
)
// OutputSelectionPolicy describes the rules for selecting an output from the
// wallet.
type OutputSelectionPolicy struct {
Account uint32
RequiredConfirmations int32
}
func (p *OutputSelectionPolicy) meetsRequiredConfs(txHeight, curHeight int32) bool {
return confirmed(p.RequiredConfirmations, txHeight, curHeight)
}
// UnspentOutputs fetches all unspent outputs from the wallet that match rules
// described in the passed policy.
func (w *Wallet) UnspentOutputs(policy OutputSelectionPolicy) ([]*TransactionOutput, error) {
var outputResults []*TransactionOutput
err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey)
_, tipHeight := w.TxStore.MainChainTip(txmgrNs)
// TODO: actually stream outputs from the db instead of fetching
// all of them at once.
outputs, err := w.TxStore.UnspentOutputs(txmgrNs)
if err != nil {
return err
}
for _, output := range outputs {
// Ignore outputs that haven't reached the required
// number of confirmations.
if !policy.meetsRequiredConfs(output.Height, tipHeight) {
continue
}
// Ignore outputs that are not controlled by the account.
_, addrs, _, err := txscript.ExtractPkScriptAddrs(
txscript.DefaultScriptVersion, output.PkScript,
w.chainParams)
if err != nil || len(addrs) == 0 {
// Cannot determine which account this belongs
// to without a valid address. TODO: Fix this
// by saving outputs per account, or accounts
// per output.
continue
}
outputAcct, err := w.Manager.AddrAccount(addrmgrNs, addrs[0])
if err != nil {
return err
}
if outputAcct != policy.Account {
continue
}
// Stakebase isn't exposed by wtxmgr so those will be
// OutputKindNormal for now.
outputSource := OutputKindNormal
if output.FromCoinBase {
outputSource = OutputKindCoinbase
}
result := &TransactionOutput{
OutPoint: output.OutPoint,
Output: wire.TxOut{
Value: int64(output.Amount),
// TODO: version is bogus but there is
// only version 0 at time of writing.
Version: txscript.DefaultScriptVersion,
PkScript: output.PkScript,
},
OutputKind: outputSource,
ContainingBlock: BlockIdentity(output.Block),
ReceiveTime: output.Received,
}
outputResults = append(outputResults, result)
}
return nil
})
return outputResults, err
}
// SelectInputs selects transaction inputs to redeem unspent outputs stored in
// the wallet. It returns the total input amount referenced by the previous
// transaction outputs, a slice of transaction inputs referencing these outputs,
// and a slice of previous output scripts from each previous output referenced
// by the corresponding input.
func (w *Wallet) SelectInputs(targetAmount dcrutil.Amount, policy OutputSelectionPolicy) (total dcrutil.Amount,
inputs []*wire.TxIn, prevScripts [][]byte, err error) {
err = walletdb.View(w.db, func(tx walletdb.ReadTx) error {
addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey)
_, tipHeight := w.TxStore.MainChainTip(txmgrNs)
if policy.Account != udb.ImportedAddrAccount {
lastAcct, err := w.Manager.LastAccount(addrmgrNs)
if err != nil {
return err
}
if policy.Account > lastAcct {
return apperrors.E{
ErrorCode: apperrors.ErrAccountNotFound,
Description: "account not found",
}
}
}
sourceImpl := w.TxStore.MakeInputSource(txmgrNs, addrmgrNs, policy.Account,
policy.RequiredConfirmations, tipHeight)
var err error
total, inputs, prevScripts, err = sourceImpl.SelectInputs(targetAmount)
return err
})
return
}
// OutputInfo describes additional info about an output which can be queried
// using an outpoint.
type OutputInfo struct {
Received time.Time
Amount dcrutil.Amount
FromCoinbase bool
}
// OutputInfo queries the wallet for additional transaction output info
// regarding an outpoint.
func (w *Wallet) OutputInfo(op *wire.OutPoint) (OutputInfo, error) {
var info OutputInfo
err := walletdb.View(w.db, func(dbtx walletdb.ReadTx) error {
txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey)
txDetails, err := w.TxStore.TxDetails(txmgrNs, &op.Hash)
if err != nil {
return err
}
if op.Index >= uint32(len(txDetails.TxRecord.MsgTx.TxOut)) {
return fmt.Errorf("output %d not found, transaction only contains %d outputs",
op.Index, len(txDetails.TxRecord.MsgTx.TxOut))
}
info.Received = txDetails.Received
info.Amount = dcrutil.Amount(txDetails.TxRecord.MsgTx.TxOut[op.Index].Value)
info.FromCoinbase = blockchain.IsCoinBaseTx(&txDetails.TxRecord.MsgTx)
return nil
})
return info, err
}