84551c616f
This commit adds wallet_best_block_timestamp to the gRPC interface. This is done in order to allow clients to calculate progress while lnd syncs to the blockchain. wallet_best_block_timestamp is exposed via the GetInfo() rpc call. Additionally, IsSynced() returns the WalletBestBlockTimestamp as the second value in the tuple that is returned, providing additional detail when querying about the status of the sync. The BtcWallet interface has also been updated accordingly. This commit was created to support the issue to [Add progress bar for chain sync] (lightninglabs/lightning-app#10) in lightning-app
299 lines
8.0 KiB
Go
299 lines
8.0 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"fmt"
|
|
"sync"
|
|
|
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
|
"github.com/lightningnetwork/lnd/lnwallet"
|
|
"github.com/roasbeef/btcd/btcec"
|
|
"github.com/roasbeef/btcd/chaincfg"
|
|
"github.com/roasbeef/btcd/chaincfg/chainhash"
|
|
"github.com/roasbeef/btcd/txscript"
|
|
"github.com/roasbeef/btcd/wire"
|
|
"github.com/roasbeef/btcutil"
|
|
)
|
|
|
|
// The block height returned by the mock BlockChainIO's GetBestBlock.
|
|
const fundingBroadcastHeight = 123
|
|
|
|
type mockSigner struct {
|
|
key *btcec.PrivateKey
|
|
}
|
|
|
|
func (m *mockSigner) SignOutputRaw(tx *wire.MsgTx,
|
|
signDesc *lnwallet.SignDescriptor) ([]byte, error) {
|
|
amt := signDesc.Output.Value
|
|
witnessScript := signDesc.WitnessScript
|
|
privKey := m.key
|
|
|
|
if !privKey.PubKey().IsEqual(signDesc.PubKey) {
|
|
return nil, fmt.Errorf("incorrect key passed")
|
|
}
|
|
|
|
switch {
|
|
case signDesc.SingleTweak != nil:
|
|
privKey = lnwallet.TweakPrivKey(privKey,
|
|
signDesc.SingleTweak)
|
|
case signDesc.DoubleTweak != nil:
|
|
privKey = lnwallet.DeriveRevocationPrivKey(privKey,
|
|
signDesc.DoubleTweak)
|
|
}
|
|
|
|
sig, err := txscript.RawTxInWitnessSignature(tx, signDesc.SigHashes,
|
|
signDesc.InputIndex, amt, witnessScript, signDesc.HashType,
|
|
privKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return sig[:len(sig)-1], nil
|
|
}
|
|
|
|
func (m *mockSigner) ComputeInputScript(tx *wire.MsgTx,
|
|
signDesc *lnwallet.SignDescriptor) (*lnwallet.InputScript, error) {
|
|
|
|
// TODO(roasbeef): expose tweaked signer from lnwallet so don't need to
|
|
// duplicate this code?
|
|
|
|
privKey := m.key
|
|
|
|
switch {
|
|
case signDesc.SingleTweak != nil:
|
|
privKey = lnwallet.TweakPrivKey(privKey,
|
|
signDesc.SingleTweak)
|
|
case signDesc.DoubleTweak != nil:
|
|
privKey = lnwallet.DeriveRevocationPrivKey(privKey,
|
|
signDesc.DoubleTweak)
|
|
}
|
|
|
|
witnessScript, err := txscript.WitnessSignature(tx, signDesc.SigHashes,
|
|
signDesc.InputIndex, signDesc.Output.Value, signDesc.Output.PkScript,
|
|
signDesc.HashType, privKey, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &lnwallet.InputScript{
|
|
Witness: witnessScript,
|
|
}, nil
|
|
}
|
|
|
|
type mockNotfier struct {
|
|
confChannel chan *chainntnfs.TxConfirmation
|
|
}
|
|
|
|
func (m *mockNotfier) RegisterConfirmationsNtfn(txid *chainhash.Hash, numConfs,
|
|
heightHint uint32) (*chainntnfs.ConfirmationEvent, error) {
|
|
return &chainntnfs.ConfirmationEvent{
|
|
Confirmed: m.confChannel,
|
|
}, nil
|
|
}
|
|
func (m *mockNotfier) RegisterBlockEpochNtfn() (*chainntnfs.BlockEpochEvent, error) {
|
|
return &chainntnfs.BlockEpochEvent{
|
|
Epochs: make(chan *chainntnfs.BlockEpoch),
|
|
Cancel: func() {},
|
|
}, nil
|
|
}
|
|
|
|
func (m *mockNotfier) Start() error {
|
|
return nil
|
|
}
|
|
|
|
func (m *mockNotfier) Stop() error {
|
|
return nil
|
|
}
|
|
func (m *mockNotfier) RegisterSpendNtfn(outpoint *wire.OutPoint,
|
|
heightHint uint32) (*chainntnfs.SpendEvent, error) {
|
|
return &chainntnfs.SpendEvent{
|
|
Spend: make(chan *chainntnfs.SpendDetail),
|
|
Cancel: func() {},
|
|
}, nil
|
|
}
|
|
|
|
// mockSpendNotifier extends the mockNotifier so that spend notifications can be
|
|
// triggered and delivered to subscribers.
|
|
type mockSpendNotifier struct {
|
|
*mockNotfier
|
|
spendMap map[wire.OutPoint][]chan *chainntnfs.SpendDetail
|
|
}
|
|
|
|
func makeMockSpendNotifier() *mockSpendNotifier {
|
|
return &mockSpendNotifier{
|
|
mockNotfier: &mockNotfier{
|
|
confChannel: make(chan *chainntnfs.TxConfirmation),
|
|
},
|
|
spendMap: make(map[wire.OutPoint][]chan *chainntnfs.SpendDetail),
|
|
}
|
|
}
|
|
|
|
func (m *mockSpendNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
|
|
heightHint uint32) (*chainntnfs.SpendEvent, error) {
|
|
|
|
spendChan := make(chan *chainntnfs.SpendDetail)
|
|
m.spendMap[*outpoint] = append(m.spendMap[*outpoint], spendChan)
|
|
return &chainntnfs.SpendEvent{
|
|
Spend: spendChan,
|
|
Cancel: func() {
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// Spend dispatches SpendDetails to all subscribers of the outpoint. The details
|
|
// will include the transaction and height provided by the caller.
|
|
func (m *mockSpendNotifier) Spend(outpoint *wire.OutPoint, height int32,
|
|
txn *wire.MsgTx) {
|
|
|
|
if spendChans, ok := m.spendMap[*outpoint]; ok {
|
|
delete(m.spendMap, *outpoint)
|
|
for _, spendChan := range spendChans {
|
|
txnHash := txn.TxHash()
|
|
spendChan <- &chainntnfs.SpendDetail{
|
|
SpentOutPoint: outpoint,
|
|
SpendingHeight: height,
|
|
SpendingTx: txn,
|
|
SpenderTxHash: &txnHash,
|
|
SpenderInputIndex: outpoint.Index,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
type mockChainIO struct{}
|
|
|
|
func (*mockChainIO) GetBestBlock() (*chainhash.Hash, int32, error) {
|
|
return activeNetParams.GenesisHash, fundingBroadcastHeight, nil
|
|
}
|
|
|
|
func (*mockChainIO) GetUtxo(op *wire.OutPoint,
|
|
heightHint uint32) (*wire.TxOut, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (*mockChainIO) GetBlockHash(blockHeight int64) (*chainhash.Hash, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (*mockChainIO) GetBlock(blockHash *chainhash.Hash) (*wire.MsgBlock, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
// mockWalletController is used by the LightningWallet, and let us mock the
|
|
// interaction with the bitcoin network.
|
|
type mockWalletController struct {
|
|
rootKey *btcec.PrivateKey
|
|
prevAddres btcutil.Address
|
|
publishedTransactions chan *wire.MsgTx
|
|
}
|
|
|
|
// BackEnd returns "mock" to signify a mock wallet controller.
|
|
func (*mockWalletController) BackEnd() string {
|
|
return "mock"
|
|
}
|
|
|
|
// FetchInputInfo will be called to get info about the inputs to the funding
|
|
// transaction.
|
|
func (*mockWalletController) FetchInputInfo(
|
|
prevOut *wire.OutPoint) (*wire.TxOut, error) {
|
|
txOut := &wire.TxOut{
|
|
Value: int64(10 * btcutil.SatoshiPerBitcoin),
|
|
PkScript: []byte("dummy"),
|
|
}
|
|
return txOut, nil
|
|
}
|
|
func (*mockWalletController) ConfirmedBalance(confs int32,
|
|
witness bool) (btcutil.Amount, error) {
|
|
return 0, nil
|
|
}
|
|
|
|
// NewAddress is called to get new addresses for delivery, change etc.
|
|
func (m *mockWalletController) NewAddress(addrType lnwallet.AddressType,
|
|
change bool) (btcutil.Address, error) {
|
|
addr, _ := btcutil.NewAddressPubKey(
|
|
m.rootKey.PubKey().SerializeCompressed(), &chaincfg.MainNetParams)
|
|
return addr, nil
|
|
}
|
|
func (*mockWalletController) GetPrivKey(a btcutil.Address) (*btcec.PrivateKey, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
// NewRawKey will be called to get keys to be used for the funding tx and the
|
|
// commitment tx.
|
|
func (m *mockWalletController) NewRawKey() (*btcec.PublicKey, error) {
|
|
return m.rootKey.PubKey(), nil
|
|
}
|
|
|
|
// FetchRootKey will be called to provide the wallet with a root key.
|
|
func (m *mockWalletController) FetchRootKey() (*btcec.PrivateKey, error) {
|
|
return m.rootKey, nil
|
|
}
|
|
func (*mockWalletController) SendOutputs(outputs []*wire.TxOut,
|
|
_ btcutil.Amount) (*chainhash.Hash, error) {
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
// ListUnspentWitness is called by the wallet when doing coin selection. We just
|
|
// need one unspent for the funding transaction.
|
|
func (*mockWalletController) ListUnspentWitness(confirms int32) ([]*lnwallet.Utxo, error) {
|
|
utxo := &lnwallet.Utxo{
|
|
AddressType: lnwallet.WitnessPubKey,
|
|
Value: btcutil.Amount(10 * btcutil.SatoshiPerBitcoin),
|
|
PkScript: make([]byte, 22),
|
|
OutPoint: wire.OutPoint{
|
|
Hash: chainhash.Hash{},
|
|
Index: 0,
|
|
},
|
|
}
|
|
var ret []*lnwallet.Utxo
|
|
ret = append(ret, utxo)
|
|
return ret, nil
|
|
}
|
|
func (*mockWalletController) ListTransactionDetails() ([]*lnwallet.TransactionDetail, error) {
|
|
return nil, nil
|
|
}
|
|
func (*mockWalletController) LockOutpoint(o wire.OutPoint) {}
|
|
func (*mockWalletController) UnlockOutpoint(o wire.OutPoint) {}
|
|
func (m *mockWalletController) PublishTransaction(tx *wire.MsgTx) error {
|
|
m.publishedTransactions <- tx
|
|
return nil
|
|
}
|
|
func (*mockWalletController) SubscribeTransactions() (lnwallet.TransactionSubscription, error) {
|
|
return nil, nil
|
|
}
|
|
func (*mockWalletController) IsSynced() (bool, int64, error) {
|
|
return true, int64(0), nil
|
|
}
|
|
func (*mockWalletController) Start() error {
|
|
return nil
|
|
}
|
|
func (*mockWalletController) Stop() error {
|
|
return nil
|
|
}
|
|
|
|
type mockPreimageCache struct {
|
|
sync.Mutex
|
|
preimageMap map[[32]byte][]byte
|
|
}
|
|
|
|
func (m *mockPreimageCache) LookupPreimage(hash []byte) ([]byte, bool) {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
var h [32]byte
|
|
copy(h[:], hash)
|
|
|
|
p, ok := m.preimageMap[h]
|
|
return p, ok
|
|
}
|
|
|
|
func (m *mockPreimageCache) AddPreimage(preimage []byte) error {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
m.preimageMap[sha256.Sum256(preimage[:])] = preimage
|
|
|
|
return nil
|
|
}
|