1 // Package wallet Copyright (c) 2015-2016 The btcsuite developers
2 package wallet
3 4 import (
5 "fmt"
6 "github.com/p9c/p9/pkg/amt"
7 "github.com/p9c/p9/pkg/btcaddr"
8 "github.com/p9c/p9/pkg/chainclient"
9 "sort"
10 11 ec "github.com/p9c/p9/pkg/ecc"
12 "github.com/p9c/p9/pkg/txauthor"
13 "github.com/p9c/p9/pkg/txscript"
14 "github.com/p9c/p9/pkg/waddrmgr"
15 "github.com/p9c/p9/pkg/walletdb"
16 "github.com/p9c/p9/pkg/wire"
17 "github.com/p9c/p9/pkg/wtxmgr"
18 )
19 20 // byAmount defines the methods needed to satisify sort.Interface to txsort credits by their output amount.
21 type byAmount []wtxmgr.Credit
22 23 func (s byAmount) Len() int { return len(s) }
24 func (s byAmount) Less(i, j int) bool { return s[i].Amount < s[j].Amount }
25 func (s byAmount) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
26 27 func makeInputSource(eligible []wtxmgr.Credit) txauthor.InputSource {
28 // Pick largest outputs first. This is only done for compatibility with previous
29 // tx creation code, not because it's a good idea.
30 sort.Sort(sort.Reverse(byAmount(eligible)))
31 // Current inputs and their total value. These are closed over by the returned
32 // input source and reused across multiple calls.
33 currentTotal := amt.Amount(0)
34 currentInputs := make([]*wire.TxIn, 0, len(eligible))
35 currentScripts := make([][]byte, 0, len(eligible))
36 currentInputValues := make([]amt.Amount, 0, len(eligible))
37 return func(target amt.Amount) (
38 amt.Amount, []*wire.TxIn,
39 []amt.Amount, [][]byte, error,
40 ) {
41 for currentTotal < target && len(eligible) != 0 {
42 nextCredit := &eligible[0]
43 eligible = eligible[1:]
44 nextInput := wire.NewTxIn(&nextCredit.OutPoint, nil, nil)
45 currentTotal += nextCredit.Amount
46 currentInputs = append(currentInputs, nextInput)
47 currentScripts = append(currentScripts, nextCredit.PkScript)
48 currentInputValues = append(currentInputValues, nextCredit.Amount)
49 }
50 return currentTotal, currentInputs, currentInputValues, currentScripts, nil
51 }
52 }
53 54 // secretSource is an implementation of txauthor.SecretSource for the wallet's address manager.
55 type secretSource struct {
56 *waddrmgr.Manager
57 addrmgrNs walletdb.ReadBucket
58 }
59 60 // GetKey gets the private key for an address if it is available
61 func (s secretSource) GetKey(addr btcaddr.Address) (privKey *ec.PrivateKey, cmpr bool, e error) {
62 var ma waddrmgr.ManagedAddress
63 ma, e = s.Address(s.addrmgrNs, addr)
64 if e != nil {
65 return nil, false, e
66 }
67 var ok bool
68 var mpka waddrmgr.ManagedPubKeyAddress
69 mpka, ok = ma.(waddrmgr.ManagedPubKeyAddress)
70 if !ok {
71 e = fmt.Errorf(
72 "managed address type for %v is `%T` but "+
73 "want waddrmgr.ManagedPubKeyAddress", addr, ma,
74 )
75 return nil, false, e
76 }
77 if privKey, e = mpka.PrivKey(); E.Chk(e) {
78 return nil, false, e
79 }
80 return privKey, ma.Compressed(), nil
81 }
82 83 // GetScript returns pay to script transaction
84 func (s secretSource) GetScript(addr btcaddr.Address) ([]byte, error) {
85 ma, e := s.Address(s.addrmgrNs, addr)
86 if e != nil {
87 return nil, e
88 }
89 msa, ok := ma.(waddrmgr.ManagedScriptAddress)
90 if !ok {
91 e := fmt.Errorf(
92 "managed address type for %v is `%T` but "+
93 "want waddrmgr.ManagedScriptAddress", addr, ma,
94 )
95 return nil, e
96 }
97 return msa.Script()
98 }
99 100 // txToOutputs creates a signed transaction which includes each output from
101 // outputs. Previous outputs to reedeem are chosen from the passed account's
102 // UTXO set and minconf policy. An additional output may be added to return
103 // change to the wallet. An appropriate fee is included based on the wallet's
104 // current relay fee. The wallet must be unlocked to create the transaction.
105 func (w *Wallet) txToOutputs(
106 outputs []*wire.TxOut, account uint32,
107 minconf int32, feeSatPerKb amt.Amount,
108 ) (tx *txauthor.AuthoredTx, e error) {
109 var chainClient chainclient.Interface
110 if chainClient, e = w.requireChainClient(); E.Chk(e) {
111 return nil, e
112 }
113 e = walletdb.Update(
114 w.db, func(dbtx walletdb.ReadWriteTx) (e error) {
115 addrmgrNs := dbtx.ReadWriteBucket(waddrmgrNamespaceKey)
116 // Get current block's height and hash.
117 var bs *waddrmgr.BlockStamp
118 if bs, e = chainClient.BlockStamp(); E.Chk(e) {
119 return
120 }
121 var eligible []wtxmgr.Credit
122 if eligible, e = w.findEligibleOutputs(dbtx, account, minconf, bs); E.Chk(e) {
123 return
124 }
125 inputSource := makeInputSource(eligible)
126 changeSource := func() (b []byte, e error) {
127 // Derive the change output script. As a hack to allow spending from the
128 // imported account, change addresses are created from account 0.
129 var changeAddr btcaddr.Address
130 if account == waddrmgr.ImportedAddrAccount {
131 changeAddr, e = w.newChangeAddress(addrmgrNs, 0)
132 } else {
133 changeAddr, e = w.newChangeAddress(addrmgrNs, account)
134 }
135 if E.Chk(e) {
136 return
137 }
138 return txscript.PayToAddrScript(changeAddr)
139 }
140 if tx, e = txauthor.NewUnsignedTransaction(outputs, feeSatPerKb, inputSource, changeSource); E.Chk(e) {
141 return
142 }
143 // Randomize change position, if change exists, before signing. This doesn't
144 // affect the serialize size, so the change amount will still be valid.
145 if tx.ChangeIndex >= 0 {
146 tx.RandomizeChangePosition()
147 }
148 return tx.AddAllInputScripts(secretSource{w.Manager, addrmgrNs})
149 },
150 )
151 if E.Chk(e) {
152 return
153 }
154 if e = validateMsgTx(tx.Tx, tx.PrevScripts, tx.PrevInputValues); E.Chk(e) {
155 return
156 }
157 if tx.ChangeIndex >= 0 && account == waddrmgr.ImportedAddrAccount {
158 changeAmount := amt.Amount(tx.Tx.TxOut[tx.ChangeIndex].Value)
159 W.F(
160 "spend from imported account produced change: moving %v from imported account into default account.",
161 changeAmount,
162 )
163 }
164 return
165 }
166 func (w *Wallet) findEligibleOutputs(
167 dbtx walletdb.ReadTx,
168 account uint32,
169 minconf int32,
170 bs *waddrmgr.BlockStamp,
171 ) ([]wtxmgr.Credit, error) {
172 addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey)
173 txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey)
174 unspent, e := w.TxStore.UnspentOutputs(txmgrNs)
175 if e != nil {
176 return nil, e
177 }
178 // TODO: Eventually all of these filters (except perhaps output locking) should
179 // be handled by the call to UnspentOutputs (or similar). Because one of these
180 // filters requires matching the output script to the desired
181 // account, this change depends on making wtxmgr a waddrmgr dependancy and
182 // requesting unspent outputs for a single account.
183 eligible := make([]wtxmgr.Credit, 0, len(unspent))
184 for i := range unspent {
185 output := &unspent[i]
186 // Only include this output if it meets the required number of confirmations.
187 // Coinbase transactions must have have reached maturity before their outputs
188 // may be spent.
189 if !confirmed(minconf, output.Height, bs.Height) {
190 continue
191 }
192 if output.FromCoinBase {
193 target := int32(w.chainParams.CoinbaseMaturity)
194 if !confirmed(target, output.Height, bs.Height) {
195 continue
196 }
197 }
198 // Locked unspent outputs are skipped.
199 if w.LockedOutpoint(output.OutPoint) {
200 continue
201 }
202 // Only include the output if it is associated with the passed account.
203 //
204 // TODO: Handle multisig outputs by determining if enough of the addresses are
205 // controlled.
206 var addrs []btcaddr.Address
207 if _, addrs, _, e = txscript.ExtractPkScriptAddrs(
208 output.PkScript, w.chainParams,
209 ); E.Chk(e) || len(addrs) != 1 {
210 continue
211 }
212 var addrAcct uint32
213 if _, addrAcct, e = w.Manager.AddrAccount(addrmgrNs, addrs[0]); E.Chk(e) ||
214 addrAcct != account {
215 continue
216 }
217 eligible = append(eligible, *output)
218 }
219 return eligible, nil
220 }
221 222 // validateMsgTx verifies transaction input scripts for tx. All previous output
223 // scripts from outputs redeemed by the transaction, in the same order they are
224 // spent, must be passed in the prevScripts slice.
225 func validateMsgTx(tx *wire.MsgTx, prevScripts [][]byte, inputValues []amt.Amount) (e error) {
226 hashCache := txscript.NewTxSigHashes(tx)
227 for i, prevScript := range prevScripts {
228 var vm *txscript.Engine
229 vm, e = txscript.NewEngine(
230 prevScript, tx, i,
231 txscript.StandardVerifyFlags, nil, hashCache, int64(inputValues[i]),
232 )
233 if E.Chk(e) {
234 return fmt.Errorf("cannot create script engine: %s", e)
235 }
236 e = vm.Execute()
237 if E.Chk(e) {
238 return fmt.Errorf("cannot validate transaction: %s", e)
239 }
240 }
241 return nil
242 }
243