lnwallet: refactor all wallet/channel interaction to use the WalletController.

This commit performs a major refactor of the current wallet,
reservation, and channel code in order to call into a WalletController
implementation rather than directly into btcwallet.

The current set of wallets tests have been modified in order to test
against *all* registered WalletController implementations rather than
only btcwallet. As a result, all future WalletControllers primary need
to ensure that their implementation passes the current set of tests
(which will be expanded into the future), providing an easy path of
integration assurance.

Rather than directly holding the private keys throughout funding and
channel creation, the burden of securing keys has been shifted to the
specified WalletController and Signer interfaces. All signing is done
via the Signer interface rather than directly, increasing flexibility
dramatically.

During channel funding, rather than creating a txscript.Engine to
verify commitment signatures, regular ECDSA sig verification is now
used instead. This is faster and more efficient.

Finally certain fields/methods within ChannelReservation and
LightningChannel have been exposed publicly in order to restrict the
amount of modifications the prior tests needed to undergo in order to
support testing directly agains the WalletController interface.
This commit is contained in:
Olaoluwa Osuntokun 2016-08-12 15:50:47 -07:00
parent 99fdb3a3a9
commit 671098325d
No known key found for this signature in database
GPG Key ID: 9CC5B105D03521A2
5 changed files with 538 additions and 575 deletions

@ -274,6 +274,9 @@ type LightningChannel struct {
// wallet can add back.
lnwallet *LightningWallet
signer Signer
signDesc *SignDescriptor
channelEvents chainntnfs.ChainNotifier
sync.RWMutex
@ -281,7 +284,8 @@ type LightningChannel struct {
ourLogCounter uint32
theirLogCounter uint32
status channelState
status channelState
Capacity btcutil.Amount
// currentHeight is the current height of our local commitment chain.
// This is also the same as the number of updates to the channel we've
@ -337,10 +341,12 @@ type LightningChannel struct {
ourLogIndex map[uint32]*list.Element
theirLogIndex map[uint32]*list.Element
fundingTxIn *wire.TxIn
fundingP2WSH []byte
LocalDeliveryScript []byte
RemoteDeliveryScript []byte
channelDB *channeldb.DB
FundingRedeemScript []byte
fundingTxIn *wire.TxIn
fundingP2WSH []byte
started int32
shutdown int32
@ -354,11 +360,13 @@ type LightningChannel struct {
// and the current settled channel state. Throughout state transitions, then
// channel will automatically persist pertinent state to the database in an
// efficient manner.
func NewLightningChannel(wallet *LightningWallet, events chainntnfs.ChainNotifier,
chanDB *channeldb.DB, state *channeldb.OpenChannel) (*LightningChannel, error) {
func NewLightningChannel(signer Signer, wallet *LightningWallet,
events chainntnfs.ChainNotifier,
state *channeldb.OpenChannel) (*LightningChannel, error) {
// TODO(roasbeef): remove events+wallet
lc := &LightningChannel{
signer: signer,
lnwallet: wallet,
channelEvents: events,
currentHeight: state.NumUpdates,
@ -370,7 +378,10 @@ func NewLightningChannel(wallet *LightningWallet, events chainntnfs.ChainNotifie
theirUpdateLog: list.New(),
ourLogIndex: make(map[uint32]*list.Element),
theirLogIndex: make(map[uint32]*list.Element),
channelDB: chanDB,
Capacity: state.Capacity,
LocalDeliveryScript: state.OurDeliveryScript,
RemoteDeliveryScript: state.TheirDeliveryScript,
FundingRedeemScript: state.FundingRedeemScript,
}
// Initialize both of our chains the current un-revoked commitment for
@ -395,6 +406,17 @@ func NewLightningChannel(wallet *LightningWallet, events chainntnfs.ChainNotifie
lc.fundingTxIn = wire.NewTxIn(state.FundingOutpoint, nil, nil)
lc.fundingP2WSH = fundingPkScript
lc.signDesc = &SignDescriptor{
PubKey: lc.channelState.OurMultiSigKey,
RedeemScript: lc.channelState.FundingRedeemScript,
Output: &wire.TxOut{
PkScript: lc.fundingP2WSH,
Value: int64(lc.channelState.Capacity),
},
HashType: txscript.SigHashAll,
InputIndex: 0,
}
return lc, nil
}
@ -480,12 +502,12 @@ func (lc *LightningChannel) fetchCommitmentView(remoteChain bool,
var delayBalance, p2wkhBalance btcutil.Amount
if remoteChain {
selfKey = lc.channelState.TheirCommitKey
remoteKey = lc.channelState.OurCommitKey.PubKey()
remoteKey = lc.channelState.OurCommitKey
delay = lc.channelState.RemoteCsvDelay
delayBalance = theirBalance
p2wkhBalance = ourBalance
} else {
selfKey = lc.channelState.OurCommitKey.PubKey()
selfKey = lc.channelState.OurCommitKey
remoteKey = lc.channelState.TheirCommitKey
delay = lc.channelState.LocalCsvDelay
delayBalance = ourBalance
@ -495,7 +517,7 @@ func (lc *LightningChannel) fetchCommitmentView(remoteChain bool,
// Generate a new commitment transaction with all the latest
// unsettled/un-timed out HTLC's.
ourCommitTx := !remoteChain
commitTx, err := createCommitTx(lc.fundingTxIn, selfKey, remoteKey,
commitTx, err := CreateCommitTx(lc.fundingTxIn, selfKey, remoteKey,
revocationKey, delay, delayBalance, p2wkhBalance)
if err != nil {
return nil, err
@ -722,11 +744,8 @@ func (lc *LightningChannel) SignNextCommitment() ([]byte, uint32, error) {
}))
// Sign their version of the new commitment transaction.
hashCache := txscript.NewTxSigHashes(newCommitView.txn)
sig, err := txscript.RawTxInWitnessSignature(newCommitView.txn,
hashCache, 0, int64(lc.channelState.Capacity),
lc.channelState.FundingRedeemScript, txscript.SigHashAll,
lc.channelState.OurMultiSigKey)
lc.signDesc.SigHashes = txscript.NewTxSigHashes(newCommitView.txn)
sig, err := lc.signer.SignOutputRaw(newCommitView.txn, lc.signDesc)
if err != nil {
return nil, 0, err
}
@ -744,7 +763,7 @@ func (lc *LightningChannel) SignNextCommitment() ([]byte, uint32, error) {
// Strip off the sighash flag on the signature in order to send it over
// the wire.
return sig[:len(sig)], lc.theirLogCounter, nil
return sig, lc.theirLogCounter, nil
}
// ReceiveNewCommitment processs a signature for a new commitment state sent by
@ -770,7 +789,7 @@ func (lc *LightningChannel) ReceiveNewCommitment(rawSig []byte,
if err != nil {
return err
}
revocationKey := deriveRevocationPubkey(theirCommitKey, revocation[:])
revocationKey := DeriveRevocationPubkey(theirCommitKey, revocation[:])
revocationHash := fastsha256.Sum256(revocation[:])
// With the revocation information calculated, construct the new
@ -857,7 +876,7 @@ func (lc *LightningChannel) RevokeCurrentCommitment() (*lnwire.CommitRevocation,
if err != nil {
return nil, err
}
revocationMsg.NextRevocationKey = deriveRevocationPubkey(theirCommitKey,
revocationMsg.NextRevocationKey = DeriveRevocationPubkey(theirCommitKey,
revocationEdge[:])
revocationMsg.NextRevocationHash = fastsha256.Sum256(revocationEdge[:])
@ -921,8 +940,8 @@ func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.CommitRevocation) (
// Verify that the revocation public key we can derive using this
// pre-image and our private key is identical to the revocation key we
// were given for their current (prior) commitment transaction.
revocationPriv := deriveRevocationPrivKey(ourCommitKey, pendingRevocation[:])
if !revocationPriv.PubKey().IsEqual(currentRevocationKey) {
revocationPub := DeriveRevocationPubkey(ourCommitKey, pendingRevocation[:])
if !revocationPub.IsEqual(currentRevocationKey) {
return nil, fmt.Errorf("revocation key mismatch")
}
@ -1067,7 +1086,7 @@ func (lc *LightningChannel) ExtendRevocationWindow() (*lnwire.CommitRevocation,
}
theirCommitKey := lc.channelState.TheirCommitKey
revMsg.NextRevocationKey = deriveRevocationPubkey(theirCommitKey,
revMsg.NextRevocationKey = DeriveRevocationPubkey(theirCommitKey,
revocation[:])
revMsg.NextRevocationHash = fastsha256.Sum256(revocation[:])
@ -1201,7 +1220,7 @@ func (lc *LightningChannel) addHTLC(commitTx *wire.MsgTx, ourCommit bool,
paymentDesc *PaymentDescriptor, revocation [32]byte, delay uint32,
isIncoming bool) error {
localKey := lc.channelState.OurCommitKey.PubKey()
localKey := lc.channelState.OurCommitKey
remoteKey := lc.channelState.TheirCommitKey
timeout := paymentDesc.Timeout
rHash := paymentDesc.RHash
@ -1288,7 +1307,7 @@ func (lc *LightningChannel) InitCooperativeClose() ([]byte, *wire.ShaHash, error
lc.status = channelClosing
// TODO(roasbeef): assumes initiator pays fees
closeTx := createCooperativeCloseTx(lc.fundingTxIn,
closeTx := CreateCooperativeCloseTx(lc.fundingTxIn,
lc.channelState.OurBalance, lc.channelState.TheirBalance,
lc.channelState.OurDeliveryScript, lc.channelState.TheirDeliveryScript,
true)
@ -1298,11 +1317,8 @@ func (lc *LightningChannel) InitCooperativeClose() ([]byte, *wire.ShaHash, error
// initiator we'll simply send our signature over the the remote party,
// using the generated txid to be notified once the closure transaction
// has been confirmed.
hashCache := txscript.NewTxSigHashes(closeTx)
closeSig, err := txscript.RawTxInWitnessSignature(closeTx,
hashCache, 0, int64(lc.channelState.Capacity),
lc.channelState.FundingRedeemScript, txscript.SigHashAll,
lc.channelState.OurMultiSigKey)
lc.signDesc.SigHashes = txscript.NewTxSigHashes(closeTx)
closeSig, err := lc.signer.SignOutputRaw(closeTx, lc.signDesc)
if err != nil {
return nil, nil, err
}
@ -1315,6 +1331,9 @@ func (lc *LightningChannel) InitCooperativeClose() ([]byte, *wire.ShaHash, error
// remote node initating a cooperative channel closure. A fully signed closure
// transaction is returned. It is the duty of the responding node to broadcast
// a signed+valid closure transaction to the network.
//
// NOTE: The passed remote sig is expected to the a fully complete signature
// including the proper sighash byte.
func (lc *LightningChannel) CompleteCooperativeClose(remoteSig []byte) (*wire.MsgTx, error) {
lc.Lock()
defer lc.Unlock()
@ -1330,35 +1349,34 @@ func (lc *LightningChannel) CompleteCooperativeClose(remoteSig []byte) (*wire.Ms
// Create the transaction used to return the current settled balance
// on this active channel back to both parties. In this current model,
// the initiator pays full fees for the cooperative close transaction.
closeTx := createCooperativeCloseTx(lc.fundingTxIn,
closeTx := CreateCooperativeCloseTx(lc.fundingTxIn,
lc.channelState.OurBalance, lc.channelState.TheirBalance,
lc.channelState.OurDeliveryScript, lc.channelState.TheirDeliveryScript,
false)
// With the transaction created, we can finally generate our half of
// the 2-of-2 multi-sig needed to redeem the funding output.
redeemScript := lc.channelState.FundingRedeemScript
hashCache := txscript.NewTxSigHashes(closeTx)
capacity := int64(lc.channelState.Capacity)
closeSig, err := txscript.RawTxInWitnessSignature(closeTx,
hashCache, 0, capacity, redeemScript, txscript.SigHashAll,
lc.channelState.OurMultiSigKey)
lc.signDesc.SigHashes = hashCache
closeSig, err := lc.signer.SignOutputRaw(closeTx, lc.signDesc)
if err != nil {
return nil, err
}
// Finally, construct the witness stack minding the order of the
// pubkeys+sigs on the stack.
ourKey := lc.channelState.OurMultiSigKey.PubKey().SerializeCompressed()
ourKey := lc.channelState.OurMultiSigKey.SerializeCompressed()
theirKey := lc.channelState.TheirMultiSigKey.SerializeCompressed()
witness := spendMultiSig(redeemScript, ourKey, closeSig,
ourSig := append(closeSig, byte(txscript.SigHashAll))
witness := SpendMultiSig(lc.signDesc.RedeemScript, ourKey, ourSig,
theirKey, remoteSig)
closeTx.TxIn[0].Witness = witness
// Validate the finalized transaction to ensure the output script is
// properly met, and that the remote peer supplied a valid signature.
vm, err := txscript.NewEngine(lc.fundingP2WSH, closeTx, 0,
txscript.StandardVerifyFlags, nil, hashCache, capacity)
txscript.StandardVerifyFlags, nil, hashCache,
int64(lc.channelState.Capacity))
if err != nil {
return nil, err
}
@ -1376,7 +1394,8 @@ func (lc *LightningChannel) DeleteState() error {
return lc.channelState.CloseChannel()
}
// StateSnapshot returns a snapshot b
// StateSnapshot returns a snapshot of the current fully committed state within
// the channel.
func (lc *LightningChannel) StateSnapshot() *channeldb.ChannelSnapshot {
lc.stateMtx.RLock()
defer lc.stateMtx.RUnlock()
@ -1384,12 +1403,12 @@ func (lc *LightningChannel) StateSnapshot() *channeldb.ChannelSnapshot {
return lc.channelState.Snapshot()
}
// createCommitTx creates a commitment transaction, spending from specified
// CreateCommitTx creates a commitment transaction, spending from specified
// funding output. The commitment transaction contains two outputs: one paying
// to the "owner" of the commitment transaction which can be spent after a
// relative block delay or revocation event, and the other paying the the
// counter-party within the channel, which can be spent immediately.
func createCommitTx(fundingOutput *wire.TxIn, selfKey, theirKey *btcec.PublicKey,
func CreateCommitTx(fundingOutput *wire.TxIn, selfKey, theirKey *btcec.PublicKey,
revokeKey *btcec.PublicKey, csvTimeout uint32, amountToSelf,
amountToThem btcutil.Amount) (*wire.MsgTx, error) {
@ -1433,13 +1452,13 @@ func createCommitTx(fundingOutput *wire.TxIn, selfKey, theirKey *btcec.PublicKey
return commitTx, nil
}
// createCooperativeCloseTx creates a transaction which if signed by both
// CreateCooperativeCloseTx creates a transaction which if signed by both
// parties, then broadcast cooperatively closes an active channel. The creation
// of the closure transaction is modified by a boolean indicating if the party
// constructing the channel is the initiator of the closure. Currently it is
// expected that the initiator pays the transaction fees for the closing
// transaction in full.
func createCooperativeCloseTx(fundingTxIn *wire.TxIn,
func CreateCooperativeCloseTx(fundingTxIn *wire.TxIn,
ourBalance, theirBalance btcutil.Amount,
ourDeliveryScript, theirDeliveryScript []byte,
initiator bool) *wire.MsgTx {

@ -13,27 +13,64 @@ import (
"github.com/lightningnetwork/lnd/lnwire"
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/chaincfg"
"github.com/roasbeef/btcd/txscript"
"github.com/roasbeef/btcd/wire"
"github.com/roasbeef/btcutil"
)
// MockEncryptorDecryptor is a mock implementation of EncryptorDecryptor that
// simply returns the passed bytes without encrypting or decrypting. This is
// used for testing purposes to be able to create a channldb instance which
// doesn't use encryption.
type MockEncryptorDecryptor struct {
var (
privPass = []byte("private-test")
// For simplicity a single priv key controls all of our test outputs.
testWalletPrivKey = []byte{
0x2b, 0xd8, 0x06, 0xc9, 0x7f, 0x0e, 0x00, 0xaf,
0x1a, 0x1f, 0xc3, 0x32, 0x8f, 0xa7, 0x63, 0xa9,
0x26, 0x97, 0x23, 0xc8, 0xdb, 0x8f, 0xac, 0x4f,
0x93, 0xaf, 0x71, 0xdb, 0x18, 0x6d, 0x6e, 0x90,
}
// We're alice :)
bobsPrivKey = []byte{
0x81, 0xb6, 0x37, 0xd8, 0xfc, 0xd2, 0xc6, 0xda,
0x63, 0x59, 0xe6, 0x96, 0x31, 0x13, 0xa1, 0x17,
0xd, 0xe7, 0x95, 0xe4, 0xb7, 0x25, 0xb8, 0x4d,
0x1e, 0xb, 0x4c, 0xfd, 0x9e, 0xc5, 0x8c, 0xe9,
}
// Use a hard-coded HD seed.
testHdSeed = [32]byte{
0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab,
0x4d, 0x92, 0x73, 0xd1, 0x90, 0x63, 0x81, 0xb4,
0x4f, 0x2f, 0x6f, 0x25, 0x88, 0xa3, 0xef, 0xb9,
0x6a, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53,
}
// The number of confirmations required to consider any created channel
// open.
numReqConfs = uint16(1)
)
type mockSigner struct {
key *btcec.PrivateKey
}
func (m *MockEncryptorDecryptor) Encrypt(n []byte) ([]byte, error) {
return n, nil
func (m *mockSigner) SignOutputRaw(tx *wire.MsgTx, signDesc *SignDescriptor) ([]byte, error) {
amt := signDesc.Output.Value
redeemScript := signDesc.RedeemScript
privKey := m.key
sig, err := txscript.RawTxInWitnessSignature(tx, signDesc.SigHashes,
signDesc.InputIndex, amt, redeemScript, txscript.SigHashAll, privKey)
if err != nil {
return nil, err
}
return sig[:len(sig)-1], nil
}
func (m *MockEncryptorDecryptor) Decrypt(n []byte) ([]byte, error) {
return n, nil
}
func (m *MockEncryptorDecryptor) OverheadSize() uint32 {
return 0
// ComputeInputScript...
func (m *mockSigner) ComputeInputScript(tx *wire.MsgTx, signDesc *SignDescriptor) (*InputScript, error) {
return nil, nil
}
// createTestChannels creates two test channels funded with 10 BTC, with 5 BTC
@ -49,7 +86,7 @@ func createTestChannels() (*LightningChannel, *LightningChannel, func(), error)
csvTimeoutAlice := uint32(5)
csvTimeoutBob := uint32(4)
redeemScript, _, err := genFundingPkScript(aliceKeyPub.SerializeCompressed(),
redeemScript, _, err := GenFundingPkScript(aliceKeyPub.SerializeCompressed(),
bobKeyPub.SerializeCompressed(), int64(channelCapacity))
if err != nil {
return nil, nil, nil, err
@ -61,26 +98,26 @@ func createTestChannels() (*LightningChannel, *LightningChannel, func(), error)
}
fundingTxIn := wire.NewTxIn(prevOut, nil, nil)
bobElkrem := elkrem.NewElkremSender(deriveElkremRoot(bobKeyPriv, aliceKeyPub))
bobElkrem := elkrem.NewElkremSender(deriveElkremRoot(bobKeyPriv, bobKeyPub, aliceKeyPub))
bobFirstRevoke, err := bobElkrem.AtIndex(0)
if err != nil {
return nil, nil, nil, err
}
bobRevokeKey := deriveRevocationPubkey(aliceKeyPub, bobFirstRevoke[:])
bobRevokeKey := DeriveRevocationPubkey(aliceKeyPub, bobFirstRevoke[:])
aliceElkrem := elkrem.NewElkremSender(deriveElkremRoot(aliceKeyPriv, bobKeyPub))
aliceElkrem := elkrem.NewElkremSender(deriveElkremRoot(aliceKeyPriv, aliceKeyPub, bobKeyPub))
aliceFirstRevoke, err := aliceElkrem.AtIndex(0)
if err != nil {
return nil, nil, nil, err
}
aliceRevokeKey := deriveRevocationPubkey(bobKeyPub, aliceFirstRevoke[:])
aliceRevokeKey := DeriveRevocationPubkey(bobKeyPub, aliceFirstRevoke[:])
aliceCommitTx, err := createCommitTx(fundingTxIn, aliceKeyPub,
aliceCommitTx, err := CreateCommitTx(fundingTxIn, aliceKeyPub,
bobKeyPub, aliceRevokeKey, csvTimeoutAlice, channelBal, channelBal)
if err != nil {
return nil, nil, nil, err
}
bobCommitTx, err := createCommitTx(fundingTxIn, bobKeyPub,
bobCommitTx, err := CreateCommitTx(fundingTxIn, bobKeyPub,
aliceKeyPub, bobRevokeKey, csvTimeoutBob, channelBal, channelBal)
if err != nil {
return nil, nil, nil, err
@ -91,25 +128,24 @@ func createTestChannels() (*LightningChannel, *LightningChannel, func(), error)
if err != nil {
return nil, nil, nil, err
}
dbAlice.RegisterCryptoSystem(&MockEncryptorDecryptor{})
bobPath, err := ioutil.TempDir("", "bobdb")
dbBob, err := channeldb.Open(bobPath, &chaincfg.TestNet3Params)
if err != nil {
return nil, nil, nil, err
}
dbBob.RegisterCryptoSystem(&MockEncryptorDecryptor{})
aliceChannelState := &channeldb.OpenChannel{
TheirLNID: testHdSeed,
ChanID: prevOut,
OurCommitKey: aliceKeyPriv,
OurCommitKey: aliceKeyPub,
TheirCommitKey: bobKeyPub,
Capacity: channelCapacity,
OurBalance: channelBal,
TheirBalance: channelBal,
OurCommitTx: aliceCommitTx,
FundingOutpoint: prevOut,
OurMultiSigKey: aliceKeyPriv,
OurMultiSigKey: aliceKeyPub,
TheirMultiSigKey: bobKeyPub,
FundingRedeemScript: redeemScript,
LocalCsvDelay: csvTimeoutAlice,
@ -122,14 +158,14 @@ func createTestChannels() (*LightningChannel, *LightningChannel, func(), error)
bobChannelState := &channeldb.OpenChannel{
TheirLNID: testHdSeed,
ChanID: prevOut,
OurCommitKey: bobKeyPriv,
OurCommitKey: bobKeyPub,
TheirCommitKey: aliceKeyPub,
Capacity: channelCapacity,
OurBalance: channelBal,
TheirBalance: channelBal,
OurCommitTx: bobCommitTx,
FundingOutpoint: prevOut,
OurMultiSigKey: bobKeyPriv,
OurMultiSigKey: bobKeyPub,
TheirMultiSigKey: aliceKeyPub,
FundingRedeemScript: redeemScript,
LocalCsvDelay: csvTimeoutBob,
@ -145,11 +181,14 @@ func createTestChannels() (*LightningChannel, *LightningChannel, func(), error)
os.RemoveAll(alicePath)
}
channelAlice, err := NewLightningChannel(nil, nil, dbAlice, aliceChannelState)
aliceSigner := &mockSigner{aliceKeyPriv}
bobSigner := &mockSigner{bobKeyPriv}
channelAlice, err := NewLightningChannel(aliceSigner, nil, nil, aliceChannelState)
if err != nil {
return nil, nil, nil, err
}
channelBob, err := NewLightningChannel(nil, nil, dbBob, bobChannelState)
channelBob, err := NewLightningChannel(bobSigner, nil, nil, bobChannelState)
if err != nil {
return nil, nil, nil, err
}
@ -456,7 +495,8 @@ func TestCooperativeChannelClosure(t *testing.T) {
if err != nil {
t.Fatalf("unable to initiate alice cooperative close: %v", err)
}
closeTx, err := bobChannel.CompleteCooperativeClose(sig)
finalSig := append(sig, byte(txscript.SigHashAll))
closeTx, err := bobChannel.CompleteCooperativeClose(finalSig)
if err != nil {
t.Fatalf("unable to complete alice cooperative close: %v", err)
}
@ -475,7 +515,8 @@ func TestCooperativeChannelClosure(t *testing.T) {
if err != nil {
t.Fatalf("unable to initiate bob cooperative close: %v", err)
}
closeTx, err = aliceChannel.CompleteCooperativeClose(sig)
finalSig = append(sig, byte(txscript.SigHashAll))
closeTx, err = aliceChannel.CompleteCooperativeClose(finalSig)
if err != nil {
t.Fatalf("unable to complete bob cooperative close: %v", err)
}

@ -1,4 +1,4 @@
package lnwallet
package lnwallet_test
import (
"bytes"
@ -11,18 +11,20 @@ import (
"time"
"github.com/boltdb/bolt"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/chainntnfs/btcdnotify"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
"github.com/roasbeef/btcd/chaincfg"
"github.com/roasbeef/btcutil/txsort"
_ "github.com/roasbeef/btcwallet/walletdb/bdb"
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/rpctest"
"github.com/roasbeef/btcd/txscript"
"github.com/roasbeef/btcd/wire"
"github.com/roasbeef/btcutil"
"github.com/roasbeef/btcutil/coinset"
"github.com/roasbeef/btcwallet/waddrmgr"
)
var (
@ -60,8 +62,8 @@ var (
// assertProperBalance asserts than the total value of the unspent outputs
// within the wallet are *exactly* amount. If unable to retrieve the current
// balance, or the assertion fails, the test will halt with a fatal error.
func assertProperBalance(t *testing.T, lw *LightningWallet, numConfirms int32, amount int64) {
balance, err := lw.CalculateBalance(numConfirms)
func assertProperBalance(t *testing.T, lw *lnwallet.LightningWallet, numConfirms int32, amount int64) {
balance, err := lw.ConfirmedBalance(numConfirms, false)
if err != nil {
t.Fatalf("unable to query for balance: %v", err)
}
@ -72,7 +74,7 @@ func assertProperBalance(t *testing.T, lw *LightningWallet, numConfirms int32, a
}
func assertChannelOpen(t *testing.T, miner *rpctest.Harness, numConfs uint32,
c <-chan *LightningChannel) *LightningChannel {
c <-chan *lnwallet.LightningChannel) *lnwallet.LightningChannel {
// Mine a single block. After this block is mined, the channel should
// be considered fully open.
if _, err := miner.Node.Generate(1); err != nil {
@ -108,9 +110,9 @@ type bobNode struct {
// Contribution returns bobNode's contribution necessary to open a payment
// channel with Alice.
func (b *bobNode) Contribution(aliceCommitKey *btcec.PublicKey) *ChannelContribution {
revokeKey := deriveRevocationPubkey(aliceCommitKey, b.revocation[:])
return &ChannelContribution{
func (b *bobNode) Contribution(aliceCommitKey *btcec.PublicKey) *lnwallet.ChannelContribution {
revokeKey := lnwallet.DeriveRevocationPubkey(aliceCommitKey, b.revocation[:])
return &lnwallet.ChannelContribution{
FundingAmount: b.fundingAmt,
Inputs: b.availableOutputs,
ChangeOutputs: b.changeOutputs,
@ -124,9 +126,9 @@ func (b *bobNode) Contribution(aliceCommitKey *btcec.PublicKey) *ChannelContribu
// SingleContribution returns bobNode's contribution to a single funded
// channel. This contribution contains no inputs nor change outputs.
func (b *bobNode) SingleContribution(aliceCommitKey *btcec.PublicKey) *ChannelContribution {
revokeKey := deriveRevocationPubkey(aliceCommitKey, b.revocation[:])
return &ChannelContribution{
func (b *bobNode) SingleContribution(aliceCommitKey *btcec.PublicKey) *lnwallet.ChannelContribution {
revokeKey := lnwallet.DeriveRevocationPubkey(aliceCommitKey, b.revocation[:])
return &lnwallet.ChannelContribution{
FundingAmount: b.fundingAmt,
MultiSigKey: b.channelKey,
CommitKey: b.channelKey,
@ -139,8 +141,8 @@ func (b *bobNode) SingleContribution(aliceCommitKey *btcec.PublicKey) *ChannelCo
// signFundingTx generates signatures for all the inputs in the funding tx
// belonging to Bob.
// NOTE: This generates the full witness stack.
func (b *bobNode) signFundingTx(fundingTx *wire.MsgTx) ([]*InputScript, error) {
bobInputScripts := make([]*InputScript, 0, len(b.availableOutputs))
func (b *bobNode) signFundingTx(fundingTx *wire.MsgTx) ([]*lnwallet.InputScript, error) {
bobInputScripts := make([]*lnwallet.InputScript, 0, len(b.availableOutputs))
bobPkScript := b.changeOutputs[0].PkScript
inputValue := int64(7e8)
@ -158,7 +160,7 @@ func (b *bobNode) signFundingTx(fundingTx *wire.MsgTx) ([]*InputScript, error) {
return nil, err
}
inputScript := &InputScript{Witness: witness}
inputScript := &lnwallet.InputScript{Witness: witness}
bobInputScripts = append(bobInputScripts, inputScript)
}
@ -217,7 +219,7 @@ func newBobNode(miner *rpctest.Harness, amt btcutil.Amount) (*bobNode, error) {
if err != nil {
return nil, err
}
found, index := findScriptOutputIndex(tx.MsgTx(), bobAddrScript)
found, index := lnwallet.FindScriptOutputIndex(tx.MsgTx(), bobAddrScript)
if !found {
return nil, fmt.Errorf("output to bob never created")
}
@ -251,14 +253,14 @@ func newBobNode(miner *rpctest.Harness, amt btcutil.Amount) (*bobNode, error) {
}, nil
}
func loadTestCredits(miner *rpctest.Harness, w *LightningWallet, numOutputs, btcPerOutput int) error {
func loadTestCredits(miner *rpctest.Harness, w *lnwallet.LightningWallet, numOutputs, btcPerOutput int) error {
// Using the mining node, spend from a coinbase output numOutputs to
// give us btcPerOutput with each output.
satoshiPerOutput := int64(btcPerOutput * 1e8)
addrs := make([]btcutil.Address, 0, numOutputs)
for i := 0; i < numOutputs; i++ {
// Grab a fresh address from the wallet to house this output.
walletAddr, err := w.NewAddress(waddrmgr.DefaultAccountNum, waddrmgr.WitnessPubKey)
walletAddr, err := w.NewAddress(lnwallet.WitnessPubKey, false)
if err != nil {
return err
}
@ -285,87 +287,59 @@ func loadTestCredits(miner *rpctest.Harness, w *LightningWallet, numOutputs, btc
return err
}
_, bestHeight, err := miner.Node.GetBestBlock()
if err != nil {
return err
}
// Wait until the wallet has finished syncing up to the main chain.
ticker := time.NewTicker(100 * time.Millisecond)
expectedBalance := btcutil.Amount(satoshiPerOutput * int64(numOutputs))
out:
for {
select {
case <-ticker.C:
if w.Manager.SyncedTo().Height == bestHeight {
balance, err := w.ConfirmedBalance(1, false)
if err != nil {
return err
}
if balance == expectedBalance {
break out
}
}
}
ticker.Stop()
// Trigger a re-scan to ensure the wallet knows of the newly created
// outputs it can spend.
if err := w.Rescan(addrs, nil); err != nil {
return err
}
return nil
}
// createTestWallet creates a test LightningWallet will a total of 20BTC
// available for funding channels.
func createTestWallet(miningNode *rpctest.Harness, netParams *chaincfg.Params) (string, *LightningWallet, error) {
privPass := []byte("private-test")
tempTestDir, err := ioutil.TempDir("", "lnwallet")
if err != nil {
return "", nil, nil
}
rpcConfig := miningNode.RPCConfig()
config := &Config{
PrivatePass: privPass,
HdSeed: testHdSeed[:],
DataDir: tempTestDir,
NetParams: netParams,
RpcHost: rpcConfig.Host,
RpcUser: rpcConfig.User,
RpcPass: rpcConfig.Pass,
CACert: rpcConfig.Certificates,
}
func createTestWallet(tempTestDir string, miningNode *rpctest.Harness,
netParams *chaincfg.Params, notifier chainntnfs.ChainNotifier,
wc lnwallet.WalletController, signer lnwallet.Signer,
bio lnwallet.BlockChainIO) (*lnwallet.LightningWallet, error) {
dbDir := filepath.Join(tempTestDir, "cdb")
cdb, err := channeldb.Open(dbDir, &chaincfg.SegNet4Params)
if err != nil {
return "", nil, err
return nil, err
}
chainNotifier, err := btcdnotify.New(&rpcConfig)
wallet, err := lnwallet.NewLightningWallet(cdb, notifier, wc, signer,
bio, netParams)
if err != nil {
return "", nil, err
}
if err := chainNotifier.Start(); err != nil {
return "", nil, err
return nil, err
}
wallet, err := NewLightningWallet(config, cdb, chainNotifier)
if err != nil {
return "", nil, err
}
if err := wallet.Startup(); err != nil {
return "", nil, err
return nil, err
}
cdb.RegisterCryptoSystem(&WaddrmgrEncryptorDecryptor{wallet.Manager})
// Load our test wallet with 10 outputs each holding 4BTC.
if err := loadTestCredits(miningNode, wallet, 10, 4); err != nil {
return "", nil, err
// Load our test wallet with 20 outputs each holding 4BTC.
if err := loadTestCredits(miningNode, wallet, 20, 4); err != nil {
return nil, err
}
return tempTestDir, wallet, nil
return wallet, nil
}
func testDualFundingReservationWorkflow(miner *rpctest.Harness, lnwallet *LightningWallet, t *testing.T) {
func testDualFundingReservationWorkflow(miner *rpctest.Harness, wallet *lnwallet.LightningWallet, t *testing.T) {
// Create the bob-test wallet which will be the other side of our funding
// channel.
fundingAmount := btcutil.Amount(5 * 1e8)
@ -376,7 +350,7 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness, lnwallet *Lightn
// Bob initiates a channel funded with 5 BTC for each side, so 10
// BTC total. He also generates 2 BTC in change.
chanReservation, err := lnwallet.InitChannelReservation(fundingAmount*2,
chanReservation, err := wallet.InitChannelReservation(fundingAmount*2,
fundingAmount, bobNode.id, numReqConfs, 4)
if err != nil {
t.Fatalf("unable to initialize funding reservation: %v", err)
@ -426,10 +400,13 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness, lnwallet *Lightn
if ourContribution.RevocationKey == nil {
t.Fatalf("alice's revocation key not found")
}
// Additionally, the funding tx should have been populated.
if chanReservation.fundingTx == nil {
fundingTx := chanReservation.FinalFundingTx()
if fundingTx == nil {
t.Fatalf("funding transaction never created!")
}
// Their funds should also be filled in.
if len(theirContribution.Inputs) != 1 {
t.Fatalf("bob's outputs for funding tx not properly selected, have %v "+
@ -455,13 +432,13 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness, lnwallet *Lightn
// Alice responds with her output, change addr, multi-sig key and signatures.
// Bob then responds with his signatures.
bobsSigs, err := bobNode.signFundingTx(chanReservation.fundingTx)
bobsSigs, err := bobNode.signFundingTx(fundingTx)
if err != nil {
t.Fatalf("unable to sign inputs for bob: %v", err)
}
commitSig, err := bobNode.signCommitTx(
chanReservation.partialState.OurCommitTx,
chanReservation.partialState.FundingRedeemScript,
chanReservation.LocalCommitTx(),
chanReservation.FundingRedeemScript(),
10e8)
if err != nil {
t.Fatalf("bob is unable to sign alice's commit tx: %v", err)
@ -474,10 +451,9 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness, lnwallet *Lightn
// txn hits a "comfortable" depth.
// The resulting active channel state should have been persisted to the DB.
fundingTx := chanReservation.FinalFundingTx()
fundingSha := fundingTx.TxSha()
nodeID := wire.ShaHash(bobNode.id)
channels, err := lnwallet.channelDB.FetchOpenChannels(&nodeID)
channels, err := wallet.ChannelDB.FetchOpenChannels(&nodeID)
if err != nil {
t.Fatalf("unable to retrieve channel from DB: %v", err)
}
@ -495,46 +471,49 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness, lnwallet *Lightn
if err != nil {
t.Fatalf("unable to init cooperative closure: %v", err)
}
aliceCloseSig = append(aliceCloseSig, byte(txscript.SigHashAll))
chanInfo := lnc.StateSnapshot()
// Obtain bob's signature for the closure transaction.
redeemScript := lnc.channelState.FundingRedeemScript
redeemScript := lnc.FundingRedeemScript
fundingOut := lnc.ChannelPoint()
fundingTxIn := wire.NewTxIn(fundingOut, nil, nil)
bobCloseTx := createCooperativeCloseTx(fundingTxIn,
lnc.channelState.TheirBalance, lnc.channelState.OurBalance,
lnc.channelState.TheirDeliveryScript, lnc.channelState.OurDeliveryScript,
bobCloseTx := lnwallet.CreateCooperativeCloseTx(fundingTxIn,
chanInfo.RemoteBalance, chanInfo.LocalBalance,
lnc.RemoteDeliveryScript, lnc.LocalDeliveryScript,
false)
bobSig, err := bobNode.signCommitTx(bobCloseTx,
redeemScript,
int64(lnc.channelState.Capacity))
bobSig, err := bobNode.signCommitTx(bobCloseTx, redeemScript, int64(lnc.Capacity))
if err != nil {
t.Fatalf("unable to generate bob's signature for closing tx: %v", err)
}
// Broadcast the transaction to the network. This transaction should
// be accepted, and found in the next mined block.
ourKey := lnc.channelState.OurMultiSigKey.PubKey().SerializeCompressed()
theirKey := lnc.channelState.TheirMultiSigKey.SerializeCompressed()
witness := spendMultiSig(redeemScript, ourKey, aliceCloseSig,
ourKey := chanReservation.OurContribution().MultiSigKey.SerializeCompressed()
theirKey := chanReservation.TheirContribution().MultiSigKey.SerializeCompressed()
witness := lnwallet.SpendMultiSig(redeemScript, ourKey, aliceCloseSig,
theirKey, bobSig)
bobCloseTx.TxIn[0].Witness = witness
if err := lnwallet.PublishTransaction(bobCloseTx); err != nil {
if err := wallet.PublishTransaction(bobCloseTx); err != nil {
t.Fatalf("broadcast of close tx rejected: %v", err)
}
}
func testFundingTransactionLockedOutputs(miner *rpctest.Harness, lnwallet *LightningWallet, t *testing.T) {
func testFundingTransactionLockedOutputs(miner *rpctest.Harness,
wallet *lnwallet.LightningWallet, t *testing.T) {
// Create two channels, both asking for 8 BTC each, totalling 16
// BTC.
// TODO(roasbeef): tests for concurrent funding.
// * also func for below
fundingAmount := btcutil.Amount(8 * 1e8)
chanReservation1, err := lnwallet.InitChannelReservation(fundingAmount,
chanReservation1, err := wallet.InitChannelReservation(fundingAmount,
fundingAmount, testHdSeed, numReqConfs, 4)
if err != nil {
t.Fatalf("unable to initialize funding reservation 1: %v", err)
}
chanReservation2, err := lnwallet.InitChannelReservation(fundingAmount,
chanReservation2, err := wallet.InitChannelReservation(fundingAmount,
fundingAmount, testHdSeed, numReqConfs, 4)
if err != nil {
t.Fatalf("unable to initialize funding reservation 2: %v", err)
@ -563,12 +542,12 @@ func testFundingTransactionLockedOutputs(miner *rpctest.Harness, lnwallet *Light
// 90 BTC. We only have around 24BTC worth of outpoints that aren't locked, so
// this should fail.
amt := btcutil.Amount(90 * 1e8)
failedReservation, err := lnwallet.InitChannelReservation(amt, amt,
failedReservation, err := wallet.InitChannelReservation(amt, amt,
testHdSeed, numReqConfs, 4)
if err == nil {
t.Fatalf("not error returned, should fail on coin selection")
}
if err != coinset.ErrCoinsNoSelectionAvailable {
if err != lnwallet.ErrInsufficientFunds {
t.Fatalf("error not coinselect error: %v", err)
}
if failedReservation != nil {
@ -576,28 +555,30 @@ func testFundingTransactionLockedOutputs(miner *rpctest.Harness, lnwallet *Light
}
}
func testFundingCancellationNotEnoughFunds(miner *rpctest.Harness, lnwallet *LightningWallet, t *testing.T) {
// Create a reservation for 22 BTC.
fundingAmount := btcutil.Amount(22 * 1e8)
chanReservation, err := lnwallet.InitChannelReservation(fundingAmount,
func testFundingCancellationNotEnoughFunds(miner *rpctest.Harness,
wallet *lnwallet.LightningWallet, t *testing.T) {
// Create a reservation for 44 BTC.
fundingAmount := btcutil.Amount(44 * 1e8)
chanReservation, err := wallet.InitChannelReservation(fundingAmount,
fundingAmount, testHdSeed, numReqConfs, 4)
if err != nil {
t.Fatalf("unable to initialize funding reservation: %v", err)
}
// There should be three locked outpoints.
lockedOutPoints := lnwallet.LockedOutpoints()
if len(lockedOutPoints) != 6 {
// There should be 12 locked outpoints.
lockedOutPoints := wallet.LockedOutpoints()
if len(lockedOutPoints) != 12 {
t.Fatalf("two outpoints should now be locked, instead %v are",
len(lockedOutPoints))
}
// Attempt to create another channel with 22 BTC, this should fail.
failedReservation, err := lnwallet.InitChannelReservation(fundingAmount,
// Attempt to create another channel with 44 BTC, this should fail.
_, err = wallet.InitChannelReservation(fundingAmount,
fundingAmount, testHdSeed, numReqConfs, 4)
if err != coinset.ErrCoinsNoSelectionAvailable {
t.Fatalf("coin selection succeded should have insufficient funds: %+v",
failedReservation)
if err != lnwallet.ErrInsufficientFunds {
t.Fatalf("coin selection succeded should have insufficient funds: %v",
err)
}
// Now cancel that old reservation.
@ -606,15 +587,16 @@ func testFundingCancellationNotEnoughFunds(miner *rpctest.Harness, lnwallet *Lig
}
// Those outpoints should no longer be locked.
lockedOutPoints = lnwallet.LockedOutpoints()
lockedOutPoints = wallet.LockedOutpoints()
if len(lockedOutPoints) != 0 {
t.Fatalf("outpoints still locked")
}
// Reservation ID should now longer be tracked.
_, ok := lnwallet.fundingLimbo[chanReservation.reservationID]
if ok {
t.Fatalf("funding reservation still in map")
// Reservation ID should no longer be tracked.
numReservations := wallet.ActiveReservations()
if len(wallet.ActiveReservations()) != 0 {
t.Fatalf("should have 0 reservations, instead have %v",
numReservations)
}
// TODO(roasbeef): create method like Balance that ignores locked
@ -622,16 +604,18 @@ func testFundingCancellationNotEnoughFunds(miner *rpctest.Harness, lnwallet *Lig
// attempting coin selection.
// Request to fund a new channel should now succeeed.
_, err = lnwallet.InitChannelReservation(fundingAmount, fundingAmount,
_, err = wallet.InitChannelReservation(fundingAmount, fundingAmount,
testHdSeed, numReqConfs, 4)
if err != nil {
t.Fatalf("unable to initialize funding reservation: %v", err)
}
}
func testCancelNonExistantReservation(miner *rpctest.Harness, lnwallet *LightningWallet, t *testing.T) {
func testCancelNonExistantReservation(miner *rpctest.Harness,
wallet *lnwallet.LightningWallet, t *testing.T) {
// Create our own reservation, give it some ID.
res := newChannelReservation(1000, 1000, 5000, lnwallet, 22, numReqConfs)
res := lnwallet.NewChannelReservation(1000, 1000, 5000, wallet, 22, numReqConfs)
// Attempt to cancel this reservation. This should fail, we know
// nothing of it.
@ -640,7 +624,9 @@ func testCancelNonExistantReservation(miner *rpctest.Harness, lnwallet *Lightnin
}
}
func testSingleFunderReservationWorkflowInitiator(miner *rpctest.Harness, lnwallet *LightningWallet, t *testing.T) {
func testSingleFunderReservationWorkflowInitiator(miner *rpctest.Harness,
lnwallet *lnwallet.LightningWallet, t *testing.T) {
// For this scenario, we (lnwallet) will be the channel initiator while bob
// will be the recipient.
@ -702,7 +688,7 @@ func testSingleFunderReservationWorkflowInitiator(miner *rpctest.Harness, lnwall
t.Fatalf("commitment sig not found")
}
// Additionally, the funding tx should have been populated.
if chanReservation.fundingTx == nil {
if chanReservation.FinalFundingTx() == nil {
t.Fatalf("funding transaction never created!")
}
// Their funds should also be filled in.
@ -737,8 +723,8 @@ func testSingleFunderReservationWorkflowInitiator(miner *rpctest.Harness, lnwall
// Now Bob can generate a signature for our version of the commitment
// transaction, allowing us to complete the reservation.
bobCommitSig, err := bobNode.signCommitTx(
chanReservation.partialState.OurCommitTx,
chanReservation.partialState.FundingRedeemScript,
chanReservation.LocalCommitTx(),
chanReservation.FundingRedeemScript(),
int64(fundingAmt))
if err != nil {
t.Fatalf("bob is unable to sign alice's commit tx: %v", err)
@ -755,7 +741,7 @@ func testSingleFunderReservationWorkflowInitiator(miner *rpctest.Harness, lnwall
fundingTx := chanReservation.FinalFundingTx()
fundingSha := fundingTx.TxSha()
nodeID := wire.ShaHash(bobNode.id)
channels, err := lnwallet.channelDB.FetchOpenChannels(&nodeID)
channels, err := lnwallet.ChannelDB.FetchOpenChannels(&nodeID)
if err != nil {
t.Fatalf("unable to retrieve channel from DB: %v", err)
}
@ -768,7 +754,9 @@ func testSingleFunderReservationWorkflowInitiator(miner *rpctest.Harness, lnwall
assertChannelOpen(t, miner, uint32(numReqConfs), chanReservation.DispatchChan())
}
func testSingleFunderReservationWorkflowResponder(miner *rpctest.Harness, lnwallet *LightningWallet, t *testing.T) {
func testSingleFunderReservationWorkflowResponder(miner *rpctest.Harness,
wallet *lnwallet.LightningWallet, t *testing.T) {
// For this scenario, bob will initiate the channel, while we simply act as
// the responder.
capacity := btcutil.Amount(4 * 1e8)
@ -783,7 +771,7 @@ func testSingleFunderReservationWorkflowResponder(miner *rpctest.Harness, lnwall
// Bob sends over a single funding request, so we allocate our
// contribution and the necessary resources.
fundingAmt := btcutil.Amount(0)
chanReservation, err := lnwallet.InitChannelReservation(capacity,
chanReservation, err := wallet.InitChannelReservation(capacity,
fundingAmt, bobNode.id, numReqConfs, 4)
if err != nil {
t.Fatalf("unable to init channel reservation: %v", err)
@ -821,7 +809,7 @@ func testSingleFunderReservationWorkflowResponder(miner *rpctest.Harness, lnwall
if err := chanReservation.ProcessSingleContribution(bobContribution); err != nil {
t.Fatalf("unable to process bob's contribution: %v", err)
}
if chanReservation.fundingTx != nil {
if chanReservation.FinalFundingTx() != nil {
t.Fatalf("funding transaction populated!")
}
if len(bobContribution.Inputs) != 1 {
@ -848,7 +836,7 @@ func testSingleFunderReservationWorkflowResponder(miner *rpctest.Harness, lnwall
t.Fatalf("bob's revocaiton key not found")
}
fundingRedeemScript, multiOut, err := genFundingPkScript(
fundingRedeemScript, multiOut, err := lnwallet.GenFundingPkScript(
ourContribution.MultiSigKey.SerializeCompressed(),
bobContribution.MultiSigKey.SerializeCompressed(),
int64(capacity))
@ -872,11 +860,11 @@ func testSingleFunderReservationWorkflowResponder(miner *rpctest.Harness, lnwall
// wallet so it can finalize the transaction by signing bob's commitment
// transaction.
fundingTxID := fundingTx.TxSha()
_, multiSigIndex := findScriptOutputIndex(fundingTx, multiOut.PkScript)
_, multiSigIndex := lnwallet.FindScriptOutputIndex(fundingTx, multiOut.PkScript)
fundingOutpoint := wire.NewOutPoint(&fundingTxID, multiSigIndex)
fundingTxIn := wire.NewTxIn(fundingOutpoint, nil, nil)
aliceCommitTx, err := createCommitTx(fundingTxIn, ourContribution.CommitKey,
aliceCommitTx, err := lnwallet.CreateCommitTx(fundingTxIn, ourContribution.CommitKey,
bobContribution.CommitKey, ourContribution.RevocationKey,
ourContribution.CsvDelay, 0, capacity)
if err != nil {
@ -897,10 +885,9 @@ func testSingleFunderReservationWorkflowResponder(miner *rpctest.Harness, lnwall
}
// Alice should have saved the funding output.
if chanReservation.partialState.FundingOutpoint != fundingOutpoint {
if chanReservation.FundingOutpoint() != fundingOutpoint {
t.Fatalf("funding outputs don't match: %#v vs %#v",
chanReservation.partialState.FundingOutpoint,
fundingOutpoint)
chanReservation.FundingOutpoint(), fundingOutpoint)
}
// Some period of time later, Bob presents us with an SPV proof
@ -913,13 +900,13 @@ func testSingleFunderReservationWorkflowResponder(miner *rpctest.Harness, lnwall
// TODO(roasbeef): bob verify alice's sig
}
func testFundingReservationInvalidCounterpartySigs(miner *rpctest.Harness, lnwallet *LightningWallet, t *testing.T) {
func testFundingReservationInvalidCounterpartySigs(miner *rpctest.Harness, lnwallet *lnwallet.LightningWallet, t *testing.T) {
}
func testFundingTransactionTxFees(miner *rpctest.Harness, lnwallet *LightningWallet, t *testing.T) {
func testFundingTransactionTxFees(miner *rpctest.Harness, lnwallet *lnwallet.LightningWallet, t *testing.T) {
}
var walletTests = []func(miner *rpctest.Harness, w *LightningWallet, test *testing.T){
var walletTests = []func(miner *rpctest.Harness, w *lnwallet.LightningWallet, test *testing.T){
testDualFundingReservationWorkflow,
testSingleFunderReservationWorkflowInitiator,
testSingleFunderReservationWorkflowResponder,
@ -934,20 +921,29 @@ var walletTests = []func(miner *rpctest.Harness, w *LightningWallet, test *testi
}
type testLnWallet struct {
lnwallet *LightningWallet
lnwallet *lnwallet.LightningWallet
cleanUpFunc func()
}
func clearWalletState(w *LightningWallet) error {
w.nextFundingID = 0
w.fundingLimbo = make(map[uint64]*ChannelReservation)
w.ResetLockedOutpoints()
func clearWalletState(w *lnwallet.LightningWallet) error {
// TODO(roasbeef): should also restore outputs to original state.
w.ResetReservations()
return w.channelDB.Wipe()
return w.ChannelDB.Wipe()
}
// TestInterfaces tests all registered interfaces with a unified set of tests
// which excersie each of the required methods found within the WalletController
// interface.
//
// NOTE: In the future, when additional implementations of the WalletController
// interface have been implemented, in order to ensure the new concrete
// implementation is automatically tested, two steps must be undertaken. First,
// one needs add a "non-captured" (_) import from the new sub-package. This
// import should trigger an init() method within the package which registeres
// the interface. Second, an additional case in the switch within the main loop
// below needs to be added which properly initializes the interface.
//
// TODO(roasbeef): purge bobNode in favor of dual lnwallet's
func TestLightningWallet(t *testing.T) {
netParams := &chaincfg.SimNetParams
@ -965,27 +961,72 @@ func TestLightningWallet(t *testing.T) {
t.Fatalf("unable to set up mining node: %v", err)
}
// Funding via 10 outputs with 4BTC each.
testDir, lnwallet, err := createTestWallet(miningNode, netParams)
rpcConfig := miningNode.RPCConfig()
chainNotifier, err := btcdnotify.New(&rpcConfig)
if err != nil {
t.Fatalf("unable to create test ln wallet: %v", err)
t.Fatalf("unable to create notifier: %v", err)
}
if err := chainNotifier.Start(); err != nil {
t.Fatalf("unable to start notifier: %v", err)
}
defer os.RemoveAll(testDir)
defer lnwallet.Shutdown()
// The wallet should now have 40BTC available for spending.
assertProperBalance(t, lnwallet, 1, 40)
// Execute every test, clearing possibly mutated wallet state after
// each step.
for _, walletTest := range walletTests {
walletTest(miningNode, lnwallet, t)
// TODO(roasbeef): possible reset mining node's chainstate to
// initial level, cleanly wipe buckets
if err := clearWalletState(lnwallet); err != nil &&
err != bolt.ErrBucketNotFound {
t.Fatalf("unable to wipe wallet state: %v", err)
var bio lnwallet.BlockChainIO
var signer lnwallet.Signer
var wc lnwallet.WalletController
for _, walletDriver := range lnwallet.RegisteredWallets() {
tempTestDir, err := ioutil.TempDir("", "lnwallet")
if err != nil {
t.Fatalf("unable to create temp directory: %v", err)
}
defer os.RemoveAll(tempTestDir)
walletType := walletDriver.WalletType
switch walletType {
case "btcwallet":
btcwalletConfig := &btcwallet.Config{
PrivatePass: privPass,
HdSeed: testHdSeed[:],
DataDir: tempTestDir,
NetParams: netParams,
RpcHost: rpcConfig.Host,
RpcUser: rpcConfig.User,
RpcPass: rpcConfig.Pass,
CACert: rpcConfig.Certificates,
}
wc, err = walletDriver.New(btcwalletConfig)
if err != nil {
t.Fatalf("unable to create btcwallet: %v", err)
}
signer = wc.(*btcwallet.BtcWallet)
bio = wc.(*btcwallet.BtcWallet)
default:
t.Fatalf("unknown wallet driver: %v", walletType)
}
// Funding via 20 outputs with 4BTC each.
lnwallet, err := createTestWallet(tempTestDir, miningNode, netParams,
chainNotifier, wc, signer, bio)
if err != nil {
t.Fatalf("unable to create test ln wallet: %v", err)
}
// The wallet should now have 80BTC available for spending.
assertProperBalance(t, lnwallet, 1, 80)
// Execute every test, clearing possibly mutated wallet state after
// each step.
for _, walletTest := range walletTests {
walletTest(miningNode, lnwallet, t)
// TODO(roasbeef): possible reset mining node's chainstate to
// initial level, cleanly wipe buckets
if err := clearWalletState(lnwallet); err != nil &&
err != bolt.ErrBucketNotFound {
t.Fatalf("unable to wipe wallet state: %v", err)
}
}
lnwallet.Shutdown()
}
}

@ -88,7 +88,6 @@ type InputScript struct {
// as a signature to our version of the commitment transaction.
// * We then verify the validity of all signatures before considering the
// channel "open".
// TODO(roasbeef): update with single funder description
type ChannelReservation struct {
// This mutex MUST be held when either reading or modifying any of the
// fields below.
@ -131,11 +130,11 @@ type ChannelReservation struct {
wallet *LightningWallet
}
// newChannelReservation creates a new channel reservation. This function is
// NewChannelReservation creates a new channel reservation. This function is
// used only internally by lnwallet. In order to concurrent safety, the creation
// of all channel reservations should be carried out via the
// lnwallet.InitChannelReservation interface.
func newChannelReservation(capacity, fundingAmt btcutil.Amount, minFeeRate btcutil.Amount,
func NewChannelReservation(capacity, fundingAmt btcutil.Amount, minFeeRate btcutil.Amount,
wallet *LightningWallet, id uint64, numConfs uint16) *ChannelReservation {
var ourBalance btcutil.Amount
var theirBalance btcutil.Amount
@ -160,7 +159,7 @@ func newChannelReservation(capacity, fundingAmt btcutil.Amount, minFeeRate btcut
OurBalance: ourBalance,
TheirBalance: theirBalance,
MinFeePerKb: minFeeRate,
Db: wallet.channelDB,
Db: wallet.ChannelDB,
},
numConfsToOpen: numConfs,
reservationID: id,
@ -315,6 +314,26 @@ func (r *ChannelReservation) FinalFundingTx() *wire.MsgTx {
return r.fundingTx
}
// FundingRedeemScript returns the fully populated funding redeem script.
//
// NOTE: This method will only return a non-nil value after either
// ProcesContribution or ProcessSingleContribution have been executed and
// returned without error.
func (r *ChannelReservation) FundingRedeemScript() []byte {
r.RLock()
defer r.RUnlock()
return r.partialState.FundingRedeemScript
}
// LocalCommitTx returns the commitment transaction for the local node involved
// in this funding reservation.
func (r *ChannelReservation) LocalCommitTx() *wire.MsgTx {
r.RLock()
defer r.RUnlock()
return r.partialState.OurCommitTx
}
// FundingOutpoint returns the outpoint of the funding transaction.
//
// NOTE: The pointer returned will only be set once the .ProcesContribution()
@ -346,6 +365,7 @@ func (r *ChannelReservation) Cancel() error {
// transaction for this pending payment channel obtains the configured number
// of confirmations. Once confirmations have been obtained, a fully initialized
// LightningChannel instance is returned, allowing for channel updates.
//
// NOTE: If this method is called before .CompleteReservation(), it will block
// indefinitely.
func (r *ChannelReservation) DispatchChan() <-chan *LightningChannel {

@ -4,7 +4,6 @@ import (
"encoding/hex"
"errors"
"fmt"
"math"
"sync"
"sync/atomic"
@ -12,17 +11,14 @@ import (
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/elkrem"
"github.com/roasbeef/btcd/btcjson"
"github.com/roasbeef/btcd/chaincfg"
"github.com/roasbeef/btcutil/hdkeychain"
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/txscript"
"github.com/roasbeef/btcd/wire"
"github.com/roasbeef/btcutil"
"github.com/roasbeef/btcutil/coinset"
"github.com/roasbeef/btcutil/txsort"
"github.com/roasbeef/btcwallet/chain"
"github.com/roasbeef/btcwallet/waddrmgr"
btcwallet "github.com/roasbeef/btcwallet/wallet"
)
const (
@ -33,6 +29,13 @@ const (
// elkremRootIndex is the top level HD key index from which secrets
// used to generate elkrem roots should be derived from.
elkremRootIndex = hdkeychain.HardenedKeyStart + 1
// identityKeyIndex is the top level HD key index which is used to
// generate/rotate identity keys.
//
// TODO(roasbeef): should instead be child to make room for future
// rotations, etc.
identityKeyIndex = hdkeychain.HardenedKeyStart + 2
)
var (
@ -221,7 +224,7 @@ type channelOpenMsg struct {
type LightningWallet struct {
// This mutex is to be held when generating external keys to be used
// as multi-sig, and commitment keys within the channel.
KeyGenMtx sync.RWMutex
keyGenMtx sync.RWMutex
// This mutex MUST be held when performing coin selection in order to
// avoid inadvertently creating multiple funding transaction which
@ -231,25 +234,27 @@ type LightningWallet struct {
// A wrapper around a namespace within boltdb reserved for ln-based
// wallet meta-data. See the 'channeldb' package for further
// information.
channelDB *channeldb.DB
ChannelDB *channeldb.DB
// Used by in order to obtain notifications about funding transaction
// reaching a specified confirmation depth, and to catch
// counterparty's broadcasting revoked commitment states.
chainNotifier chainntnfs.ChainNotifier
// The core wallet, all non Lightning Network specific interaction is
// proxied to the internal wallet.
*btcwallet.Wallet
// wallet is the the core wallet, all non Lightning Network specific
// interaction is proxied to the internal wallet.
WalletController
// Signer is the wallet's current Signer implementation. This Signer is
// used to generate signature for all inputs to potential funding
// transactions, as well as for spends from the funding transaction to
// update the commitment state.
Signer Signer
// chainIO is an instance of the BlockChainIO interface. chainIO is
// used to lookup the existance of outputs within the utxo set.
chainIO BlockChainIO
// An active RPC connection to a full-node. In the case of a btcd node,
// websockets are used for notifications. If using Bitcoin Core,
// notifications are either generated via long-polling or the usage of
// ZeroMQ.
// TODO(roasbeef): make into interface need: getrawtransaction + gettxout
// * getrawtransaction -> verify proof of channel links
// * gettxout -> verify inputs to funding tx exist and are unspent
rpc *chain.RPCClient
// rootKey is the root HD key dervied from a WalletController private
// key. This rootKey is used to derive all LN specific secrets.
rootKey *hdkeychain.ExtendedKey
@ -269,7 +274,12 @@ type LightningWallet struct {
// TODO(roasbeef): zombie garbage collection routine to solve
// lost-object/starvation problem/attack.
cfg *Config
// lockedOutPoints is a set of the currently locked outpoint. This
// information is kept in order to provide an easy way to unlock all
// the currently locked outpoints.
lockedOutPoints map[wire.OutPoint]struct{}
netParams *chaincfg.Params
started int32
shutdown int32
@ -286,21 +296,12 @@ type LightningWallet struct {
//
// NOTE: The passed channeldb, and ChainNotifier should already be fully
// initialized/started before being passed as a function arugment.
func NewLightningWallet(config *Config, cdb *channeldb.DB,
notifier chainntnfs.ChainNotifier) (*LightningWallet, error) {
func NewLightningWallet(cdb *channeldb.DB, notifier chainntnfs.ChainNotifier,
wallet WalletController, signer Signer, bio BlockChainIO,
netParams *chaincfg.Params) (*LightningWallet, error) {
// Ensure the wallet exists or create it when the create flag is set.
netDir := networkDir(config.DataDir, config.NetParams)
// TODO(roasbeef): need a another wallet level config
var pubPass []byte
if config.PublicPass == nil {
pubPass = defaultPubPassphrase
} else {
pubPass = config.PublicPass
}
loader := btcwallet.NewLoader(config.NetParams, netDir)
walletExists, err := loader.WalletExists()
// Fetch the root derivation key from the wallet's HD chain. We'll use
// this to generate specific Lightning related secrets on the fly.
rootKey, err := wallet.FetchRootKey()
@ -308,68 +309,25 @@ func NewLightningWallet(config *Config, cdb *channeldb.DB,
return nil, err
}
var createID bool
var wallet *btcwallet.Wallet
if !walletExists {
// Wallet has never been created, perform initial set up.
wallet, err = loader.CreateNewWallet(pubPass, config.PrivatePass,
config.HdSeed)
if err != nil {
return nil, err
}
createID = true
} 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(config.PrivatePass); err != nil {
return nil, err
}
// If we just created the wallet, then reserve, and store a key for
// our ID within the Lightning Network.
if createID {
account := uint32(waddrmgr.DefaultAccountNum)
adrs, err := wallet.Manager.NextInternalAddresses(account, 1, waddrmgr.WitnessPubKey)
if err != nil {
return nil, err
}
idPubkeyHash := adrs[0].Address().ScriptAddress()
if err := cdb.PutIdKey(idPubkeyHash); err != nil {
return nil, err
}
walletLog.Infof("stored identity key pubkey hash in channeldb")
}
// Create a special websockets rpc client for btcd which will be used
// by the wallet for notifications, calls, etc.
rpcc, err := chain.NewRPCClient(config.NetParams, config.RpcHost,
config.RpcUser, config.RpcPass, config.CACert, false, 20)
// TODO(roasbeef): always re-derive on the fly?
rootKeyRaw := rootKey.Serialize()
rootMasterKey, err := hdkeychain.NewMaster(rootKeyRaw, netParams)
if err != nil {
return nil, err
}
return &LightningWallet{
chainNotifier: notifier,
rpc: rpcc,
Wallet: wallet,
channelDB: cdb,
msgChan: make(chan interface{}, msgBufferSize),
// TODO(roasbeef): make this atomic.Uint32 instead? Which is
// faster, locks or CAS? I'm guessing CAS because assembly:
// * https://golang.org/src/sync/atomic/asm_amd64.s
nextFundingID: 0,
cfg: config,
fundingLimbo: make(map[uint64]*ChannelReservation),
quit: make(chan struct{}),
rootKey: rootMasterKey,
chainNotifier: notifier,
Signer: signer,
WalletController: wallet,
chainIO: bio,
ChannelDB: cdb,
msgChan: make(chan interface{}, msgBufferSize),
nextFundingID: 0,
fundingLimbo: make(map[uint64]*ChannelReservation),
lockedOutPoints: make(map[wire.OutPoint]struct{}),
quit: make(chan struct{}),
}, nil
}
@ -381,16 +339,10 @@ func (l *LightningWallet) Startup() error {
return nil
}
// Establish an RPC connection in additino to starting the goroutines
// in the underlying wallet.
if err := l.rpc.Start(); err != nil {
// Start the underlying wallet controller.
if err := l.Start(); err != nil {
return err
}
l.Start()
// Pass the rpc client into the wallet so it can sync up to the
// current main chain.
l.SynchronizeRPC(l.rpc)
l.wg.Add(1)
// TODO(roasbeef): multiple request handlers?
@ -407,16 +359,59 @@ func (l *LightningWallet) Shutdown() error {
// Signal the underlying wallet controller to shutdown, waiting until
// all active goroutines have been shutdown.
l.Stop()
l.WaitForShutdown()
l.rpc.Shutdown()
if err := l.Stop(); err != nil {
return err
}
close(l.quit)
l.wg.Wait()
return nil
}
// LockOutpoints returns a list of all currently locked outpoint.
func (l *LightningWallet) LockedOutpoints() []*wire.OutPoint {
outPoints := make([]*wire.OutPoint, 0, len(l.lockedOutPoints))
for outPoint := range l.lockedOutPoints {
outPoints = append(outPoints, &outPoint)
}
return outPoints
}
// ResetReservations reset the volatile wallet state which trakcs all currently
// active reservations.
func (l *LightningWallet) ResetReservations() {
l.nextFundingID = 0
l.fundingLimbo = make(map[uint64]*ChannelReservation)
for outpoint := range l.lockedOutPoints {
l.UnlockOutpoint(outpoint)
}
l.lockedOutPoints = make(map[wire.OutPoint]struct{})
}
// ActiveReservations returns a slice of all the currently active
// (non-cancalled) reservations.
func (l *LightningWallet) ActiveReservations() []*ChannelReservation {
reservations := make([]*ChannelReservation, 0, len(l.fundingLimbo))
for _, reservation := range l.fundingLimbo {
reservations = append(reservations, reservation)
}
return reservations
}
// GetIdentitykey returns the identity private key of the wallet.
// TODO(roasbeef): should be moved elsewhere
func (l *LightningWallet) GetIdentitykey() (*btcec.PrivateKey, error) {
identityKey, err := l.rootKey.Child(identityKeyIndex)
if err != nil {
return nil, err
}
return identityKey.ECPrivKey()
}
// requestHandler is the primary goroutine(s) resposible for handling, and
// dispatching relies to all messages.
func (l *LightningWallet) requestHandler() {
@ -489,16 +484,9 @@ func (l *LightningWallet) InitChannelReservation(capacity,
// handleFundingReserveRequest processes a message intending to create, and
// validate a funding reservation request.
func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg) {
// Create a limbo and record entry for this newly pending funding request.
l.limboMtx.Lock()
id := l.nextFundingID
reservation := newChannelReservation(req.capacity, req.fundingAmount,
id := atomic.AddUint64(&l.nextFundingID, 1)
reservation := NewChannelReservation(req.capacity, req.fundingAmount,
req.minFeeRate, l, id, req.numConfs)
l.nextFundingID++
l.fundingLimbo[id] = reservation
l.limboMtx.Unlock()
// Grab the mutex on the ChannelReservation to ensure thead-safety
reservation.Lock()
@ -526,27 +514,26 @@ func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg
// Grab two fresh keys from our HD chain, one will be used for the
// multi-sig funding transaction, and the other for the commitment
// transaction.
multiSigKey, err := l.getNextRawKey()
multiSigKey, err := l.NewRawKey()
if err != nil {
req.err <- err
req.resp <- nil
return
}
commitKey, err := l.getNextRawKey()
commitKey, err := l.NewRawKey()
if err != nil {
req.err <- err
req.resp <- nil
return
}
reservation.partialState.OurMultiSigKey = multiSigKey
ourContribution.MultiSigKey = multiSigKey.PubKey()
ourContribution.MultiSigKey = multiSigKey
reservation.partialState.OurCommitKey = commitKey
ourContribution.CommitKey = commitKey.PubKey()
ourContribution.CommitKey = commitKey
// Generate a fresh address to be used in the case of a cooperative
// channel close.
deliveryAddress, err := l.NewAddress(waddrmgr.DefaultAccountNum,
waddrmgr.WitnessPubKey)
deliveryAddress, err := l.NewAddress(WitnessPubKey, false)
if err != nil {
req.err <- err
req.resp <- nil
@ -561,6 +548,12 @@ func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg
reservation.partialState.OurDeliveryScript = deliveryScript
ourContribution.DeliveryAddress = deliveryAddress
// Create a limbo and record entry for this newly pending funding
// request.
l.limboMtx.Lock()
l.fundingLimbo[id] = reservation
l.limboMtx.Unlock()
// Funding reservation request succesfully handled. The funding inputs
// will be marked as unavailable until the reservation is either
// completed, or cancecled.
@ -591,6 +584,7 @@ func (l *LightningWallet) handleFundingCancelRequest(req *fundingReserveCancelMs
// Mark all previously locked outpoints as usuable for future funding
// requests.
for _, unusedInput := range pendingReservation.ourContribution.Inputs {
delete(l.lockedOutPoints, unusedInput.PreviousOutPoint)
l.UnlockOutpoint(unusedInput.PreviousOutPoint)
}
@ -652,7 +646,7 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
// Finally, add the 2-of-2 multi-sig output which will set up the lightning
// channel.
channelCapacity := int64(pendingReservation.partialState.Capacity)
redeemScript, multiSigOut, err := genFundingPkScript(ourKey.PubKey().SerializeCompressed(),
redeemScript, multiSigOut, err := GenFundingPkScript(ourKey.SerializeCompressed(),
theirKey.SerializeCompressed(), channelCapacity)
if err != nil {
req.err <- err
@ -660,21 +654,6 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
}
pendingReservation.partialState.FundingRedeemScript = redeemScript
// Register intent for notifications related to the funding output.
// This'll allow us to properly track the number of confirmations the
// funding tx has once it has been broadcasted.
// TODO(roasbeef): remove
lastBlock := l.Manager.SyncedTo()
scriptAddr, err := l.Manager.ImportScript(redeemScript, &lastBlock)
if err != nil {
req.err <- err
return
}
if err := l.rpc.NotifyReceived([]btcutil.Address{scriptAddr.Address()}); err != nil {
req.err <- err
return
}
// Sort the transaction. Since both side agree to a cannonical
// ordering, by sorting we no longer need to send the entire
// transaction. Only signatures will be exchanged.
@ -684,81 +663,30 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
// Next, sign all inputs that are ours, collecting the signatures in
// order of the inputs.
pendingReservation.ourFundingInputScripts = make([]*InputScript, 0, len(ourContribution.Inputs))
hashCache := txscript.NewTxSigHashes(fundingTx)
signDesc := SignDescriptor{
HashType: txscript.SigHashAll,
SigHashes: txscript.NewTxSigHashes(fundingTx),
}
for i, txIn := range fundingTx.TxIn {
// Does the wallet know about the txin?
txDetail, _ := l.TxStore.TxDetails(&txIn.PreviousOutPoint.Hash)
if txDetail == nil {
info, err := l.FetchInputInfo(&txIn.PreviousOutPoint)
if err == ErrNotMine {
continue
}
// Is this our txin?
prevIndex := txIn.PreviousOutPoint.Index
prevOut := txDetail.TxRecord.MsgTx.TxOut[prevIndex]
_, addrs, _, _ := txscript.ExtractPkScriptAddrs(prevOut.PkScript, l.cfg.NetParams)
apkh := addrs[0]
ai, err := l.Manager.Address(apkh)
if err != nil {
req.err <- fmt.Errorf("cannot get address info: %v", err)
return
}
pka := ai.(waddrmgr.ManagedPubKeyAddress)
privKey, err := pka.PrivKey()
if err != nil {
req.err <- fmt.Errorf("cannot get private key: %v", err)
} else if err != nil {
req.err <- err
return
}
var witnessProgram []byte
inputScript := &InputScript{}
signDesc.Output = info
signDesc.InputIndex = i
// If we're spending p2wkh output nested within a p2sh output,
// then we'll need to attach a sigScript in addition to witness
// data.
if pka.IsNestedWitness() {
pubKey := privKey.PubKey()
pubKeyHash := btcutil.Hash160(pubKey.SerializeCompressed())
// Next, we'll generate a valid sigScript that'll allow us to spend
// the p2sh output. The sigScript will contain only a single push of
// the p2wkh witness program corresponding to the matching public key
// of this address.
p2wkhAddr, err := btcutil.NewAddressWitnessPubKeyHash(pubKeyHash,
l.cfg.NetParams)
if err != nil {
req.err <- fmt.Errorf("unable to create p2wkh addr: %v", err)
return
}
witnessProgram, err = txscript.PayToAddrScript(p2wkhAddr)
if err != nil {
req.err <- fmt.Errorf("unable to create witness program: %v", err)
return
}
bldr := txscript.NewScriptBuilder()
bldr.AddData(witnessProgram)
sigScript, err := bldr.Script()
if err != nil {
req.err <- fmt.Errorf("unable to create scriptsig: %v", err)
return
}
txIn.SignatureScript = sigScript
inputScript.ScriptSig = sigScript
} else {
witnessProgram = prevOut.PkScript
}
// Generate a valid witness stack for the input.
inputValue := prevOut.Value
witnessScript, err := txscript.WitnessScript(fundingTx, hashCache, i,
inputValue, witnessProgram, txscript.SigHashAll, privKey, true)
inputScript, err := l.Signer.ComputeInputScript(fundingTx, &signDesc)
if err != nil {
req.err <- fmt.Errorf("cannot create witnessscript: %s", err)
req.err <- err
return
}
txIn.Witness = witnessScript
inputScript.Witness = witnessScript
txIn.SignatureScript = inputScript.ScriptSig
txIn.Witness = inputScript.Witness
pendingReservation.ourFundingInputScripts = append(
pendingReservation.ourFundingInputScripts,
inputScript,
@ -766,10 +694,10 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
}
// Locate the index of the multi-sig outpoint in order to record it
// since the outputs are cannonically sorted. If this is a sigle funder
// since the outputs are cannonically sorted. If this is a single funder
// workflow, then we'll also need to send this to the remote node.
fundingTxID := fundingTx.TxSha()
_, multiSigIndex := findScriptOutputIndex(fundingTx, multiSigOut.PkScript)
_, multiSigIndex := FindScriptOutputIndex(fundingTx, multiSigOut.PkScript)
fundingOutpoint := wire.NewOutPoint(&fundingTxID, multiSigIndex)
pendingReservation.partialState.FundingOutpoint = fundingOutpoint
@ -799,7 +727,7 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
return
}
theirCommitKey := theirContribution.CommitKey
ourRevokeKey := deriveRevocationPubkey(theirCommitKey, firstPreimage[:])
ourRevokeKey := DeriveRevocationPubkey(theirCommitKey, firstPreimage[:])
// Create the txIn to our commitment transaction; required to construct
// the commitment transactions.
@ -811,14 +739,14 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
ourBalance := ourContribution.FundingAmount
theirBalance := theirContribution.FundingAmount
ourCommitKey := ourContribution.CommitKey
ourCommitTx, err := createCommitTx(fundingTxIn, ourCommitKey, theirCommitKey,
ourCommitTx, err := CreateCommitTx(fundingTxIn, ourCommitKey, theirCommitKey,
ourRevokeKey, ourContribution.CsvDelay,
ourBalance, theirBalance)
if err != nil {
req.err <- err
return
}
theirCommitTx, err := createCommitTx(fundingTxIn, theirCommitKey, ourCommitKey,
theirCommitTx, err := CreateCommitTx(fundingTxIn, theirCommitKey, ourCommitKey,
theirContribution.RevocationKey, theirContribution.CsvDelay,
theirBalance, ourBalance)
if err != nil {
@ -849,10 +777,15 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
// Generate a signature for their version of the initial commitment
// transaction.
hashCache = txscript.NewTxSigHashes(theirCommitTx)
channelBalance := pendingReservation.partialState.Capacity
sigTheirCommit, err := txscript.RawTxInWitnessSignature(theirCommitTx, hashCache, 0,
int64(channelBalance), redeemScript, txscript.SigHashAll, ourKey)
signDesc = SignDescriptor{
RedeemScript: redeemScript,
PubKey: ourKey,
Output: multiSigOut,
HashType: txscript.SigHashAll,
SigHashes: txscript.NewTxSigHashes(theirCommitTx),
InputIndex: 0,
}
sigTheirCommit, err := l.Signer.SignOutputRaw(theirCommitTx, &signDesc)
if err != nil {
req.err <- err
return
@ -890,7 +823,7 @@ func (l *LightningWallet) handleSingleContribution(req *addSingleContributionMsg
ourKey := pendingReservation.partialState.OurMultiSigKey
theirKey := theirContribution.MultiSigKey
channelCapacity := int64(pendingReservation.partialState.Capacity)
redeemScript, _, err := genFundingPkScript(ourKey.PubKey().SerializeCompressed(),
redeemScript, _, err := GenFundingPkScript(ourKey.SerializeCompressed(),
theirKey.SerializeCompressed(), channelCapacity)
if err != nil {
req.err <- err
@ -915,7 +848,7 @@ func (l *LightningWallet) handleSingleContribution(req *addSingleContributionMsg
}
pendingReservation.partialState.LocalElkrem = elkremSender
theirCommitKey := theirContribution.CommitKey
ourRevokeKey := deriveRevocationPubkey(theirCommitKey, firstPreimage[:])
ourRevokeKey := DeriveRevocationPubkey(theirCommitKey, firstPreimage[:])
// Initialize an empty sha-chain for them, tracking the current pending
// revocation hash (we don't yet know the pre-image so we can't add it
@ -976,25 +909,16 @@ func (l *LightningWallet) handleFundingCounterPartySigs(msg *addCounterPartySigs
// Fetch the alleged previous output along with the
// pkscript referenced by this input.
prevOut := txin.PreviousOutPoint
output, err := l.rpc.GetTxOut(&prevOut.Hash, prevOut.Index, false)
output, err := l.chainIO.GetUtxo(&prevOut.Hash, prevOut.Index)
if output == nil {
msg.err <- fmt.Errorf("input to funding tx does not exist: %v", err)
return
}
pkScript, err := hex.DecodeString(output.ScriptPubKey.Hex)
if err != nil {
msg.err <- err
return
}
// Sadly, gettxout returns the output value in BTC
// instead of satoshis.
inputValue := int64(output.Value) * 1e8
// Ensure that the witness+sigScript combo is valid.
vm, err := txscript.NewEngine(pkScript,
vm, err := txscript.NewEngine(output.PkScript,
fundingTx, i, txscript.StandardVerifyFlags, nil,
fundingHashCache, inputValue)
fundingHashCache, output.Value)
if err != nil {
// TODO(roasbeef): cancel at this stage if invalid sigs?
msg.err <- fmt.Errorf("cannot create script engine: %s", err)
@ -1014,54 +938,37 @@ func (l *LightningWallet) handleFundingCounterPartySigs(msg *addCounterPartySigs
pendingReservation.theirCommitmentSig = msg.theirCommitmentSig
commitTx := pendingReservation.partialState.OurCommitTx
theirKey := pendingReservation.theirContribution.MultiSigKey
ourKey := pendingReservation.partialState.OurMultiSigKey
// Re-generate both the redeemScript and p2sh output. We sign the
// redeemScript script, but include the p2sh output as the subscript
// for verification.
redeemScript := pendingReservation.partialState.FundingRedeemScript
p2wsh, err := witnessScriptHash(redeemScript)
if err != nil {
msg.err <- err
return
}
// First, we sign our copy of the commitment transaction ourselves.
channelValue := int64(pendingReservation.partialState.Capacity)
hashCache := txscript.NewTxSigHashes(commitTx)
ourCommitSig, err := txscript.RawTxInWitnessSignature(commitTx, hashCache, 0,
channelValue, redeemScript, txscript.SigHashAll, ourKey)
if err != nil {
msg.err <- err
return
}
// Next, create the spending scriptSig, and then verify that the script
// is complete, allowing us to spend from the funding transaction.
theirCommitSig := msg.theirCommitmentSig
ourKeySer := ourKey.PubKey().SerializeCompressed()
theirKeySer := theirKey.SerializeCompressed()
witness := spendMultiSig(redeemScript, ourKeySer, ourCommitSig,
theirKeySer, theirCommitSig)
// Finally, create an instance of a Script VM, and ensure that the
// Script executes succesfully.
commitTx.TxIn[0].Witness = witness
vm, err := txscript.NewEngine(p2wsh,
commitTx, 0, txscript.StandardVerifyFlags, nil,
nil, channelValue)
channelValue := int64(pendingReservation.partialState.Capacity)
hashCache := txscript.NewTxSigHashes(commitTx)
sigHash, err := txscript.CalcWitnessSigHash(redeemScript, hashCache,
txscript.SigHashAll, commitTx, 0, channelValue)
if err != nil {
msg.err <- err
return
}
if err := vm.Execute(); err != nil {
msg.err <- fmt.Errorf("counterparty's commitment signature is invalid: %v", err)
return
}
// Strip and store the signature to ensure that our commitment
// transaction doesn't stay hot.
commitTx.TxIn[0].Witness = nil
walletLog.Infof("sighash verify: %v", hex.EncodeToString(sigHash))
walletLog.Infof("initer verifying tx: %v", spew.Sdump(commitTx))
// Verify that we've received a valid signature from the remote party
// for our version of the commitment transaction.
sig, err := btcec.ParseSignature(theirCommitSig, btcec.S256())
if err != nil {
msg.err <- err
return
} else if !sig.Verify(sigHash, theirKey) {
msg.err <- fmt.Errorf("counterparty's commitment signature is invalid")
return
}
pendingReservation.partialState.OurCommitSig = theirCommitSig
// Funding complete, this entry can be removed from limbo.
@ -1126,14 +1033,14 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) {
theirCommitKey := pendingReservation.theirContribution.CommitKey
ourBalance := pendingReservation.ourContribution.FundingAmount
theirBalance := pendingReservation.theirContribution.FundingAmount
ourCommitTx, err := createCommitTx(fundingTxIn, ourCommitKey, theirCommitKey,
ourCommitTx, err := CreateCommitTx(fundingTxIn, ourCommitKey, theirCommitKey,
pendingReservation.ourContribution.RevocationKey,
pendingReservation.ourContribution.CsvDelay, ourBalance, theirBalance)
if err != nil {
req.err <- err
return
}
theirCommitTx, err := createCommitTx(fundingTxIn, theirCommitKey, ourCommitKey,
theirCommitTx, err := CreateCommitTx(fundingTxIn, theirCommitKey, ourCommitKey,
req.revokeKey, pendingReservation.theirContribution.CsvDelay,
theirBalance, ourBalance)
if err != nil {
@ -1148,65 +1055,51 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) {
pendingReservation.partialState.OurCommitTx = ourCommitTx
txsort.InPlaceSort(theirCommitTx)
// Verify that their signature of valid for our current commitment
// transaction. Re-generate both the redeemScript and p2sh output. We
// sign the redeemScript script, but include the p2sh output as the
// subscript for verification.
// TODO(roasbeef): replace with regular sighash calculation once the PR
// is merged.
redeemScript := pendingReservation.partialState.FundingRedeemScript
p2wsh, err := witnessScriptHash(redeemScript)
if err != nil {
req.err <- err
return
}
// TODO(roasbeef): remove all duplication after merge.
// First, we sign our copy of the commitment transaction ourselves.
channelValue := int64(pendingReservation.partialState.Capacity)
hashCache := txscript.NewTxSigHashes(ourCommitTx)
theirKey := pendingReservation.theirContribution.MultiSigKey
ourKey := pendingReservation.partialState.OurMultiSigKey
ourCommitSig, err := txscript.RawTxInWitnessSignature(ourCommitTx, hashCache, 0,
channelValue, redeemScript, txscript.SigHashAll, ourKey)
sigHash, err := txscript.CalcWitnessSigHash(redeemScript, hashCache,
txscript.SigHashAll, ourCommitTx, 0, channelValue)
if err != nil {
req.err <- err
return
}
// Next, create the spending scriptSig, and then verify that the script
// is complete, allowing us to spend from the funding transaction.
ourKeySer := ourKey.PubKey().SerializeCompressed()
theirKeySer := theirKey.SerializeCompressed()
witness := spendMultiSig(redeemScript, ourKeySer, ourCommitSig,
theirKeySer, req.theirCommitmentSig)
// Finally, create an instance of a Script VM, and ensure that the
// Script executes succesfully.
ourCommitTx.TxIn[0].Witness = witness
// TODO(roasbeef): replace engine with plain sighash check
vm, err := txscript.NewEngine(p2wsh, ourCommitTx, 0,
txscript.StandardVerifyFlags, nil, nil, channelValue)
// Verify that we've received a valid signature from the remote party
// for our version of the commitment transaction.
sig, err := btcec.ParseSignature(req.theirCommitmentSig, btcec.S256())
if err != nil {
req.err <- err
return
}
if err := vm.Execute(); err != nil {
req.err <- fmt.Errorf("counterparty's commitment signature is invalid: %v", err)
} else if !sig.Verify(sigHash, theirKey) {
req.err <- fmt.Errorf("counterparty's commitment signature is invalid")
return
}
// Strip and store the signature to ensure that our commitment
// transaction doesn't stay hot.
ourCommitTx.TxIn[0].Witness = nil
pendingReservation.partialState.OurCommitSig = req.theirCommitmentSig
// With their signature for our version of the commitment transactions
// verified, we can now generate a signature for their version,
// allowing the funding transaction to be safely broadcast.
hashCache = txscript.NewTxSigHashes(theirCommitTx)
sigTheirCommit, err := txscript.RawTxInWitnessSignature(theirCommitTx, hashCache, 0,
channelValue, redeemScript, txscript.SigHashAll, ourKey)
p2wsh, err := witnessScriptHash(redeemScript)
if err != nil {
req.err <- err
return
}
signDesc := SignDescriptor{
RedeemScript: redeemScript,
PubKey: ourKey,
Output: &wire.TxOut{
PkScript: p2wsh,
Value: channelValue,
},
HashType: txscript.SigHashAll,
SigHashes: txscript.NewTxSigHashes(theirCommitTx),
InputIndex: 0,
}
sigTheirCommit, err := l.Signer.SignOutputRaw(theirCommitTx, &signDesc)
if err != nil {
req.err <- err
return
@ -1249,8 +1142,7 @@ func (l *LightningWallet) handleChannelOpen(req *channelOpenMsg) {
// Finally, create and officially open the payment channel!
// TODO(roasbeef): CreationTime once tx is 'open'
channel, _ := NewLightningChannel(l, l.chainNotifier, l.channelDB,
res.partialState)
channel, _ := NewLightningChannel(l.Signer, l, l.chainNotifier, res.partialState)
res.chanOpen <- channel
req.err <- nil
@ -1291,61 +1183,11 @@ out:
// Finally, create and officially open the payment channel!
// TODO(roasbeef): CreationTime once tx is 'open'
channel, _ := NewLightningChannel(l, l.chainNotifier, l.channelDB,
channel, _ := NewLightningChannel(l.Signer, l, l.chainNotifier,
res.partialState)
res.chanOpen <- channel
}
// getNextRawKey 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.
// TODO(roasbeef): on shutdown, write state of pending keys, then read back?
func (l *LightningWallet) getNextRawKey() (*btcec.PrivateKey, error) {
l.KeyGenMtx.Lock()
defer l.KeyGenMtx.Unlock()
nextAddr, err := l.Manager.NextExternalAddresses(waddrmgr.DefaultAccountNum,
1, waddrmgr.WitnessPubKey)
if err != nil {
return nil, err
}
pkAddr := nextAddr[0].(waddrmgr.ManagedPubKeyAddress)
return pkAddr.PrivKey()
}
// ListUnspentWitness returns a slice of all the unspent outputs the wallet
// controls which pay to witness programs either directly or indirectly.
func (l *LightningWallet) ListUnspentWitness(minConfs int32) ([]*btcjson.ListUnspentResult, error) {
// First, grab all the unfiltered currently unspent outputs.
maxConfs := int32(math.MaxInt32)
unspentOutputs, err := l.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([]*btcjson.ListUnspentResult, 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) {
witnessOutputs = append(witnessOutputs, output)
}
}
return witnessOutputs, nil
}
// selectCoinsAndChange performs coin selection in order to obtain witness
// outputs which sum to at least 'numCoins' amount of satoshis. If coin
// selection is succesful/possible, then the selected coins are available within