386 lines
10 KiB
Go
386 lines
10 KiB
Go
package lnd
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"fmt"
|
|
"sync"
|
|
"sync/atomic"
|
|
|
|
"github.com/btcsuite/btcd/btcec"
|
|
"github.com/btcsuite/btcd/chaincfg"
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
"github.com/btcsuite/btcd/txscript"
|
|
"github.com/btcsuite/btcd/wire"
|
|
"github.com/btcsuite/btcutil"
|
|
"github.com/btcsuite/btcwallet/wallet/txauthor"
|
|
|
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
|
"github.com/lightningnetwork/lnd/input"
|
|
"github.com/lightningnetwork/lnd/keychain"
|
|
"github.com/lightningnetwork/lnd/lnwallet"
|
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
|
)
|
|
|
|
var (
|
|
coinPkScript, _ = hex.DecodeString("001431df1bde03c074d0cf21ea2529427e1499b8f1de")
|
|
)
|
|
|
|
// 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 *input.SignDescriptor) (input.Signature, error) {
|
|
amt := signDesc.Output.Value
|
|
witnessScript := signDesc.WitnessScript
|
|
privKey := m.key
|
|
|
|
if !privKey.PubKey().IsEqual(signDesc.KeyDesc.PubKey) {
|
|
return nil, fmt.Errorf("incorrect key passed")
|
|
}
|
|
|
|
switch {
|
|
case signDesc.SingleTweak != nil:
|
|
privKey = input.TweakPrivKey(privKey,
|
|
signDesc.SingleTweak)
|
|
case signDesc.DoubleTweak != nil:
|
|
privKey = input.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 btcec.ParseDERSignature(sig[:len(sig)-1], btcec.S256())
|
|
}
|
|
|
|
func (m *mockSigner) ComputeInputScript(tx *wire.MsgTx,
|
|
signDesc *input.SignDescriptor) (*input.Script, 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 = input.TweakPrivKey(privKey,
|
|
signDesc.SingleTweak)
|
|
case signDesc.DoubleTweak != nil:
|
|
privKey = input.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 &input.Script{
|
|
Witness: witnessScript,
|
|
}, nil
|
|
}
|
|
|
|
type mockNotfier struct {
|
|
confChannel chan *chainntnfs.TxConfirmation
|
|
}
|
|
|
|
func (m *mockNotfier) RegisterConfirmationsNtfn(txid *chainhash.Hash,
|
|
_ []byte, numConfs, heightHint uint32) (*chainntnfs.ConfirmationEvent, error) {
|
|
return &chainntnfs.ConfirmationEvent{
|
|
Confirmed: m.confChannel,
|
|
}, nil
|
|
}
|
|
func (m *mockNotfier) RegisterBlockEpochNtfn(
|
|
bestBlock *chainntnfs.BlockEpoch) (*chainntnfs.BlockEpochEvent, error) {
|
|
return &chainntnfs.BlockEpochEvent{
|
|
Epochs: make(chan *chainntnfs.BlockEpoch),
|
|
Cancel: func() {},
|
|
}, nil
|
|
}
|
|
|
|
func (m *mockNotfier) Start() error {
|
|
return nil
|
|
}
|
|
|
|
func (m *mockNotfier) Started() bool {
|
|
return true
|
|
}
|
|
|
|
func (m *mockNotfier) Stop() error {
|
|
return nil
|
|
}
|
|
func (m *mockNotfier) RegisterSpendNtfn(outpoint *wire.OutPoint, _ []byte,
|
|
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
|
|
spends map[wire.OutPoint]*chainntnfs.SpendDetail
|
|
mtx sync.Mutex
|
|
}
|
|
|
|
func makeMockSpendNotifier() *mockSpendNotifier {
|
|
return &mockSpendNotifier{
|
|
mockNotfier: &mockNotfier{
|
|
confChannel: make(chan *chainntnfs.TxConfirmation),
|
|
},
|
|
spendMap: make(map[wire.OutPoint][]chan *chainntnfs.SpendDetail),
|
|
spends: make(map[wire.OutPoint]*chainntnfs.SpendDetail),
|
|
}
|
|
}
|
|
|
|
func (m *mockSpendNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
|
|
_ []byte, heightHint uint32) (*chainntnfs.SpendEvent, error) {
|
|
m.mtx.Lock()
|
|
defer m.mtx.Unlock()
|
|
|
|
spendChan := make(chan *chainntnfs.SpendDetail, 1)
|
|
if detail, ok := m.spends[*outpoint]; ok {
|
|
// Deliver spend immediately if details are already known.
|
|
spendChan <- &chainntnfs.SpendDetail{
|
|
SpentOutPoint: detail.SpentOutPoint,
|
|
SpendingHeight: detail.SpendingHeight,
|
|
SpendingTx: detail.SpendingTx,
|
|
SpenderTxHash: detail.SpenderTxHash,
|
|
SpenderInputIndex: detail.SpenderInputIndex,
|
|
}
|
|
} else {
|
|
// Otherwise, queue the notification for delivery if the spend
|
|
// is ever received.
|
|
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) {
|
|
m.mtx.Lock()
|
|
defer m.mtx.Unlock()
|
|
|
|
txnHash := txn.TxHash()
|
|
details := &chainntnfs.SpendDetail{
|
|
SpentOutPoint: outpoint,
|
|
SpendingHeight: height,
|
|
SpendingTx: txn,
|
|
SpenderTxHash: &txnHash,
|
|
SpenderInputIndex: outpoint.Index,
|
|
}
|
|
|
|
// Cache details in case of late registration.
|
|
if _, ok := m.spends[*outpoint]; !ok {
|
|
m.spends[*outpoint] = details
|
|
}
|
|
|
|
// Deliver any backlogged spend notifications.
|
|
if spendChans, ok := m.spendMap[*outpoint]; ok {
|
|
delete(m.spendMap, *outpoint)
|
|
for _, spendChan := range spendChans {
|
|
spendChan <- &chainntnfs.SpendDetail{
|
|
SpentOutPoint: details.SpentOutPoint,
|
|
SpendingHeight: details.SpendingHeight,
|
|
SpendingTx: details.SpendingTx,
|
|
SpenderTxHash: details.SpenderTxHash,
|
|
SpenderInputIndex: details.SpenderInputIndex,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
type mockChainIO struct {
|
|
bestHeight int32
|
|
}
|
|
|
|
var _ lnwallet.BlockChainIO = (*mockChainIO)(nil)
|
|
|
|
func (m *mockChainIO) GetBestBlock() (*chainhash.Hash, int32, error) {
|
|
return activeNetParams.GenesisHash, m.bestHeight, nil
|
|
}
|
|
|
|
func (*mockChainIO) GetUtxo(op *wire.OutPoint, _ []byte,
|
|
heightHint uint32, _ <-chan struct{}) (*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
|
|
publishedTransactions chan *wire.MsgTx
|
|
index uint32
|
|
utxos []*lnwallet.Utxo
|
|
}
|
|
|
|
// 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) (*lnwallet.Utxo, error) {
|
|
utxo := &lnwallet.Utxo{
|
|
AddressType: lnwallet.WitnessPubKey,
|
|
Value: 10 * btcutil.SatoshiPerBitcoin,
|
|
PkScript: []byte("dummy"),
|
|
Confirmations: 1,
|
|
OutPoint: *prevOut,
|
|
}
|
|
return utxo, nil
|
|
}
|
|
func (*mockWalletController) ConfirmedBalance(confs int32) (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) LastUnusedAddress(addrType lnwallet.AddressType) (
|
|
btcutil.Address, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (*mockWalletController) IsOurAddress(a btcutil.Address) bool {
|
|
return false
|
|
}
|
|
|
|
func (*mockWalletController) SendOutputs(outputs []*wire.TxOut,
|
|
_ chainfee.SatPerKWeight, _ string) (*wire.MsgTx, error) {
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func (*mockWalletController) CreateSimpleTx(outputs []*wire.TxOut,
|
|
_ chainfee.SatPerKWeight, _ bool) (*txauthor.AuthoredTx, error) {
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
// ListUnspentWitness is called by the wallet when doing coin selection. We just
|
|
// need one unspent for the funding transaction.
|
|
func (m *mockWalletController) ListUnspentWitness(minconfirms,
|
|
maxconfirms int32) ([]*lnwallet.Utxo, error) {
|
|
|
|
// If the mock already has a list of utxos, return it.
|
|
if m.utxos != nil {
|
|
return m.utxos, nil
|
|
}
|
|
|
|
// Otherwise create one to return.
|
|
utxo := &lnwallet.Utxo{
|
|
AddressType: lnwallet.WitnessPubKey,
|
|
Value: btcutil.Amount(10 * btcutil.SatoshiPerBitcoin),
|
|
PkScript: coinPkScript,
|
|
OutPoint: wire.OutPoint{
|
|
Hash: chainhash.Hash{},
|
|
Index: m.index,
|
|
},
|
|
}
|
|
atomic.AddUint32(&m.index, 1)
|
|
var ret []*lnwallet.Utxo
|
|
ret = append(ret, utxo)
|
|
return ret, nil
|
|
}
|
|
func (*mockWalletController) ListTransactionDetails(_, _ int32) ([]*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, _ string) 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 mockSecretKeyRing struct {
|
|
rootKey *btcec.PrivateKey
|
|
}
|
|
|
|
func (m *mockSecretKeyRing) DeriveNextKey(keyFam keychain.KeyFamily) (keychain.KeyDescriptor, error) {
|
|
return keychain.KeyDescriptor{
|
|
PubKey: m.rootKey.PubKey(),
|
|
}, nil
|
|
}
|
|
|
|
func (m *mockSecretKeyRing) DeriveKey(keyLoc keychain.KeyLocator) (keychain.KeyDescriptor, error) {
|
|
return keychain.KeyDescriptor{
|
|
PubKey: m.rootKey.PubKey(),
|
|
}, nil
|
|
}
|
|
|
|
func (m *mockSecretKeyRing) DerivePrivKey(keyDesc keychain.KeyDescriptor) (*btcec.PrivateKey, error) {
|
|
return m.rootKey, nil
|
|
}
|
|
|
|
func (m *mockSecretKeyRing) ScalarMult(keyDesc keychain.KeyDescriptor,
|
|
pubKey *btcec.PublicKey) ([]byte, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (m *mockSecretKeyRing) ECDH(_ keychain.KeyDescriptor,
|
|
pubKey *btcec.PublicKey) ([32]byte, error) {
|
|
|
|
return [32]byte{}, nil
|
|
}
|
|
|
|
func (m *mockSecretKeyRing) SignDigest(_ keychain.KeyDescriptor,
|
|
digest [32]byte) (*btcec.Signature, error) {
|
|
|
|
return m.rootKey.Sign(digest[:])
|
|
}
|
|
|
|
func (m *mockSecretKeyRing) SignDigestCompact(_ keychain.KeyDescriptor,
|
|
digest [32]byte) ([]byte, error) {
|
|
|
|
return btcec.SignCompact(btcec.S256(), m.rootKey, digest[:], true)
|
|
}
|
|
|
|
var _ keychain.SecretKeyRing = (*mockSecretKeyRing)(nil)
|