Merge pull request #17 from lightningnetwork/wallet-interface-refactor
Refactor the lnwallet package to directly use the WalletController interface
This commit is contained in:
commit
dfe37cd699
@ -106,7 +106,7 @@ type OpenChannel struct {
|
|||||||
//ReserveAmount btcutil.Amount
|
//ReserveAmount btcutil.Amount
|
||||||
|
|
||||||
// Keys for both sides to be used for the commitment transactions.
|
// Keys for both sides to be used for the commitment transactions.
|
||||||
OurCommitKey *btcec.PrivateKey
|
OurCommitKey *btcec.PublicKey
|
||||||
TheirCommitKey *btcec.PublicKey
|
TheirCommitKey *btcec.PublicKey
|
||||||
|
|
||||||
// Tracking total channel capacity, and the amount of funds allocated
|
// Tracking total channel capacity, and the amount of funds allocated
|
||||||
@ -123,7 +123,7 @@ type OpenChannel struct {
|
|||||||
// The outpoint of the final funding transaction.
|
// The outpoint of the final funding transaction.
|
||||||
FundingOutpoint *wire.OutPoint
|
FundingOutpoint *wire.OutPoint
|
||||||
|
|
||||||
OurMultiSigKey *btcec.PrivateKey
|
OurMultiSigKey *btcec.PublicKey
|
||||||
TheirMultiSigKey *btcec.PublicKey
|
TheirMultiSigKey *btcec.PublicKey
|
||||||
FundingRedeemScript []byte
|
FundingRedeemScript []byte
|
||||||
|
|
||||||
@ -200,7 +200,7 @@ func (c *OpenChannel) FullSync() error {
|
|||||||
chanIDBucket.Put(b.Bytes(), nil)
|
chanIDBucket.Put(b.Bytes(), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
return putOpenChannel(chanBucket, nodeChanBucket, c, c.Db.cryptoSystem)
|
return putOpenChannel(chanBucket, nodeChanBucket, c)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -362,7 +362,7 @@ func putClosedChannelSummary(tx *bolt.Tx, chanID []byte) error {
|
|||||||
// putChannel serializes, and stores the current state of the channel in its
|
// putChannel serializes, and stores the current state of the channel in its
|
||||||
// entirety.
|
// entirety.
|
||||||
func putOpenChannel(openChanBucket *bolt.Bucket, nodeChanBucket *bolt.Bucket,
|
func putOpenChannel(openChanBucket *bolt.Bucket, nodeChanBucket *bolt.Bucket,
|
||||||
channel *OpenChannel, encryptor EncryptorDecryptor) error {
|
channel *OpenChannel) error {
|
||||||
|
|
||||||
// First write out all the "common" fields using the field's prefix
|
// First write out all the "common" fields using the field's prefix
|
||||||
// appened with the channel's ID. These fields go into a top-level bucket
|
// appened with the channel's ID. These fields go into a top-level bucket
|
||||||
@ -387,13 +387,13 @@ func putOpenChannel(openChanBucket *bolt.Bucket, nodeChanBucket *bolt.Bucket,
|
|||||||
if err := putChannelIDs(nodeChanBucket, channel); err != nil {
|
if err := putChannelIDs(nodeChanBucket, channel); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := putChanCommitKeys(nodeChanBucket, channel, encryptor); err != nil {
|
if err := putChanCommitKeys(nodeChanBucket, channel); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := putChanCommitTxns(nodeChanBucket, channel); err != nil {
|
if err := putChanCommitTxns(nodeChanBucket, channel); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := putChanFundingInfo(nodeChanBucket, channel, encryptor); err != nil {
|
if err := putChanFundingInfo(nodeChanBucket, channel); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := putChanEklremState(nodeChanBucket, channel); err != nil {
|
if err := putChanEklremState(nodeChanBucket, channel); err != nil {
|
||||||
@ -411,7 +411,7 @@ func putOpenChannel(openChanBucket *bolt.Bucket, nodeChanBucket *bolt.Bucket,
|
|||||||
// An EncryptorDecryptor is required to decrypt sensitive information stored
|
// An EncryptorDecryptor is required to decrypt sensitive information stored
|
||||||
// within the database.
|
// within the database.
|
||||||
func fetchOpenChannel(openChanBucket *bolt.Bucket, nodeChanBucket *bolt.Bucket,
|
func fetchOpenChannel(openChanBucket *bolt.Bucket, nodeChanBucket *bolt.Bucket,
|
||||||
chanID *wire.OutPoint, decryptor EncryptorDecryptor) (*OpenChannel, error) {
|
chanID *wire.OutPoint) (*OpenChannel, error) {
|
||||||
|
|
||||||
channel := &OpenChannel{
|
channel := &OpenChannel{
|
||||||
ChanID: chanID,
|
ChanID: chanID,
|
||||||
@ -421,13 +421,13 @@ func fetchOpenChannel(openChanBucket *bolt.Bucket, nodeChanBucket *bolt.Bucket,
|
|||||||
if err := fetchChannelIDs(nodeChanBucket, channel); err != nil {
|
if err := fetchChannelIDs(nodeChanBucket, channel); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := fetchChanCommitKeys(nodeChanBucket, channel, decryptor); err != nil {
|
if err := fetchChanCommitKeys(nodeChanBucket, channel); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := fetchChanCommitTxns(nodeChanBucket, channel); err != nil {
|
if err := fetchChanCommitTxns(nodeChanBucket, channel); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := fetchChanFundingInfo(nodeChanBucket, channel, decryptor); err != nil {
|
if err := fetchChanFundingInfo(nodeChanBucket, channel); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := fetchChanEklremState(nodeChanBucket, channel); err != nil {
|
if err := fetchChanEklremState(nodeChanBucket, channel); err != nil {
|
||||||
@ -791,8 +791,7 @@ func fetchChannelIDs(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func putChanCommitKeys(nodeChanBucket *bolt.Bucket, channel *OpenChannel,
|
func putChanCommitKeys(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error {
|
||||||
ed EncryptorDecryptor) error {
|
|
||||||
|
|
||||||
// Construct the key which stores the commitment keys: ckk || channelID.
|
// Construct the key which stores the commitment keys: ckk || channelID.
|
||||||
// TODO(roasbeef): factor into func
|
// TODO(roasbeef): factor into func
|
||||||
@ -810,12 +809,7 @@ func putChanCommitKeys(nodeChanBucket *bolt.Bucket, channel *OpenChannel,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptedPriv, err := ed.Encrypt(channel.OurCommitKey.Serialize())
|
if _, err := b.Write(channel.OurCommitKey.SerializeCompressed()); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := b.Write(encryptedPriv); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -829,8 +823,7 @@ func deleteChanCommitKeys(nodeChanBucket *bolt.Bucket, chanID []byte) error {
|
|||||||
return nodeChanBucket.Delete(commitKey)
|
return nodeChanBucket.Delete(commitKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchChanCommitKeys(nodeChanBucket *bolt.Bucket, channel *OpenChannel,
|
func fetchChanCommitKeys(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error {
|
||||||
ed EncryptorDecryptor) error {
|
|
||||||
|
|
||||||
// Construct the key which stores the commitment keys: ckk || channelID.
|
// Construct the key which stores the commitment keys: ckk || channelID.
|
||||||
// TODO(roasbeef): factor into func
|
// TODO(roasbeef): factor into func
|
||||||
@ -850,12 +843,7 @@ func fetchChanCommitKeys(nodeChanBucket *bolt.Bucket, channel *OpenChannel,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
decryptedPriv, err := ed.Decrypt(keyBytes[33:])
|
channel.OurCommitKey, err = btcec.ParsePubKey(keyBytes[33:], btcec.S256())
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
channel.OurCommitKey, _ = btcec.PrivKeyFromBytes(btcec.S256(), decryptedPriv)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -939,9 +927,7 @@ func fetchChanCommitTxns(nodeChanBucket *bolt.Bucket, channel *OpenChannel) erro
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func putChanFundingInfo(nodeChanBucket *bolt.Bucket, channel *OpenChannel,
|
func putChanFundingInfo(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error {
|
||||||
ed EncryptorDecryptor) error {
|
|
||||||
|
|
||||||
var bc bytes.Buffer
|
var bc bytes.Buffer
|
||||||
if err := writeOutpoint(&bc, channel.ChanID); err != nil {
|
if err := writeOutpoint(&bc, channel.ChanID); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -956,11 +942,8 @@ func putChanFundingInfo(nodeChanBucket *bolt.Bucket, channel *OpenChannel,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptedPriv, err := ed.Encrypt(channel.OurMultiSigKey.Serialize())
|
ourSerKey := channel.OurMultiSigKey.SerializeCompressed()
|
||||||
if err != nil {
|
if err := wire.WriteVarBytes(&b, 0, ourSerKey); err != nil {
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := wire.WriteVarBytes(&b, 0, encryptedPriv); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
theirSerKey := channel.TheirMultiSigKey.SerializeCompressed()
|
theirSerKey := channel.TheirMultiSigKey.SerializeCompressed()
|
||||||
@ -989,9 +972,7 @@ func deleteChanFundingInfo(nodeChanBucket *bolt.Bucket, chanID []byte) error {
|
|||||||
return nodeChanBucket.Delete(fundTxnKey)
|
return nodeChanBucket.Delete(fundTxnKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchChanFundingInfo(nodeChanBucket *bolt.Bucket, channel *OpenChannel,
|
func fetchChanFundingInfo(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error {
|
||||||
ed EncryptorDecryptor) error {
|
|
||||||
|
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
if err := writeOutpoint(&b, channel.ChanID); err != nil {
|
if err := writeOutpoint(&b, channel.ChanID); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -1008,17 +989,16 @@ func fetchChanFundingInfo(nodeChanBucket *bolt.Bucket, channel *OpenChannel,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptedPrivBytes, err := wire.ReadVarBytes(infoBytes, 0, 100, "")
|
ourKeyBytes, err := wire.ReadVarBytes(infoBytes, 0, 34, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
decryptedPriv, err := ed.Decrypt(encryptedPrivBytes)
|
channel.OurMultiSigKey, err = btcec.ParsePubKey(ourKeyBytes, btcec.S256())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
channel.OurMultiSigKey, _ = btcec.PrivKeyFromBytes(btcec.S256(), decryptedPriv)
|
|
||||||
|
|
||||||
theirKeyBytes, err := wire.ReadVarBytes(infoBytes, 0, 33, "")
|
theirKeyBytes, err := wire.ReadVarBytes(infoBytes, 0, 34, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -78,23 +78,6 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
type MockEncryptorDecryptor struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockEncryptorDecryptor) Encrypt(n []byte) ([]byte, error) {
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockEncryptorDecryptor) Decrypt(n []byte) ([]byte, error) {
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockEncryptorDecryptor) OverheadSize() uint32 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ EncryptorDecryptor = (*MockEncryptorDecryptor)(nil)
|
|
||||||
|
|
||||||
func TestOpenChannelPutGetDelete(t *testing.T) {
|
func TestOpenChannelPutGetDelete(t *testing.T) {
|
||||||
// First, create a temporary directory to be used for the duration of
|
// First, create a temporary directory to be used for the duration of
|
||||||
// this test.
|
// this test.
|
||||||
@ -111,7 +94,6 @@ func TestOpenChannelPutGetDelete(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create channeldb: %v", err)
|
t.Fatalf("unable to create channeldb: %v", err)
|
||||||
}
|
}
|
||||||
cdb.RegisterCryptoSystem(&MockEncryptorDecryptor{})
|
|
||||||
defer cdb.Close()
|
defer cdb.Close()
|
||||||
|
|
||||||
privKey, pubKey := btcec.PrivKeyFromBytes(btcec.S256(), key[:])
|
privKey, pubKey := btcec.PrivKeyFromBytes(btcec.S256(), key[:])
|
||||||
@ -144,7 +126,7 @@ func TestOpenChannelPutGetDelete(t *testing.T) {
|
|||||||
TheirLNID: key,
|
TheirLNID: key,
|
||||||
ChanID: id,
|
ChanID: id,
|
||||||
MinFeePerKb: btcutil.Amount(5000),
|
MinFeePerKb: btcutil.Amount(5000),
|
||||||
OurCommitKey: privKey,
|
OurCommitKey: privKey.PubKey(),
|
||||||
TheirCommitKey: pubKey,
|
TheirCommitKey: pubKey,
|
||||||
Capacity: btcutil.Amount(10000),
|
Capacity: btcutil.Amount(10000),
|
||||||
OurBalance: btcutil.Amount(3000),
|
OurBalance: btcutil.Amount(3000),
|
||||||
@ -154,7 +136,7 @@ func TestOpenChannelPutGetDelete(t *testing.T) {
|
|||||||
LocalElkrem: sender,
|
LocalElkrem: sender,
|
||||||
RemoteElkrem: receiver,
|
RemoteElkrem: receiver,
|
||||||
FundingOutpoint: testOutpoint,
|
FundingOutpoint: testOutpoint,
|
||||||
OurMultiSigKey: privKey,
|
OurMultiSigKey: privKey.PubKey(),
|
||||||
TheirMultiSigKey: privKey.PubKey(),
|
TheirMultiSigKey: privKey.PubKey(),
|
||||||
FundingRedeemScript: script,
|
FundingRedeemScript: script,
|
||||||
TheirCurrentRevocation: privKey.PubKey(),
|
TheirCurrentRevocation: privKey.PubKey(),
|
||||||
@ -195,8 +177,8 @@ func TestOpenChannelPutGetDelete(t *testing.T) {
|
|||||||
t.Fatalf("fee/kb doens't match")
|
t.Fatalf("fee/kb doens't match")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bytes.Equal(state.OurCommitKey.Serialize(),
|
if !bytes.Equal(state.OurCommitKey.SerializeCompressed(),
|
||||||
newState.OurCommitKey.Serialize()) {
|
newState.OurCommitKey.SerializeCompressed()) {
|
||||||
t.Fatalf("our commit key dont't match")
|
t.Fatalf("our commit key dont't match")
|
||||||
}
|
}
|
||||||
if !bytes.Equal(state.TheirCommitKey.SerializeCompressed(),
|
if !bytes.Equal(state.TheirCommitKey.SerializeCompressed(),
|
||||||
@ -234,8 +216,8 @@ func TestOpenChannelPutGetDelete(t *testing.T) {
|
|||||||
t.Fatalf("funding outpoint doesn't match")
|
t.Fatalf("funding outpoint doesn't match")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bytes.Equal(state.OurMultiSigKey.Serialize(),
|
if !bytes.Equal(state.OurMultiSigKey.SerializeCompressed(),
|
||||||
newState.OurMultiSigKey.Serialize()) {
|
newState.OurMultiSigKey.SerializeCompressed()) {
|
||||||
t.Fatalf("our multisig key doesn't match")
|
t.Fatalf("our multisig key doesn't match")
|
||||||
}
|
}
|
||||||
if !bytes.Equal(state.TheirMultiSigKey.SerializeCompressed(),
|
if !bytes.Equal(state.TheirMultiSigKey.SerializeCompressed(),
|
||||||
|
@ -27,14 +27,6 @@ var bufPool = &sync.Pool{
|
|||||||
New: func() interface{} { return new(bytes.Buffer) },
|
New: func() interface{} { return new(bytes.Buffer) },
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncryptorDecryptor...
|
|
||||||
// TODO(roasbeef): ability to rotate EncryptorDecryptor's across DB
|
|
||||||
type EncryptorDecryptor interface {
|
|
||||||
Encrypt(in []byte) ([]byte, error)
|
|
||||||
Decrypt(in []byte) ([]byte, error)
|
|
||||||
OverheadSize() uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// DB is the primary datastore for the LND daemon. The database stores
|
// DB is the primary datastore for the LND daemon. The database stores
|
||||||
// information related to nodes, routing data, open/closed channels, fee
|
// information related to nodes, routing data, open/closed channels, fee
|
||||||
// schedules, and reputation data.
|
// schedules, and reputation data.
|
||||||
@ -42,8 +34,6 @@ type DB struct {
|
|||||||
store *bolt.DB
|
store *bolt.DB
|
||||||
|
|
||||||
netParams *chaincfg.Params
|
netParams *chaincfg.Params
|
||||||
|
|
||||||
cryptoSystem EncryptorDecryptor
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open opens an existing channeldb created under the passed namespace with
|
// Open opens an existing channeldb created under the passed namespace with
|
||||||
@ -66,12 +56,6 @@ func Open(dbPath string, netParams *chaincfg.Params) (*DB, error) {
|
|||||||
return &DB{store: bdb, netParams: netParams}, nil
|
return &DB{store: bdb, netParams: netParams}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterCryptoSystem registers an implementation of the EncryptorDecryptor
|
|
||||||
// interface for use within the database to encrypt/decrypt sensitive data.
|
|
||||||
func (d *DB) RegisterCryptoSystem(ed EncryptorDecryptor) {
|
|
||||||
d.cryptoSystem = ed
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wipe completely deletes all saved state within all used buckets within the
|
// Wipe completely deletes all saved state within all used buckets within the
|
||||||
// database. The deletion is done in a single transaction, therefore this
|
// database. The deletion is done in a single transaction, therefore this
|
||||||
// operation is fully atomic.
|
// operation is fully atomic.
|
||||||
@ -179,7 +163,7 @@ func (d *DB) FetchOpenChannels(nodeID *wire.ShaHash) ([]*OpenChannel, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
oChannel, err := fetchOpenChannel(openChanBucket,
|
oChannel, err := fetchOpenChannel(openChanBucket,
|
||||||
nodeChanBucket, chanID, d.cryptoSystem)
|
nodeChanBucket, chanID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -469,7 +469,6 @@ func (f *fundingManager) handleFundingComplete(fmsg *fundingCompleteMsg) {
|
|||||||
// Append a sighash type of SigHashAll to the signature as it's the
|
// Append a sighash type of SigHashAll to the signature as it's the
|
||||||
// sighash type used implicitly within this type of channel for
|
// sighash type used implicitly within this type of channel for
|
||||||
// commitment transactions.
|
// commitment transactions.
|
||||||
commitSig = append(commitSig, byte(txscript.SigHashAll))
|
|
||||||
revokeKey := fmsg.msg.RevocationKey
|
revokeKey := fmsg.msg.RevocationKey
|
||||||
if err := resCtx.reservation.CompleteReservationSingle(revokeKey, fundingOut, commitSig); err != nil {
|
if err := resCtx.reservation.CompleteReservationSingle(revokeKey, fundingOut, commitSig); err != nil {
|
||||||
// TODO(roasbeef): better error logging: peerID, channelID, etc.
|
// TODO(roasbeef): better error logging: peerID, channelID, etc.
|
||||||
@ -521,7 +520,7 @@ func (f *fundingManager) handleFundingSignComplete(fmsg *fundingSignCompleteMsg)
|
|||||||
// The remote peer has responded with a signature for our commitment
|
// The remote peer has responded with a signature for our commitment
|
||||||
// transaction. We'll verify the signature for validity, then commit
|
// transaction. We'll verify the signature for validity, then commit
|
||||||
// the state to disk as we can now open the channel.
|
// the state to disk as we can now open the channel.
|
||||||
commitSig := append(fmsg.msg.CommitSignature.Serialize(), byte(txscript.SigHashAll))
|
commitSig := fmsg.msg.CommitSignature.Serialize()
|
||||||
if err := resCtx.reservation.CompleteReservation(nil, commitSig); err != nil {
|
if err := resCtx.reservation.CompleteReservation(nil, commitSig); err != nil {
|
||||||
fndgLog.Errorf("unable to complete reservation sign complete: %v", err)
|
fndgLog.Errorf("unable to complete reservation sign complete: %v", err)
|
||||||
fmsg.peer.Disconnect()
|
fmsg.peer.Disconnect()
|
||||||
|
14
glide.lock
generated
14
glide.lock
generated
@ -1,5 +1,5 @@
|
|||||||
hash: 348cab6c25a05211caed34dc711bd1cb5a51800bc87d3609127ff9271b206c42
|
hash: d11bb1bf4ed6df842559ffff8fcf859509382395ba38fc29a0de546526ecbcd4
|
||||||
updated: 2016-09-02T08:34:25.843272647-04:00
|
updated: 2016-09-08T12:08:13.98576317-07:00
|
||||||
imports:
|
imports:
|
||||||
- name: github.com/awalterschulze/gographviz
|
- name: github.com/awalterschulze/gographviz
|
||||||
version: cafbade2d58068c3992f12afe46742195c673d2b
|
version: cafbade2d58068c3992f12afe46742195c673d2b
|
||||||
@ -51,7 +51,7 @@ imports:
|
|||||||
- name: github.com/codahale/chacha20poly1305
|
- name: github.com/codahale/chacha20poly1305
|
||||||
version: f8a5c48301822c3d7dd26d78e68ea2968db0ab20
|
version: f8a5c48301822c3d7dd26d78e68ea2968db0ab20
|
||||||
- name: github.com/davecgh/go-spew
|
- name: github.com/davecgh/go-spew
|
||||||
version: 6cf5744a041a0022271cefed95ba843f6d87fd51
|
version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9
|
||||||
subpackages:
|
subpackages:
|
||||||
- spew
|
- spew
|
||||||
- name: github.com/golang/protobuf
|
- name: github.com/golang/protobuf
|
||||||
@ -82,7 +82,7 @@ imports:
|
|||||||
- txsort
|
- txsort
|
||||||
- base58
|
- base58
|
||||||
- name: github.com/roasbeef/btcwallet
|
- name: github.com/roasbeef/btcwallet
|
||||||
version: d7d402cc4135a53230ce068dcc51252c58a11a3d
|
version: 1fd2d6224698e14591d06f2a10b24e86494cc19f
|
||||||
subpackages:
|
subpackages:
|
||||||
- chain
|
- chain
|
||||||
- waddrmgr
|
- waddrmgr
|
||||||
@ -102,7 +102,7 @@ imports:
|
|||||||
- name: github.com/urfave/cli
|
- name: github.com/urfave/cli
|
||||||
version: a14d7d367bc02b1f57d88de97926727f2d936387
|
version: a14d7d367bc02b1f57d88de97926727f2d936387
|
||||||
- name: golang.org/x/crypto
|
- name: golang.org/x/crypto
|
||||||
version: f160b6bf95857cd862817875dd958be022e587c4
|
version: 9e590154d2353f3f5e1b24da7275686040dcf491
|
||||||
subpackages:
|
subpackages:
|
||||||
- hkdf
|
- hkdf
|
||||||
- nacl/secretbox
|
- nacl/secretbox
|
||||||
@ -113,7 +113,7 @@ imports:
|
|||||||
- pbkdf2
|
- pbkdf2
|
||||||
- ssh/terminal
|
- ssh/terminal
|
||||||
- name: golang.org/x/net
|
- name: golang.org/x/net
|
||||||
version: 1358eff22f0dd0c54fc521042cc607f6ff4b531a
|
version: 9313baa13d9262e49d07b20ed57dceafcd7240cc
|
||||||
subpackages:
|
subpackages:
|
||||||
- context
|
- context
|
||||||
- http2
|
- http2
|
||||||
@ -122,7 +122,7 @@ imports:
|
|||||||
- lex/httplex
|
- lex/httplex
|
||||||
- internal/timeseries
|
- internal/timeseries
|
||||||
- name: golang.org/x/sys
|
- name: golang.org/x/sys
|
||||||
version: a646d33e2ee3172a661fc09bca23bb4889a41bc8
|
version: 30de6d19a3bd89a5f38ae4028e23aaa5582648af
|
||||||
subpackages:
|
subpackages:
|
||||||
- unix
|
- unix
|
||||||
- name: google.golang.org/grpc
|
- name: google.golang.org/grpc
|
||||||
|
@ -37,6 +37,7 @@ import:
|
|||||||
- hdkeychain
|
- hdkeychain
|
||||||
- txsort
|
- txsort
|
||||||
- package: github.com/roasbeef/btcwallet
|
- package: github.com/roasbeef/btcwallet
|
||||||
|
version: master
|
||||||
subpackages:
|
subpackages:
|
||||||
- chain
|
- chain
|
||||||
- waddrmgr
|
- waddrmgr
|
||||||
|
22
lnd.go
22
lnd.go
@ -18,6 +18,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc"
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet"
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
|
||||||
"github.com/roasbeef/btcrpcclient"
|
"github.com/roasbeef/btcrpcclient"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -116,9 +117,8 @@ func lndMain() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create, and start the lnwallet, which handles the core payment
|
// TODO(roasbeef): paarse config here select chosen WalletController
|
||||||
// channel logic, and exposes control via proxy state machines.
|
walletConfig := &btcwallet.Config{
|
||||||
walletConfig := &lnwallet.Config{
|
|
||||||
PrivatePass: []byte("hello"),
|
PrivatePass: []byte("hello"),
|
||||||
DataDir: filepath.Join(loadedConfig.DataDir, "lnwallet"),
|
DataDir: filepath.Join(loadedConfig.DataDir, "lnwallet"),
|
||||||
RpcHost: fmt.Sprintf("%v:%v", rpcIP[0], activeNetParams.rpcPort),
|
RpcHost: fmt.Sprintf("%v:%v", rpcIP[0], activeNetParams.rpcPort),
|
||||||
@ -127,7 +127,18 @@ func lndMain() error {
|
|||||||
CACert: rpcCert,
|
CACert: rpcCert,
|
||||||
NetParams: activeNetParams.Params,
|
NetParams: activeNetParams.Params,
|
||||||
}
|
}
|
||||||
wallet, err := lnwallet.NewLightningWallet(walletConfig, chanDB, notifier)
|
wc, err := btcwallet.New(walletConfig)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("unable to create wallet controller: %v\n", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
signer := wc
|
||||||
|
bio := wc
|
||||||
|
|
||||||
|
// Create, and start the lnwallet, which handles the core payment
|
||||||
|
// channel logic, and exposes control via proxy state machines.
|
||||||
|
wallet, err := lnwallet.NewLightningWallet(chanDB, notifier,
|
||||||
|
wc, signer, bio, activeNetParams.Params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("unable to create wallet: %v\n", err)
|
fmt.Printf("unable to create wallet: %v\n", err)
|
||||||
return err
|
return err
|
||||||
@ -138,9 +149,6 @@ func lndMain() error {
|
|||||||
}
|
}
|
||||||
ltndLog.Info("LightningWallet opened")
|
ltndLog.Info("LightningWallet opened")
|
||||||
|
|
||||||
ec := &lnwallet.WaddrmgrEncryptorDecryptor{wallet.Manager}
|
|
||||||
chanDB.RegisterCryptoSystem(ec)
|
|
||||||
|
|
||||||
// Set up the core server which will listen for incoming peer
|
// Set up the core server which will listen for incoming peer
|
||||||
// connections.
|
// connections.
|
||||||
defaultListenAddrs := []string{
|
defaultListenAddrs := []string{
|
||||||
|
55
lnwallet/btcwallet/blockchain.go
Normal file
55
lnwallet/btcwallet/blockchain.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package btcwallet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
|
||||||
|
"github.com/roasbeef/btcd/wire"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetCurrentHeight returns the current height of the known block within the
|
||||||
|
// main chain.
|
||||||
|
//
|
||||||
|
// This method is a part of the lnwallet.BlockChainIO interface.
|
||||||
|
func (b *BtcWallet) GetCurrentHeight() (int32, error) {
|
||||||
|
_, height, err := b.rpc.GetBestBlock()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return height, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTxOut returns the original output referenced by the passed outpoint.
|
||||||
|
//
|
||||||
|
// This method is a part of the lnwallet.BlockChainIO interface.
|
||||||
|
func (b *BtcWallet) GetUtxo(txid *wire.ShaHash, index uint32) (*wire.TxOut, error) {
|
||||||
|
txout, err := b.rpc.GetTxOut(txid, index, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pkScript, err := hex.DecodeString(txout.ScriptPubKey.Hex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &wire.TxOut{
|
||||||
|
// Sadly, gettxout returns the output value in BTC
|
||||||
|
// instead of satoshis.
|
||||||
|
Value: int64(txout.Value) * 1e8,
|
||||||
|
PkScript: pkScript,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTransaction returns the full transaction identified by the passed
|
||||||
|
// transaction ID.
|
||||||
|
//
|
||||||
|
// This method is a part of the lnwallet.BlockChainIO interface.
|
||||||
|
func (b *BtcWallet) GetTransaction(txid *wire.ShaHash) (*wire.MsgTx, error) {
|
||||||
|
tx, err := b.rpc.GetRawTransaction(txid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.MsgTx(), nil
|
||||||
|
}
|
380
lnwallet/btcwallet/btcwallet.go
Normal file
380
lnwallet/btcwallet/btcwallet.go
Normal file
@ -0,0 +1,380 @@
|
|||||||
|
package btcwallet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
|
"github.com/roasbeef/btcd/btcec"
|
||||||
|
"github.com/roasbeef/btcd/chaincfg"
|
||||||
|
"github.com/roasbeef/btcd/txscript"
|
||||||
|
"github.com/roasbeef/btcd/wire"
|
||||||
|
"github.com/roasbeef/btcutil"
|
||||||
|
"github.com/roasbeef/btcwallet/chain"
|
||||||
|
"github.com/roasbeef/btcwallet/waddrmgr"
|
||||||
|
base "github.com/roasbeef/btcwallet/wallet"
|
||||||
|
"github.com/roasbeef/btcwallet/walletdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultAccount = uint32(waddrmgr.DefaultAccountNum)
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
lnNamespace = []byte("ln")
|
||||||
|
rootKey = []byte("ln-root")
|
||||||
|
)
|
||||||
|
|
||||||
|
// BtcWallet is an implementation of the lnwallet.WalletController interface
|
||||||
|
// backed by an active instance of btcwallet. At the time of the writing of
|
||||||
|
// this documentation, this implementation requires a full btcd node to
|
||||||
|
// operate.
|
||||||
|
type BtcWallet struct {
|
||||||
|
// wallet is an active instance of btcwallet.
|
||||||
|
wallet *base.Wallet
|
||||||
|
|
||||||
|
// rpc is an an active RPC connection to btcd full-node.
|
||||||
|
rpc *chain.RPCClient
|
||||||
|
|
||||||
|
// lnNamespace is a namespace within btcwallet's walletdb used to store
|
||||||
|
// persistent state required by the WalletController interface but not
|
||||||
|
// natively supported by btcwallet.
|
||||||
|
lnNamespace walletdb.Namespace
|
||||||
|
|
||||||
|
netParams *chaincfg.Params
|
||||||
|
|
||||||
|
// utxoCache is a cache used to speed up repeated calls to
|
||||||
|
// FetchInputInfo.
|
||||||
|
utxoCache map[wire.OutPoint]*wire.TxOut
|
||||||
|
cacheMtx sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// A compile time check to ensure that BtcWallet implements the
|
||||||
|
// WalletController interface.
|
||||||
|
var _ lnwallet.WalletController = (*BtcWallet)(nil)
|
||||||
|
|
||||||
|
// New returns a new fully initialized instance of BtcWallet given a valid
|
||||||
|
// confirguration struct.
|
||||||
|
func New(cfg *Config) (*BtcWallet, error) {
|
||||||
|
// Ensure the wallet exists or create it when the create flag is set.
|
||||||
|
netDir := networkDir(cfg.DataDir, cfg.NetParams)
|
||||||
|
|
||||||
|
var pubPass []byte
|
||||||
|
if cfg.PublicPass == nil {
|
||||||
|
pubPass = defaultPubPassphrase
|
||||||
|
} else {
|
||||||
|
pubPass = cfg.PublicPass
|
||||||
|
}
|
||||||
|
|
||||||
|
loader := base.NewLoader(cfg.NetParams, netDir)
|
||||||
|
walletExists, err := loader.WalletExists()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var wallet *base.Wallet
|
||||||
|
if !walletExists {
|
||||||
|
// Wallet has never been created, perform initial set up.
|
||||||
|
wallet, err = loader.CreateNewWallet(pubPass, cfg.PrivatePass,
|
||||||
|
cfg.HdSeed)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Wallet has been created and been initialized at this point, open it
|
||||||
|
// along with all the required DB namepsaces, and the DB itself.
|
||||||
|
wallet, err = loader.OpenExistingWallet(pubPass, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := wallet.Manager.Unlock(cfg.PrivatePass); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a special websockets rpc client for btcd which will be used
|
||||||
|
// by the wallet for notifications, calls, etc.
|
||||||
|
rpcc, err := chain.NewRPCClient(cfg.NetParams, cfg.RpcHost,
|
||||||
|
cfg.RpcUser, cfg.RpcPass, cfg.CACert, false, 20)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
db := wallet.Database()
|
||||||
|
walletNamespace, err := db.Namespace(lnNamespace)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &BtcWallet{
|
||||||
|
wallet: wallet,
|
||||||
|
rpc: rpcc,
|
||||||
|
lnNamespace: walletNamespace,
|
||||||
|
netParams: cfg.NetParams,
|
||||||
|
utxoCache: make(map[wire.OutPoint]*wire.TxOut),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start initializes the underlying rpc connection, the wallet itself, and
|
||||||
|
// begins syncing to the current available blockchain state.
|
||||||
|
//
|
||||||
|
// This is a part of the WalletController interface.
|
||||||
|
func (b *BtcWallet) Start() error {
|
||||||
|
// Establish an RPC connection in additino to starting the goroutines
|
||||||
|
// in the underlying wallet.
|
||||||
|
if err := b.rpc.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the underlying btcwallet core.
|
||||||
|
b.wallet.Start()
|
||||||
|
|
||||||
|
// Pass the rpc client into the wallet so it can sync up to the
|
||||||
|
// current main chain.
|
||||||
|
b.wallet.SynchronizeRPC(b.rpc)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop signals the wallet for shutdown. Shutdown may entail closing
|
||||||
|
// any active sockets, database handles, stopping goroutines, etc.
|
||||||
|
//
|
||||||
|
// This is a part of the WalletController interface.
|
||||||
|
func (b *BtcWallet) Stop() error {
|
||||||
|
b.wallet.Stop()
|
||||||
|
|
||||||
|
b.wallet.WaitForShutdown()
|
||||||
|
|
||||||
|
b.rpc.Shutdown()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfirmedBalance returns the sum of all the wallet's unspent outputs that
|
||||||
|
// have at least confs confirmations. If confs is set to zero, then all unspent
|
||||||
|
// outputs, including those currently in the mempool will be included in the
|
||||||
|
// final sum.
|
||||||
|
//
|
||||||
|
// This is a part of the WalletController interface.
|
||||||
|
func (b *BtcWallet) ConfirmedBalance(confs int32, witness bool) (btcutil.Amount, error) {
|
||||||
|
var balance btcutil.Amount
|
||||||
|
|
||||||
|
if witness {
|
||||||
|
witnessOutputs, err := b.ListUnspentWitness(confs)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, witnessOutput := range witnessOutputs {
|
||||||
|
balance += witnessOutput.Value
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
outputSum, err := b.wallet.CalculateBalance(confs)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
balance = outputSum
|
||||||
|
}
|
||||||
|
|
||||||
|
return balance, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAddress returns the next external or internal address for the wallet
|
||||||
|
// dicatated by the value of the `change` paramter. If change is true, then an
|
||||||
|
// internal address will be returned, otherwise an external address should be
|
||||||
|
// returned.
|
||||||
|
//
|
||||||
|
// This is a part of the WalletController interface.
|
||||||
|
func (b *BtcWallet) NewAddress(t lnwallet.AddressType, change bool) (btcutil.Address, error) {
|
||||||
|
var addrType waddrmgr.AddressType
|
||||||
|
|
||||||
|
switch t {
|
||||||
|
case lnwallet.WitnessPubKey:
|
||||||
|
addrType = waddrmgr.WitnessPubKey
|
||||||
|
case lnwallet.NestedWitnessPubKey:
|
||||||
|
addrType = waddrmgr.NestedWitnessPubKey
|
||||||
|
case lnwallet.PubKeyHash:
|
||||||
|
addrType = waddrmgr.PubKeyHash
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown address type")
|
||||||
|
}
|
||||||
|
|
||||||
|
if change {
|
||||||
|
return b.wallet.NewAddress(defaultAccount, addrType)
|
||||||
|
} else {
|
||||||
|
return b.wallet.NewChangeAddress(defaultAccount, addrType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPrivKey retrives the underlying private key associated with the passed
|
||||||
|
// address. If the we're unable to locate the proper private key, then a
|
||||||
|
// non-nil error will be returned.
|
||||||
|
//
|
||||||
|
// This is a part of the WalletController interface.
|
||||||
|
func (b *BtcWallet) GetPrivKey(a btcutil.Address) (*btcec.PrivateKey, error) {
|
||||||
|
// Using the ID address, request the private key coresponding to the
|
||||||
|
// address from the wallet's address manager.
|
||||||
|
walletAddr, err := b.wallet.Manager.Address(a)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return walletAddr.(waddrmgr.ManagedPubKeyAddress).PrivKey()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRawKey retrieves the next key within our HD key-chain for use within as a
|
||||||
|
// multi-sig key within the funding transaction, or within the commitment
|
||||||
|
// transaction's outputs.
|
||||||
|
//
|
||||||
|
// This is a part of the WalletController interface.
|
||||||
|
func (b *BtcWallet) NewRawKey() (*btcec.PublicKey, error) {
|
||||||
|
nextAddr, err := b.wallet.Manager.NextExternalAddresses(defaultAccount,
|
||||||
|
1, waddrmgr.WitnessPubKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pkAddr := nextAddr[0].(waddrmgr.ManagedPubKeyAddress)
|
||||||
|
|
||||||
|
return pkAddr.PubKey(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetchRootKey returns a root key which is meanted to be used as an initial
|
||||||
|
// seed/salt to generate any Lightning specific secrets.
|
||||||
|
//
|
||||||
|
// This is a part of the WalletController interface.
|
||||||
|
func (b *BtcWallet) FetchRootKey() (*btcec.PrivateKey, error) {
|
||||||
|
// Fetch the root address hash from the database, this is persisted
|
||||||
|
// locally within the database, then used to obtain the key from the
|
||||||
|
// wallet based on the address hash.
|
||||||
|
var rootAddrHash []byte
|
||||||
|
if err := b.lnNamespace.Update(func(tx walletdb.Tx) error {
|
||||||
|
rootBucket := tx.RootBucket()
|
||||||
|
|
||||||
|
rootAddrHash = rootBucket.Get(rootKey)
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if rootAddrHash == nil {
|
||||||
|
// Otherwise, we need to generate a fresh address from the
|
||||||
|
// wallet, then stores it's hash160 within the database so we
|
||||||
|
// can look up the exact key later.
|
||||||
|
rootAddr, err := b.wallet.Manager.NextExternalAddresses(defaultAccount,
|
||||||
|
1, waddrmgr.WitnessPubKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.lnNamespace.Update(func(tx walletdb.Tx) error {
|
||||||
|
rootBucket := tx.RootBucket()
|
||||||
|
|
||||||
|
rootAddrHash = rootAddr[0].Address().ScriptAddress()
|
||||||
|
if err := rootBucket.Put(rootKey, rootAddrHash); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// With the root address hash obtained, generate the corresponding
|
||||||
|
// address, then retrieve the managed address from the wallet which
|
||||||
|
// will allow us to obtain the private key.
|
||||||
|
rootAddr, err := btcutil.NewAddressWitnessPubKeyHash(rootAddrHash,
|
||||||
|
b.netParams)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
walletAddr, err := b.wallet.Manager.Address(rootAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return walletAddr.(waddrmgr.ManagedPubKeyAddress).PrivKey()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendOutputs funds, signs, and broadcasts a Bitcoin transaction paying out to
|
||||||
|
// the specified outputs. In the case the wallet has insufficient funds, or the
|
||||||
|
// outputs are non-standard, a non-nil error will be be returned.
|
||||||
|
//
|
||||||
|
// This is a part of the WalletController interface.
|
||||||
|
func (b *BtcWallet) SendOutputs(outputs []*wire.TxOut) (*wire.ShaHash, error) {
|
||||||
|
return b.wallet.SendOutputs(outputs, defaultAccount, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LockOutpoint marks an outpoint as locked meaning it will no longer be deemed
|
||||||
|
// as eligible for coin selection. Locking outputs are utilized in order to
|
||||||
|
// avoid race conditions when selecting inputs for usage when funding a
|
||||||
|
// channel.
|
||||||
|
//
|
||||||
|
// This is a part of the WalletController interface.
|
||||||
|
func (b *BtcWallet) LockOutpoint(o wire.OutPoint) {
|
||||||
|
b.wallet.LockOutpoint(o)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnlockOutpoint unlocks an previously locked output, marking it eligible for
|
||||||
|
// coin seleciton.
|
||||||
|
//
|
||||||
|
// This is a part of the WalletController interface.
|
||||||
|
func (b *BtcWallet) UnlockOutpoint(o wire.OutPoint) {
|
||||||
|
b.wallet.UnlockOutpoint(o)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListUnspentWitness returns a slice of all the unspent outputs the wallet
|
||||||
|
// controls which pay to witness programs either directly or indirectly.
|
||||||
|
//
|
||||||
|
// This is a part of the WalletController interface.
|
||||||
|
func (b *BtcWallet) ListUnspentWitness(minConfs int32) ([]*lnwallet.Utxo, error) {
|
||||||
|
// First, grab all the unfiltered currently unspent outputs.
|
||||||
|
maxConfs := int32(math.MaxInt32)
|
||||||
|
unspentOutputs, err := b.wallet.ListUnspent(minConfs, maxConfs, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next, we'll run through all the regular outputs, only saving those
|
||||||
|
// which are p2wkh outputs or a p2wsh output nested within a p2sh output.
|
||||||
|
witnessOutputs := make([]*lnwallet.Utxo, 0, len(unspentOutputs))
|
||||||
|
for _, output := range unspentOutputs {
|
||||||
|
pkScript, err := hex.DecodeString(output.ScriptPubKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(roasbeef): this assumes all p2sh outputs returned by
|
||||||
|
// the wallet are nested p2sh...
|
||||||
|
if txscript.IsPayToWitnessPubKeyHash(pkScript) ||
|
||||||
|
txscript.IsPayToScriptHash(pkScript) {
|
||||||
|
txid, err := wire.NewShaHashFromStr(output.TxID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
utxo := &lnwallet.Utxo{
|
||||||
|
Value: btcutil.Amount(output.Amount * 1e8),
|
||||||
|
OutPoint: wire.OutPoint{
|
||||||
|
Hash: *txid,
|
||||||
|
Index: output.Vout,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
witnessOutputs = append(witnessOutputs, utxo)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return witnessOutputs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PublishTransaction performs cursory validation (dust checks, etc), then
|
||||||
|
// finally broadcasts the passed transaction to the Bitcoin network.
|
||||||
|
func (b *BtcWallet) PublishTransaction(tx *wire.MsgTx) error {
|
||||||
|
return b.wallet.PublishTransaction(tx)
|
||||||
|
}
|
94
lnwallet/btcwallet/config.go
Normal file
94
lnwallet/btcwallet/config.go
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
package btcwallet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/roasbeef/btcd/chaincfg"
|
||||||
|
"github.com/roasbeef/btcd/wire"
|
||||||
|
"github.com/roasbeef/btcutil"
|
||||||
|
_ "github.com/roasbeef/btcwallet/walletdb/bdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
lnwalletHomeDir = btcutil.AppDataDir("lnwallet", false)
|
||||||
|
defaultDataDir = lnwalletHomeDir
|
||||||
|
|
||||||
|
defaultLogFilename = "lnwallet.log"
|
||||||
|
defaultLogDirname = "logs"
|
||||||
|
defaultLogDir = filepath.Join(lnwalletHomeDir, defaultLogDirname)
|
||||||
|
|
||||||
|
btcdHomeDir = btcutil.AppDataDir("btcd", false)
|
||||||
|
btcdHomedirCAFile = filepath.Join(btcdHomeDir, "rpc.cert")
|
||||||
|
defaultRPCKeyFile = filepath.Join(lnwalletHomeDir, "rpc.key")
|
||||||
|
defaultRPCCertFile = filepath.Join(lnwalletHomeDir, "rpc.cert")
|
||||||
|
|
||||||
|
// defaultPubPassphrase is the default public wallet passphrase which is
|
||||||
|
// used when the user indicates they do not want additional protection
|
||||||
|
// provided by having all public data in the wallet encrypted by a
|
||||||
|
// passphrase only known to them.
|
||||||
|
defaultPubPassphrase = []byte("public")
|
||||||
|
|
||||||
|
walletDbName = "lnwallet.db"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config is a struct which houses configuration paramters which modify the
|
||||||
|
// instance of BtcWallet generated by the New() function.
|
||||||
|
type Config struct {
|
||||||
|
// DataDir is the name of the directory where the wallet's persistent
|
||||||
|
// state should be sotred.
|
||||||
|
DataDir string
|
||||||
|
|
||||||
|
// LogDir is the name of the directory which should be used to store
|
||||||
|
// generated log files.
|
||||||
|
LogDir string
|
||||||
|
|
||||||
|
// DebugLevel is a string representing the level of verbosity the
|
||||||
|
// logger should use.
|
||||||
|
DebugLevel string
|
||||||
|
|
||||||
|
// RpcHost is the host and port to use to reach the rpc sever.
|
||||||
|
RpcHost string // localhost:18334
|
||||||
|
|
||||||
|
// RpcUser is the username which should be used to authentiate with the
|
||||||
|
// rpc server.
|
||||||
|
RpcUser string
|
||||||
|
|
||||||
|
// RpcPass is the password which should be used to authenticate the
|
||||||
|
// connection with the RPC server.
|
||||||
|
RpcPass string
|
||||||
|
|
||||||
|
// RpcNoTLS denotes if a TLS connection should be attempted when
|
||||||
|
// connecting to the RPC server.
|
||||||
|
RpcNoTLS bool
|
||||||
|
|
||||||
|
// RPCCert directory where the TLS certificate of the RPC sever is
|
||||||
|
// stored. If the RpcNoTLS is false, then this value will be unused.
|
||||||
|
RPCCert string
|
||||||
|
RPCKey string
|
||||||
|
|
||||||
|
// CACert is the raw RPC cert for btcd.
|
||||||
|
CACert []byte
|
||||||
|
|
||||||
|
PrivatePass []byte
|
||||||
|
PublicPass []byte
|
||||||
|
HdSeed []byte
|
||||||
|
|
||||||
|
NetParams *chaincfg.Params
|
||||||
|
}
|
||||||
|
|
||||||
|
// networkDir returns the directory name of a network directory to hold wallet
|
||||||
|
// files.
|
||||||
|
func networkDir(dataDir string, chainParams *chaincfg.Params) string {
|
||||||
|
netname := chainParams.Name
|
||||||
|
|
||||||
|
// For now, we must always name the testnet data directory as "testnet"
|
||||||
|
// and not "testnet3" or any other version, as the chaincfg testnet3
|
||||||
|
// paramaters will likely be switched to being named "testnet3" in the
|
||||||
|
// future. This is done to future proof that change, and an upgrade
|
||||||
|
// plan to move the testnet3 data directory can be worked out later.
|
||||||
|
if chainParams.Net == wire.TestNet3 {
|
||||||
|
netname = "testnet"
|
||||||
|
}
|
||||||
|
|
||||||
|
return filepath.Join(dataDir, netname)
|
||||||
|
}
|
45
lnwallet/btcwallet/driver.go
Normal file
45
lnwallet/btcwallet/driver.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package btcwallet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
walletType = "btcwallet"
|
||||||
|
)
|
||||||
|
|
||||||
|
// createNewWallet creates a new instance of BtcWallet given the proper list of
|
||||||
|
// initialization parameters. This function is the factory function required to
|
||||||
|
// properly create an instance of the lnwallet.WalletDriver struct for
|
||||||
|
// BtcWallet.
|
||||||
|
func createNewWallet(args ...interface{}) (lnwallet.WalletController, error) {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return nil, fmt.Errorf("incorrect number of arguments to .New(...), "+
|
||||||
|
"expected 1, instead passed %v", len(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
config, ok := args[0].(*Config)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("first argument to btcdnotifier.New is " +
|
||||||
|
"incorrect, expected a *btcrpcclient.ConnConfig")
|
||||||
|
}
|
||||||
|
|
||||||
|
return New(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// init registers a driver for the BtcWallet concrete implementation of the
|
||||||
|
// lnwallet.WalletController interface.
|
||||||
|
func init() {
|
||||||
|
// Register the driver.
|
||||||
|
driver := &lnwallet.WalletDriver{
|
||||||
|
WalletType: walletType,
|
||||||
|
New: createNewWallet,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := lnwallet.RegisterWallet(driver); err != nil {
|
||||||
|
panic(fmt.Sprintf("failed to register wallet driver '%s': %v",
|
||||||
|
walletType, err))
|
||||||
|
}
|
||||||
|
}
|
174
lnwallet/btcwallet/signer.go
Normal file
174
lnwallet/btcwallet/signer.go
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
package btcwallet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
|
"github.com/roasbeef/btcd/txscript"
|
||||||
|
"github.com/roasbeef/btcd/wire"
|
||||||
|
"github.com/roasbeef/btcutil"
|
||||||
|
"github.com/roasbeef/btcwallet/waddrmgr"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FetchInputInfo queries for the WalletController's knowledge of the passed
|
||||||
|
// outpoint. If the base wallet determines this output is under its control,
|
||||||
|
// then the original txout should be returned. Otherwise, a non-nil error value
|
||||||
|
// of ErrNotMine should be returned instead.
|
||||||
|
//
|
||||||
|
// This is a part of the WalletController interface.
|
||||||
|
func (b *BtcWallet) FetchInputInfo(prevOut *wire.OutPoint) (*wire.TxOut, error) {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
output *wire.TxOut
|
||||||
|
)
|
||||||
|
|
||||||
|
// First check to see if the output is already within the utxo cache.
|
||||||
|
// If so we can return directly saving usk a disk access.
|
||||||
|
b.cacheMtx.RLock()
|
||||||
|
if output, ok := b.utxoCache[*prevOut]; ok {
|
||||||
|
b.cacheMtx.RUnlock()
|
||||||
|
return output, nil
|
||||||
|
}
|
||||||
|
b.cacheMtx.RUnlock()
|
||||||
|
|
||||||
|
// Otherwse, we manually look up the output within the tx store.
|
||||||
|
txDetail, err := b.wallet.TxStore.TxDetails(&prevOut.Hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if txDetail == nil {
|
||||||
|
return nil, lnwallet.ErrNotMine
|
||||||
|
}
|
||||||
|
|
||||||
|
output = txDetail.TxRecord.MsgTx.TxOut[prevOut.Index]
|
||||||
|
|
||||||
|
b.cacheMtx.Lock()
|
||||||
|
b.utxoCache[*prevOut] = output
|
||||||
|
b.cacheMtx.Unlock()
|
||||||
|
|
||||||
|
return output, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetchOutputKey attempts to fetch the managed address corresponding to the
|
||||||
|
// passed output script. This function is used to look up the proper key which
|
||||||
|
// should be used to sign a specified input.
|
||||||
|
func (b *BtcWallet) fetchOutputAddr(script []byte) (waddrmgr.ManagedAddress, error) {
|
||||||
|
_, addrs, _, err := txscript.ExtractPkScriptAddrs(script, b.netParams)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the case of a multi-sig output, several address may be extracted.
|
||||||
|
// Therefore, we simply select the key for the first address we know
|
||||||
|
// of.
|
||||||
|
for _, addr := range addrs {
|
||||||
|
wAddr, err := b.wallet.Manager.Address(addr)
|
||||||
|
if err == nil {
|
||||||
|
return wAddr, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(roasbeef): use the errors.wrap package
|
||||||
|
return nil, fmt.Errorf("address not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignOutputRaw generates a signature for the passed transaction according to
|
||||||
|
// the data within the passed SignDescriptor.
|
||||||
|
//
|
||||||
|
// This is a part of the WalletController interface.
|
||||||
|
func (b *BtcWallet) SignOutputRaw(tx *wire.MsgTx, signDesc *lnwallet.SignDescriptor) ([]byte, error) {
|
||||||
|
redeemScript := signDesc.RedeemScript
|
||||||
|
walletAddr, err := b.fetchOutputAddr(redeemScript)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
privKey, err := walletAddr.(waddrmgr.ManagedPubKeyAddress).PrivKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
amt := signDesc.Output.Value
|
||||||
|
sig, err := txscript.RawTxInWitnessSignature(tx, signDesc.SigHashes, 0,
|
||||||
|
amt, redeemScript, txscript.SigHashAll, privKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chop off the sighash flag at the end of the signature.
|
||||||
|
return sig[:len(sig)-1], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ComputeInputScript generates a complete InputIndex for the passed
|
||||||
|
// transaction with the signature as defined within the passed SignDescriptor.
|
||||||
|
// This method is capable of generating the proper input script for both
|
||||||
|
// regular p2wkh output and p2wkh outputs nested within a regualr p2sh output.
|
||||||
|
//
|
||||||
|
// This is a part of the WalletController interface.
|
||||||
|
func (b *BtcWallet) ComputeInputScript(tx *wire.MsgTx,
|
||||||
|
signDesc *lnwallet.SignDescriptor) (*lnwallet.InputScript, error) {
|
||||||
|
|
||||||
|
outputScript := signDesc.Output.PkScript
|
||||||
|
walletAddr, err := b.fetchOutputAddr(outputScript)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pka := walletAddr.(waddrmgr.ManagedPubKeyAddress)
|
||||||
|
privKey, err := pka.PrivKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var witnessProgram []byte
|
||||||
|
inputScript := &lnwallet.InputScript{}
|
||||||
|
|
||||||
|
// If we're spending p2wkh output nested within a p2sh output, then
|
||||||
|
// we'll need to attach a sigScript in addition to witness data.
|
||||||
|
switch {
|
||||||
|
case 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,
|
||||||
|
b.netParams)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
witnessProgram, err = txscript.PayToAddrScript(p2wkhAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
bldr := txscript.NewScriptBuilder()
|
||||||
|
bldr.AddData(witnessProgram)
|
||||||
|
sigScript, err := bldr.Script()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
inputScript.ScriptSig = sigScript
|
||||||
|
// Otherwise, this is a regular p2wkh output, so we include the
|
||||||
|
// witness program itself as the subscript to generate the proper
|
||||||
|
// sighash digest. As part of the new sighash digest algorithm, the
|
||||||
|
// p2wkh witness program will be expanded into a regular p2kh
|
||||||
|
// script.
|
||||||
|
default:
|
||||||
|
witnessProgram = outputScript
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a valid witness stack for the input.
|
||||||
|
witnessScript, err := txscript.WitnessScript(tx, signDesc.SigHashes,
|
||||||
|
signDesc.InputIndex, signDesc.Output.Value, witnessProgram,
|
||||||
|
txscript.SigHashAll, privKey, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
inputScript.Witness = witnessScript
|
||||||
|
|
||||||
|
return inputScript, nil
|
||||||
|
}
|
@ -274,6 +274,9 @@ type LightningChannel struct {
|
|||||||
// wallet can add back.
|
// wallet can add back.
|
||||||
lnwallet *LightningWallet
|
lnwallet *LightningWallet
|
||||||
|
|
||||||
|
signer Signer
|
||||||
|
signDesc *SignDescriptor
|
||||||
|
|
||||||
channelEvents chainntnfs.ChainNotifier
|
channelEvents chainntnfs.ChainNotifier
|
||||||
|
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
@ -281,7 +284,8 @@ type LightningChannel struct {
|
|||||||
ourLogCounter uint32
|
ourLogCounter uint32
|
||||||
theirLogCounter uint32
|
theirLogCounter uint32
|
||||||
|
|
||||||
status channelState
|
status channelState
|
||||||
|
Capacity btcutil.Amount
|
||||||
|
|
||||||
// currentHeight is the current height of our local commitment chain.
|
// 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
|
// 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
|
ourLogIndex map[uint32]*list.Element
|
||||||
theirLogIndex map[uint32]*list.Element
|
theirLogIndex map[uint32]*list.Element
|
||||||
|
|
||||||
fundingTxIn *wire.TxIn
|
LocalDeliveryScript []byte
|
||||||
fundingP2WSH []byte
|
RemoteDeliveryScript []byte
|
||||||
|
|
||||||
channelDB *channeldb.DB
|
FundingRedeemScript []byte
|
||||||
|
fundingTxIn *wire.TxIn
|
||||||
|
fundingP2WSH []byte
|
||||||
|
|
||||||
started int32
|
started int32
|
||||||
shutdown int32
|
shutdown int32
|
||||||
@ -354,11 +360,13 @@ type LightningChannel struct {
|
|||||||
// and the current settled channel state. Throughout state transitions, then
|
// and the current settled channel state. Throughout state transitions, then
|
||||||
// channel will automatically persist pertinent state to the database in an
|
// channel will automatically persist pertinent state to the database in an
|
||||||
// efficient manner.
|
// efficient manner.
|
||||||
func NewLightningChannel(wallet *LightningWallet, events chainntnfs.ChainNotifier,
|
func NewLightningChannel(signer Signer, wallet *LightningWallet,
|
||||||
chanDB *channeldb.DB, state *channeldb.OpenChannel) (*LightningChannel, error) {
|
events chainntnfs.ChainNotifier,
|
||||||
|
state *channeldb.OpenChannel) (*LightningChannel, error) {
|
||||||
|
|
||||||
// TODO(roasbeef): remove events+wallet
|
// TODO(roasbeef): remove events+wallet
|
||||||
lc := &LightningChannel{
|
lc := &LightningChannel{
|
||||||
|
signer: signer,
|
||||||
lnwallet: wallet,
|
lnwallet: wallet,
|
||||||
channelEvents: events,
|
channelEvents: events,
|
||||||
currentHeight: state.NumUpdates,
|
currentHeight: state.NumUpdates,
|
||||||
@ -370,7 +378,10 @@ func NewLightningChannel(wallet *LightningWallet, events chainntnfs.ChainNotifie
|
|||||||
theirUpdateLog: list.New(),
|
theirUpdateLog: list.New(),
|
||||||
ourLogIndex: make(map[uint32]*list.Element),
|
ourLogIndex: make(map[uint32]*list.Element),
|
||||||
theirLogIndex: 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
|
// 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.fundingTxIn = wire.NewTxIn(state.FundingOutpoint, nil, nil)
|
||||||
lc.fundingP2WSH = fundingPkScript
|
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
|
return lc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -480,12 +502,12 @@ func (lc *LightningChannel) fetchCommitmentView(remoteChain bool,
|
|||||||
var delayBalance, p2wkhBalance btcutil.Amount
|
var delayBalance, p2wkhBalance btcutil.Amount
|
||||||
if remoteChain {
|
if remoteChain {
|
||||||
selfKey = lc.channelState.TheirCommitKey
|
selfKey = lc.channelState.TheirCommitKey
|
||||||
remoteKey = lc.channelState.OurCommitKey.PubKey()
|
remoteKey = lc.channelState.OurCommitKey
|
||||||
delay = lc.channelState.RemoteCsvDelay
|
delay = lc.channelState.RemoteCsvDelay
|
||||||
delayBalance = theirBalance
|
delayBalance = theirBalance
|
||||||
p2wkhBalance = ourBalance
|
p2wkhBalance = ourBalance
|
||||||
} else {
|
} else {
|
||||||
selfKey = lc.channelState.OurCommitKey.PubKey()
|
selfKey = lc.channelState.OurCommitKey
|
||||||
remoteKey = lc.channelState.TheirCommitKey
|
remoteKey = lc.channelState.TheirCommitKey
|
||||||
delay = lc.channelState.LocalCsvDelay
|
delay = lc.channelState.LocalCsvDelay
|
||||||
delayBalance = ourBalance
|
delayBalance = ourBalance
|
||||||
@ -495,7 +517,7 @@ func (lc *LightningChannel) fetchCommitmentView(remoteChain bool,
|
|||||||
// Generate a new commitment transaction with all the latest
|
// Generate a new commitment transaction with all the latest
|
||||||
// unsettled/un-timed out HTLC's.
|
// unsettled/un-timed out HTLC's.
|
||||||
ourCommitTx := !remoteChain
|
ourCommitTx := !remoteChain
|
||||||
commitTx, err := createCommitTx(lc.fundingTxIn, selfKey, remoteKey,
|
commitTx, err := CreateCommitTx(lc.fundingTxIn, selfKey, remoteKey,
|
||||||
revocationKey, delay, delayBalance, p2wkhBalance)
|
revocationKey, delay, delayBalance, p2wkhBalance)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -722,11 +744,8 @@ func (lc *LightningChannel) SignNextCommitment() ([]byte, uint32, error) {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
// Sign their version of the new commitment transaction.
|
// Sign their version of the new commitment transaction.
|
||||||
hashCache := txscript.NewTxSigHashes(newCommitView.txn)
|
lc.signDesc.SigHashes = txscript.NewTxSigHashes(newCommitView.txn)
|
||||||
sig, err := txscript.RawTxInWitnessSignature(newCommitView.txn,
|
sig, err := lc.signer.SignOutputRaw(newCommitView.txn, lc.signDesc)
|
||||||
hashCache, 0, int64(lc.channelState.Capacity),
|
|
||||||
lc.channelState.FundingRedeemScript, txscript.SigHashAll,
|
|
||||||
lc.channelState.OurMultiSigKey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
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
|
// Strip off the sighash flag on the signature in order to send it over
|
||||||
// the wire.
|
// 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
|
// ReceiveNewCommitment processs a signature for a new commitment state sent by
|
||||||
@ -770,7 +789,7 @@ func (lc *LightningChannel) ReceiveNewCommitment(rawSig []byte,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
revocationKey := deriveRevocationPubkey(theirCommitKey, revocation[:])
|
revocationKey := DeriveRevocationPubkey(theirCommitKey, revocation[:])
|
||||||
revocationHash := fastsha256.Sum256(revocation[:])
|
revocationHash := fastsha256.Sum256(revocation[:])
|
||||||
|
|
||||||
// With the revocation information calculated, construct the new
|
// With the revocation information calculated, construct the new
|
||||||
@ -857,7 +876,7 @@ func (lc *LightningChannel) RevokeCurrentCommitment() (*lnwire.CommitRevocation,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
revocationMsg.NextRevocationKey = deriveRevocationPubkey(theirCommitKey,
|
revocationMsg.NextRevocationKey = DeriveRevocationPubkey(theirCommitKey,
|
||||||
revocationEdge[:])
|
revocationEdge[:])
|
||||||
revocationMsg.NextRevocationHash = fastsha256.Sum256(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
|
// Verify that the revocation public key we can derive using this
|
||||||
// pre-image and our private key is identical to the revocation key we
|
// pre-image and our private key is identical to the revocation key we
|
||||||
// were given for their current (prior) commitment transaction.
|
// were given for their current (prior) commitment transaction.
|
||||||
revocationPriv := deriveRevocationPrivKey(ourCommitKey, pendingRevocation[:])
|
revocationPub := DeriveRevocationPubkey(ourCommitKey, pendingRevocation[:])
|
||||||
if !revocationPriv.PubKey().IsEqual(currentRevocationKey) {
|
if !revocationPub.IsEqual(currentRevocationKey) {
|
||||||
return nil, fmt.Errorf("revocation key mismatch")
|
return nil, fmt.Errorf("revocation key mismatch")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1067,7 +1086,7 @@ func (lc *LightningChannel) ExtendRevocationWindow() (*lnwire.CommitRevocation,
|
|||||||
}
|
}
|
||||||
|
|
||||||
theirCommitKey := lc.channelState.TheirCommitKey
|
theirCommitKey := lc.channelState.TheirCommitKey
|
||||||
revMsg.NextRevocationKey = deriveRevocationPubkey(theirCommitKey,
|
revMsg.NextRevocationKey = DeriveRevocationPubkey(theirCommitKey,
|
||||||
revocation[:])
|
revocation[:])
|
||||||
revMsg.NextRevocationHash = fastsha256.Sum256(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,
|
paymentDesc *PaymentDescriptor, revocation [32]byte, delay uint32,
|
||||||
isIncoming bool) error {
|
isIncoming bool) error {
|
||||||
|
|
||||||
localKey := lc.channelState.OurCommitKey.PubKey()
|
localKey := lc.channelState.OurCommitKey
|
||||||
remoteKey := lc.channelState.TheirCommitKey
|
remoteKey := lc.channelState.TheirCommitKey
|
||||||
timeout := paymentDesc.Timeout
|
timeout := paymentDesc.Timeout
|
||||||
rHash := paymentDesc.RHash
|
rHash := paymentDesc.RHash
|
||||||
@ -1288,7 +1307,7 @@ func (lc *LightningChannel) InitCooperativeClose() ([]byte, *wire.ShaHash, error
|
|||||||
lc.status = channelClosing
|
lc.status = channelClosing
|
||||||
|
|
||||||
// TODO(roasbeef): assumes initiator pays fees
|
// TODO(roasbeef): assumes initiator pays fees
|
||||||
closeTx := createCooperativeCloseTx(lc.fundingTxIn,
|
closeTx := CreateCooperativeCloseTx(lc.fundingTxIn,
|
||||||
lc.channelState.OurBalance, lc.channelState.TheirBalance,
|
lc.channelState.OurBalance, lc.channelState.TheirBalance,
|
||||||
lc.channelState.OurDeliveryScript, lc.channelState.TheirDeliveryScript,
|
lc.channelState.OurDeliveryScript, lc.channelState.TheirDeliveryScript,
|
||||||
true)
|
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,
|
// initiator we'll simply send our signature over the the remote party,
|
||||||
// using the generated txid to be notified once the closure transaction
|
// using the generated txid to be notified once the closure transaction
|
||||||
// has been confirmed.
|
// has been confirmed.
|
||||||
hashCache := txscript.NewTxSigHashes(closeTx)
|
lc.signDesc.SigHashes = txscript.NewTxSigHashes(closeTx)
|
||||||
closeSig, err := txscript.RawTxInWitnessSignature(closeTx,
|
closeSig, err := lc.signer.SignOutputRaw(closeTx, lc.signDesc)
|
||||||
hashCache, 0, int64(lc.channelState.Capacity),
|
|
||||||
lc.channelState.FundingRedeemScript, txscript.SigHashAll,
|
|
||||||
lc.channelState.OurMultiSigKey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
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
|
// remote node initating a cooperative channel closure. A fully signed closure
|
||||||
// transaction is returned. It is the duty of the responding node to broadcast
|
// transaction is returned. It is the duty of the responding node to broadcast
|
||||||
// a signed+valid closure transaction to the network.
|
// 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) {
|
func (lc *LightningChannel) CompleteCooperativeClose(remoteSig []byte) (*wire.MsgTx, error) {
|
||||||
lc.Lock()
|
lc.Lock()
|
||||||
defer lc.Unlock()
|
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
|
// Create the transaction used to return the current settled balance
|
||||||
// on this active channel back to both parties. In this current model,
|
// on this active channel back to both parties. In this current model,
|
||||||
// the initiator pays full fees for the cooperative close transaction.
|
// 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.OurBalance, lc.channelState.TheirBalance,
|
||||||
lc.channelState.OurDeliveryScript, lc.channelState.TheirDeliveryScript,
|
lc.channelState.OurDeliveryScript, lc.channelState.TheirDeliveryScript,
|
||||||
false)
|
false)
|
||||||
|
|
||||||
// With the transaction created, we can finally generate our half of
|
// With the transaction created, we can finally generate our half of
|
||||||
// the 2-of-2 multi-sig needed to redeem the funding output.
|
// the 2-of-2 multi-sig needed to redeem the funding output.
|
||||||
redeemScript := lc.channelState.FundingRedeemScript
|
|
||||||
hashCache := txscript.NewTxSigHashes(closeTx)
|
hashCache := txscript.NewTxSigHashes(closeTx)
|
||||||
capacity := int64(lc.channelState.Capacity)
|
lc.signDesc.SigHashes = hashCache
|
||||||
closeSig, err := txscript.RawTxInWitnessSignature(closeTx,
|
closeSig, err := lc.signer.SignOutputRaw(closeTx, lc.signDesc)
|
||||||
hashCache, 0, capacity, redeemScript, txscript.SigHashAll,
|
|
||||||
lc.channelState.OurMultiSigKey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally, construct the witness stack minding the order of the
|
// Finally, construct the witness stack minding the order of the
|
||||||
// pubkeys+sigs on the stack.
|
// pubkeys+sigs on the stack.
|
||||||
ourKey := lc.channelState.OurMultiSigKey.PubKey().SerializeCompressed()
|
ourKey := lc.channelState.OurMultiSigKey.SerializeCompressed()
|
||||||
theirKey := lc.channelState.TheirMultiSigKey.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)
|
theirKey, remoteSig)
|
||||||
closeTx.TxIn[0].Witness = witness
|
closeTx.TxIn[0].Witness = witness
|
||||||
|
|
||||||
// Validate the finalized transaction to ensure the output script is
|
// Validate the finalized transaction to ensure the output script is
|
||||||
// properly met, and that the remote peer supplied a valid signature.
|
// properly met, and that the remote peer supplied a valid signature.
|
||||||
vm, err := txscript.NewEngine(lc.fundingP2WSH, closeTx, 0,
|
vm, err := txscript.NewEngine(lc.fundingP2WSH, closeTx, 0,
|
||||||
txscript.StandardVerifyFlags, nil, hashCache, capacity)
|
txscript.StandardVerifyFlags, nil, hashCache,
|
||||||
|
int64(lc.channelState.Capacity))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -1376,7 +1394,8 @@ func (lc *LightningChannel) DeleteState() error {
|
|||||||
return lc.channelState.CloseChannel()
|
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 {
|
func (lc *LightningChannel) StateSnapshot() *channeldb.ChannelSnapshot {
|
||||||
lc.stateMtx.RLock()
|
lc.stateMtx.RLock()
|
||||||
defer lc.stateMtx.RUnlock()
|
defer lc.stateMtx.RUnlock()
|
||||||
@ -1384,12 +1403,12 @@ func (lc *LightningChannel) StateSnapshot() *channeldb.ChannelSnapshot {
|
|||||||
return lc.channelState.Snapshot()
|
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
|
// funding output. The commitment transaction contains two outputs: one paying
|
||||||
// to the "owner" of the commitment transaction which can be spent after a
|
// 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
|
// relative block delay or revocation event, and the other paying the the
|
||||||
// counter-party within the channel, which can be spent immediately.
|
// 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,
|
revokeKey *btcec.PublicKey, csvTimeout uint32, amountToSelf,
|
||||||
amountToThem btcutil.Amount) (*wire.MsgTx, error) {
|
amountToThem btcutil.Amount) (*wire.MsgTx, error) {
|
||||||
|
|
||||||
@ -1433,13 +1452,13 @@ func createCommitTx(fundingOutput *wire.TxIn, selfKey, theirKey *btcec.PublicKey
|
|||||||
return commitTx, nil
|
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
|
// parties, then broadcast cooperatively closes an active channel. The creation
|
||||||
// of the closure transaction is modified by a boolean indicating if the party
|
// 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
|
// constructing the channel is the initiator of the closure. Currently it is
|
||||||
// expected that the initiator pays the transaction fees for the closing
|
// expected that the initiator pays the transaction fees for the closing
|
||||||
// transaction in full.
|
// transaction in full.
|
||||||
func createCooperativeCloseTx(fundingTxIn *wire.TxIn,
|
func CreateCooperativeCloseTx(fundingTxIn *wire.TxIn,
|
||||||
ourBalance, theirBalance btcutil.Amount,
|
ourBalance, theirBalance btcutil.Amount,
|
||||||
ourDeliveryScript, theirDeliveryScript []byte,
|
ourDeliveryScript, theirDeliveryScript []byte,
|
||||||
initiator bool) *wire.MsgTx {
|
initiator bool) *wire.MsgTx {
|
||||||
|
@ -13,27 +13,64 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
"github.com/roasbeef/btcd/btcec"
|
"github.com/roasbeef/btcd/btcec"
|
||||||
"github.com/roasbeef/btcd/chaincfg"
|
"github.com/roasbeef/btcd/chaincfg"
|
||||||
|
"github.com/roasbeef/btcd/txscript"
|
||||||
"github.com/roasbeef/btcd/wire"
|
"github.com/roasbeef/btcd/wire"
|
||||||
"github.com/roasbeef/btcutil"
|
"github.com/roasbeef/btcutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockEncryptorDecryptor is a mock implementation of EncryptorDecryptor that
|
var (
|
||||||
// simply returns the passed bytes without encrypting or decrypting. This is
|
privPass = []byte("private-test")
|
||||||
// used for testing purposes to be able to create a channldb instance which
|
|
||||||
// doesn't use encryption.
|
// For simplicity a single priv key controls all of our test outputs.
|
||||||
type MockEncryptorDecryptor struct {
|
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) {
|
func (m *mockSigner) SignOutputRaw(tx *wire.MsgTx, signDesc *SignDescriptor) ([]byte, error) {
|
||||||
return n, nil
|
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) {
|
// ComputeInputScript...
|
||||||
return n, nil
|
func (m *mockSigner) ComputeInputScript(tx *wire.MsgTx, signDesc *SignDescriptor) (*InputScript, error) {
|
||||||
}
|
return nil, nil
|
||||||
|
|
||||||
func (m *MockEncryptorDecryptor) OverheadSize() uint32 {
|
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// createTestChannels creates two test channels funded with 10 BTC, with 5 BTC
|
// 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)
|
csvTimeoutAlice := uint32(5)
|
||||||
csvTimeoutBob := uint32(4)
|
csvTimeoutBob := uint32(4)
|
||||||
|
|
||||||
redeemScript, _, err := genFundingPkScript(aliceKeyPub.SerializeCompressed(),
|
redeemScript, _, err := GenFundingPkScript(aliceKeyPub.SerializeCompressed(),
|
||||||
bobKeyPub.SerializeCompressed(), int64(channelCapacity))
|
bobKeyPub.SerializeCompressed(), int64(channelCapacity))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
@ -61,26 +98,26 @@ func createTestChannels() (*LightningChannel, *LightningChannel, func(), error)
|
|||||||
}
|
}
|
||||||
fundingTxIn := wire.NewTxIn(prevOut, nil, nil)
|
fundingTxIn := wire.NewTxIn(prevOut, nil, nil)
|
||||||
|
|
||||||
bobElkrem := elkrem.NewElkremSender(deriveElkremRoot(bobKeyPriv, aliceKeyPub))
|
bobElkrem := elkrem.NewElkremSender(deriveElkremRoot(bobKeyPriv, bobKeyPub, aliceKeyPub))
|
||||||
bobFirstRevoke, err := bobElkrem.AtIndex(0)
|
bobFirstRevoke, err := bobElkrem.AtIndex(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
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)
|
aliceFirstRevoke, err := aliceElkrem.AtIndex(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
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)
|
bobKeyPub, aliceRevokeKey, csvTimeoutAlice, channelBal, channelBal)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
bobCommitTx, err := createCommitTx(fundingTxIn, bobKeyPub,
|
bobCommitTx, err := CreateCommitTx(fundingTxIn, bobKeyPub,
|
||||||
aliceKeyPub, bobRevokeKey, csvTimeoutBob, channelBal, channelBal)
|
aliceKeyPub, bobRevokeKey, csvTimeoutBob, channelBal, channelBal)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
@ -91,25 +128,24 @@ func createTestChannels() (*LightningChannel, *LightningChannel, func(), error)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
dbAlice.RegisterCryptoSystem(&MockEncryptorDecryptor{})
|
|
||||||
bobPath, err := ioutil.TempDir("", "bobdb")
|
bobPath, err := ioutil.TempDir("", "bobdb")
|
||||||
dbBob, err := channeldb.Open(bobPath, &chaincfg.TestNet3Params)
|
dbBob, err := channeldb.Open(bobPath, &chaincfg.TestNet3Params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
dbBob.RegisterCryptoSystem(&MockEncryptorDecryptor{})
|
|
||||||
|
|
||||||
aliceChannelState := &channeldb.OpenChannel{
|
aliceChannelState := &channeldb.OpenChannel{
|
||||||
TheirLNID: testHdSeed,
|
TheirLNID: testHdSeed,
|
||||||
ChanID: prevOut,
|
ChanID: prevOut,
|
||||||
OurCommitKey: aliceKeyPriv,
|
OurCommitKey: aliceKeyPub,
|
||||||
TheirCommitKey: bobKeyPub,
|
TheirCommitKey: bobKeyPub,
|
||||||
Capacity: channelCapacity,
|
Capacity: channelCapacity,
|
||||||
OurBalance: channelBal,
|
OurBalance: channelBal,
|
||||||
TheirBalance: channelBal,
|
TheirBalance: channelBal,
|
||||||
OurCommitTx: aliceCommitTx,
|
OurCommitTx: aliceCommitTx,
|
||||||
FundingOutpoint: prevOut,
|
FundingOutpoint: prevOut,
|
||||||
OurMultiSigKey: aliceKeyPriv,
|
OurMultiSigKey: aliceKeyPub,
|
||||||
TheirMultiSigKey: bobKeyPub,
|
TheirMultiSigKey: bobKeyPub,
|
||||||
FundingRedeemScript: redeemScript,
|
FundingRedeemScript: redeemScript,
|
||||||
LocalCsvDelay: csvTimeoutAlice,
|
LocalCsvDelay: csvTimeoutAlice,
|
||||||
@ -122,14 +158,14 @@ func createTestChannels() (*LightningChannel, *LightningChannel, func(), error)
|
|||||||
bobChannelState := &channeldb.OpenChannel{
|
bobChannelState := &channeldb.OpenChannel{
|
||||||
TheirLNID: testHdSeed,
|
TheirLNID: testHdSeed,
|
||||||
ChanID: prevOut,
|
ChanID: prevOut,
|
||||||
OurCommitKey: bobKeyPriv,
|
OurCommitKey: bobKeyPub,
|
||||||
TheirCommitKey: aliceKeyPub,
|
TheirCommitKey: aliceKeyPub,
|
||||||
Capacity: channelCapacity,
|
Capacity: channelCapacity,
|
||||||
OurBalance: channelBal,
|
OurBalance: channelBal,
|
||||||
TheirBalance: channelBal,
|
TheirBalance: channelBal,
|
||||||
OurCommitTx: bobCommitTx,
|
OurCommitTx: bobCommitTx,
|
||||||
FundingOutpoint: prevOut,
|
FundingOutpoint: prevOut,
|
||||||
OurMultiSigKey: bobKeyPriv,
|
OurMultiSigKey: bobKeyPub,
|
||||||
TheirMultiSigKey: aliceKeyPub,
|
TheirMultiSigKey: aliceKeyPub,
|
||||||
FundingRedeemScript: redeemScript,
|
FundingRedeemScript: redeemScript,
|
||||||
LocalCsvDelay: csvTimeoutBob,
|
LocalCsvDelay: csvTimeoutBob,
|
||||||
@ -145,11 +181,14 @@ func createTestChannels() (*LightningChannel, *LightningChannel, func(), error)
|
|||||||
os.RemoveAll(alicePath)
|
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 {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
channelBob, err := NewLightningChannel(nil, nil, dbBob, bobChannelState)
|
channelBob, err := NewLightningChannel(bobSigner, nil, nil, bobChannelState)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
@ -456,7 +495,8 @@ func TestCooperativeChannelClosure(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to initiate alice cooperative close: %v", err)
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("unable to complete alice cooperative close: %v", err)
|
t.Fatalf("unable to complete alice cooperative close: %v", err)
|
||||||
}
|
}
|
||||||
@ -475,7 +515,8 @@ func TestCooperativeChannelClosure(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to initiate bob cooperative close: %v", err)
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("unable to complete bob cooperative close: %v", err)
|
t.Fatalf("unable to complete bob cooperative close: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -1,73 +0,0 @@
|
|||||||
package lnwallet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/hex"
|
|
||||||
|
|
||||||
"github.com/roasbeef/btcd/btcjson"
|
|
||||||
"github.com/roasbeef/btcd/wire"
|
|
||||||
"github.com/roasbeef/btcutil"
|
|
||||||
"github.com/roasbeef/btcutil/coinset"
|
|
||||||
)
|
|
||||||
|
|
||||||
// lnCoin represents a single unspet output. Its purpose is to convert a regular
|
|
||||||
// output to a struct adhering to the coinset.Coin interface
|
|
||||||
type lnCoin struct {
|
|
||||||
hash *wire.ShaHash
|
|
||||||
index uint32
|
|
||||||
value btcutil.Amount
|
|
||||||
pkScript []byte
|
|
||||||
numConfs int64
|
|
||||||
valueAge int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *lnCoin) Hash() *wire.ShaHash { return l.hash }
|
|
||||||
func (l *lnCoin) Index() uint32 { return l.index }
|
|
||||||
func (l *lnCoin) Value() btcutil.Amount { return l.value }
|
|
||||||
func (l *lnCoin) PkScript() []byte { return l.pkScript }
|
|
||||||
func (l *lnCoin) NumConfs() int64 { return l.numConfs }
|
|
||||||
func (l *lnCoin) ValueAge() int64 { return l.valueAge }
|
|
||||||
|
|
||||||
// Ensure lnCoin adheres to the coinset.Coin interface.
|
|
||||||
var _ coinset.Coin = (*lnCoin)(nil)
|
|
||||||
|
|
||||||
// newLnCoin creates a new "coin" from the passed output. Coins are required
|
|
||||||
// in order to perform coin selection upon.
|
|
||||||
func newLnCoin(output *btcjson.ListUnspentResult) (coinset.Coin, error) {
|
|
||||||
txid, err := wire.NewShaHashFromStr(output.TxID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pkScript, err := hex.DecodeString(output.ScriptPubKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &lnCoin{
|
|
||||||
hash: txid,
|
|
||||||
// btcjson.ListUnspentResult shows the amount in BTC,
|
|
||||||
// translate into Satoshi so coin selection can work properly.
|
|
||||||
value: btcutil.Amount(output.Amount * 1e8),
|
|
||||||
index: output.Vout,
|
|
||||||
pkScript: pkScript,
|
|
||||||
numConfs: output.Confirmations,
|
|
||||||
// TODO(roasbeef): output.Amount should be a int64, damn json-RPC :/
|
|
||||||
valueAge: output.Confirmations * int64(output.Amount),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// outputsToCoins converts a slice of transaction outputs to a coin-selectable
|
|
||||||
// slice of "Coins"s.
|
|
||||||
func outputsToCoins(outputs []*btcjson.ListUnspentResult) ([]coinset.Coin, error) {
|
|
||||||
coins := make([]coinset.Coin, len(outputs))
|
|
||||||
for i, output := range outputs {
|
|
||||||
coin, err := newLnCoin(output)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
coins[i] = coin
|
|
||||||
}
|
|
||||||
|
|
||||||
return coins, nil
|
|
||||||
}
|
|
@ -1,60 +1,15 @@
|
|||||||
package lnwallet
|
package lnwallet
|
||||||
|
|
||||||
import (
|
// Config..
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/roasbeef/btcd/chaincfg"
|
|
||||||
"github.com/roasbeef/btcutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// TODO(roasbeef): lnwallet config file
|
|
||||||
lnwalletHomeDir = btcutil.AppDataDir("lnwallet", false)
|
|
||||||
defaultDataDir = lnwalletHomeDir
|
|
||||||
|
|
||||||
defaultLogFilename = "lnwallet.log"
|
|
||||||
defaultLogDirname = "logs"
|
|
||||||
defaultLogDir = filepath.Join(lnwalletHomeDir, defaultLogDirname)
|
|
||||||
|
|
||||||
btcdHomeDir = btcutil.AppDataDir("btcd", false)
|
|
||||||
btcdHomedirCAFile = filepath.Join(btcdHomeDir, "rpc.cert")
|
|
||||||
defaultRPCKeyFile = filepath.Join(lnwalletHomeDir, "rpc.key")
|
|
||||||
defaultRPCCertFile = filepath.Join(lnwalletHomeDir, "rpc.cert")
|
|
||||||
|
|
||||||
// defaultPubPassphrase is the default public wallet passphrase which is
|
|
||||||
// used when the user indicates they do not want additional protection
|
|
||||||
// provided by having all public data in the wallet encrypted by a
|
|
||||||
// passphrase only known to them.
|
|
||||||
defaultPubPassphrase = []byte("public")
|
|
||||||
|
|
||||||
walletDbName = "lnwallet.db"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Config...
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
DataDir string
|
// default csv time
|
||||||
LogDir string
|
// default cltv time
|
||||||
|
// default wait for funding time
|
||||||
DebugLevel string
|
// default wait for closure time
|
||||||
|
// min amount to accept channel
|
||||||
RpcHost string // localhost:18334
|
// min fee imformation
|
||||||
RpcUser string
|
// * or possibly interface to predict fees
|
||||||
RpcPass string
|
// max htlcs in flight?
|
||||||
RpcNoTLS bool
|
// possible secret derivation functions
|
||||||
|
//
|
||||||
RPCCert string
|
|
||||||
RPCKey string
|
|
||||||
|
|
||||||
CACert []byte
|
|
||||||
|
|
||||||
PrivatePass []byte
|
|
||||||
PublicPass []byte
|
|
||||||
HdSeed []byte
|
|
||||||
|
|
||||||
// Which bitcoin network are we using?
|
|
||||||
NetParams *chaincfg.Params
|
|
||||||
}
|
|
||||||
|
|
||||||
// setDefaults...
|
|
||||||
func setDefaults(confg *Config) {
|
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,43 @@
|
|||||||
package lnwallet
|
package lnwallet
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/roasbeef/btcd/btcec"
|
"github.com/roasbeef/btcd/btcec"
|
||||||
|
"github.com/roasbeef/btcd/txscript"
|
||||||
"github.com/roasbeef/btcd/wire"
|
"github.com/roasbeef/btcd/wire"
|
||||||
"github.com/roasbeef/btcutil"
|
"github.com/roasbeef/btcutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ErrNotMine is an error denoting that a WalletController instance is unable
|
||||||
|
// to spend a specifid output.
|
||||||
|
var ErrNotMine = errors.New("the passed output doesn't belong to the wallet")
|
||||||
|
|
||||||
|
// AddressType is a enum-like type which denotes the possible address types
|
||||||
|
// WalletController supports.
|
||||||
|
type AddressType uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
// WitnessPubKey represents a p2wkh address.
|
||||||
|
WitnessPubKey AddressType = iota
|
||||||
|
|
||||||
|
// NestedWitnessPubKey represents a p2sh output which is itself a
|
||||||
|
// nested p2wkh output.
|
||||||
|
NestedWitnessPubKey
|
||||||
|
|
||||||
|
// PublicKey represents a regular p2pkh output.
|
||||||
|
PubKeyHash
|
||||||
|
)
|
||||||
|
|
||||||
|
// Utxo is an unspent output denoted by its outpoint, and output value of the
|
||||||
|
// original output.
|
||||||
|
type Utxo struct {
|
||||||
|
Value btcutil.Amount
|
||||||
|
wire.OutPoint
|
||||||
|
}
|
||||||
|
|
||||||
// WalletController defines an abstract interface for controlling a local Pure
|
// WalletController defines an abstract interface for controlling a local Pure
|
||||||
// Go wallet, a local or remote wallet via an RPC mechanism, or possibly even
|
// Go wallet, a local or remote wallet via an RPC mechanism, or possibly even
|
||||||
// a daemon assisted hardware wallet. This interface serves the purpose of
|
// a daemon assisted hardware wallet. This interface serves the purpose of
|
||||||
@ -17,82 +49,71 @@ import (
|
|||||||
// behavior of all interface methods in order to ensure identical behavior
|
// behavior of all interface methods in order to ensure identical behavior
|
||||||
// across all concrete implementations.
|
// across all concrete implementations.
|
||||||
type WalletController interface {
|
type WalletController interface {
|
||||||
|
// FetchInputInfo queries for the WalletController's knowledge of the
|
||||||
|
// passed outpoint. If the base wallet determines this output is under
|
||||||
|
// its control, then the original txout should be returned. Otherwise,
|
||||||
|
// a non-nil error value of ErrNotMine should be returned instead.
|
||||||
|
FetchInputInfo(prevOut *wire.OutPoint) (*wire.TxOut, error)
|
||||||
|
|
||||||
// ConfirmedBalance returns the sum of all the wallet's unspent outputs
|
// ConfirmedBalance returns the sum of all the wallet's unspent outputs
|
||||||
// that have at least confs confirmations. If confs is set to zero,
|
// that have at least confs confirmations. If confs is set to zero,
|
||||||
// then all unspent outputs, including those currently in the mempool
|
// then all unspent outputs, including those currently in the mempool
|
||||||
// will be included in the final sum.
|
// will be included in the final sum.
|
||||||
ConfirmedBalance(confs int32) btcutil.Amount
|
ConfirmedBalance(confs int32, witness bool) (btcutil.Amount, error)
|
||||||
|
|
||||||
// NewAddress returns the next external address for the wallet. The
|
// NewAddress returns the next external or internal address for the
|
||||||
// type of address returned is dictated by the wallet's capabilities,
|
// wallet dicatated by the value of the `change` paramter. If change is
|
||||||
// and may be of type: p2sh, p2pkh, p2wkh, p2wsh, etc.
|
// true, then an internal address should be used, otherwise an external
|
||||||
NewAddress(witness bool) (btcutil.Address, error)
|
// address should be returned. The type of address returned is dictated
|
||||||
|
// by the wallet's capabilities, and may be of type: p2sh, p2pkh,
|
||||||
// NewChangeAddress returns a new change address for the wallet. If the
|
// p2wkh, p2wsh, etc.
|
||||||
// underlying wallet supports hd key chains, then this address should be
|
NewAddress(addrType AddressType, change bool) (btcutil.Address, error)
|
||||||
// dervied from an internal branch.
|
|
||||||
NewChangeAddress(witness bool) (btcutil.Address, error)
|
|
||||||
|
|
||||||
// GetPrivKey retrives the underlying private key associated with the
|
// GetPrivKey retrives the underlying private key associated with the
|
||||||
// passed address. If the wallet is unable to locate this private key
|
// passed address. If the wallet is unable to locate this private key
|
||||||
// due to the address not being under control of the wallet, then an
|
// due to the address not being under control of the wallet, then an
|
||||||
// error should be returned.
|
// error should be returned.
|
||||||
GetPrivKey(a *btcutil.Address) (*btcec.PrivateKey, error)
|
// TODO(roasbeef): should instead take tadge's derivation scheme in
|
||||||
|
GetPrivKey(a btcutil.Address) (*btcec.PrivateKey, error)
|
||||||
|
|
||||||
// NewRawKey returns a raw private key controlled by the wallet. These
|
// NewRawKey returns a raw private key controlled by the wallet. These
|
||||||
// keys are used for the 2-of-2 multi-sig outputs for funding
|
// keys are used for the 2-of-2 multi-sig outputs for funding
|
||||||
// transactions, as well as the pub key used for commitment transactions.
|
// transactions, as well as the pub key used for commitment transactions.
|
||||||
// TODO(roasbeef): key pool due to cancelled reservations??
|
// TODO(roasbeef): may be scrapped, see above TODO
|
||||||
NewRawKey() (*btcec.PrivateKey, error)
|
NewRawKey() (*btcec.PublicKey, error)
|
||||||
|
|
||||||
// FetchIdentityKey returns a private key which will be utilized as the
|
// FetchRootKey returns a root key which will be used by the
|
||||||
// wallet's Lightning Network identity for authentication purposes.
|
// LightningWallet to deterministically generate secrets. The private
|
||||||
// TODO(roasbeef): rotate identity key?
|
// key returned by this method should remain constant in-between
|
||||||
FetchIdentityKey() (*btcec.PrivateKey, error)
|
// WalletController restarts.
|
||||||
|
FetchRootKey() (*btcec.PrivateKey, error)
|
||||||
|
|
||||||
// FundTransaction creates a new unsigned transactions paying to the
|
// SendOutputs funds, signs, and broadcasts a Bitcoin transaction
|
||||||
// passed outputs, possibly using the specified change address. The
|
// paying out to the specified outputs. In the case the wallet has
|
||||||
// includeFee parameter dictates if the wallet should also provide
|
// insufficient funds, or the outputs are non-standard, and error
|
||||||
// enough the funds necessary to create an adequate fee or not.
|
// should be returned.
|
||||||
FundTransaction(outputs []*wire.TxOut, changeAddr btcutil.Address,
|
SendOutputs(outputs []*wire.TxOut) (*wire.ShaHash, error)
|
||||||
includeFee bool) (*wire.MsgTx, error)
|
|
||||||
|
|
||||||
// SignTransaction performs potentially a sparse, or full signing of
|
|
||||||
// all inputs within the passed transaction that are spendable by the
|
|
||||||
// wallet.
|
|
||||||
SignTransaction(tx *wire.MsgTx) error
|
|
||||||
|
|
||||||
// BroadcastTransaction performs cursory validation (dust checks, etc),
|
|
||||||
// then finally broadcasts the passed transaction to the Bitcoin network.
|
|
||||||
BroadcastTransaction(tx *wire.MsgTx) error
|
|
||||||
|
|
||||||
// SendMany funds, signs, and broadcasts a Bitcoin transaction paying
|
|
||||||
// out to the specified outputs. In the case the wallet has insufficient
|
|
||||||
// funds, or the outputs are non-standard, and error should be returned.
|
|
||||||
SendMany(outputs []*wire.TxOut) (*wire.ShaHash, error)
|
|
||||||
|
|
||||||
// ListUnspentWitness returns all unspent outputs which are version 0
|
// ListUnspentWitness returns all unspent outputs which are version 0
|
||||||
// witness programs. The 'confirms' parameter indicates the minimum
|
// witness programs. The 'confirms' parameter indicates the minimum
|
||||||
// number of confirmations an output needs in order to be returned by
|
// number of confirmations an output needs in order to be returned by
|
||||||
// this method. Passing -1 as 'confirms' indicates that even unconfirmed
|
// this method. Passing -1 as 'confirms' indicates that even
|
||||||
// outputs should be returned.
|
// unconfirmed outputs should be returned.
|
||||||
ListUnspentWitness(confirms int32) ([]*wire.OutPoint, error)
|
ListUnspentWitness(confirms int32) ([]*Utxo, error)
|
||||||
|
|
||||||
// LockOutpoint marks an outpoint as locked meaning it will no longer
|
// LockOutpoint marks an outpoint as locked meaning it will no longer
|
||||||
// be deemed as eligble for coin selection. Locking outputs are utilized
|
// be deemed as eligible for coin selection. Locking outputs are
|
||||||
// in order to avoid race conditions when selecting inputs for usage when
|
// utilized in order to avoid race conditions when selecting inputs for
|
||||||
// funding a channel.
|
// usage when funding a channel.
|
||||||
LockOutpoint(o wire.OutPoint)
|
LockOutpoint(o wire.OutPoint)
|
||||||
|
|
||||||
// UnlockOutpoint unlocks an previously locked output, marking it
|
// UnlockOutpoint unlocks an previously locked output, marking it
|
||||||
// eligible for coin seleciton.
|
// eligible for coin seleciton.
|
||||||
UnlockOutpoint(o wire.OutPoint)
|
UnlockOutpoint(o wire.OutPoint)
|
||||||
|
|
||||||
// ImportScript imports the serialize public key script, or redeem
|
// PublishTransaction performs cursory validation (dust checks, etc),
|
||||||
// script into the wallet's database. Scripts to be imported include
|
// then finally broadcasts the passed transaction to the Bitcoin network.
|
||||||
// the 2-of-2 script for funding transactions, commitment scripts,
|
PublishTransaction(tx *wire.MsgTx) error
|
||||||
// HTLCs scripts, and so on.
|
|
||||||
ImportScript(b []byte) error
|
|
||||||
|
|
||||||
// Start initializes the wallet, making any neccessary connections,
|
// Start initializes the wallet, making any neccessary connections,
|
||||||
// starting up required goroutines etc.
|
// starting up required goroutines etc.
|
||||||
@ -101,11 +122,147 @@ type WalletController interface {
|
|||||||
// Stop signals the wallet for shutdown. Shutdown may entail closing
|
// Stop signals the wallet for shutdown. Shutdown may entail closing
|
||||||
// any active sockets, database handles, stopping goroutines, etc.
|
// any active sockets, database handles, stopping goroutines, etc.
|
||||||
Stop() error
|
Stop() error
|
||||||
|
}
|
||||||
// WaitForShutdown blocks until the wallet finishes the shutdown
|
|
||||||
// procedure triggered by a prior call to Stop().
|
// BlockChainIO is a dedicated source which will be used to obtain queries
|
||||||
WaitForShutdown() error
|
// related to the current state of the blockchain. The data returned by each of
|
||||||
|
// the defined methods within this interface should always return the most up
|
||||||
// TODO(roasbeef): ImportPriv?
|
// to date data possible.
|
||||||
// * segwitty flag?
|
//
|
||||||
|
// TODO(roasbeef): move to diff package perhaps?
|
||||||
|
type BlockChainIO interface {
|
||||||
|
// GetCurrentHeight returns the current height of the valid most-work
|
||||||
|
// chain the implementation is aware of.
|
||||||
|
GetCurrentHeight() (int32, error)
|
||||||
|
|
||||||
|
// GetTxOut returns the original output referenced by the passed
|
||||||
|
// outpoint.
|
||||||
|
GetUtxo(txid *wire.ShaHash, index uint32) (*wire.TxOut, error)
|
||||||
|
|
||||||
|
// GetTransaction returns the full transaction identified by the passed
|
||||||
|
// transaction ID.
|
||||||
|
GetTransaction(txid *wire.ShaHash) (*wire.MsgTx, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignDescriptor houses the necessary information required to succesfully sign
|
||||||
|
// a given output. This struct is used by the Signer interface in order to gain
|
||||||
|
// access to critial data needed to generate a valid signature.
|
||||||
|
type SignDescriptor struct {
|
||||||
|
// Pubkey is the public key to which the signature should be generated
|
||||||
|
// over. The Signer should then generate a signature with the private
|
||||||
|
// key corresponding to this public key.
|
||||||
|
PubKey *btcec.PublicKey
|
||||||
|
|
||||||
|
// RedeemScript is the full script required to properly redeem the
|
||||||
|
// output. This field will only be populated if a p2wsh or a p2sh
|
||||||
|
// output is being signed.
|
||||||
|
RedeemScript []byte
|
||||||
|
|
||||||
|
// Output is the target output which should be signed. The PkScript and
|
||||||
|
// Value fields within the output should be properly populated,
|
||||||
|
// otherwise an invalid signature may be generated.
|
||||||
|
Output *wire.TxOut
|
||||||
|
|
||||||
|
// HashType is the target sighash type that should be used when
|
||||||
|
// generating the final sighash, and signature.
|
||||||
|
HashType txscript.SigHashType
|
||||||
|
|
||||||
|
// SigHashes is the pre-computed sighash midstate to be used when
|
||||||
|
// generating the final sighash for signing.
|
||||||
|
SigHashes *txscript.TxSigHashes
|
||||||
|
|
||||||
|
// InputIndex is the target input within the transaction that should be
|
||||||
|
// signed.
|
||||||
|
InputIndex int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signer represents an abstract object capable of generating raw signatures as
|
||||||
|
// well as full complete input scripts given a valid SignDescriptor and
|
||||||
|
// transaction. This interface fully abstracts away signing paving the way for
|
||||||
|
// Signer implementations such as hardware wallets, hardware tokens, HSM's, or
|
||||||
|
// simply a regular wallet.
|
||||||
|
type Signer interface {
|
||||||
|
// SignOutputRaw generates a signature for the passed transaction
|
||||||
|
// according to the data within the passed SignDescriptor.
|
||||||
|
//
|
||||||
|
// NOTE: The resulting signature should be void of a sighash byte.
|
||||||
|
SignOutputRaw(tx *wire.MsgTx, signDesc *SignDescriptor) ([]byte, error)
|
||||||
|
|
||||||
|
// ComputeInputScript generates a complete InputIndex for the passed
|
||||||
|
// transaction with the signature as defined within the passed
|
||||||
|
// SignDescriptor. This method should be capable of generating the
|
||||||
|
// proper input script for both regular p2wkh output and p2wkh outputs
|
||||||
|
// nested within a regualr p2sh output.
|
||||||
|
ComputeInputScript(tx *wire.MsgTx, signDesc *SignDescriptor) (*InputScript, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WalletDriver represents a "driver" for a particular concrete
|
||||||
|
// WalletController implementation. A driver is indentified by a globally
|
||||||
|
// unique string identifier along with a 'New()' method which is responsible
|
||||||
|
// for initializing a particular WalletController concrete implementation.
|
||||||
|
type WalletDriver struct {
|
||||||
|
// WalletType is a string which uniquely identifes the WalletController
|
||||||
|
// that this driver, drives.
|
||||||
|
WalletType string
|
||||||
|
|
||||||
|
// New creates a new instance of a concrete WalletController
|
||||||
|
// implementation given a variadic set up arguments. The function takes
|
||||||
|
// a varidaic number of interface paramters in order to provide
|
||||||
|
// initialization flexibility, thereby accomodating several potential
|
||||||
|
// WalletController implementations.
|
||||||
|
New func(args ...interface{}) (WalletController, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
wallets = make(map[string]*WalletDriver)
|
||||||
|
registerMtx sync.Mutex
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegisteredWallets returns a slice of all currently registered notifiers.
|
||||||
|
//
|
||||||
|
// NOTE: This function is safe for concurrent access.
|
||||||
|
func RegisteredWallets() []*WalletDriver {
|
||||||
|
registerMtx.Lock()
|
||||||
|
defer registerMtx.Unlock()
|
||||||
|
|
||||||
|
registeredWallets := make([]*WalletDriver, 0, len(wallets))
|
||||||
|
for _, wallet := range wallets {
|
||||||
|
registeredWallets = append(registeredWallets, wallet)
|
||||||
|
}
|
||||||
|
|
||||||
|
return registeredWallets
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterWallet registers a WalletDriver which is capable of driving a
|
||||||
|
// concrete WalletController interface. In the case that this driver has
|
||||||
|
// already been registered, an error is returned.
|
||||||
|
//
|
||||||
|
// NOTE: This function is safe for concurrent access.
|
||||||
|
func RegisterWallet(driver *WalletDriver) error {
|
||||||
|
registerMtx.Lock()
|
||||||
|
defer registerMtx.Unlock()
|
||||||
|
|
||||||
|
if _, ok := wallets[driver.WalletType]; ok {
|
||||||
|
return fmt.Errorf("wallet already registered")
|
||||||
|
}
|
||||||
|
|
||||||
|
wallets[driver.WalletType] = driver
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SupportedWallets returns a slice of strings that represents the walelt
|
||||||
|
// drivers that have been registered and are therefore supported.
|
||||||
|
//
|
||||||
|
// NOTE: This function is safe for concurrent access.
|
||||||
|
func SupportedWallets() []string {
|
||||||
|
registerMtx.Lock()
|
||||||
|
defer registerMtx.Unlock()
|
||||||
|
|
||||||
|
supportedWallets := make([]string, 0, len(wallets))
|
||||||
|
for walletName := range wallets {
|
||||||
|
supportedWallets = append(supportedWallets, walletName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return supportedWallets
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package lnwallet
|
package lnwallet_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -11,18 +11,20 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/boltdb/bolt"
|
"github.com/boltdb/bolt"
|
||||||
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||||
"github.com/lightningnetwork/lnd/chainntnfs/btcdnotify"
|
"github.com/lightningnetwork/lnd/chainntnfs/btcdnotify"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
|
||||||
"github.com/roasbeef/btcd/chaincfg"
|
"github.com/roasbeef/btcd/chaincfg"
|
||||||
"github.com/roasbeef/btcutil/txsort"
|
"github.com/roasbeef/btcutil/txsort"
|
||||||
|
_ "github.com/roasbeef/btcwallet/walletdb/bdb"
|
||||||
|
|
||||||
"github.com/roasbeef/btcd/btcec"
|
"github.com/roasbeef/btcd/btcec"
|
||||||
"github.com/roasbeef/btcd/rpctest"
|
"github.com/roasbeef/btcd/rpctest"
|
||||||
"github.com/roasbeef/btcd/txscript"
|
"github.com/roasbeef/btcd/txscript"
|
||||||
"github.com/roasbeef/btcd/wire"
|
"github.com/roasbeef/btcd/wire"
|
||||||
"github.com/roasbeef/btcutil"
|
"github.com/roasbeef/btcutil"
|
||||||
"github.com/roasbeef/btcutil/coinset"
|
|
||||||
"github.com/roasbeef/btcwallet/waddrmgr"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -60,8 +62,8 @@ var (
|
|||||||
// assertProperBalance asserts than the total value of the unspent outputs
|
// assertProperBalance asserts than the total value of the unspent outputs
|
||||||
// within the wallet are *exactly* amount. If unable to retrieve the current
|
// 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.
|
// balance, or the assertion fails, the test will halt with a fatal error.
|
||||||
func assertProperBalance(t *testing.T, lw *LightningWallet, numConfirms int32, amount int64) {
|
func assertProperBalance(t *testing.T, lw *lnwallet.LightningWallet, numConfirms int32, amount int64) {
|
||||||
balance, err := lw.CalculateBalance(numConfirms)
|
balance, err := lw.ConfirmedBalance(numConfirms, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to query for balance: %v", err)
|
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,
|
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
|
// Mine a single block. After this block is mined, the channel should
|
||||||
// be considered fully open.
|
// be considered fully open.
|
||||||
if _, err := miner.Node.Generate(1); err != nil {
|
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
|
// Contribution returns bobNode's contribution necessary to open a payment
|
||||||
// channel with Alice.
|
// channel with Alice.
|
||||||
func (b *bobNode) Contribution(aliceCommitKey *btcec.PublicKey) *ChannelContribution {
|
func (b *bobNode) Contribution(aliceCommitKey *btcec.PublicKey) *lnwallet.ChannelContribution {
|
||||||
revokeKey := deriveRevocationPubkey(aliceCommitKey, b.revocation[:])
|
revokeKey := lnwallet.DeriveRevocationPubkey(aliceCommitKey, b.revocation[:])
|
||||||
return &ChannelContribution{
|
return &lnwallet.ChannelContribution{
|
||||||
FundingAmount: b.fundingAmt,
|
FundingAmount: b.fundingAmt,
|
||||||
Inputs: b.availableOutputs,
|
Inputs: b.availableOutputs,
|
||||||
ChangeOutputs: b.changeOutputs,
|
ChangeOutputs: b.changeOutputs,
|
||||||
@ -124,9 +126,9 @@ func (b *bobNode) Contribution(aliceCommitKey *btcec.PublicKey) *ChannelContribu
|
|||||||
|
|
||||||
// SingleContribution returns bobNode's contribution to a single funded
|
// SingleContribution returns bobNode's contribution to a single funded
|
||||||
// channel. This contribution contains no inputs nor change outputs.
|
// channel. This contribution contains no inputs nor change outputs.
|
||||||
func (b *bobNode) SingleContribution(aliceCommitKey *btcec.PublicKey) *ChannelContribution {
|
func (b *bobNode) SingleContribution(aliceCommitKey *btcec.PublicKey) *lnwallet.ChannelContribution {
|
||||||
revokeKey := deriveRevocationPubkey(aliceCommitKey, b.revocation[:])
|
revokeKey := lnwallet.DeriveRevocationPubkey(aliceCommitKey, b.revocation[:])
|
||||||
return &ChannelContribution{
|
return &lnwallet.ChannelContribution{
|
||||||
FundingAmount: b.fundingAmt,
|
FundingAmount: b.fundingAmt,
|
||||||
MultiSigKey: b.channelKey,
|
MultiSigKey: b.channelKey,
|
||||||
CommitKey: 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
|
// signFundingTx generates signatures for all the inputs in the funding tx
|
||||||
// belonging to Bob.
|
// belonging to Bob.
|
||||||
// NOTE: This generates the full witness stack.
|
// NOTE: This generates the full witness stack.
|
||||||
func (b *bobNode) signFundingTx(fundingTx *wire.MsgTx) ([]*InputScript, error) {
|
func (b *bobNode) signFundingTx(fundingTx *wire.MsgTx) ([]*lnwallet.InputScript, error) {
|
||||||
bobInputScripts := make([]*InputScript, 0, len(b.availableOutputs))
|
bobInputScripts := make([]*lnwallet.InputScript, 0, len(b.availableOutputs))
|
||||||
bobPkScript := b.changeOutputs[0].PkScript
|
bobPkScript := b.changeOutputs[0].PkScript
|
||||||
|
|
||||||
inputValue := int64(7e8)
|
inputValue := int64(7e8)
|
||||||
@ -158,7 +160,7 @@ func (b *bobNode) signFundingTx(fundingTx *wire.MsgTx) ([]*InputScript, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
inputScript := &InputScript{Witness: witness}
|
inputScript := &lnwallet.InputScript{Witness: witness}
|
||||||
bobInputScripts = append(bobInputScripts, inputScript)
|
bobInputScripts = append(bobInputScripts, inputScript)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,7 +219,7 @@ func newBobNode(miner *rpctest.Harness, amt btcutil.Amount) (*bobNode, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
found, index := findScriptOutputIndex(tx.MsgTx(), bobAddrScript)
|
found, index := lnwallet.FindScriptOutputIndex(tx.MsgTx(), bobAddrScript)
|
||||||
if !found {
|
if !found {
|
||||||
return nil, fmt.Errorf("output to bob never created")
|
return nil, fmt.Errorf("output to bob never created")
|
||||||
}
|
}
|
||||||
@ -251,14 +253,14 @@ func newBobNode(miner *rpctest.Harness, amt btcutil.Amount) (*bobNode, error) {
|
|||||||
}, nil
|
}, 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
|
// Using the mining node, spend from a coinbase output numOutputs to
|
||||||
// give us btcPerOutput with each output.
|
// give us btcPerOutput with each output.
|
||||||
satoshiPerOutput := int64(btcPerOutput * 1e8)
|
satoshiPerOutput := int64(btcPerOutput * 1e8)
|
||||||
addrs := make([]btcutil.Address, 0, numOutputs)
|
addrs := make([]btcutil.Address, 0, numOutputs)
|
||||||
for i := 0; i < numOutputs; i++ {
|
for i := 0; i < numOutputs; i++ {
|
||||||
// Grab a fresh address from the wallet to house this output.
|
// 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -285,87 +287,59 @@ func loadTestCredits(miner *rpctest.Harness, w *LightningWallet, numOutputs, btc
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, bestHeight, err := miner.Node.GetBestBlock()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait until the wallet has finished syncing up to the main chain.
|
// Wait until the wallet has finished syncing up to the main chain.
|
||||||
ticker := time.NewTicker(100 * time.Millisecond)
|
ticker := time.NewTicker(100 * time.Millisecond)
|
||||||
|
expectedBalance := btcutil.Amount(satoshiPerOutput * int64(numOutputs))
|
||||||
out:
|
out:
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
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
|
break out
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ticker.Stop()
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// createTestWallet creates a test LightningWallet will a total of 20BTC
|
// createTestWallet creates a test LightningWallet will a total of 20BTC
|
||||||
// available for funding channels.
|
// available for funding channels.
|
||||||
func createTestWallet(miningNode *rpctest.Harness, netParams *chaincfg.Params) (string, *LightningWallet, error) {
|
func createTestWallet(tempTestDir string, miningNode *rpctest.Harness,
|
||||||
privPass := []byte("private-test")
|
netParams *chaincfg.Params, notifier chainntnfs.ChainNotifier,
|
||||||
tempTestDir, err := ioutil.TempDir("", "lnwallet")
|
wc lnwallet.WalletController, signer lnwallet.Signer,
|
||||||
if err != nil {
|
bio lnwallet.BlockChainIO) (*lnwallet.LightningWallet, error) {
|
||||||
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,
|
|
||||||
}
|
|
||||||
|
|
||||||
dbDir := filepath.Join(tempTestDir, "cdb")
|
dbDir := filepath.Join(tempTestDir, "cdb")
|
||||||
cdb, err := channeldb.Open(dbDir, &chaincfg.SegNet4Params)
|
cdb, err := channeldb.Open(dbDir, &chaincfg.SegNet4Params)
|
||||||
if err != nil {
|
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 {
|
if err != nil {
|
||||||
return "", nil, err
|
return nil, err
|
||||||
}
|
|
||||||
if err := chainNotifier.Start(); err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wallet, err := NewLightningWallet(config, cdb, chainNotifier)
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
if err := wallet.Startup(); err != nil {
|
if err := wallet.Startup(); err != nil {
|
||||||
return "", nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cdb.RegisterCryptoSystem(&WaddrmgrEncryptorDecryptor{wallet.Manager})
|
// Load our test wallet with 20 outputs each holding 4BTC.
|
||||||
|
if err := loadTestCredits(miningNode, wallet, 20, 4); err != nil {
|
||||||
// Load our test wallet with 10 outputs each holding 4BTC.
|
return nil, err
|
||||||
if err := loadTestCredits(miningNode, wallet, 10, 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
|
// Create the bob-test wallet which will be the other side of our funding
|
||||||
// channel.
|
// channel.
|
||||||
fundingAmount := btcutil.Amount(5 * 1e8)
|
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
|
// Bob initiates a channel funded with 5 BTC for each side, so 10
|
||||||
// BTC total. He also generates 2 BTC in change.
|
// 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)
|
fundingAmount, bobNode.id, numReqConfs, 4)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to initialize funding reservation: %v", err)
|
t.Fatalf("unable to initialize funding reservation: %v", err)
|
||||||
@ -426,10 +400,13 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness, lnwallet *Lightn
|
|||||||
if ourContribution.RevocationKey == nil {
|
if ourContribution.RevocationKey == nil {
|
||||||
t.Fatalf("alice's revocation key not found")
|
t.Fatalf("alice's revocation key not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Additionally, the funding tx should have been populated.
|
// Additionally, the funding tx should have been populated.
|
||||||
if chanReservation.fundingTx == nil {
|
fundingTx := chanReservation.FinalFundingTx()
|
||||||
|
if fundingTx == nil {
|
||||||
t.Fatalf("funding transaction never created!")
|
t.Fatalf("funding transaction never created!")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Their funds should also be filled in.
|
// Their funds should also be filled in.
|
||||||
if len(theirContribution.Inputs) != 1 {
|
if len(theirContribution.Inputs) != 1 {
|
||||||
t.Fatalf("bob's outputs for funding tx not properly selected, have %v "+
|
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.
|
// Alice responds with her output, change addr, multi-sig key and signatures.
|
||||||
// Bob then responds with his signatures.
|
// Bob then responds with his signatures.
|
||||||
bobsSigs, err := bobNode.signFundingTx(chanReservation.fundingTx)
|
bobsSigs, err := bobNode.signFundingTx(fundingTx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to sign inputs for bob: %v", err)
|
t.Fatalf("unable to sign inputs for bob: %v", err)
|
||||||
}
|
}
|
||||||
commitSig, err := bobNode.signCommitTx(
|
commitSig, err := bobNode.signCommitTx(
|
||||||
chanReservation.partialState.OurCommitTx,
|
chanReservation.LocalCommitTx(),
|
||||||
chanReservation.partialState.FundingRedeemScript,
|
chanReservation.FundingRedeemScript(),
|
||||||
10e8)
|
10e8)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("bob is unable to sign alice's commit tx: %v", err)
|
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.
|
// txn hits a "comfortable" depth.
|
||||||
|
|
||||||
// The resulting active channel state should have been persisted to the DB.
|
// The resulting active channel state should have been persisted to the DB.
|
||||||
fundingTx := chanReservation.FinalFundingTx()
|
|
||||||
fundingSha := fundingTx.TxSha()
|
fundingSha := fundingTx.TxSha()
|
||||||
nodeID := wire.ShaHash(bobNode.id)
|
nodeID := wire.ShaHash(bobNode.id)
|
||||||
channels, err := lnwallet.channelDB.FetchOpenChannels(&nodeID)
|
channels, err := wallet.ChannelDB.FetchOpenChannels(&nodeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to retrieve channel from DB: %v", err)
|
t.Fatalf("unable to retrieve channel from DB: %v", err)
|
||||||
}
|
}
|
||||||
@ -495,46 +471,49 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness, lnwallet *Lightn
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to init cooperative closure: %v", err)
|
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.
|
// Obtain bob's signature for the closure transaction.
|
||||||
redeemScript := lnc.channelState.FundingRedeemScript
|
redeemScript := lnc.FundingRedeemScript
|
||||||
fundingOut := lnc.ChannelPoint()
|
fundingOut := lnc.ChannelPoint()
|
||||||
fundingTxIn := wire.NewTxIn(fundingOut, nil, nil)
|
fundingTxIn := wire.NewTxIn(fundingOut, nil, nil)
|
||||||
bobCloseTx := createCooperativeCloseTx(fundingTxIn,
|
bobCloseTx := lnwallet.CreateCooperativeCloseTx(fundingTxIn,
|
||||||
lnc.channelState.TheirBalance, lnc.channelState.OurBalance,
|
chanInfo.RemoteBalance, chanInfo.LocalBalance,
|
||||||
lnc.channelState.TheirDeliveryScript, lnc.channelState.OurDeliveryScript,
|
lnc.RemoteDeliveryScript, lnc.LocalDeliveryScript,
|
||||||
false)
|
false)
|
||||||
bobSig, err := bobNode.signCommitTx(bobCloseTx,
|
bobSig, err := bobNode.signCommitTx(bobCloseTx, redeemScript, int64(lnc.Capacity))
|
||||||
redeemScript,
|
|
||||||
int64(lnc.channelState.Capacity))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to generate bob's signature for closing tx: %v", err)
|
t.Fatalf("unable to generate bob's signature for closing tx: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Broadcast the transaction to the network. This transaction should
|
// Broadcast the transaction to the network. This transaction should
|
||||||
// be accepted, and found in the next mined block.
|
// be accepted, and found in the next mined block.
|
||||||
ourKey := lnc.channelState.OurMultiSigKey.PubKey().SerializeCompressed()
|
ourKey := chanReservation.OurContribution().MultiSigKey.SerializeCompressed()
|
||||||
theirKey := lnc.channelState.TheirMultiSigKey.SerializeCompressed()
|
theirKey := chanReservation.TheirContribution().MultiSigKey.SerializeCompressed()
|
||||||
witness := spendMultiSig(redeemScript, ourKey, aliceCloseSig,
|
witness := lnwallet.SpendMultiSig(redeemScript, ourKey, aliceCloseSig,
|
||||||
theirKey, bobSig)
|
theirKey, bobSig)
|
||||||
bobCloseTx.TxIn[0].Witness = witness
|
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)
|
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
|
// Create two channels, both asking for 8 BTC each, totalling 16
|
||||||
// BTC.
|
// BTC.
|
||||||
// TODO(roasbeef): tests for concurrent funding.
|
// TODO(roasbeef): tests for concurrent funding.
|
||||||
// * also func for below
|
// * also func for below
|
||||||
fundingAmount := btcutil.Amount(8 * 1e8)
|
fundingAmount := btcutil.Amount(8 * 1e8)
|
||||||
chanReservation1, err := lnwallet.InitChannelReservation(fundingAmount,
|
chanReservation1, err := wallet.InitChannelReservation(fundingAmount,
|
||||||
fundingAmount, testHdSeed, numReqConfs, 4)
|
fundingAmount, testHdSeed, numReqConfs, 4)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to initialize funding reservation 1: %v", err)
|
t.Fatalf("unable to initialize funding reservation 1: %v", err)
|
||||||
}
|
}
|
||||||
chanReservation2, err := lnwallet.InitChannelReservation(fundingAmount,
|
chanReservation2, err := wallet.InitChannelReservation(fundingAmount,
|
||||||
fundingAmount, testHdSeed, numReqConfs, 4)
|
fundingAmount, testHdSeed, numReqConfs, 4)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to initialize funding reservation 2: %v", err)
|
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
|
// 90 BTC. We only have around 24BTC worth of outpoints that aren't locked, so
|
||||||
// this should fail.
|
// this should fail.
|
||||||
amt := btcutil.Amount(90 * 1e8)
|
amt := btcutil.Amount(90 * 1e8)
|
||||||
failedReservation, err := lnwallet.InitChannelReservation(amt, amt,
|
failedReservation, err := wallet.InitChannelReservation(amt, amt,
|
||||||
testHdSeed, numReqConfs, 4)
|
testHdSeed, numReqConfs, 4)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("not error returned, should fail on coin selection")
|
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)
|
t.Fatalf("error not coinselect error: %v", err)
|
||||||
}
|
}
|
||||||
if failedReservation != nil {
|
if failedReservation != nil {
|
||||||
@ -576,28 +555,30 @@ func testFundingTransactionLockedOutputs(miner *rpctest.Harness, lnwallet *Light
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testFundingCancellationNotEnoughFunds(miner *rpctest.Harness, lnwallet *LightningWallet, t *testing.T) {
|
func testFundingCancellationNotEnoughFunds(miner *rpctest.Harness,
|
||||||
// Create a reservation for 22 BTC.
|
wallet *lnwallet.LightningWallet, t *testing.T) {
|
||||||
fundingAmount := btcutil.Amount(22 * 1e8)
|
|
||||||
chanReservation, err := lnwallet.InitChannelReservation(fundingAmount,
|
// Create a reservation for 44 BTC.
|
||||||
|
fundingAmount := btcutil.Amount(44 * 1e8)
|
||||||
|
chanReservation, err := wallet.InitChannelReservation(fundingAmount,
|
||||||
fundingAmount, testHdSeed, numReqConfs, 4)
|
fundingAmount, testHdSeed, numReqConfs, 4)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to initialize funding reservation: %v", err)
|
t.Fatalf("unable to initialize funding reservation: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// There should be three locked outpoints.
|
// There should be 12 locked outpoints.
|
||||||
lockedOutPoints := lnwallet.LockedOutpoints()
|
lockedOutPoints := wallet.LockedOutpoints()
|
||||||
if len(lockedOutPoints) != 6 {
|
if len(lockedOutPoints) != 12 {
|
||||||
t.Fatalf("two outpoints should now be locked, instead %v are",
|
t.Fatalf("two outpoints should now be locked, instead %v are",
|
||||||
len(lockedOutPoints))
|
len(lockedOutPoints))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to create another channel with 22 BTC, this should fail.
|
// Attempt to create another channel with 44 BTC, this should fail.
|
||||||
failedReservation, err := lnwallet.InitChannelReservation(fundingAmount,
|
_, err = wallet.InitChannelReservation(fundingAmount,
|
||||||
fundingAmount, testHdSeed, numReqConfs, 4)
|
fundingAmount, testHdSeed, numReqConfs, 4)
|
||||||
if err != coinset.ErrCoinsNoSelectionAvailable {
|
if err != lnwallet.ErrInsufficientFunds {
|
||||||
t.Fatalf("coin selection succeded should have insufficient funds: %+v",
|
t.Fatalf("coin selection succeded should have insufficient funds: %v",
|
||||||
failedReservation)
|
err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now cancel that old reservation.
|
// Now cancel that old reservation.
|
||||||
@ -606,15 +587,16 @@ func testFundingCancellationNotEnoughFunds(miner *rpctest.Harness, lnwallet *Lig
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Those outpoints should no longer be locked.
|
// Those outpoints should no longer be locked.
|
||||||
lockedOutPoints = lnwallet.LockedOutpoints()
|
lockedOutPoints = wallet.LockedOutpoints()
|
||||||
if len(lockedOutPoints) != 0 {
|
if len(lockedOutPoints) != 0 {
|
||||||
t.Fatalf("outpoints still locked")
|
t.Fatalf("outpoints still locked")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reservation ID should now longer be tracked.
|
// Reservation ID should no longer be tracked.
|
||||||
_, ok := lnwallet.fundingLimbo[chanReservation.reservationID]
|
numReservations := wallet.ActiveReservations()
|
||||||
if ok {
|
if len(wallet.ActiveReservations()) != 0 {
|
||||||
t.Fatalf("funding reservation still in map")
|
t.Fatalf("should have 0 reservations, instead have %v",
|
||||||
|
numReservations)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(roasbeef): create method like Balance that ignores locked
|
// TODO(roasbeef): create method like Balance that ignores locked
|
||||||
@ -622,16 +604,18 @@ func testFundingCancellationNotEnoughFunds(miner *rpctest.Harness, lnwallet *Lig
|
|||||||
// attempting coin selection.
|
// attempting coin selection.
|
||||||
|
|
||||||
// Request to fund a new channel should now succeeed.
|
// Request to fund a new channel should now succeeed.
|
||||||
_, err = lnwallet.InitChannelReservation(fundingAmount, fundingAmount,
|
_, err = wallet.InitChannelReservation(fundingAmount, fundingAmount,
|
||||||
testHdSeed, numReqConfs, 4)
|
testHdSeed, numReqConfs, 4)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to initialize funding reservation: %v", err)
|
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.
|
// 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
|
// Attempt to cancel this reservation. This should fail, we know
|
||||||
// nothing of it.
|
// 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
|
// For this scenario, we (lnwallet) will be the channel initiator while bob
|
||||||
// will be the recipient.
|
// will be the recipient.
|
||||||
|
|
||||||
@ -702,7 +688,7 @@ func testSingleFunderReservationWorkflowInitiator(miner *rpctest.Harness, lnwall
|
|||||||
t.Fatalf("commitment sig not found")
|
t.Fatalf("commitment sig not found")
|
||||||
}
|
}
|
||||||
// Additionally, the funding tx should have been populated.
|
// Additionally, the funding tx should have been populated.
|
||||||
if chanReservation.fundingTx == nil {
|
if chanReservation.FinalFundingTx() == nil {
|
||||||
t.Fatalf("funding transaction never created!")
|
t.Fatalf("funding transaction never created!")
|
||||||
}
|
}
|
||||||
// Their funds should also be filled in.
|
// 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
|
// Now Bob can generate a signature for our version of the commitment
|
||||||
// transaction, allowing us to complete the reservation.
|
// transaction, allowing us to complete the reservation.
|
||||||
bobCommitSig, err := bobNode.signCommitTx(
|
bobCommitSig, err := bobNode.signCommitTx(
|
||||||
chanReservation.partialState.OurCommitTx,
|
chanReservation.LocalCommitTx(),
|
||||||
chanReservation.partialState.FundingRedeemScript,
|
chanReservation.FundingRedeemScript(),
|
||||||
int64(fundingAmt))
|
int64(fundingAmt))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("bob is unable to sign alice's commit tx: %v", err)
|
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()
|
fundingTx := chanReservation.FinalFundingTx()
|
||||||
fundingSha := fundingTx.TxSha()
|
fundingSha := fundingTx.TxSha()
|
||||||
nodeID := wire.ShaHash(bobNode.id)
|
nodeID := wire.ShaHash(bobNode.id)
|
||||||
channels, err := lnwallet.channelDB.FetchOpenChannels(&nodeID)
|
channels, err := lnwallet.ChannelDB.FetchOpenChannels(&nodeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to retrieve channel from DB: %v", err)
|
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())
|
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
|
// For this scenario, bob will initiate the channel, while we simply act as
|
||||||
// the responder.
|
// the responder.
|
||||||
capacity := btcutil.Amount(4 * 1e8)
|
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
|
// Bob sends over a single funding request, so we allocate our
|
||||||
// contribution and the necessary resources.
|
// contribution and the necessary resources.
|
||||||
fundingAmt := btcutil.Amount(0)
|
fundingAmt := btcutil.Amount(0)
|
||||||
chanReservation, err := lnwallet.InitChannelReservation(capacity,
|
chanReservation, err := wallet.InitChannelReservation(capacity,
|
||||||
fundingAmt, bobNode.id, numReqConfs, 4)
|
fundingAmt, bobNode.id, numReqConfs, 4)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to init channel reservation: %v", err)
|
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 {
|
if err := chanReservation.ProcessSingleContribution(bobContribution); err != nil {
|
||||||
t.Fatalf("unable to process bob's contribution: %v", err)
|
t.Fatalf("unable to process bob's contribution: %v", err)
|
||||||
}
|
}
|
||||||
if chanReservation.fundingTx != nil {
|
if chanReservation.FinalFundingTx() != nil {
|
||||||
t.Fatalf("funding transaction populated!")
|
t.Fatalf("funding transaction populated!")
|
||||||
}
|
}
|
||||||
if len(bobContribution.Inputs) != 1 {
|
if len(bobContribution.Inputs) != 1 {
|
||||||
@ -848,7 +836,7 @@ func testSingleFunderReservationWorkflowResponder(miner *rpctest.Harness, lnwall
|
|||||||
t.Fatalf("bob's revocaiton key not found")
|
t.Fatalf("bob's revocaiton key not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
fundingRedeemScript, multiOut, err := genFundingPkScript(
|
fundingRedeemScript, multiOut, err := lnwallet.GenFundingPkScript(
|
||||||
ourContribution.MultiSigKey.SerializeCompressed(),
|
ourContribution.MultiSigKey.SerializeCompressed(),
|
||||||
bobContribution.MultiSigKey.SerializeCompressed(),
|
bobContribution.MultiSigKey.SerializeCompressed(),
|
||||||
int64(capacity))
|
int64(capacity))
|
||||||
@ -872,11 +860,11 @@ func testSingleFunderReservationWorkflowResponder(miner *rpctest.Harness, lnwall
|
|||||||
// wallet so it can finalize the transaction by signing bob's commitment
|
// wallet so it can finalize the transaction by signing bob's commitment
|
||||||
// transaction.
|
// transaction.
|
||||||
fundingTxID := fundingTx.TxSha()
|
fundingTxID := fundingTx.TxSha()
|
||||||
_, multiSigIndex := findScriptOutputIndex(fundingTx, multiOut.PkScript)
|
_, multiSigIndex := lnwallet.FindScriptOutputIndex(fundingTx, multiOut.PkScript)
|
||||||
fundingOutpoint := wire.NewOutPoint(&fundingTxID, multiSigIndex)
|
fundingOutpoint := wire.NewOutPoint(&fundingTxID, multiSigIndex)
|
||||||
|
|
||||||
fundingTxIn := wire.NewTxIn(fundingOutpoint, nil, nil)
|
fundingTxIn := wire.NewTxIn(fundingOutpoint, nil, nil)
|
||||||
aliceCommitTx, err := createCommitTx(fundingTxIn, ourContribution.CommitKey,
|
aliceCommitTx, err := lnwallet.CreateCommitTx(fundingTxIn, ourContribution.CommitKey,
|
||||||
bobContribution.CommitKey, ourContribution.RevocationKey,
|
bobContribution.CommitKey, ourContribution.RevocationKey,
|
||||||
ourContribution.CsvDelay, 0, capacity)
|
ourContribution.CsvDelay, 0, capacity)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -897,10 +885,9 @@ func testSingleFunderReservationWorkflowResponder(miner *rpctest.Harness, lnwall
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Alice should have saved the funding output.
|
// 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",
|
t.Fatalf("funding outputs don't match: %#v vs %#v",
|
||||||
chanReservation.partialState.FundingOutpoint,
|
chanReservation.FundingOutpoint(), fundingOutpoint)
|
||||||
fundingOutpoint)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some period of time later, Bob presents us with an SPV proof
|
// 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
|
// 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,
|
testDualFundingReservationWorkflow,
|
||||||
testSingleFunderReservationWorkflowInitiator,
|
testSingleFunderReservationWorkflowInitiator,
|
||||||
testSingleFunderReservationWorkflowResponder,
|
testSingleFunderReservationWorkflowResponder,
|
||||||
@ -934,20 +921,29 @@ var walletTests = []func(miner *rpctest.Harness, w *LightningWallet, test *testi
|
|||||||
}
|
}
|
||||||
|
|
||||||
type testLnWallet struct {
|
type testLnWallet struct {
|
||||||
lnwallet *LightningWallet
|
lnwallet *lnwallet.LightningWallet
|
||||||
cleanUpFunc func()
|
cleanUpFunc func()
|
||||||
}
|
}
|
||||||
|
|
||||||
func clearWalletState(w *LightningWallet) error {
|
func clearWalletState(w *lnwallet.LightningWallet) error {
|
||||||
w.nextFundingID = 0
|
|
||||||
w.fundingLimbo = make(map[uint64]*ChannelReservation)
|
|
||||||
w.ResetLockedOutpoints()
|
|
||||||
|
|
||||||
// TODO(roasbeef): should also restore outputs to original state.
|
// 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
|
// TODO(roasbeef): purge bobNode in favor of dual lnwallet's
|
||||||
func TestLightningWallet(t *testing.T) {
|
func TestLightningWallet(t *testing.T) {
|
||||||
netParams := &chaincfg.SimNetParams
|
netParams := &chaincfg.SimNetParams
|
||||||
@ -965,27 +961,72 @@ func TestLightningWallet(t *testing.T) {
|
|||||||
t.Fatalf("unable to set up mining node: %v", err)
|
t.Fatalf("unable to set up mining node: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Funding via 10 outputs with 4BTC each.
|
rpcConfig := miningNode.RPCConfig()
|
||||||
testDir, lnwallet, err := createTestWallet(miningNode, netParams)
|
|
||||||
|
chainNotifier, err := btcdnotify.New(&rpcConfig)
|
||||||
if err != nil {
|
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.
|
var bio lnwallet.BlockChainIO
|
||||||
assertProperBalance(t, lnwallet, 1, 40)
|
var signer lnwallet.Signer
|
||||||
|
var wc lnwallet.WalletController
|
||||||
// Execute every test, clearing possibly mutated wallet state after
|
for _, walletDriver := range lnwallet.RegisteredWallets() {
|
||||||
// each step.
|
tempTestDir, err := ioutil.TempDir("", "lnwallet")
|
||||||
for _, walletTest := range walletTests {
|
if err != nil {
|
||||||
walletTest(miningNode, lnwallet, t)
|
t.Fatalf("unable to create temp directory: %v", err)
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
|
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.
|
// as a signature to our version of the commitment transaction.
|
||||||
// * We then verify the validity of all signatures before considering the
|
// * We then verify the validity of all signatures before considering the
|
||||||
// channel "open".
|
// channel "open".
|
||||||
// TODO(roasbeef): update with single funder description
|
|
||||||
type ChannelReservation struct {
|
type ChannelReservation struct {
|
||||||
// This mutex MUST be held when either reading or modifying any of the
|
// This mutex MUST be held when either reading or modifying any of the
|
||||||
// fields below.
|
// fields below.
|
||||||
@ -131,11 +130,11 @@ type ChannelReservation struct {
|
|||||||
wallet *LightningWallet
|
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
|
// used only internally by lnwallet. In order to concurrent safety, the creation
|
||||||
// of all channel reservations should be carried out via the
|
// of all channel reservations should be carried out via the
|
||||||
// lnwallet.InitChannelReservation interface.
|
// 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 {
|
wallet *LightningWallet, id uint64, numConfs uint16) *ChannelReservation {
|
||||||
var ourBalance btcutil.Amount
|
var ourBalance btcutil.Amount
|
||||||
var theirBalance btcutil.Amount
|
var theirBalance btcutil.Amount
|
||||||
@ -160,7 +159,7 @@ func newChannelReservation(capacity, fundingAmt btcutil.Amount, minFeeRate btcut
|
|||||||
OurBalance: ourBalance,
|
OurBalance: ourBalance,
|
||||||
TheirBalance: theirBalance,
|
TheirBalance: theirBalance,
|
||||||
MinFeePerKb: minFeeRate,
|
MinFeePerKb: minFeeRate,
|
||||||
Db: wallet.channelDB,
|
Db: wallet.ChannelDB,
|
||||||
},
|
},
|
||||||
numConfsToOpen: numConfs,
|
numConfsToOpen: numConfs,
|
||||||
reservationID: id,
|
reservationID: id,
|
||||||
@ -315,6 +314,26 @@ func (r *ChannelReservation) FinalFundingTx() *wire.MsgTx {
|
|||||||
return r.fundingTx
|
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.
|
// FundingOutpoint returns the outpoint of the funding transaction.
|
||||||
//
|
//
|
||||||
// NOTE: The pointer returned will only be set once the .ProcesContribution()
|
// 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
|
// transaction for this pending payment channel obtains the configured number
|
||||||
// of confirmations. Once confirmations have been obtained, a fully initialized
|
// of confirmations. Once confirmations have been obtained, a fully initialized
|
||||||
// LightningChannel instance is returned, allowing for channel updates.
|
// LightningChannel instance is returned, allowing for channel updates.
|
||||||
|
//
|
||||||
// NOTE: If this method is called before .CompleteReservation(), it will block
|
// NOTE: If this method is called before .CompleteReservation(), it will block
|
||||||
// indefinitely.
|
// indefinitely.
|
||||||
func (r *ChannelReservation) DispatchChan() <-chan *LightningChannel {
|
func (r *ChannelReservation) DispatchChan() <-chan *LightningChannel {
|
||||||
|
@ -58,9 +58,9 @@ func genMultiSigScript(aPub, bPub []byte) ([]byte, error) {
|
|||||||
return bldr.Script()
|
return bldr.Script()
|
||||||
}
|
}
|
||||||
|
|
||||||
// genFundingPkScript creates a redeem script, and its matching p2wsh
|
// GenFundingPkScript creates a redeem script, and its matching p2wsh
|
||||||
// output for the funding transaction.
|
// output for the funding transaction.
|
||||||
func genFundingPkScript(aPub, bPub []byte, amt int64) ([]byte, *wire.TxOut, error) {
|
func GenFundingPkScript(aPub, bPub []byte, amt int64) ([]byte, *wire.TxOut, error) {
|
||||||
// As a sanity check, ensure that the passed amount is above zero.
|
// As a sanity check, ensure that the passed amount is above zero.
|
||||||
if amt <= 0 {
|
if amt <= 0 {
|
||||||
return nil, nil, fmt.Errorf("can't create FundTx script with " +
|
return nil, nil, fmt.Errorf("can't create FundTx script with " +
|
||||||
@ -85,7 +85,7 @@ func genFundingPkScript(aPub, bPub []byte, amt int64) ([]byte, *wire.TxOut, erro
|
|||||||
|
|
||||||
// spendMultiSig generates the witness stack required to redeem the 2-of-2 p2wsh
|
// spendMultiSig generates the witness stack required to redeem the 2-of-2 p2wsh
|
||||||
// multi-sig output.
|
// multi-sig output.
|
||||||
func spendMultiSig(redeemScript, pubA, sigA, pubB, sigB []byte) [][]byte {
|
func SpendMultiSig(redeemScript, pubA, sigA, pubB, sigB []byte) [][]byte {
|
||||||
witness := make([][]byte, 4)
|
witness := make([][]byte, 4)
|
||||||
|
|
||||||
// When spending a p2wsh multi-sig script, rather than an OP_0, we add
|
// When spending a p2wsh multi-sig script, rather than an OP_0, we add
|
||||||
@ -114,7 +114,8 @@ func spendMultiSig(redeemScript, pubA, sigA, pubB, sigB []byte) [][]byte {
|
|||||||
// matching 'script'. Additionally, a boolean is returned indicating if
|
// matching 'script'. Additionally, a boolean is returned indicating if
|
||||||
// a matching output was found at all.
|
// a matching output was found at all.
|
||||||
// NOTE: The search stops after the first matching script is found.
|
// NOTE: The search stops after the first matching script is found.
|
||||||
func findScriptOutputIndex(tx *wire.MsgTx, script []byte) (bool, uint32) {
|
// TODO(roasbeef): shouldn't be public?
|
||||||
|
func FindScriptOutputIndex(tx *wire.MsgTx, script []byte) (bool, uint32) {
|
||||||
found := false
|
found := false
|
||||||
index := uint32(0)
|
index := uint32(0)
|
||||||
for i, txOut := range tx.TxOut {
|
for i, txOut := range tx.TxOut {
|
||||||
@ -670,7 +671,7 @@ func commitSpendNoDelay(commitScript []byte, outputAmt btcutil.Amount,
|
|||||||
return wire.TxWitness(witness), nil
|
return wire.TxWitness(witness), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// deriveRevocationPubkey derives the revocation public key given the
|
// DeriveRevocationPubkey derives the revocation public key given the
|
||||||
// counter-party's commitment key, and revocation pre-image derived via a
|
// counter-party's commitment key, and revocation pre-image derived via a
|
||||||
// pseudo-random-function. In the event that we (for some reason) broadcast a
|
// pseudo-random-function. In the event that we (for some reason) broadcast a
|
||||||
// revoked commitment transaction, then if the other party knows the revocation
|
// revoked commitment transaction, then if the other party knows the revocation
|
||||||
@ -689,7 +690,7 @@ func commitSpendNoDelay(commitScript []byte, outputAmt btcutil.Amount,
|
|||||||
// revokePriv := commitPriv + revokePreimge mod N
|
// revokePriv := commitPriv + revokePreimge mod N
|
||||||
//
|
//
|
||||||
// Where N is the order of the sub-group.
|
// Where N is the order of the sub-group.
|
||||||
func deriveRevocationPubkey(commitPubKey *btcec.PublicKey,
|
func DeriveRevocationPubkey(commitPubKey *btcec.PublicKey,
|
||||||
revokePreimage []byte) *btcec.PublicKey {
|
revokePreimage []byte) *btcec.PublicKey {
|
||||||
|
|
||||||
// First we need to convert the revocation hash into a point on the
|
// First we need to convert the revocation hash into a point on the
|
||||||
@ -703,7 +704,7 @@ func deriveRevocationPubkey(commitPubKey *btcec.PublicKey,
|
|||||||
return &btcec.PublicKey{X: revokeX, Y: revokeY}
|
return &btcec.PublicKey{X: revokeX, Y: revokeY}
|
||||||
}
|
}
|
||||||
|
|
||||||
// deriveRevocationPrivKey derives the revocation private key given a node's
|
// DeriveRevocationPrivKey derives the revocation private key given a node's
|
||||||
// commitment private key, and the pre-image to a previously seen revocation
|
// commitment private key, and the pre-image to a previously seen revocation
|
||||||
// hash. Using this derived private key, a node is able to claim the output
|
// hash. Using this derived private key, a node is able to claim the output
|
||||||
// within the commitment transaction of a node in the case that they broadcast
|
// within the commitment transaction of a node in the case that they broadcast
|
||||||
@ -713,7 +714,7 @@ func deriveRevocationPubkey(commitPubKey *btcec.PublicKey,
|
|||||||
// revokePriv := commitPriv + revokePreimage mod N
|
// revokePriv := commitPriv + revokePreimage mod N
|
||||||
//
|
//
|
||||||
// Where N is the order of the sub-group.
|
// Where N is the order of the sub-group.
|
||||||
func deriveRevocationPrivKey(commitPrivKey *btcec.PrivateKey,
|
func DeriveRevocationPrivKey(commitPrivKey *btcec.PrivateKey,
|
||||||
revokePreimage []byte) *btcec.PrivateKey {
|
revokePreimage []byte) *btcec.PrivateKey {
|
||||||
|
|
||||||
// Convert the revocation pre-image into a scalar value so we can
|
// Convert the revocation pre-image into a scalar value so we can
|
||||||
@ -742,12 +743,13 @@ func deriveRevocationPrivKey(commitPrivKey *btcec.PrivateKey,
|
|||||||
//
|
//
|
||||||
// [1]: https://eprint.iacr.org/2010/264.pdf
|
// [1]: https://eprint.iacr.org/2010/264.pdf
|
||||||
// [2]: https://tools.ietf.org/html/rfc5869
|
// [2]: https://tools.ietf.org/html/rfc5869
|
||||||
func deriveElkremRoot(localMultiSigKey *btcec.PrivateKey,
|
func deriveElkremRoot(elkremDerivationRoot *btcec.PrivateKey,
|
||||||
|
localMultiSigKey *btcec.PublicKey,
|
||||||
remoteMultiSigKey *btcec.PublicKey) wire.ShaHash {
|
remoteMultiSigKey *btcec.PublicKey) wire.ShaHash {
|
||||||
|
|
||||||
secret := localMultiSigKey.Serialize()
|
secret := elkremDerivationRoot.Serialize()
|
||||||
salt := remoteMultiSigKey.SerializeCompressed()
|
salt := localMultiSigKey.SerializeCompressed()
|
||||||
info := []byte("elkrem")
|
info := remoteMultiSigKey.SerializeCompressed()
|
||||||
|
|
||||||
rootReader := hkdf.New(sha256.New, secret, salt, info)
|
rootReader := hkdf.New(sha256.New, secret, salt, info)
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ func TestCommitmentSpendValidation(t *testing.T) {
|
|||||||
channelBalance := btcutil.Amount(1 * 10e8)
|
channelBalance := btcutil.Amount(1 * 10e8)
|
||||||
csvTimeout := uint32(5)
|
csvTimeout := uint32(5)
|
||||||
revocationPreimage := testHdSeed[:]
|
revocationPreimage := testHdSeed[:]
|
||||||
revokePubKey := deriveRevocationPubkey(bobKeyPub, revocationPreimage)
|
revokePubKey := DeriveRevocationPubkey(bobKeyPub, revocationPreimage)
|
||||||
|
|
||||||
// With all the test data set up, we create the commitment transaction.
|
// With all the test data set up, we create the commitment transaction.
|
||||||
// We only focus on a single party's transactions, as the scripts are
|
// We only focus on a single party's transactions, as the scripts are
|
||||||
@ -50,7 +50,7 @@ func TestCommitmentSpendValidation(t *testing.T) {
|
|||||||
// This is Alice's commitment transaction, so she must wait a CSV delay
|
// This is Alice's commitment transaction, so she must wait a CSV delay
|
||||||
// of 5 blocks before sweeping the output, while bob can spend
|
// of 5 blocks before sweeping the output, while bob can spend
|
||||||
// immediately with either the revocation key, or his regular key.
|
// immediately with either the revocation key, or his regular key.
|
||||||
commitmentTx, err := createCommitTx(fakeFundingTxIn, aliceKeyPub,
|
commitmentTx, err := CreateCommitTx(fakeFundingTxIn, aliceKeyPub,
|
||||||
bobKeyPub, revokePubKey, csvTimeout, channelBalance, channelBalance)
|
bobKeyPub, revokePubKey, csvTimeout, channelBalance, channelBalance)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create commitment transaction: %v", nil)
|
t.Fatalf("unable to create commitment transaction: %v", nil)
|
||||||
@ -96,7 +96,7 @@ func TestCommitmentSpendValidation(t *testing.T) {
|
|||||||
// Next, we'll test bob spending with the derived revocation key to
|
// Next, we'll test bob spending with the derived revocation key to
|
||||||
// simulate the scenario when alice broadcasts this commitmen
|
// simulate the scenario when alice broadcasts this commitmen
|
||||||
// transaction after it's been revoked.
|
// transaction after it's been revoked.
|
||||||
revokePrivKey := deriveRevocationPrivKey(bobKeyPriv, revocationPreimage)
|
revokePrivKey := DeriveRevocationPrivKey(bobKeyPriv, revocationPreimage)
|
||||||
bobWitnessSpend, err := commitSpendRevoke(delayScript, channelBalance,
|
bobWitnessSpend, err := commitSpendRevoke(delayScript, channelBalance,
|
||||||
revokePrivKey, sweepTx)
|
revokePrivKey, sweepTx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -144,9 +144,9 @@ func TestRevocationKeyDerivation(t *testing.T) {
|
|||||||
|
|
||||||
priv, pub := btcec.PrivKeyFromBytes(btcec.S256(), testWalletPrivKey)
|
priv, pub := btcec.PrivKeyFromBytes(btcec.S256(), testWalletPrivKey)
|
||||||
|
|
||||||
revocationPub := deriveRevocationPubkey(pub, revocationPreimage)
|
revocationPub := DeriveRevocationPubkey(pub, revocationPreimage)
|
||||||
|
|
||||||
revocationPriv := deriveRevocationPrivKey(priv, revocationPreimage)
|
revocationPriv := DeriveRevocationPrivKey(priv, revocationPreimage)
|
||||||
x, y := btcec.S256().ScalarBaseMult(revocationPriv.D.Bytes())
|
x, y := btcec.S256().ScalarBaseMult(revocationPriv.D.Bytes())
|
||||||
derivedRevPub := &btcec.PublicKey{
|
derivedRevPub := &btcec.PublicKey{
|
||||||
Curve: btcec.S256(),
|
Curve: btcec.S256(),
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
package lnwallet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/roasbeef/btcd/chaincfg"
|
|
||||||
"github.com/roasbeef/btcd/wire"
|
|
||||||
_ "github.com/roasbeef/btcwallet/walletdb/bdb"
|
|
||||||
)
|
|
||||||
|
|
||||||
// networkDir returns the directory name of a network directory to hold wallet
|
|
||||||
// files.
|
|
||||||
func networkDir(dataDir string, chainParams *chaincfg.Params) string {
|
|
||||||
netname := chainParams.Name
|
|
||||||
|
|
||||||
// For now, we must always name the testnet data directory as "testnet"
|
|
||||||
// and not "testnet3" or any other version, as the chaincfg testnet3
|
|
||||||
// paramaters will likely be switched to being named "testnet3" in the
|
|
||||||
// future. This is done to future proof that change, and an upgrade
|
|
||||||
// plan to move the testnet3 data directory can be worked out later.
|
|
||||||
if chainParams.Net == wire.TestNet3 {
|
|
||||||
netname = "testnet"
|
|
||||||
}
|
|
||||||
|
|
||||||
return filepath.Join(dataDir, netname)
|
|
||||||
}
|
|
@ -4,7 +4,6 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
@ -12,23 +11,31 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/elkrem"
|
"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/btcec"
|
||||||
"github.com/roasbeef/btcd/txscript"
|
"github.com/roasbeef/btcd/txscript"
|
||||||
"github.com/roasbeef/btcd/wire"
|
"github.com/roasbeef/btcd/wire"
|
||||||
"github.com/roasbeef/btcutil"
|
"github.com/roasbeef/btcutil"
|
||||||
"github.com/roasbeef/btcutil/coinset"
|
|
||||||
"github.com/roasbeef/btcutil/txsort"
|
"github.com/roasbeef/btcutil/txsort"
|
||||||
"github.com/roasbeef/btcwallet/chain"
|
|
||||||
"github.com/roasbeef/btcwallet/waddrmgr"
|
|
||||||
btcwallet "github.com/roasbeef/btcwallet/wallet"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// The size of the buffered queue of requests to the wallet from the
|
// The size of the buffered queue of requests to the wallet from the
|
||||||
// outside word.
|
// outside word.
|
||||||
msgBufferSize = 100
|
msgBufferSize = 100
|
||||||
|
|
||||||
|
// 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 (
|
var (
|
||||||
@ -217,7 +224,7 @@ type channelOpenMsg struct {
|
|||||||
type LightningWallet struct {
|
type LightningWallet struct {
|
||||||
// This mutex is to be held when generating external keys to be used
|
// This mutex is to be held when generating external keys to be used
|
||||||
// as multi-sig, and commitment keys within the channel.
|
// 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
|
// This mutex MUST be held when performing coin selection in order to
|
||||||
// avoid inadvertently creating multiple funding transaction which
|
// avoid inadvertently creating multiple funding transaction which
|
||||||
@ -227,25 +234,30 @@ type LightningWallet struct {
|
|||||||
// A wrapper around a namespace within boltdb reserved for ln-based
|
// A wrapper around a namespace within boltdb reserved for ln-based
|
||||||
// wallet meta-data. See the 'channeldb' package for further
|
// wallet meta-data. See the 'channeldb' package for further
|
||||||
// information.
|
// information.
|
||||||
channelDB *channeldb.DB
|
ChannelDB *channeldb.DB
|
||||||
|
|
||||||
// Used by in order to obtain notifications about funding transaction
|
// Used by in order to obtain notifications about funding transaction
|
||||||
// reaching a specified confirmation depth, and to catch
|
// reaching a specified confirmation depth, and to catch
|
||||||
// counterparty's broadcasting revoked commitment states.
|
// counterparty's broadcasting revoked commitment states.
|
||||||
chainNotifier chainntnfs.ChainNotifier
|
chainNotifier chainntnfs.ChainNotifier
|
||||||
|
|
||||||
// The core wallet, all non Lightning Network specific interaction is
|
// wallet is the the core wallet, all non Lightning Network specific
|
||||||
// proxied to the internal wallet.
|
// interaction is proxied to the internal wallet.
|
||||||
*btcwallet.Wallet
|
WalletController
|
||||||
|
|
||||||
// An active RPC connection to a full-node. In the case of a btcd node,
|
// Signer is the wallet's current Signer implementation. This Signer is
|
||||||
// websockets are used for notifications. If using Bitcoin Core,
|
// used to generate signature for all inputs to potential funding
|
||||||
// notifications are either generated via long-polling or the usage of
|
// transactions, as well as for spends from the funding transaction to
|
||||||
// ZeroMQ.
|
// update the commitment state.
|
||||||
// TODO(roasbeef): make into interface need: getrawtransaction + gettxout
|
Signer Signer
|
||||||
// * getrawtransaction -> verify proof of channel links
|
|
||||||
// * gettxout -> verify inputs to funding tx exist and are unspent
|
// chainIO is an instance of the BlockChainIO interface. chainIO is
|
||||||
rpc *chain.RPCClient
|
// used to lookup the existance of outputs within the utxo set.
|
||||||
|
chainIO BlockChainIO
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
// All messages to the wallet are to be sent accross this channel.
|
// All messages to the wallet are to be sent accross this channel.
|
||||||
msgChan chan interface{}
|
msgChan chan interface{}
|
||||||
@ -262,7 +274,12 @@ type LightningWallet struct {
|
|||||||
// TODO(roasbeef): zombie garbage collection routine to solve
|
// TODO(roasbeef): zombie garbage collection routine to solve
|
||||||
// lost-object/starvation problem/attack.
|
// 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
|
started int32
|
||||||
shutdown int32
|
shutdown int32
|
||||||
@ -279,86 +296,38 @@ type LightningWallet struct {
|
|||||||
//
|
//
|
||||||
// NOTE: The passed channeldb, and ChainNotifier should already be fully
|
// NOTE: The passed channeldb, and ChainNotifier should already be fully
|
||||||
// initialized/started before being passed as a function arugment.
|
// initialized/started before being passed as a function arugment.
|
||||||
func NewLightningWallet(config *Config, cdb *channeldb.DB,
|
func NewLightningWallet(cdb *channeldb.DB, notifier chainntnfs.ChainNotifier,
|
||||||
notifier chainntnfs.ChainNotifier) (*LightningWallet, error) {
|
wallet WalletController, signer Signer, bio BlockChainIO,
|
||||||
|
netParams *chaincfg.Params) (*LightningWallet, error) {
|
||||||
|
|
||||||
// Ensure the wallet exists or create it when the create flag is set.
|
// TODO(roasbeef): need a another wallet level config
|
||||||
netDir := networkDir(config.DataDir, config.NetParams)
|
|
||||||
|
|
||||||
var pubPass []byte
|
// Fetch the root derivation key from the wallet's HD chain. We'll use
|
||||||
if config.PublicPass == nil {
|
// this to generate specific Lightning related secrets on the fly.
|
||||||
pubPass = defaultPubPassphrase
|
rootKey, err := wallet.FetchRootKey()
|
||||||
} else {
|
|
||||||
pubPass = config.PublicPass
|
|
||||||
}
|
|
||||||
|
|
||||||
loader := btcwallet.NewLoader(config.NetParams, netDir)
|
|
||||||
walletExists, err := loader.WalletExists()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var createID bool
|
// TODO(roasbeef): always re-derive on the fly?
|
||||||
var wallet *btcwallet.Wallet
|
rootKeyRaw := rootKey.Serialize()
|
||||||
if !walletExists {
|
rootMasterKey, err := hdkeychain.NewMaster(rootKeyRaw, netParams)
|
||||||
// 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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &LightningWallet{
|
return &LightningWallet{
|
||||||
chainNotifier: notifier,
|
rootKey: rootMasterKey,
|
||||||
rpc: rpcc,
|
chainNotifier: notifier,
|
||||||
Wallet: wallet,
|
Signer: signer,
|
||||||
channelDB: cdb,
|
WalletController: wallet,
|
||||||
msgChan: make(chan interface{}, msgBufferSize),
|
chainIO: bio,
|
||||||
// TODO(roasbeef): make this atomic.Uint32 instead? Which is
|
ChannelDB: cdb,
|
||||||
// faster, locks or CAS? I'm guessing CAS because assembly:
|
msgChan: make(chan interface{}, msgBufferSize),
|
||||||
// * https://golang.org/src/sync/atomic/asm_amd64.s
|
nextFundingID: 0,
|
||||||
nextFundingID: 0,
|
fundingLimbo: make(map[uint64]*ChannelReservation),
|
||||||
cfg: config,
|
lockedOutPoints: make(map[wire.OutPoint]struct{}),
|
||||||
fundingLimbo: make(map[uint64]*ChannelReservation),
|
quit: make(chan struct{}),
|
||||||
quit: make(chan struct{}),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -370,16 +339,10 @@ func (l *LightningWallet) Startup() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Establish an RPC connection in additino to starting the goroutines
|
// Start the underlying wallet controller.
|
||||||
// in the underlying wallet.
|
if err := l.Start(); err != nil {
|
||||||
if err := l.rpc.Start(); err != nil {
|
|
||||||
return err
|
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)
|
l.wg.Add(1)
|
||||||
// TODO(roasbeef): multiple request handlers?
|
// TODO(roasbeef): multiple request handlers?
|
||||||
@ -396,16 +359,59 @@ func (l *LightningWallet) Shutdown() error {
|
|||||||
|
|
||||||
// Signal the underlying wallet controller to shutdown, waiting until
|
// Signal the underlying wallet controller to shutdown, waiting until
|
||||||
// all active goroutines have been shutdown.
|
// all active goroutines have been shutdown.
|
||||||
l.Stop()
|
if err := l.Stop(); err != nil {
|
||||||
l.WaitForShutdown()
|
return err
|
||||||
|
}
|
||||||
l.rpc.Shutdown()
|
|
||||||
|
|
||||||
close(l.quit)
|
close(l.quit)
|
||||||
l.wg.Wait()
|
l.wg.Wait()
|
||||||
return nil
|
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
|
// requestHandler is the primary goroutine(s) resposible for handling, and
|
||||||
// dispatching relies to all messages.
|
// dispatching relies to all messages.
|
||||||
func (l *LightningWallet) requestHandler() {
|
func (l *LightningWallet) requestHandler() {
|
||||||
@ -478,16 +484,9 @@ func (l *LightningWallet) InitChannelReservation(capacity,
|
|||||||
// handleFundingReserveRequest processes a message intending to create, and
|
// handleFundingReserveRequest processes a message intending to create, and
|
||||||
// validate a funding reservation request.
|
// validate a funding reservation request.
|
||||||
func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg) {
|
func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg) {
|
||||||
// Create a limbo and record entry for this newly pending funding request.
|
id := atomic.AddUint64(&l.nextFundingID, 1)
|
||||||
l.limboMtx.Lock()
|
reservation := NewChannelReservation(req.capacity, req.fundingAmount,
|
||||||
|
|
||||||
id := l.nextFundingID
|
|
||||||
reservation := newChannelReservation(req.capacity, req.fundingAmount,
|
|
||||||
req.minFeeRate, l, id, req.numConfs)
|
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
|
// Grab the mutex on the ChannelReservation to ensure thead-safety
|
||||||
reservation.Lock()
|
reservation.Lock()
|
||||||
@ -502,7 +501,9 @@ func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg
|
|||||||
// don't need to perform any coin selection. Otherwise, attempt to
|
// don't need to perform any coin selection. Otherwise, attempt to
|
||||||
// obtain enough coins to meet the required funding amount.
|
// obtain enough coins to meet the required funding amount.
|
||||||
if req.fundingAmount != 0 {
|
if req.fundingAmount != 0 {
|
||||||
if err := l.selectCoinsAndChange(req.fundingAmount,
|
// TODO(roasbeef): consult model for proper fee rate
|
||||||
|
feeRate := uint64(10)
|
||||||
|
if err := l.selectCoinsAndChange(feeRate, req.fundingAmount,
|
||||||
ourContribution); err != nil {
|
ourContribution); err != nil {
|
||||||
req.err <- err
|
req.err <- err
|
||||||
req.resp <- nil
|
req.resp <- nil
|
||||||
@ -513,27 +514,26 @@ func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg
|
|||||||
// Grab two fresh keys from our HD chain, one will be used for the
|
// Grab two fresh keys from our HD chain, one will be used for the
|
||||||
// multi-sig funding transaction, and the other for the commitment
|
// multi-sig funding transaction, and the other for the commitment
|
||||||
// transaction.
|
// transaction.
|
||||||
multiSigKey, err := l.getNextRawKey()
|
multiSigKey, err := l.NewRawKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
req.err <- err
|
req.err <- err
|
||||||
req.resp <- nil
|
req.resp <- nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
commitKey, err := l.getNextRawKey()
|
commitKey, err := l.NewRawKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
req.err <- err
|
req.err <- err
|
||||||
req.resp <- nil
|
req.resp <- nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
reservation.partialState.OurMultiSigKey = multiSigKey
|
reservation.partialState.OurMultiSigKey = multiSigKey
|
||||||
ourContribution.MultiSigKey = multiSigKey.PubKey()
|
ourContribution.MultiSigKey = multiSigKey
|
||||||
reservation.partialState.OurCommitKey = commitKey
|
reservation.partialState.OurCommitKey = commitKey
|
||||||
ourContribution.CommitKey = commitKey.PubKey()
|
ourContribution.CommitKey = commitKey
|
||||||
|
|
||||||
// Generate a fresh address to be used in the case of a cooperative
|
// Generate a fresh address to be used in the case of a cooperative
|
||||||
// channel close.
|
// channel close.
|
||||||
deliveryAddress, err := l.NewAddress(waddrmgr.DefaultAccountNum,
|
deliveryAddress, err := l.NewAddress(WitnessPubKey, false)
|
||||||
waddrmgr.WitnessPubKey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
req.err <- err
|
req.err <- err
|
||||||
req.resp <- nil
|
req.resp <- nil
|
||||||
@ -548,6 +548,12 @@ func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg
|
|||||||
reservation.partialState.OurDeliveryScript = deliveryScript
|
reservation.partialState.OurDeliveryScript = deliveryScript
|
||||||
ourContribution.DeliveryAddress = deliveryAddress
|
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
|
// Funding reservation request succesfully handled. The funding inputs
|
||||||
// will be marked as unavailable until the reservation is either
|
// will be marked as unavailable until the reservation is either
|
||||||
// completed, or cancecled.
|
// completed, or cancecled.
|
||||||
@ -578,6 +584,7 @@ func (l *LightningWallet) handleFundingCancelRequest(req *fundingReserveCancelMs
|
|||||||
// Mark all previously locked outpoints as usuable for future funding
|
// Mark all previously locked outpoints as usuable for future funding
|
||||||
// requests.
|
// requests.
|
||||||
for _, unusedInput := range pendingReservation.ourContribution.Inputs {
|
for _, unusedInput := range pendingReservation.ourContribution.Inputs {
|
||||||
|
delete(l.lockedOutPoints, unusedInput.PreviousOutPoint)
|
||||||
l.UnlockOutpoint(unusedInput.PreviousOutPoint)
|
l.UnlockOutpoint(unusedInput.PreviousOutPoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -639,7 +646,7 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
|
|||||||
// Finally, add the 2-of-2 multi-sig output which will set up the lightning
|
// Finally, add the 2-of-2 multi-sig output which will set up the lightning
|
||||||
// channel.
|
// channel.
|
||||||
channelCapacity := int64(pendingReservation.partialState.Capacity)
|
channelCapacity := int64(pendingReservation.partialState.Capacity)
|
||||||
redeemScript, multiSigOut, err := genFundingPkScript(ourKey.PubKey().SerializeCompressed(),
|
redeemScript, multiSigOut, err := GenFundingPkScript(ourKey.SerializeCompressed(),
|
||||||
theirKey.SerializeCompressed(), channelCapacity)
|
theirKey.SerializeCompressed(), channelCapacity)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
req.err <- err
|
req.err <- err
|
||||||
@ -647,21 +654,6 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
|
|||||||
}
|
}
|
||||||
pendingReservation.partialState.FundingRedeemScript = redeemScript
|
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
|
// Sort the transaction. Since both side agree to a cannonical
|
||||||
// ordering, by sorting we no longer need to send the entire
|
// ordering, by sorting we no longer need to send the entire
|
||||||
// transaction. Only signatures will be exchanged.
|
// transaction. Only signatures will be exchanged.
|
||||||
@ -671,81 +663,30 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
|
|||||||
// Next, sign all inputs that are ours, collecting the signatures in
|
// Next, sign all inputs that are ours, collecting the signatures in
|
||||||
// order of the inputs.
|
// order of the inputs.
|
||||||
pendingReservation.ourFundingInputScripts = make([]*InputScript, 0, len(ourContribution.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 {
|
for i, txIn := range fundingTx.TxIn {
|
||||||
// Does the wallet know about the txin?
|
info, err := l.FetchInputInfo(&txIn.PreviousOutPoint)
|
||||||
txDetail, _ := l.TxStore.TxDetails(&txIn.PreviousOutPoint.Hash)
|
if err == ErrNotMine {
|
||||||
if txDetail == nil {
|
|
||||||
continue
|
continue
|
||||||
}
|
} else if err != nil {
|
||||||
|
req.err <- err
|
||||||
// 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)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var witnessProgram []byte
|
signDesc.Output = info
|
||||||
inputScript := &InputScript{}
|
signDesc.InputIndex = i
|
||||||
|
|
||||||
// If we're spending p2wkh output nested within a p2sh output,
|
inputScript, err := l.Signer.ComputeInputScript(fundingTx, &signDesc)
|
||||||
// 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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
req.err <- fmt.Errorf("cannot create witnessscript: %s", err)
|
req.err <- err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
txIn.Witness = witnessScript
|
|
||||||
inputScript.Witness = witnessScript
|
|
||||||
|
|
||||||
|
txIn.SignatureScript = inputScript.ScriptSig
|
||||||
|
txIn.Witness = inputScript.Witness
|
||||||
pendingReservation.ourFundingInputScripts = append(
|
pendingReservation.ourFundingInputScripts = append(
|
||||||
pendingReservation.ourFundingInputScripts,
|
pendingReservation.ourFundingInputScripts,
|
||||||
inputScript,
|
inputScript,
|
||||||
@ -753,10 +694,10 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Locate the index of the multi-sig outpoint in order to record it
|
// 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.
|
// workflow, then we'll also need to send this to the remote node.
|
||||||
fundingTxID := fundingTx.TxSha()
|
fundingTxID := fundingTx.TxSha()
|
||||||
_, multiSigIndex := findScriptOutputIndex(fundingTx, multiSigOut.PkScript)
|
_, multiSigIndex := FindScriptOutputIndex(fundingTx, multiSigOut.PkScript)
|
||||||
fundingOutpoint := wire.NewOutPoint(&fundingTxID, multiSigIndex)
|
fundingOutpoint := wire.NewOutPoint(&fundingTxID, multiSigIndex)
|
||||||
pendingReservation.partialState.FundingOutpoint = fundingOutpoint
|
pendingReservation.partialState.FundingOutpoint = fundingOutpoint
|
||||||
|
|
||||||
@ -767,11 +708,17 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
|
|||||||
pendingReservation.partialState.RemoteElkrem = e
|
pendingReservation.partialState.RemoteElkrem = e
|
||||||
pendingReservation.partialState.TheirCurrentRevocation = theirContribution.RevocationKey
|
pendingReservation.partialState.TheirCurrentRevocation = theirContribution.RevocationKey
|
||||||
|
|
||||||
|
masterElkremRoot, err := l.deriveMasterElkremRoot()
|
||||||
|
if err != nil {
|
||||||
|
req.err <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Now that we have their commitment key, we can create the revocation
|
// Now that we have their commitment key, we can create the revocation
|
||||||
// key for the first version of our commitment transaction. To do so,
|
// key for the first version of our commitment transaction. To do so,
|
||||||
// we'll first create our elkrem root, then grab the first pre-iamge
|
// we'll first create our elkrem root, then grab the first pre-iamge
|
||||||
// from it.
|
// from it.
|
||||||
elkremRoot := deriveElkremRoot(ourKey, theirKey)
|
elkremRoot := deriveElkremRoot(masterElkremRoot, ourKey, theirKey)
|
||||||
elkremSender := elkrem.NewElkremSender(elkremRoot)
|
elkremSender := elkrem.NewElkremSender(elkremRoot)
|
||||||
pendingReservation.partialState.LocalElkrem = elkremSender
|
pendingReservation.partialState.LocalElkrem = elkremSender
|
||||||
firstPreimage, err := elkremSender.AtIndex(0)
|
firstPreimage, err := elkremSender.AtIndex(0)
|
||||||
@ -780,7 +727,7 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
theirCommitKey := theirContribution.CommitKey
|
theirCommitKey := theirContribution.CommitKey
|
||||||
ourRevokeKey := deriveRevocationPubkey(theirCommitKey, firstPreimage[:])
|
ourRevokeKey := DeriveRevocationPubkey(theirCommitKey, firstPreimage[:])
|
||||||
|
|
||||||
// Create the txIn to our commitment transaction; required to construct
|
// Create the txIn to our commitment transaction; required to construct
|
||||||
// the commitment transactions.
|
// the commitment transactions.
|
||||||
@ -792,14 +739,14 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
|
|||||||
ourBalance := ourContribution.FundingAmount
|
ourBalance := ourContribution.FundingAmount
|
||||||
theirBalance := theirContribution.FundingAmount
|
theirBalance := theirContribution.FundingAmount
|
||||||
ourCommitKey := ourContribution.CommitKey
|
ourCommitKey := ourContribution.CommitKey
|
||||||
ourCommitTx, err := createCommitTx(fundingTxIn, ourCommitKey, theirCommitKey,
|
ourCommitTx, err := CreateCommitTx(fundingTxIn, ourCommitKey, theirCommitKey,
|
||||||
ourRevokeKey, ourContribution.CsvDelay,
|
ourRevokeKey, ourContribution.CsvDelay,
|
||||||
ourBalance, theirBalance)
|
ourBalance, theirBalance)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
req.err <- err
|
req.err <- err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
theirCommitTx, err := createCommitTx(fundingTxIn, theirCommitKey, ourCommitKey,
|
theirCommitTx, err := CreateCommitTx(fundingTxIn, theirCommitKey, ourCommitKey,
|
||||||
theirContribution.RevocationKey, theirContribution.CsvDelay,
|
theirContribution.RevocationKey, theirContribution.CsvDelay,
|
||||||
theirBalance, ourBalance)
|
theirBalance, ourBalance)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -830,10 +777,15 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
|
|||||||
|
|
||||||
// Generate a signature for their version of the initial commitment
|
// Generate a signature for their version of the initial commitment
|
||||||
// transaction.
|
// transaction.
|
||||||
hashCache = txscript.NewTxSigHashes(theirCommitTx)
|
signDesc = SignDescriptor{
|
||||||
channelBalance := pendingReservation.partialState.Capacity
|
RedeemScript: redeemScript,
|
||||||
sigTheirCommit, err := txscript.RawTxInWitnessSignature(theirCommitTx, hashCache, 0,
|
PubKey: ourKey,
|
||||||
int64(channelBalance), redeemScript, txscript.SigHashAll, ourKey)
|
Output: multiSigOut,
|
||||||
|
HashType: txscript.SigHashAll,
|
||||||
|
SigHashes: txscript.NewTxSigHashes(theirCommitTx),
|
||||||
|
InputIndex: 0,
|
||||||
|
}
|
||||||
|
sigTheirCommit, err := l.Signer.SignOutputRaw(theirCommitTx, &signDesc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
req.err <- err
|
req.err <- err
|
||||||
return
|
return
|
||||||
@ -871,7 +823,7 @@ func (l *LightningWallet) handleSingleContribution(req *addSingleContributionMsg
|
|||||||
ourKey := pendingReservation.partialState.OurMultiSigKey
|
ourKey := pendingReservation.partialState.OurMultiSigKey
|
||||||
theirKey := theirContribution.MultiSigKey
|
theirKey := theirContribution.MultiSigKey
|
||||||
channelCapacity := int64(pendingReservation.partialState.Capacity)
|
channelCapacity := int64(pendingReservation.partialState.Capacity)
|
||||||
redeemScript, _, err := genFundingPkScript(ourKey.PubKey().SerializeCompressed(),
|
redeemScript, _, err := GenFundingPkScript(ourKey.SerializeCompressed(),
|
||||||
theirKey.SerializeCompressed(), channelCapacity)
|
theirKey.SerializeCompressed(), channelCapacity)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
req.err <- err
|
req.err <- err
|
||||||
@ -879,9 +831,15 @@ func (l *LightningWallet) handleSingleContribution(req *addSingleContributionMsg
|
|||||||
}
|
}
|
||||||
pendingReservation.partialState.FundingRedeemScript = redeemScript
|
pendingReservation.partialState.FundingRedeemScript = redeemScript
|
||||||
|
|
||||||
|
masterElkremRoot, err := l.deriveMasterElkremRoot()
|
||||||
|
if err != nil {
|
||||||
|
req.err <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Now that we know their commitment key, we can create the revocation
|
// Now that we know their commitment key, we can create the revocation
|
||||||
// key for our version of the initial commitment transaction.
|
// key for our version of the initial commitment transaction.
|
||||||
elkremRoot := deriveElkremRoot(ourKey, theirKey)
|
elkremRoot := deriveElkremRoot(masterElkremRoot, ourKey, theirKey)
|
||||||
elkremSender := elkrem.NewElkremSender(elkremRoot)
|
elkremSender := elkrem.NewElkremSender(elkremRoot)
|
||||||
firstPreimage, err := elkremSender.AtIndex(0)
|
firstPreimage, err := elkremSender.AtIndex(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -890,7 +848,7 @@ func (l *LightningWallet) handleSingleContribution(req *addSingleContributionMsg
|
|||||||
}
|
}
|
||||||
pendingReservation.partialState.LocalElkrem = elkremSender
|
pendingReservation.partialState.LocalElkrem = elkremSender
|
||||||
theirCommitKey := theirContribution.CommitKey
|
theirCommitKey := theirContribution.CommitKey
|
||||||
ourRevokeKey := deriveRevocationPubkey(theirCommitKey, firstPreimage[:])
|
ourRevokeKey := DeriveRevocationPubkey(theirCommitKey, firstPreimage[:])
|
||||||
|
|
||||||
// Initialize an empty sha-chain for them, tracking the current pending
|
// 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
|
// revocation hash (we don't yet know the pre-image so we can't add it
|
||||||
@ -951,25 +909,16 @@ func (l *LightningWallet) handleFundingCounterPartySigs(msg *addCounterPartySigs
|
|||||||
// Fetch the alleged previous output along with the
|
// Fetch the alleged previous output along with the
|
||||||
// pkscript referenced by this input.
|
// pkscript referenced by this input.
|
||||||
prevOut := txin.PreviousOutPoint
|
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 {
|
if output == nil {
|
||||||
msg.err <- fmt.Errorf("input to funding tx does not exist: %v", err)
|
msg.err <- fmt.Errorf("input to funding tx does not exist: %v", err)
|
||||||
return
|
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.
|
// Ensure that the witness+sigScript combo is valid.
|
||||||
vm, err := txscript.NewEngine(pkScript,
|
vm, err := txscript.NewEngine(output.PkScript,
|
||||||
fundingTx, i, txscript.StandardVerifyFlags, nil,
|
fundingTx, i, txscript.StandardVerifyFlags, nil,
|
||||||
fundingHashCache, inputValue)
|
fundingHashCache, output.Value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO(roasbeef): cancel at this stage if invalid sigs?
|
// TODO(roasbeef): cancel at this stage if invalid sigs?
|
||||||
msg.err <- fmt.Errorf("cannot create script engine: %s", err)
|
msg.err <- fmt.Errorf("cannot create script engine: %s", err)
|
||||||
@ -989,54 +938,37 @@ func (l *LightningWallet) handleFundingCounterPartySigs(msg *addCounterPartySigs
|
|||||||
pendingReservation.theirCommitmentSig = msg.theirCommitmentSig
|
pendingReservation.theirCommitmentSig = msg.theirCommitmentSig
|
||||||
commitTx := pendingReservation.partialState.OurCommitTx
|
commitTx := pendingReservation.partialState.OurCommitTx
|
||||||
theirKey := pendingReservation.theirContribution.MultiSigKey
|
theirKey := pendingReservation.theirContribution.MultiSigKey
|
||||||
ourKey := pendingReservation.partialState.OurMultiSigKey
|
|
||||||
|
|
||||||
// Re-generate both the redeemScript and p2sh output. We sign the
|
// Re-generate both the redeemScript and p2sh output. We sign the
|
||||||
// redeemScript script, but include the p2sh output as the subscript
|
// redeemScript script, but include the p2sh output as the subscript
|
||||||
// for verification.
|
// for verification.
|
||||||
redeemScript := pendingReservation.partialState.FundingRedeemScript
|
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
|
// Next, create the spending scriptSig, and then verify that the script
|
||||||
// is complete, allowing us to spend from the funding transaction.
|
// is complete, allowing us to spend from the funding transaction.
|
||||||
theirCommitSig := msg.theirCommitmentSig
|
theirCommitSig := msg.theirCommitmentSig
|
||||||
ourKeySer := ourKey.PubKey().SerializeCompressed()
|
channelValue := int64(pendingReservation.partialState.Capacity)
|
||||||
theirKeySer := theirKey.SerializeCompressed()
|
hashCache := txscript.NewTxSigHashes(commitTx)
|
||||||
witness := spendMultiSig(redeemScript, ourKeySer, ourCommitSig,
|
sigHash, err := txscript.CalcWitnessSigHash(redeemScript, hashCache,
|
||||||
theirKeySer, theirCommitSig)
|
txscript.SigHashAll, commitTx, 0, channelValue)
|
||||||
|
|
||||||
// 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)
|
|
||||||
if err != nil {
|
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)
|
msg.err <- fmt.Errorf("counterparty's commitment signature is invalid: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strip and store the signature to ensure that our commitment
|
walletLog.Infof("sighash verify: %v", hex.EncodeToString(sigHash))
|
||||||
// transaction doesn't stay hot.
|
walletLog.Infof("initer verifying tx: %v", spew.Sdump(commitTx))
|
||||||
commitTx.TxIn[0].Witness = nil
|
|
||||||
|
// 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
|
pendingReservation.partialState.OurCommitSig = theirCommitSig
|
||||||
|
|
||||||
// Funding complete, this entry can be removed from limbo.
|
// Funding complete, this entry can be removed from limbo.
|
||||||
@ -1101,14 +1033,14 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) {
|
|||||||
theirCommitKey := pendingReservation.theirContribution.CommitKey
|
theirCommitKey := pendingReservation.theirContribution.CommitKey
|
||||||
ourBalance := pendingReservation.ourContribution.FundingAmount
|
ourBalance := pendingReservation.ourContribution.FundingAmount
|
||||||
theirBalance := pendingReservation.theirContribution.FundingAmount
|
theirBalance := pendingReservation.theirContribution.FundingAmount
|
||||||
ourCommitTx, err := createCommitTx(fundingTxIn, ourCommitKey, theirCommitKey,
|
ourCommitTx, err := CreateCommitTx(fundingTxIn, ourCommitKey, theirCommitKey,
|
||||||
pendingReservation.ourContribution.RevocationKey,
|
pendingReservation.ourContribution.RevocationKey,
|
||||||
pendingReservation.ourContribution.CsvDelay, ourBalance, theirBalance)
|
pendingReservation.ourContribution.CsvDelay, ourBalance, theirBalance)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
req.err <- err
|
req.err <- err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
theirCommitTx, err := createCommitTx(fundingTxIn, theirCommitKey, ourCommitKey,
|
theirCommitTx, err := CreateCommitTx(fundingTxIn, theirCommitKey, ourCommitKey,
|
||||||
req.revokeKey, pendingReservation.theirContribution.CsvDelay,
|
req.revokeKey, pendingReservation.theirContribution.CsvDelay,
|
||||||
theirBalance, ourBalance)
|
theirBalance, ourBalance)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1123,65 +1055,51 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) {
|
|||||||
pendingReservation.partialState.OurCommitTx = ourCommitTx
|
pendingReservation.partialState.OurCommitTx = ourCommitTx
|
||||||
txsort.InPlaceSort(theirCommitTx)
|
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
|
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)
|
channelValue := int64(pendingReservation.partialState.Capacity)
|
||||||
hashCache := txscript.NewTxSigHashes(ourCommitTx)
|
hashCache := txscript.NewTxSigHashes(ourCommitTx)
|
||||||
theirKey := pendingReservation.theirContribution.MultiSigKey
|
theirKey := pendingReservation.theirContribution.MultiSigKey
|
||||||
ourKey := pendingReservation.partialState.OurMultiSigKey
|
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 {
|
if err != nil {
|
||||||
req.err <- err
|
req.err <- err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next, create the spending scriptSig, and then verify that the script
|
// Verify that we've received a valid signature from the remote party
|
||||||
// is complete, allowing us to spend from the funding transaction.
|
// for our version of the commitment transaction.
|
||||||
ourKeySer := ourKey.PubKey().SerializeCompressed()
|
sig, err := btcec.ParseSignature(req.theirCommitmentSig, btcec.S256())
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
req.err <- err
|
req.err <- err
|
||||||
return
|
return
|
||||||
}
|
} else if !sig.Verify(sigHash, theirKey) {
|
||||||
if err := vm.Execute(); err != nil {
|
req.err <- fmt.Errorf("counterparty's commitment signature is invalid")
|
||||||
req.err <- fmt.Errorf("counterparty's commitment signature is invalid: %v", err)
|
|
||||||
return
|
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
|
pendingReservation.partialState.OurCommitSig = req.theirCommitmentSig
|
||||||
|
|
||||||
// With their signature for our version of the commitment transactions
|
// With their signature for our version of the commitment transactions
|
||||||
// verified, we can now generate a signature for their version,
|
// verified, we can now generate a signature for their version,
|
||||||
// allowing the funding transaction to be safely broadcast.
|
// allowing the funding transaction to be safely broadcast.
|
||||||
hashCache = txscript.NewTxSigHashes(theirCommitTx)
|
p2wsh, err := witnessScriptHash(redeemScript)
|
||||||
sigTheirCommit, err := txscript.RawTxInWitnessSignature(theirCommitTx, hashCache, 0,
|
if err != nil {
|
||||||
channelValue, redeemScript, txscript.SigHashAll, ourKey)
|
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 {
|
if err != nil {
|
||||||
req.err <- err
|
req.err <- err
|
||||||
return
|
return
|
||||||
@ -1224,8 +1142,7 @@ func (l *LightningWallet) handleChannelOpen(req *channelOpenMsg) {
|
|||||||
|
|
||||||
// Finally, create and officially open the payment channel!
|
// Finally, create and officially open the payment channel!
|
||||||
// TODO(roasbeef): CreationTime once tx is 'open'
|
// TODO(roasbeef): CreationTime once tx is 'open'
|
||||||
channel, _ := NewLightningChannel(l, l.chainNotifier, l.channelDB,
|
channel, _ := NewLightningChannel(l.Signer, l, l.chainNotifier, res.partialState)
|
||||||
res.partialState)
|
|
||||||
|
|
||||||
res.chanOpen <- channel
|
res.chanOpen <- channel
|
||||||
req.err <- nil
|
req.err <- nil
|
||||||
@ -1266,68 +1183,18 @@ out:
|
|||||||
|
|
||||||
// Finally, create and officially open the payment channel!
|
// Finally, create and officially open the payment channel!
|
||||||
// TODO(roasbeef): CreationTime once tx is 'open'
|
// TODO(roasbeef): CreationTime once tx is 'open'
|
||||||
channel, _ := NewLightningChannel(l, l.chainNotifier, l.channelDB,
|
channel, _ := NewLightningChannel(l.Signer, l, l.chainNotifier,
|
||||||
res.partialState)
|
res.partialState)
|
||||||
res.chanOpen <- channel
|
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
|
// selectCoinsAndChange performs coin selection in order to obtain witness
|
||||||
// outputs which sum to at least 'numCoins' amount of satoshis. If coin
|
// outputs which sum to at least 'numCoins' amount of satoshis. If coin
|
||||||
// selection is succesful/possible, then the selected coins are available within
|
// selection is succesful/possible, then the selected coins are available within
|
||||||
// the passed contribution's inputs. If necessary, a change address will also be
|
// the passed contribution's inputs. If necessary, a change address will also be
|
||||||
// generated.
|
// generated.
|
||||||
// TODO(roasbeef): remove hardcoded fees and req'd confs for outputs.
|
// TODO(roasbeef): remove hardcoded fees and req'd confs for outputs.
|
||||||
func (l *LightningWallet) selectCoinsAndChange(numCoins btcutil.Amount,
|
func (l *LightningWallet) selectCoinsAndChange(feeRate uint64, amt btcutil.Amount,
|
||||||
contribution *ChannelContribution) error {
|
contribution *ChannelContribution) error {
|
||||||
|
|
||||||
// We hold the coin select mutex while querying for outputs, and
|
// We hold the coin select mutex while querying for outputs, and
|
||||||
@ -1337,22 +1204,10 @@ func (l *LightningWallet) selectCoinsAndChange(numCoins btcutil.Amount,
|
|||||||
// when we encounter an error condition.
|
// when we encounter an error condition.
|
||||||
l.coinSelectMtx.Lock()
|
l.coinSelectMtx.Lock()
|
||||||
|
|
||||||
// TODO(roasbeef): check if balance is insufficient, if so then select
|
|
||||||
// on two channels, one is a time.After that will bail out with
|
|
||||||
// insuffcient funds, the other is a notification that the balance has
|
|
||||||
// been updated make(chan struct{}, 1).
|
|
||||||
|
|
||||||
// Find all unlocked unspent witness outputs with greater than 1
|
// Find all unlocked unspent witness outputs with greater than 1
|
||||||
// confirmation.
|
// confirmation.
|
||||||
// TODO(roasbeef): make num confs a configuration paramter
|
// TODO(roasbeef): make num confs a configuration paramter
|
||||||
unspentOutputs, err := l.ListUnspentWitness(1)
|
coins, err := l.ListUnspentWitness(1)
|
||||||
if err != nil {
|
|
||||||
l.coinSelectMtx.Unlock()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert the outputs to coins for coin selection below.
|
|
||||||
coins, err := outputsToCoins(unspentOutputs)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.coinSelectMtx.Unlock()
|
l.coinSelectMtx.Unlock()
|
||||||
return err
|
return err
|
||||||
@ -1360,17 +1215,8 @@ func (l *LightningWallet) selectCoinsAndChange(numCoins btcutil.Amount,
|
|||||||
|
|
||||||
// Peform coin selection over our available, unlocked unspent outputs
|
// Peform coin selection over our available, unlocked unspent outputs
|
||||||
// in order to find enough coins to meet the funding amount requirements.
|
// in order to find enough coins to meet the funding amount requirements.
|
||||||
//
|
// TODO(roasbeef): take in sat/byte
|
||||||
// TODO(roasbeef): Should extend coinset with optimal coin selection
|
selectedCoins, changeAmt, err := coinSelect(feeRate, amt, coins)
|
||||||
// heuristics for our use case.
|
|
||||||
// NOTE: this current selection assumes "priority" is still a thing.
|
|
||||||
selector := &coinset.MaxValueAgeCoinSelector{
|
|
||||||
MaxInputs: 10,
|
|
||||||
MinChangeAmount: 10000,
|
|
||||||
}
|
|
||||||
// TODO(roasbeef): don't hardcode fee...
|
|
||||||
totalWithFee := numCoins + 10000
|
|
||||||
selectedCoins, err := selector.CoinSelect(totalWithFee, coins)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.coinSelectMtx.Unlock()
|
l.coinSelectMtx.Unlock()
|
||||||
return err
|
return err
|
||||||
@ -1379,57 +1225,151 @@ func (l *LightningWallet) selectCoinsAndChange(numCoins btcutil.Amount,
|
|||||||
// Lock the selected coins. These coins are now "reserved", this
|
// Lock the selected coins. These coins are now "reserved", this
|
||||||
// prevents concurrent funding requests from referring to and this
|
// prevents concurrent funding requests from referring to and this
|
||||||
// double-spending the same set of coins.
|
// double-spending the same set of coins.
|
||||||
contribution.Inputs = make([]*wire.TxIn, len(selectedCoins.Coins()))
|
contribution.Inputs = make([]*wire.TxIn, len(selectedCoins))
|
||||||
for i, coin := range selectedCoins.Coins() {
|
for i, coin := range selectedCoins {
|
||||||
txout := wire.NewOutPoint(coin.Hash(), coin.Index())
|
l.lockedOutPoints[*coin] = struct{}{}
|
||||||
l.LockOutpoint(*txout)
|
l.LockOutpoint(*coin)
|
||||||
|
|
||||||
// Empty sig script, we'll actually sign if this reservation is
|
// Empty sig script, we'll actually sign if this reservation is
|
||||||
// queued up to be completed (the other side accepts).
|
// queued up to be completed (the other side accepts).
|
||||||
outPoint := wire.NewOutPoint(coin.Hash(), coin.Index())
|
contribution.Inputs[i] = wire.NewTxIn(coin, nil, nil)
|
||||||
contribution.Inputs[i] = wire.NewTxIn(outPoint, nil, nil)
|
}
|
||||||
|
|
||||||
|
// Record any change output(s) generated as a result of the coin
|
||||||
|
// selection.
|
||||||
|
if changeAmt != 0 {
|
||||||
|
changeAddr, err := l.NewAddress(WitnessPubKey, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
changeScript, err := txscript.PayToAddrScript(changeAddr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
contribution.ChangeOutputs = make([]*wire.TxOut, 1)
|
||||||
|
contribution.ChangeOutputs[0] = &wire.TxOut{
|
||||||
|
Value: int64(changeAmt),
|
||||||
|
PkScript: changeScript,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
l.coinSelectMtx.Unlock()
|
l.coinSelectMtx.Unlock()
|
||||||
|
|
||||||
// Create some possibly neccessary change outputs.
|
|
||||||
selectedTotalValue := coinset.NewCoinSet(selectedCoins.Coins()).TotalValue()
|
|
||||||
if selectedTotalValue > totalWithFee {
|
|
||||||
// Change is necessary. Query for an available change address to
|
|
||||||
// send the remainder to.
|
|
||||||
contribution.ChangeOutputs = make([]*wire.TxOut, 1)
|
|
||||||
changeAddr, err := l.NewChangeAddress(waddrmgr.DefaultAccountNum,
|
|
||||||
waddrmgr.WitnessPubKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
changeAddrScript, err := txscript.PayToAddrScript(changeAddr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
changeAmount := selectedTotalValue - totalWithFee
|
|
||||||
contribution.ChangeOutputs[0] = wire.NewTxOut(int64(changeAmount),
|
|
||||||
changeAddrScript)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(roasbeef): re-calculate fees here to minFeePerKB, may need more inputs
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type WaddrmgrEncryptorDecryptor struct {
|
// deriveMasterElkremRoot derives the private key which serves as the master
|
||||||
M *waddrmgr.Manager
|
// elkrem root. This master secret is used as the secret input to a HKDF to
|
||||||
|
// generate elkrem secrets based on random, but public data.
|
||||||
|
func (l *LightningWallet) deriveMasterElkremRoot() (*btcec.PrivateKey, error) {
|
||||||
|
masterElkremRoot, err := l.rootKey.Child(elkremRootIndex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return masterElkremRoot.ECPrivKey()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WaddrmgrEncryptorDecryptor) Encrypt(p []byte) ([]byte, error) {
|
// selectInputs selects a slice of inputs necessary to meet the specified
|
||||||
return w.M.Encrypt(waddrmgr.CKTPrivate, p)
|
// selection amount. If input selectino is unable to suceed to to insuffcient
|
||||||
|
// funds, a non-nil error is returned. Additionally, the total amount of the
|
||||||
|
// selected coins are returned in order for the caller to properly handle
|
||||||
|
// change+fees.
|
||||||
|
func selectInputs(amt btcutil.Amount, coins []*Utxo) (btcutil.Amount, []*wire.OutPoint, error) {
|
||||||
|
var (
|
||||||
|
selectedUtxos []*wire.OutPoint
|
||||||
|
satSelected btcutil.Amount
|
||||||
|
)
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
for satSelected < amt {
|
||||||
|
// If we're about to go past the number of available coins,
|
||||||
|
// then exit with an error.
|
||||||
|
if i > len(coins)-1 {
|
||||||
|
return 0, nil, ErrInsufficientFunds
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, collect this new coin as it may be used for final
|
||||||
|
// coin selection.
|
||||||
|
coin := coins[i]
|
||||||
|
utxo := &wire.OutPoint{
|
||||||
|
Hash: coin.Hash,
|
||||||
|
Index: coin.Index,
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedUtxos = append(selectedUtxos, utxo)
|
||||||
|
satSelected += coin.Value
|
||||||
|
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
return satSelected, selectedUtxos, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WaddrmgrEncryptorDecryptor) Decrypt(c []byte) ([]byte, error) {
|
// coinSelect attemps to select a sufficient amount of coins, including a
|
||||||
return w.M.Decrypt(waddrmgr.CKTPrivate, c)
|
// change output to fund amt satoshis, adhearing to the specified fee rate. The
|
||||||
}
|
// specified fee rate should be expressed in sat/byte for coin selection to
|
||||||
|
// function properly.
|
||||||
|
func coinSelect(feeRate uint64, amt btcutil.Amount,
|
||||||
|
coins []*Utxo) ([]*wire.OutPoint, btcutil.Amount, error) {
|
||||||
|
|
||||||
func (w *WaddrmgrEncryptorDecryptor) OverheadSize() uint32 {
|
const (
|
||||||
return 24
|
// txOverhead is the overhead of a transaction residing within
|
||||||
|
// the version number and lock time.
|
||||||
|
txOverhead = 8
|
||||||
|
|
||||||
|
// p2wkhSpendSize an estimate of the number of bytes it takes
|
||||||
|
// to spend a p2wkh output.
|
||||||
|
//
|
||||||
|
// (p2wkh witness) + txid + index + varint script size + sequence
|
||||||
|
// TODO(roasbeef): div by 3 due to witness size?
|
||||||
|
p2wkhSpendSize = (1 + 73 + 1 + 33) + 32 + 4 + 1 + 4
|
||||||
|
|
||||||
|
// p2wkhOutputSize is an estimate of the size of a regualr
|
||||||
|
// p2wkh output.
|
||||||
|
//
|
||||||
|
// 8 (output) + 1 (var int script) + 22 (p2wkh output)
|
||||||
|
p2wkhOutputSize = 8 + 1 + 22
|
||||||
|
|
||||||
|
// p2wkhOutputSize is an estimate of the p2wsh funding uotput.
|
||||||
|
p2wshOutputSize = 8 + 1 + 34
|
||||||
|
)
|
||||||
|
|
||||||
|
var estimatedSize int
|
||||||
|
|
||||||
|
amtNeeded := amt
|
||||||
|
for {
|
||||||
|
// First perform an initial round of coin selection to estimate
|
||||||
|
// the required fee.
|
||||||
|
totalSat, selectedUtxos, err := selectInputs(amtNeeded, coins)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Based on the selected coins, estimate the size of the final
|
||||||
|
// fully signed transaction.
|
||||||
|
estimatedSize = ((len(selectedUtxos) * p2wkhSpendSize) +
|
||||||
|
p2wshOutputSize + txOverhead)
|
||||||
|
|
||||||
|
// The difference bteween the selected amount and the amount
|
||||||
|
// requested will be used to pay fees, and generate a change
|
||||||
|
// output with the remaining.
|
||||||
|
overShootAmt := totalSat - amtNeeded
|
||||||
|
|
||||||
|
// Based on the estimated size and fee rate, if the excess
|
||||||
|
// amount isn't enough to pay fees, then increase the requested
|
||||||
|
// coin amount by the estimate required fee, performing another
|
||||||
|
// round of coin selection.
|
||||||
|
requiredFee := btcutil.Amount(uint64(estimatedSize) * feeRate)
|
||||||
|
if overShootAmt < requiredFee {
|
||||||
|
amtNeeded += requiredFee
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the fee is sufficient, then calculate the size of the change output.
|
||||||
|
changeAmt := overShootAmt - requiredFee
|
||||||
|
|
||||||
|
return selectedUtxos, changeAmt, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
4
peer.go
4
peer.go
@ -218,8 +218,8 @@ func newPeer(conn net.Conn, server *server, btcNet wire.BitcoinNet, inbound bool
|
|||||||
func (p *peer) loadActiveChannels(chans []*channeldb.OpenChannel) error {
|
func (p *peer) loadActiveChannels(chans []*channeldb.OpenChannel) error {
|
||||||
for _, dbChan := range chans {
|
for _, dbChan := range chans {
|
||||||
chanID := dbChan.ChanID
|
chanID := dbChan.ChanID
|
||||||
lnChan, err := lnwallet.NewLightningChannel(p.server.lnwallet,
|
lnChan, err := lnwallet.NewLightningChannel(p.server.lnwallet.Signer,
|
||||||
p.server.chainNotifier, p.server.chanDB, dbChan)
|
p.server.lnwallet, p.server.chainNotifier, dbChan)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
46
rpcserver.go
46
rpcserver.go
@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/lightningnetwork/lnd/lndc"
|
"github.com/lightningnetwork/lnd/lndc"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc"
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
"github.com/roasbeef/btcd/txscript"
|
"github.com/roasbeef/btcd/txscript"
|
||||||
"github.com/roasbeef/btcd/wire"
|
"github.com/roasbeef/btcd/wire"
|
||||||
@ -96,7 +97,7 @@ func (r *rpcServer) sendCoinsOnChain(paymentMap map[string]int64) (*wire.ShaHash
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.server.lnwallet.SendOutputs(outputs, defaultAccount, 1)
|
return r.server.lnwallet.SendOutputs(outputs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendCoins executes a request to send coins to a particular address. Unlike
|
// SendCoins executes a request to send coins to a particular address. Unlike
|
||||||
@ -136,23 +137,19 @@ func (r *rpcServer) SendMany(ctx context.Context,
|
|||||||
func (r *rpcServer) NewAddress(ctx context.Context,
|
func (r *rpcServer) NewAddress(ctx context.Context,
|
||||||
in *lnrpc.NewAddressRequest) (*lnrpc.NewAddressResponse, error) {
|
in *lnrpc.NewAddressRequest) (*lnrpc.NewAddressResponse, error) {
|
||||||
|
|
||||||
r.server.lnwallet.KeyGenMtx.Lock()
|
|
||||||
defer r.server.lnwallet.KeyGenMtx.Unlock()
|
|
||||||
|
|
||||||
// Translate the gRPC proto address type to the wallet controller's
|
// Translate the gRPC proto address type to the wallet controller's
|
||||||
// available address types.
|
// available address types.
|
||||||
var addrType waddrmgr.AddressType
|
var addrType lnwallet.AddressType
|
||||||
switch in.Type {
|
switch in.Type {
|
||||||
case lnrpc.NewAddressRequest_WITNESS_PUBKEY_HASH:
|
case lnrpc.NewAddressRequest_WITNESS_PUBKEY_HASH:
|
||||||
addrType = waddrmgr.WitnessPubKey
|
addrType = lnwallet.WitnessPubKey
|
||||||
case lnrpc.NewAddressRequest_NESTED_PUBKEY_HASH:
|
case lnrpc.NewAddressRequest_NESTED_PUBKEY_HASH:
|
||||||
addrType = waddrmgr.NestedWitnessPubKey
|
addrType = lnwallet.NestedWitnessPubKey
|
||||||
case lnrpc.NewAddressRequest_PUBKEY_HASH:
|
case lnrpc.NewAddressRequest_PUBKEY_HASH:
|
||||||
addrType = waddrmgr.PubKeyHash
|
addrType = lnwallet.PubKeyHash
|
||||||
}
|
}
|
||||||
|
|
||||||
addr, err := r.server.lnwallet.NewAddress(defaultAccount,
|
addr, err := r.server.lnwallet.NewAddress(addrType, false)
|
||||||
addrType)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -378,35 +375,14 @@ func (r *rpcServer) ListPeers(ctx context.Context,
|
|||||||
func (r *rpcServer) WalletBalance(ctx context.Context,
|
func (r *rpcServer) WalletBalance(ctx context.Context,
|
||||||
in *lnrpc.WalletBalanceRequest) (*lnrpc.WalletBalanceResponse, error) {
|
in *lnrpc.WalletBalanceRequest) (*lnrpc.WalletBalanceResponse, error) {
|
||||||
|
|
||||||
var balance float64
|
balance, err := r.server.lnwallet.ConfirmedBalance(1, in.WitnessOnly)
|
||||||
|
if err != nil {
|
||||||
if in.WitnessOnly {
|
return nil, err
|
||||||
witnessOutputs, err := r.server.lnwallet.ListUnspentWitness(1)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need to convert from BTC to satoshi here otherwise, and
|
|
||||||
// incorrect sum will be returned.
|
|
||||||
var outputSum btcutil.Amount
|
|
||||||
for _, witnessOutput := range witnessOutputs {
|
|
||||||
outputSum += btcutil.Amount(witnessOutput.Amount * 1e8)
|
|
||||||
}
|
|
||||||
|
|
||||||
balance = outputSum.ToBTC()
|
|
||||||
} else {
|
|
||||||
// TODO(roasbeef): make num confs a param
|
|
||||||
outputSum, err := r.server.lnwallet.CalculateBalance(1)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
balance = outputSum.ToBTC()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rpcsLog.Debugf("[walletbalance] balance=%v", balance)
|
rpcsLog.Debugf("[walletbalance] balance=%v", balance)
|
||||||
|
|
||||||
return &lnrpc.WalletBalanceResponse{balance}, nil
|
return &lnrpc.WalletBalanceResponse{balance.ToBTC()}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PendingChannels returns a list of all the channels that are currently
|
// PendingChannels returns a list of all the channels that are currently
|
||||||
|
33
server.go
33
server.go
@ -18,7 +18,6 @@ import (
|
|||||||
|
|
||||||
"github.com/BitfuryLightning/tools/routing"
|
"github.com/BitfuryLightning/tools/routing"
|
||||||
"github.com/BitfuryLightning/tools/rt/graph"
|
"github.com/BitfuryLightning/tools/rt/graph"
|
||||||
"github.com/roasbeef/btcwallet/waddrmgr"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// server is the main server of the Lightning Network Daemon. The server
|
// server is the main server of the Lightning Network Daemon. The server
|
||||||
@ -66,7 +65,7 @@ type server struct {
|
|||||||
func newServer(listenAddrs []string, notifier chainntnfs.ChainNotifier,
|
func newServer(listenAddrs []string, notifier chainntnfs.ChainNotifier,
|
||||||
wallet *lnwallet.LightningWallet, chanDB *channeldb.DB) (*server, error) {
|
wallet *lnwallet.LightningWallet, chanDB *channeldb.DB) (*server, error) {
|
||||||
|
|
||||||
privKey, err := getIdentityPrivKey(chanDB, wallet)
|
privKey, err := wallet.GetIdentitykey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -493,33 +492,3 @@ func (s *server) listener(l net.Listener) {
|
|||||||
|
|
||||||
s.wg.Done()
|
s.wg.Done()
|
||||||
}
|
}
|
||||||
|
|
||||||
// getIdentityPrivKey gets the identity private key out of the wallet DB.
|
|
||||||
func getIdentityPrivKey(c *channeldb.DB,
|
|
||||||
w *lnwallet.LightningWallet) (*btcec.PrivateKey, error) {
|
|
||||||
|
|
||||||
// First retrieve the current identity address for this peer.
|
|
||||||
adr, err := c.GetIdAdr()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Using the ID address, request the private key coresponding to the
|
|
||||||
// address from the wallet's address manager.
|
|
||||||
adr2, err := w.Manager.Address(adr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
serializedKey := adr2.(waddrmgr.ManagedPubKeyAddress).PubKey().SerializeCompressed()
|
|
||||||
keyEncoded := hex.EncodeToString(serializedKey)
|
|
||||||
ltndLog.Infof("identity address: %v", adr)
|
|
||||||
ltndLog.Infof("identity pubkey retrieved: %v", keyEncoded)
|
|
||||||
|
|
||||||
priv, err := adr2.(waddrmgr.ManagedPubKeyAddress).PrivKey()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return priv, nil
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user