forked from lightningnetwork/lnd
-
Notifications
You must be signed in to change notification settings - Fork 0
/
btcwallet.go
380 lines (323 loc) · 11 KB
/
btcwallet.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
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
package btcwallet
import (
"encoding/hex"
"fmt"
"math"
"sync"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/chaincfg"
"github.com/roasbeef/btcd/txscript"
"github.com/roasbeef/btcd/wire"
"github.com/roasbeef/btcutil"
"github.com/roasbeef/btcwallet/chain"
"github.com/roasbeef/btcwallet/waddrmgr"
base "github.com/roasbeef/btcwallet/wallet"
"github.com/roasbeef/btcwallet/walletdb"
)
const (
defaultAccount = uint32(waddrmgr.DefaultAccountNum)
)
var (
lnNamespace = []byte("ln")
rootKey = []byte("ln-root")
)
// BtcWallet is an implementation of the lnwallet.WalletController interface
// backed by an active instance of btcwallet. At the time of the writing of
// this documentation, this implementation requires a full btcd node to
// operate.
type BtcWallet struct {
// wallet is an active instance of btcwallet.
wallet *base.Wallet
// rpc is an an active RPC connection to btcd full-node.
rpc *chain.RPCClient
// lnNamespace is a namespace within btcwallet's walletdb used to store
// persistent state required by the WalletController interface but not
// natively supported by btcwallet.
lnNamespace walletdb.Namespace
netParams *chaincfg.Params
// utxoCache is a cache used to speed up repeated calls to
// FetchInputInfo.
utxoCache map[wire.OutPoint]*wire.TxOut
cacheMtx sync.RWMutex
}
// A compile time check to ensure that BtcWallet implements the
// WalletController interface.
var _ lnwallet.WalletController = (*BtcWallet)(nil)
// New returns a new fully initialized instance of BtcWallet given a valid
// confirguration struct.
func New(cfg *Config) (*BtcWallet, error) {
// Ensure the wallet exists or create it when the create flag is set.
netDir := networkDir(cfg.DataDir, cfg.NetParams)
var pubPass []byte
if cfg.PublicPass == nil {
pubPass = defaultPubPassphrase
} else {
pubPass = cfg.PublicPass
}
loader := base.NewLoader(cfg.NetParams, netDir)
walletExists, err := loader.WalletExists()
if err != nil {
return nil, err
}
var wallet *base.Wallet
if !walletExists {
// Wallet has never been created, perform initial set up.
wallet, err = loader.CreateNewWallet(pubPass, cfg.PrivatePass,
cfg.HdSeed)
if err != nil {
return nil, err
}
} else {
// Wallet has been created and been initialized at this point, open it
// along with all the required DB namepsaces, and the DB itself.
wallet, err = loader.OpenExistingWallet(pubPass, false)
if err != nil {
return nil, err
}
}
if err := wallet.Manager.Unlock(cfg.PrivatePass); err != nil {
return nil, err
}
// Create a special websockets rpc client for btcd which will be used
// by the wallet for notifications, calls, etc.
rpcc, err := chain.NewRPCClient(cfg.NetParams, cfg.RpcHost,
cfg.RpcUser, cfg.RpcPass, cfg.CACert, false, 20)
if err != nil {
return nil, err
}
db := wallet.Database()
walletNamespace, err := db.Namespace(lnNamespace)
if err != nil {
return nil, err
}
return &BtcWallet{
wallet: wallet,
rpc: rpcc,
lnNamespace: walletNamespace,
netParams: cfg.NetParams,
utxoCache: make(map[wire.OutPoint]*wire.TxOut),
}, nil
}
// Start initializes the underlying rpc connection, the wallet itself, and
// begins syncing to the current available blockchain state.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) Start() error {
// Establish an RPC connection in additino to starting the goroutines
// in the underlying wallet.
if err := b.rpc.Start(); err != nil {
return err
}
// Start the underlying btcwallet core.
b.wallet.Start()
// Pass the rpc client into the wallet so it can sync up to the
// current main chain.
b.wallet.SynchronizeRPC(b.rpc)
return nil
}
// Stop signals the wallet for shutdown. Shutdown may entail closing
// any active sockets, database handles, stopping goroutines, etc.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) Stop() error {
b.wallet.Stop()
b.wallet.WaitForShutdown()
b.rpc.Shutdown()
return nil
}
// ConfirmedBalance returns the sum of all the wallet's unspent outputs that
// have at least confs confirmations. If confs is set to zero, then all unspent
// outputs, including those currently in the mempool will be included in the
// final sum.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) ConfirmedBalance(confs int32, witness bool) (btcutil.Amount, error) {
var balance btcutil.Amount
if witness {
witnessOutputs, err := b.ListUnspentWitness(confs)
if err != nil {
return 0, err
}
for _, witnessOutput := range witnessOutputs {
balance += witnessOutput.Value
}
} else {
outputSum, err := b.wallet.CalculateBalance(confs)
if err != nil {
return 0, err
}
balance = outputSum
}
return balance, nil
}
// NewAddress returns the next external or internal address for the wallet
// dicatated by the value of the `change` paramter. If change is true, then an
// internal address will be returned, otherwise an external address should be
// returned.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) NewAddress(t lnwallet.AddressType, change bool) (btcutil.Address, error) {
var addrType waddrmgr.AddressType
switch t {
case lnwallet.WitnessPubKey:
addrType = waddrmgr.WitnessPubKey
case lnwallet.NestedWitnessPubKey:
addrType = waddrmgr.NestedWitnessPubKey
case lnwallet.PubKeyHash:
addrType = waddrmgr.PubKeyHash
default:
return nil, fmt.Errorf("unknown address type")
}
if change {
return b.wallet.NewAddress(defaultAccount, addrType)
} else {
return b.wallet.NewChangeAddress(defaultAccount, addrType)
}
}
// GetPrivKey retrives the underlying private key associated with the passed
// address. If the we're unable to locate the proper private key, then a
// non-nil error will be returned.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) GetPrivKey(a btcutil.Address) (*btcec.PrivateKey, error) {
// Using the ID address, request the private key coresponding to the
// address from the wallet's address manager.
walletAddr, err := b.wallet.Manager.Address(a)
if err != nil {
return nil, err
}
return walletAddr.(waddrmgr.ManagedPubKeyAddress).PrivKey()
}
// NewRawKey retrieves the next key within our HD key-chain for use within as a
// multi-sig key within the funding transaction, or within the commitment
// transaction's outputs.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) NewRawKey() (*btcec.PublicKey, error) {
nextAddr, err := b.wallet.Manager.NextExternalAddresses(defaultAccount,
1, waddrmgr.WitnessPubKey)
if err != nil {
return nil, err
}
pkAddr := nextAddr[0].(waddrmgr.ManagedPubKeyAddress)
return pkAddr.PubKey(), nil
}
// FetchRootKey returns a root key which is meanted to be used as an initial
// seed/salt to generate any Lightning specific secrets.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) FetchRootKey() (*btcec.PrivateKey, error) {
// Fetch the root address hash from the database, this is persisted
// locally within the database, then used to obtain the key from the
// wallet based on the address hash.
var rootAddrHash []byte
if err := b.lnNamespace.Update(func(tx walletdb.Tx) error {
rootBucket := tx.RootBucket()
rootAddrHash = rootBucket.Get(rootKey)
return nil
}); err != nil {
return nil, err
}
if rootAddrHash == nil {
// Otherwise, we need to generate a fresh address from the
// wallet, then stores it's hash160 within the database so we
// can look up the exact key later.
rootAddr, err := b.wallet.Manager.NextExternalAddresses(defaultAccount,
1, waddrmgr.WitnessPubKey)
if err != nil {
return nil, err
}
if err := b.lnNamespace.Update(func(tx walletdb.Tx) error {
rootBucket := tx.RootBucket()
rootAddrHash = rootAddr[0].Address().ScriptAddress()
if err := rootBucket.Put(rootKey, rootAddrHash); err != nil {
return err
}
return nil
}); err != nil {
return nil, err
}
}
// With the root address hash obtained, generate the corresponding
// address, then retrieve the managed address from the wallet which
// will allow us to obtain the private key.
rootAddr, err := btcutil.NewAddressWitnessPubKeyHash(rootAddrHash,
b.netParams)
if err != nil {
return nil, err
}
walletAddr, err := b.wallet.Manager.Address(rootAddr)
if err != nil {
return nil, err
}
return walletAddr.(waddrmgr.ManagedPubKeyAddress).PrivKey()
}
// SendOutputs funds, signs, and broadcasts a Bitcoin transaction paying out to
// the specified outputs. In the case the wallet has insufficient funds, or the
// outputs are non-standard, a non-nil error will be be returned.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) SendOutputs(outputs []*wire.TxOut) (*wire.ShaHash, error) {
return b.wallet.SendOutputs(outputs, defaultAccount, 1)
}
// LockOutpoint marks an outpoint as locked meaning it will no longer be deemed
// as eligible for coin selection. Locking outputs are utilized in order to
// avoid race conditions when selecting inputs for usage when funding a
// channel.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) LockOutpoint(o wire.OutPoint) {
b.wallet.LockOutpoint(o)
}
// UnlockOutpoint unlocks an previously locked output, marking it eligible for
// coin seleciton.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) UnlockOutpoint(o wire.OutPoint) {
b.wallet.UnlockOutpoint(o)
}
// ListUnspentWitness returns a slice of all the unspent outputs the wallet
// controls which pay to witness programs either directly or indirectly.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) ListUnspentWitness(minConfs int32) ([]*lnwallet.Utxo, error) {
// First, grab all the unfiltered currently unspent outputs.
maxConfs := int32(math.MaxInt32)
unspentOutputs, err := b.wallet.ListUnspent(minConfs, maxConfs, nil)
if err != nil {
return nil, err
}
// Next, we'll run through all the regular outputs, only saving those
// which are p2wkh outputs or a p2wsh output nested within a p2sh output.
witnessOutputs := make([]*lnwallet.Utxo, 0, len(unspentOutputs))
for _, output := range unspentOutputs {
pkScript, err := hex.DecodeString(output.ScriptPubKey)
if err != nil {
return nil, err
}
// TODO(roasbeef): this assumes all p2sh outputs returned by
// the wallet are nested p2sh...
if txscript.IsPayToWitnessPubKeyHash(pkScript) ||
txscript.IsPayToScriptHash(pkScript) {
txid, err := wire.NewShaHashFromStr(output.TxID)
if err != nil {
return nil, err
}
utxo := &lnwallet.Utxo{
Value: btcutil.Amount(output.Amount * 1e8),
OutPoint: wire.OutPoint{
Hash: *txid,
Index: output.Vout,
},
}
witnessOutputs = append(witnessOutputs, utxo)
}
}
return witnessOutputs, nil
}
// PublishTransaction performs cursory validation (dust checks, etc), then
// finally broadcasts the passed transaction to the Bitcoin network.
func (b *BtcWallet) PublishTransaction(tx *wire.MsgTx) error {
return b.wallet.PublishTransaction(tx)
}