2016-08-13 01:29:38 +03:00
|
|
|
package btcwallet
|
|
|
|
|
|
|
|
import (
|
2016-10-15 06:15:50 +03:00
|
|
|
"bytes"
|
2016-08-13 01:29:38 +03:00
|
|
|
"encoding/hex"
|
|
|
|
"fmt"
|
|
|
|
"math"
|
|
|
|
"sync"
|
2016-12-09 05:29:55 +03:00
|
|
|
"time"
|
2016-08-13 01:29:38 +03:00
|
|
|
|
|
|
|
"github.com/lightningnetwork/lnd/lnwallet"
|
|
|
|
"github.com/roasbeef/btcd/btcec"
|
|
|
|
"github.com/roasbeef/btcd/chaincfg"
|
2017-01-06 00:56:27 +03:00
|
|
|
"github.com/roasbeef/btcd/chaincfg/chainhash"
|
2016-08-13 01:29:38 +03:00
|
|
|
"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 (
|
2017-04-24 05:19:17 +03:00
|
|
|
lnNamespace = []byte("ln")
|
|
|
|
rootKey = []byte("ln-root")
|
|
|
|
waddrmgrNamespaceKey = []byte("waddrmgr")
|
2016-08-13 01:29:38 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
2017-05-25 03:34:03 +03:00
|
|
|
chain chain.Interface
|
2016-08-13 01:29:38 +03:00
|
|
|
|
2017-04-24 05:19:17 +03:00
|
|
|
db walletdb.DB
|
|
|
|
|
|
|
|
cfg *Config
|
2016-08-13 01:29:38 +03:00
|
|
|
|
|
|
|
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
|
2016-10-16 02:02:09 +03:00
|
|
|
// configuration struct.
|
2017-04-24 05:19:17 +03:00
|
|
|
func New(cfg Config) (*BtcWallet, error) {
|
2016-08-13 01:29:38 +03:00
|
|
|
// Ensure the wallet exists or create it when the create flag is set.
|
2017-10-12 12:07:58 +03:00
|
|
|
netDir := NetworkDir(cfg.DataDir, cfg.NetParams)
|
2016-08-13 01:29:38 +03:00
|
|
|
|
|
|
|
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 {
|
2017-11-23 09:29:32 +03:00
|
|
|
// Wallet has been created and been initialized at this point,
|
|
|
|
// open it along with all the required DB namepsaces, and the
|
|
|
|
// DB itself.
|
2016-08-13 01:29:38 +03:00
|
|
|
wallet, err = loader.OpenExistingWallet(pubPass, false)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-24 05:19:17 +03:00
|
|
|
// Create a bucket within the wallet's database dedicated to storing
|
|
|
|
// our LN specific data.
|
2016-08-13 01:29:38 +03:00
|
|
|
db := wallet.Database()
|
2017-04-24 05:19:17 +03:00
|
|
|
err = walletdb.Update(db, func(tx walletdb.ReadWriteTx) error {
|
|
|
|
_, err := tx.CreateTopLevelBucket(lnNamespace)
|
|
|
|
if err != nil && err != walletdb.ErrBucketExists {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
2016-08-13 01:29:38 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &BtcWallet{
|
2017-04-24 05:19:17 +03:00
|
|
|
cfg: &cfg,
|
|
|
|
wallet: wallet,
|
|
|
|
db: db,
|
2017-05-25 03:34:03 +03:00
|
|
|
chain: cfg.ChainSource,
|
2017-04-24 05:19:17 +03:00
|
|
|
netParams: cfg.NetParams,
|
|
|
|
utxoCache: make(map[wire.OutPoint]*wire.TxOut),
|
2016-08-13 01:29:38 +03:00
|
|
|
}, 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 {
|
2017-06-08 03:01:17 +03:00
|
|
|
// Establish an RPC connection in addition to starting the goroutines
|
2016-08-13 01:29:38 +03:00
|
|
|
// in the underlying wallet.
|
2017-05-25 03:34:03 +03:00
|
|
|
if err := b.chain.Start(); err != nil {
|
2016-08-13 01:29:38 +03:00
|
|
|
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.
|
2017-05-25 03:34:03 +03:00
|
|
|
b.wallet.SynchronizeRPC(b.chain)
|
2016-08-13 01:29:38 +03:00
|
|
|
|
2017-04-24 05:17:54 +03:00
|
|
|
if err := b.wallet.Unlock(b.cfg.PrivatePass, nil); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-08-13 01:29:38 +03:00
|
|
|
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()
|
|
|
|
|
2017-05-25 03:34:03 +03:00
|
|
|
b.chain.Stop()
|
2016-08-13 01:29:38 +03:00
|
|
|
|
|
|
|
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.NewChangeAddress(defaultAccount, addrType)
|
|
|
|
}
|
2017-02-23 22:56:47 +03:00
|
|
|
|
|
|
|
return b.wallet.NewAddress(defaultAccount, addrType)
|
2016-08-13 01:29:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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) {
|
2017-04-24 05:19:17 +03:00
|
|
|
// Using the ID address, request the private key corresponding to the
|
2016-08-13 01:29:38 +03:00
|
|
|
// address from the wallet's address manager.
|
2017-04-24 05:19:17 +03:00
|
|
|
return b.wallet.PrivKeyForAddress(a)
|
2016-08-13 01:29:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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) {
|
2017-02-08 07:01:12 +03:00
|
|
|
addr, err := b.wallet.NewAddress(defaultAccount,
|
|
|
|
waddrmgr.WitnessPubKey)
|
2016-08-13 01:29:38 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2017-04-24 05:19:17 +03:00
|
|
|
return b.wallet.PubKeyForAddress(addr)
|
2016-08-13 01:29:38 +03:00
|
|
|
}
|
|
|
|
|
2017-02-08 07:01:12 +03:00
|
|
|
// FetchRootKey returns a root key which is intended to be used as an initial
|
2016-08-13 01:29:38 +03:00
|
|
|
// 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
|
2017-04-24 05:19:17 +03:00
|
|
|
if err := walletdb.View(b.db, func(tx walletdb.ReadTx) error {
|
|
|
|
lnBucket := tx.ReadBucket(lnNamespace)
|
2016-08-13 01:29:38 +03:00
|
|
|
|
2017-04-24 05:19:17 +03:00
|
|
|
rootAddrHash = lnBucket.Get(rootKey)
|
2016-08-13 01:29:38 +03:00
|
|
|
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.
|
2017-04-24 05:19:17 +03:00
|
|
|
if err := walletdb.Update(b.db, func(tx walletdb.ReadWriteTx) error {
|
|
|
|
addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey)
|
|
|
|
addrs, err := b.wallet.Manager.NextExternalAddresses(addrmgrNs,
|
|
|
|
defaultAccount, 1, waddrmgr.WitnessPubKey)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
rootAddr := addrs[0].Address()
|
|
|
|
|
|
|
|
lnBucket := tx.ReadWriteBucket(lnNamespace)
|
|
|
|
|
|
|
|
rootAddrHash = rootAddr.ScriptAddress()
|
|
|
|
return lnBucket.Put(rootKey, rootAddrHash)
|
2016-08-13 01:29:38 +03:00
|
|
|
}); 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
|
|
|
|
}
|
2017-04-24 05:19:17 +03:00
|
|
|
|
|
|
|
priv, err := b.wallet.PrivKeyForAddress(rootAddr)
|
2016-08-13 01:29:38 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2017-04-24 05:19:17 +03:00
|
|
|
return priv, nil
|
2016-08-13 01:29:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
2017-11-23 09:29:32 +03:00
|
|
|
func (b *BtcWallet) SendOutputs(outputs []*wire.TxOut,
|
|
|
|
feeSatPerByte btcutil.Amount) (*chainhash.Hash, error) {
|
|
|
|
|
|
|
|
// The fee rate is passed in using units of sat/byte, so we'll scale
|
|
|
|
// this up to sat/KB as the SendOutputs method requires this unit.
|
|
|
|
feeSatPerKB := feeSatPerByte * 1024
|
|
|
|
|
|
|
|
return b.wallet.SendOutputs(outputs, defaultAccount, 1, feeSatPerKB)
|
2016-08-13 01:29:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2017-10-03 04:52:45 +03:00
|
|
|
var addressType lnwallet.AddressType
|
|
|
|
if txscript.IsPayToWitnessPubKeyHash(pkScript) {
|
|
|
|
addressType = lnwallet.WitnessPubKey
|
|
|
|
} else if txscript.IsPayToScriptHash(pkScript) {
|
|
|
|
// TODO(roasbeef): This assumes all p2sh outputs returned by the
|
|
|
|
// wallet are nested p2pkh. We can't check the redeem script because
|
|
|
|
// the btcwallet service does not include it.
|
|
|
|
addressType = lnwallet.NestedWitnessPubKey
|
|
|
|
}
|
|
|
|
|
|
|
|
if addressType == lnwallet.WitnessPubKey ||
|
|
|
|
addressType == lnwallet.NestedWitnessPubKey {
|
|
|
|
|
2017-01-06 00:56:27 +03:00
|
|
|
txid, err := chainhash.NewHashFromStr(output.TxID)
|
2016-08-13 01:29:38 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
utxo := &lnwallet.Utxo{
|
2017-10-03 04:52:45 +03:00
|
|
|
AddressType: addressType,
|
|
|
|
Value: btcutil.Amount(output.Amount * 1e8),
|
|
|
|
PkScript: pkScript,
|
2016-08-13 01:29:38 +03:00
|
|
|
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)
|
|
|
|
}
|
2016-10-15 06:15:50 +03:00
|
|
|
|
|
|
|
// extractBalanceDelta extracts the net balance delta from the PoV of the
|
|
|
|
// wallet given a TransactionSummary.
|
|
|
|
func extractBalanceDelta(txSummary base.TransactionSummary) (btcutil.Amount, error) {
|
2017-01-06 00:56:27 +03:00
|
|
|
tx := wire.NewMsgTx(1)
|
2016-10-15 06:15:50 +03:00
|
|
|
txReader := bytes.NewReader(txSummary.Transaction)
|
|
|
|
if err := tx.Deserialize(txReader); err != nil {
|
|
|
|
return -1, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// For each input we debit the wallet's outflow for this transaction,
|
|
|
|
// and for each output we credit the wallet's inflow for this
|
|
|
|
// transaction.
|
|
|
|
var balanceDelta btcutil.Amount
|
|
|
|
for _, input := range txSummary.MyInputs {
|
|
|
|
balanceDelta -= input.PreviousAmount
|
|
|
|
}
|
|
|
|
for _, output := range txSummary.MyOutputs {
|
|
|
|
balanceDelta += btcutil.Amount(tx.TxOut[output.Index].Value)
|
|
|
|
}
|
|
|
|
|
|
|
|
return balanceDelta, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// minedTransactionsToDetails is a helper function which converts a summary
|
|
|
|
// information about mined transactions to a TransactionDetail.
|
|
|
|
func minedTransactionsToDetails(currentHeight int32,
|
|
|
|
block base.Block) ([]*lnwallet.TransactionDetail, error) {
|
|
|
|
|
|
|
|
details := make([]*lnwallet.TransactionDetail, 0, len(block.Transactions))
|
|
|
|
for _, tx := range block.Transactions {
|
|
|
|
txDetail := &lnwallet.TransactionDetail{
|
|
|
|
Hash: *tx.Hash,
|
|
|
|
NumConfirmations: currentHeight - block.Height + 1,
|
|
|
|
BlockHash: block.Hash,
|
|
|
|
BlockHeight: block.Height,
|
|
|
|
Timestamp: block.Timestamp,
|
|
|
|
TotalFees: int64(tx.Fee),
|
|
|
|
}
|
|
|
|
|
|
|
|
balanceDelta, err := extractBalanceDelta(tx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
txDetail.Value = balanceDelta
|
|
|
|
|
|
|
|
details = append(details, txDetail)
|
|
|
|
}
|
|
|
|
|
|
|
|
return details, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// unminedTransactionsToDetail is a helper funciton which converts a summary
|
|
|
|
// for a unconfirmed transaction to a transaction detail.
|
|
|
|
func unminedTransactionsToDetail(summary base.TransactionSummary) (*lnwallet.TransactionDetail, error) {
|
|
|
|
txDetail := &lnwallet.TransactionDetail{
|
|
|
|
Hash: *summary.Hash,
|
|
|
|
TotalFees: int64(summary.Fee),
|
|
|
|
Timestamp: summary.Timestamp,
|
|
|
|
}
|
|
|
|
|
|
|
|
balanceDelta, err := extractBalanceDelta(summary)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
txDetail.Value = balanceDelta
|
|
|
|
|
|
|
|
return txDetail, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ListTransactionDetails returns a list of all transactions which are
|
|
|
|
// relevant to the wallet.
|
|
|
|
//
|
|
|
|
// This is a part of the WalletController interface.
|
|
|
|
func (b *BtcWallet) ListTransactionDetails() ([]*lnwallet.TransactionDetail, error) {
|
|
|
|
// Grab the best block the wallet knows of, we'll use this to calculate
|
|
|
|
// # of confirmations shortly below.
|
|
|
|
bestBlock := b.wallet.Manager.SyncedTo()
|
|
|
|
currentHeight := bestBlock.Height
|
|
|
|
|
|
|
|
// TODO(roasbeef): can replace with start "wallet birthday"
|
|
|
|
start := base.NewBlockIdentifierFromHeight(0)
|
|
|
|
stop := base.NewBlockIdentifierFromHeight(bestBlock.Height)
|
|
|
|
txns, err := b.wallet.GetTransactions(start, stop, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
txDetails := make([]*lnwallet.TransactionDetail, 0,
|
|
|
|
len(txns.MinedTransactions)+len(txns.UnminedTransactions))
|
|
|
|
|
2017-06-08 03:01:17 +03:00
|
|
|
// For both confirmed and unconfirmed transactions, create a
|
2016-10-15 06:15:50 +03:00
|
|
|
// TransactionDetail which re-packages the data returned by the base
|
|
|
|
// wallet.
|
|
|
|
for _, blockPackage := range txns.MinedTransactions {
|
|
|
|
details, err := minedTransactionsToDetails(currentHeight, blockPackage)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
txDetails = append(txDetails, details...)
|
|
|
|
}
|
|
|
|
for _, tx := range txns.UnminedTransactions {
|
|
|
|
detail, err := unminedTransactionsToDetail(tx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
txDetails = append(txDetails, detail)
|
|
|
|
}
|
|
|
|
|
|
|
|
return txDetails, nil
|
|
|
|
}
|
2016-10-16 00:11:18 +03:00
|
|
|
|
|
|
|
// txSubscriptionClient encapsulates the transaction notification client from
|
|
|
|
// the base wallet. Notifications received from the client will be proxied over
|
|
|
|
// two distinct channels.
|
|
|
|
type txSubscriptionClient struct {
|
|
|
|
txClient base.TransactionNotificationsClient
|
|
|
|
|
|
|
|
confirmed chan *lnwallet.TransactionDetail
|
|
|
|
unconfirmed chan *lnwallet.TransactionDetail
|
|
|
|
|
|
|
|
w *base.Wallet
|
|
|
|
|
|
|
|
wg sync.WaitGroup
|
|
|
|
quit chan struct{}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ConfirmedTransactions returns a channel which will be sent on as new
|
|
|
|
// relevant transactions are confirmed.
|
|
|
|
//
|
|
|
|
// This is part of the TransactionSubscription interface.
|
|
|
|
func (t *txSubscriptionClient) ConfirmedTransactions() chan *lnwallet.TransactionDetail {
|
|
|
|
return t.confirmed
|
|
|
|
}
|
|
|
|
|
|
|
|
// UnconfirmedTransactions returns a channel which will be sent on as
|
|
|
|
// new relevant transactions are seen within the network.
|
|
|
|
//
|
|
|
|
// This is part of the TransactionSubscription interface.
|
|
|
|
func (t *txSubscriptionClient) UnconfirmedTransactions() chan *lnwallet.TransactionDetail {
|
|
|
|
return t.unconfirmed
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cancel finalizes the subscription, cleaning up any resources allocated.
|
|
|
|
//
|
|
|
|
// This is part of the TransactionSubscription interface.
|
|
|
|
func (t *txSubscriptionClient) Cancel() {
|
|
|
|
close(t.quit)
|
|
|
|
t.wg.Wait()
|
|
|
|
|
|
|
|
t.txClient.Done()
|
|
|
|
}
|
|
|
|
|
|
|
|
// notificationProxier proxies the notifications received by the underlying
|
|
|
|
// wallet's notification client to a higher-level TransactionSubscription
|
|
|
|
// client.
|
|
|
|
func (t *txSubscriptionClient) notificationProxier() {
|
|
|
|
out:
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case txNtfn := <-t.txClient.C:
|
|
|
|
// TODO(roasbeef): handle detached blocks
|
|
|
|
currentHeight := t.w.Manager.SyncedTo().Height
|
|
|
|
|
|
|
|
// Launch a goroutine to re-package and send
|
|
|
|
// notifications for any newly confirmed transactions.
|
|
|
|
go func() {
|
|
|
|
for _, block := range txNtfn.AttachedBlocks {
|
|
|
|
details, err := minedTransactionsToDetails(currentHeight, block)
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, d := range details {
|
|
|
|
select {
|
|
|
|
case t.confirmed <- d:
|
|
|
|
case <-t.quit:
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
// Launch a goroutine to re-package and send
|
|
|
|
// notifications for any newly unconfirmed transactions.
|
|
|
|
go func() {
|
|
|
|
for _, tx := range txNtfn.UnminedTransactions {
|
|
|
|
detail, err := unminedTransactionsToDetail(tx)
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
select {
|
|
|
|
case t.unconfirmed <- detail:
|
|
|
|
case <-t.quit:
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
case <-t.quit:
|
|
|
|
break out
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
t.wg.Done()
|
|
|
|
}
|
|
|
|
|
|
|
|
// SubscribeTransactions returns a TransactionSubscription client which
|
|
|
|
// is capable of receiving async notifications as new transactions
|
|
|
|
// related to the wallet are seen within the network, or found in
|
|
|
|
// blocks.
|
|
|
|
//
|
|
|
|
// This is a part of the WalletController interface.
|
|
|
|
func (b *BtcWallet) SubscribeTransactions() (lnwallet.TransactionSubscription, error) {
|
|
|
|
walletClient := b.wallet.NtfnServer.TransactionNotifications()
|
|
|
|
|
|
|
|
txClient := &txSubscriptionClient{
|
|
|
|
txClient: walletClient,
|
|
|
|
confirmed: make(chan *lnwallet.TransactionDetail),
|
|
|
|
unconfirmed: make(chan *lnwallet.TransactionDetail),
|
|
|
|
w: b.wallet,
|
|
|
|
quit: make(chan struct{}),
|
|
|
|
}
|
|
|
|
txClient.wg.Add(1)
|
|
|
|
go txClient.notificationProxier()
|
|
|
|
|
|
|
|
return txClient, nil
|
|
|
|
}
|
2016-12-09 05:29:55 +03:00
|
|
|
|
|
|
|
// IsSynced returns a boolean indicating if from the PoV of the wallet,
|
|
|
|
// it has fully synced to the current best block in the main chain.
|
|
|
|
//
|
|
|
|
// This is a part of the WalletController interface.
|
|
|
|
func (b *BtcWallet) IsSynced() (bool, error) {
|
|
|
|
// Grab the best chain state the wallet is currently aware of.
|
|
|
|
syncState := b.wallet.Manager.SyncedTo()
|
|
|
|
|
2017-05-25 03:34:31 +03:00
|
|
|
var (
|
|
|
|
bestHash *chainhash.Hash
|
|
|
|
bestHeight int32
|
|
|
|
err error
|
|
|
|
)
|
|
|
|
|
|
|
|
// Next, query the chain backend to grab the info about the tip of the
|
|
|
|
// main chain.
|
|
|
|
switch backend := b.cfg.ChainSource.(type) {
|
2017-06-05 07:06:52 +03:00
|
|
|
case *chain.NeutrinoClient:
|
|
|
|
header, height, err := backend.CS.BlockHeaders.ChainTip()
|
2017-05-25 03:34:31 +03:00
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
bh := header.BlockHash()
|
|
|
|
bestHash = &bh
|
|
|
|
bestHeight = int32(height)
|
|
|
|
|
|
|
|
case *chain.RPCClient:
|
|
|
|
bestHash, bestHeight, err = backend.GetBestBlock()
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
2016-12-09 05:29:55 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// If the wallet hasn't yet fully synced to the node's best chain tip,
|
|
|
|
// then we're not yet fully synced.
|
|
|
|
if syncState.Height < bestHeight {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the wallet is on par with the current best chain tip, then we
|
2017-05-25 03:34:31 +03:00
|
|
|
// still may not yet be synced as the chain backend may still be
|
|
|
|
// catching up to the main chain. So we'll grab the block header in
|
|
|
|
// order to make a guess based on the current time stamp.
|
|
|
|
var blockHeader *wire.BlockHeader
|
|
|
|
switch backend := b.cfg.ChainSource.(type) {
|
|
|
|
|
2017-06-05 07:06:52 +03:00
|
|
|
case *chain.NeutrinoClient:
|
|
|
|
bh, _, err := backend.CS.BlockHeaders.FetchHeader(bestHash)
|
2017-05-25 03:34:31 +03:00
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
2017-06-05 07:06:52 +03:00
|
|
|
blockHeader = bh
|
2017-05-25 03:34:31 +03:00
|
|
|
|
|
|
|
case *chain.RPCClient:
|
|
|
|
blockHeader, err = backend.GetBlockHeader(bestHash)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
2016-12-09 05:29:55 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// If the timestamp no the best header is more than 2 hours in the
|
|
|
|
// past, then we're not yet synced.
|
|
|
|
minus24Hours := time.Now().Add(-2 * time.Hour)
|
|
|
|
return !blockHeader.Timestamp.Before(minus24Hours), nil
|
|
|
|
}
|