381 lines
11 KiB
Go
381 lines
11 KiB
Go
|
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)
|
||
|
}
|