-
Notifications
You must be signed in to change notification settings - Fork 602
/
Copy pathutxos.go
226 lines (193 loc) · 6.85 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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
// Copyright (c) 2016 The Decred developers
// Copyright (c) 2017 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package wallet
import (
"errors"
"fmt"
"github.com/btcsuite/btcd/btcutil/hdkeychain"
"github.com/btcsuite/btcd/btcutil/psbt"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/walletdb"
)
var (
// ErrNotMine is an error denoting that a Wallet instance is unable to
// spend a specified output.
ErrNotMine = errors.New("the passed output does not belong to the " +
"wallet")
)
// 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)
syncBlock := w.Manager.SyncedTo()
// 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, syncBlock.Height) {
continue
}
// Ignore outputs that are not controlled by the account.
_, addrs, _, err := txscript.ExtractPkScriptAddrs(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),
PkScript: output.PkScript,
},
OutputKind: outputSource,
ContainingBlock: BlockIdentity(output.Block),
ReceiveTime: output.Received,
}
outputResults = append(outputResults, result)
}
return nil
})
return outputResults, err
}
// FetchInputInfo queries for the wallet's knowledge of the passed outpoint. If
// the wallet determines this output is under its control, then the original
// full transaction, the target txout, the derivation info and the number of
// confirmations are returned. Otherwise, a non-nil error value of ErrNotMine
// is returned instead.
//
// NOTE: This method is kept for compatibility.
func (w *Wallet) FetchInputInfo(prevOut *wire.OutPoint) (*wire.MsgTx,
*wire.TxOut, *psbt.Bip32Derivation, int64, error) {
tx, txOut, confs, err := w.FetchOutpointInfo(prevOut)
if err != nil {
return nil, nil, nil, 0, err
}
derivation, err := w.FetchDerivationInfo(txOut.PkScript)
if err != nil {
return nil, nil, nil, 0, err
}
return tx, txOut, derivation, confs, nil
}
// fetchOutputAddr attempts to fetch the managed address corresponding to the
// passed output script. This function is used to look up the proper key which
// should be used to sign a specified input.
func (w *Wallet) fetchOutputAddr(script []byte) (waddrmgr.ManagedAddress, error) {
_, addrs, _, err := txscript.ExtractPkScriptAddrs(script, w.chainParams)
if err != nil {
return nil, err
}
// If the case of a multi-sig output, several address may be extracted.
// Therefore, we simply select the key for the first address we know
// of.
for _, addr := range addrs {
addr, err := w.AddressInfo(addr)
if err == nil {
return addr, nil
}
}
return nil, ErrNotMine
}
// FetchOutpointInfo queries for the wallet's knowledge of the passed outpoint.
// If the wallet determines this output is under its control, the original full
// transaction, the target txout and the number of confirmations are returned.
// Otherwise, a non-nil error value of ErrNotMine is returned instead.
func (w *Wallet) FetchOutpointInfo(prevOut *wire.OutPoint) (*wire.MsgTx,
*wire.TxOut, int64, error) {
// We manually look up the output within the tx store.
txid := &prevOut.Hash
txDetail, err := UnstableAPI(w).TxDetails(txid)
if err != nil {
return nil, nil, 0, err
} else if txDetail == nil {
return nil, nil, 0, ErrNotMine
}
// With the output retrieved, we'll make an additional check to ensure
// we actually have control of this output. We do this because the
// check above only guarantees that the transaction is somehow relevant
// to us, like in the event of us being the sender of the transaction.
numOutputs := uint32(len(txDetail.TxRecord.MsgTx.TxOut))
if prevOut.Index >= numOutputs {
return nil, nil, 0, fmt.Errorf("invalid output index %v for "+
"transaction with %v outputs", prevOut.Index,
numOutputs)
}
pkScript := txDetail.TxRecord.MsgTx.TxOut[prevOut.Index].PkScript
// Determine the number of confirmations the output currently has.
_, currentHeight, err := w.chainClient.GetBestBlock()
if err != nil {
return nil, nil, 0, fmt.Errorf("unable to retrieve current "+
"height: %w", err)
}
confs := int64(0)
if txDetail.Block.Height != -1 {
confs = int64(currentHeight - txDetail.Block.Height)
}
return &txDetail.TxRecord.MsgTx, &wire.TxOut{
Value: txDetail.TxRecord.MsgTx.TxOut[prevOut.Index].Value,
PkScript: pkScript,
}, confs, nil
}
// FetchDerivationInfo queries for the wallet's knowledge of the passed
// pkScript and constructs the derivation info and returns it.
func (w *Wallet) FetchDerivationInfo(pkScript []byte) (*psbt.Bip32Derivation,
error) {
addr, err := w.fetchOutputAddr(pkScript)
if err != nil {
return nil, err
}
pubKeyAddr, ok := addr.(waddrmgr.ManagedPubKeyAddress)
if !ok {
return nil, ErrNotMine
}
keyScope, derivationPath, _ := pubKeyAddr.DerivationInfo()
derivation := &psbt.Bip32Derivation{
PubKey: pubKeyAddr.PubKey().SerializeCompressed(),
MasterKeyFingerprint: derivationPath.MasterKeyFingerprint,
Bip32Path: []uint32{
keyScope.Purpose + hdkeychain.HardenedKeyStart,
keyScope.Coin + hdkeychain.HardenedKeyStart,
derivationPath.Account,
derivationPath.Branch,
derivationPath.Index,
},
}
return derivation, nil
}