Merge pull request #1726 from Roasbeef/fix-closed-chan-summaries-and-chancfg

multi: ensure closed channel summaries are fully populated, ensure KeyLocators are always set
This commit is contained in:
Olaoluwa Osuntokun 2018-08-15 20:11:01 -07:00 committed by GitHub
commit fc8626c806
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 175 additions and 65 deletions

8
Gopkg.lock generated

@ -120,7 +120,7 @@
revision = "ab6388e0c60ae4834a1f57511e20c17b5f78be4b"
[[projects]]
digest = "1:a9bb59675f2aa863eff2bab6f0668b5e6feacaf6b7978fe5a654b068d5ca8e36"
digest = "1:04bf3f47dafa64588795c5e0329dc662e867c3afa191051821dbacbe09ba2ca8"
name = "github.com/btcsuite/btcwallet"
packages = [
"chain",
@ -140,7 +140,7 @@
"wtxmgr",
]
pruneopts = "UT"
revision = "1ede0a1a66bad8f7db796615e496a0a0faba5182"
revision = "5fb94231d0c814f02ffc3110eee588278151b4e1"
[[projects]]
branch = "master"
@ -279,7 +279,7 @@
revision = "462a8a75388506b68f76661af8d649f0b88e5301"
[[projects]]
digest = "1:461543cea211913463b96ed3bf8cbdadf23c5ac8ec205bcbbc79edeba9e93a9a"
digest = "1:11ab77a97c0db5cfe9c82f16cb7c47213612033e8bd711e6ddc9a32615fc747d"
name = "github.com/lightninglabs/neutrino"
packages = [
".",
@ -288,7 +288,7 @@
"headerlist",
]
pruneopts = "UT"
revision = "b451667d69910cd20995452c56e02441896a3349"
revision = "0d0ce901538af81e234c1b2376babf20fe976b09"
[[projects]]
digest = "1:58ab6d6525898cbeb86dc29a68f8e9bfe95254b9032134eb9458779574872260"

@ -44,7 +44,7 @@
[[constraint]]
name = "github.com/lightninglabs/neutrino"
revision = "b451667d69910cd20995452c56e02441896a3349"
revision = "0d0ce901538af81e234c1b2376babf20fe976b09"
[[constraint]]
name = "github.com/lightningnetwork/lightning-onion"
@ -72,7 +72,7 @@
[[constraint]]
name = "github.com/btcsuite/btcwallet"
revision = "1ede0a1a66bad8f7db796615e496a0a0faba5182"
revision = "5fb94231d0c814f02ffc3110eee588278151b4e1"
[[constraint]]
name = "github.com/tv42/zbase32"

@ -165,18 +165,38 @@ func createTestChannelState(cdb *DB) (*OpenChannel, error) {
CsvDelay: uint16(rand.Int31()),
MultiSigKey: keychain.KeyDescriptor{
PubKey: privKey.PubKey(),
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamilyMultiSig,
Index: 9,
},
},
RevocationBasePoint: keychain.KeyDescriptor{
PubKey: privKey.PubKey(),
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamilyRevocationBase,
Index: 8,
},
},
PaymentBasePoint: keychain.KeyDescriptor{
PubKey: privKey.PubKey(),
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamilyPaymentBase,
Index: 7,
},
},
DelayBasePoint: keychain.KeyDescriptor{
PubKey: privKey.PubKey(),
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamilyDelayBase,
Index: 6,
},
},
HtlcBasePoint: keychain.KeyDescriptor{
PubKey: privKey.PubKey(),
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamilyHtlcBase,
Index: 5,
},
},
}
@ -772,6 +792,7 @@ func TestFetchClosedChannels(t *testing.T) {
TimeLockedBalance: state.RemoteCommitment.LocalBalance.ToSatoshis() + 10000,
CloseType: RemoteForceClose,
IsPending: true,
LocalChanConfig: state.LocalChanCfg,
}
if err := state.CloseChannel(summary); err != nil {
t.Fatalf("unable to close channel: %v", err)

@ -502,16 +502,19 @@ func (c *chainWatcher) dispatchCooperativeClose(commitSpend *chainntnfs.SpendDet
// database. We can do this as a cooperatively closed channel has all
// its outputs resolved after only one confirmation.
closeSummary := &channeldb.ChannelCloseSummary{
ChanPoint: c.cfg.chanState.FundingOutpoint,
ChainHash: c.cfg.chanState.ChainHash,
ClosingTXID: *commitSpend.SpenderTxHash,
RemotePub: c.cfg.chanState.IdentityPub,
Capacity: c.cfg.chanState.Capacity,
CloseHeight: uint32(commitSpend.SpendingHeight),
SettledBalance: localAmt,
CloseType: channeldb.CooperativeClose,
ShortChanID: c.cfg.chanState.ShortChanID(),
IsPending: false,
ChanPoint: c.cfg.chanState.FundingOutpoint,
ChainHash: c.cfg.chanState.ChainHash,
ClosingTXID: *commitSpend.SpenderTxHash,
RemotePub: c.cfg.chanState.IdentityPub,
Capacity: c.cfg.chanState.Capacity,
CloseHeight: uint32(commitSpend.SpendingHeight),
SettledBalance: localAmt,
CloseType: channeldb.CooperativeClose,
ShortChanID: c.cfg.chanState.ShortChanID(),
IsPending: false,
RemoteCurrentRevocation: c.cfg.chanState.RemoteCurrentRevocation,
RemoteNextRevocation: c.cfg.chanState.RemoteNextRevocation,
LocalChanConfig: c.cfg.chanState.LocalChanCfg,
}
err := c.cfg.chanState.CloseChannel(closeSummary)
if err != nil && err != channeldb.ErrNoActiveChannels &&
@ -559,15 +562,18 @@ func (c *chainWatcher) dispatchLocalForceClose(
// usage by related sub-systems.
chanSnapshot := forceClose.ChanSnapshot
closeSummary := &channeldb.ChannelCloseSummary{
ChanPoint: chanSnapshot.ChannelPoint,
ChainHash: chanSnapshot.ChainHash,
ClosingTXID: forceClose.CloseTx.TxHash(),
RemotePub: &chanSnapshot.RemoteIdentity,
Capacity: chanSnapshot.Capacity,
CloseType: channeldb.LocalForceClose,
IsPending: true,
ShortChanID: c.cfg.chanState.ShortChanID(),
CloseHeight: uint32(commitSpend.SpendingHeight),
ChanPoint: chanSnapshot.ChannelPoint,
ChainHash: chanSnapshot.ChainHash,
ClosingTXID: forceClose.CloseTx.TxHash(),
RemotePub: &chanSnapshot.RemoteIdentity,
Capacity: chanSnapshot.Capacity,
CloseType: channeldb.LocalForceClose,
IsPending: true,
ShortChanID: c.cfg.chanState.ShortChanID(),
CloseHeight: uint32(commitSpend.SpendingHeight),
RemoteCurrentRevocation: c.cfg.chanState.RemoteCurrentRevocation,
RemoteNextRevocation: c.cfg.chanState.RemoteNextRevocation,
LocalChanConfig: c.cfg.chanState.LocalChanCfg,
}
// If our commitment output isn't dust or we have active HTLC's on the
@ -739,16 +745,19 @@ func (c *chainWatcher) dispatchContractBreach(spendEvent *chainntnfs.SpendDetail
// TODO(roasbeef): instead mark we got all the monies?
settledBalance := remoteCommit.LocalBalance.ToSatoshis()
closeSummary := channeldb.ChannelCloseSummary{
ChanPoint: c.cfg.chanState.FundingOutpoint,
ChainHash: c.cfg.chanState.ChainHash,
ClosingTXID: *spendEvent.SpenderTxHash,
CloseHeight: spendHeight,
RemotePub: c.cfg.chanState.IdentityPub,
Capacity: c.cfg.chanState.Capacity,
SettledBalance: settledBalance,
CloseType: channeldb.BreachClose,
IsPending: true,
ShortChanID: c.cfg.chanState.ShortChanID(),
ChanPoint: c.cfg.chanState.FundingOutpoint,
ChainHash: c.cfg.chanState.ChainHash,
ClosingTXID: *spendEvent.SpenderTxHash,
CloseHeight: spendHeight,
RemotePub: c.cfg.chanState.IdentityPub,
Capacity: c.cfg.chanState.Capacity,
SettledBalance: settledBalance,
CloseType: channeldb.BreachClose,
IsPending: true,
ShortChanID: c.cfg.chanState.ShortChanID(),
RemoteCurrentRevocation: c.cfg.chanState.RemoteCurrentRevocation,
RemoteNextRevocation: c.cfg.chanState.RemoteNextRevocation,
LocalChanConfig: c.cfg.chanState.LocalChanCfg,
}
if err := c.cfg.chanState.CloseChannel(&closeSummary); err != nil {

@ -521,12 +521,17 @@ func (f *fundingManager) Start() error {
// mined since the channel was initiated reaches
// maxWaitNumBlocksFundingConf and we are not the channel
// initiator.
localBalance := ch.LocalCommitment.LocalBalance.ToSatoshis()
closeInfo := &channeldb.ChannelCloseSummary{
ChainHash: ch.ChainHash,
ChanPoint: ch.FundingOutpoint,
RemotePub: ch.IdentityPub,
CloseType: channeldb.FundingCanceled,
ChainHash: ch.ChainHash,
ChanPoint: ch.FundingOutpoint,
RemotePub: ch.IdentityPub,
Capacity: ch.Capacity,
SettledBalance: localBalance,
CloseType: channeldb.FundingCanceled,
RemoteCurrentRevocation: ch.RemoteCurrentRevocation,
RemoteNextRevocation: ch.RemoteNextRevocation,
LocalChanConfig: ch.LocalChanCfg,
}
if err := ch.CloseChannel(closeInfo); err != nil {
@ -1371,11 +1376,17 @@ func (f *fundingManager) handleFundingCreated(fmsg *fundingCreatedMsg) {
// we use this convenience method to delete the pending OpenChannel
// from the database.
deleteFromDatabase := func() {
localBalance := completeChan.LocalCommitment.LocalBalance.ToSatoshis()
closeInfo := &channeldb.ChannelCloseSummary{
ChanPoint: completeChan.FundingOutpoint,
ChainHash: completeChan.ChainHash,
RemotePub: completeChan.IdentityPub,
CloseType: channeldb.FundingCanceled,
ChanPoint: completeChan.FundingOutpoint,
ChainHash: completeChan.ChainHash,
RemotePub: completeChan.IdentityPub,
CloseType: channeldb.FundingCanceled,
Capacity: completeChan.Capacity,
SettledBalance: localBalance,
RemoteCurrentRevocation: completeChan.RemoteCurrentRevocation,
RemoteNextRevocation: completeChan.RemoteNextRevocation,
LocalChanConfig: completeChan.LocalChanCfg,
}
if err := completeChan.CloseChannel(closeInfo); err != nil {

@ -139,7 +139,10 @@ func (b *BtcWalletKeyRing) createAccountIfNotExists(
//
// NOTE: This is part of the keychain.KeyRing interface.
func (b *BtcWalletKeyRing) DeriveNextKey(keyFam KeyFamily) (KeyDescriptor, error) {
var pubKey *btcec.PublicKey
var (
pubKey *btcec.PublicKey
keyLoc KeyLocator
)
db := b.wallet.Database()
err := walletdb.Update(db, func(tx walletdb.ReadWriteTx) error {
@ -165,7 +168,21 @@ func (b *BtcWalletKeyRing) DeriveNextKey(keyFam KeyFamily) (KeyDescriptor, error
return err
}
pubKey = addrs[0].(waddrmgr.ManagedPubKeyAddress).PubKey()
// Extract the first address, ensuring that it is of the proper
// interface type, otherwise we can't manipulate it below.
addr, ok := addrs[0].(waddrmgr.ManagedPubKeyAddress)
if !ok {
return fmt.Errorf("address is not a managed pubkey " +
"addr")
}
pubKey = addr.PubKey()
_, pathInfo, _ := addr.DerivationInfo()
keyLoc = KeyLocator{
Family: keyFam,
Index: pathInfo.Index,
}
return nil
})
@ -174,7 +191,8 @@ func (b *BtcWalletKeyRing) DeriveNextKey(keyFam KeyFamily) (KeyDescriptor, error
}
return KeyDescriptor{
PubKey: pubKey,
PubKey: pubKey,
KeyLocator: keyLoc,
}, nil
}

@ -83,7 +83,7 @@ const (
// will vary per channel and use case) is the final element which allows us to
// deterministically derive keys.
type KeyLocator struct {
// TODO(roasbeef); add the key scope as well??
// TODO(roasbeef): add the key scope as well??
// Family is the family of key being identified.
Family KeyFamily
@ -92,8 +92,8 @@ type KeyLocator struct {
Index uint32
}
// IsEmpty returns true if a KeyLocator is "empty". This may be the case where we
// learn of a key from a remote party for a contract, but don't know the
// IsEmpty returns true if a KeyLocator is "empty". This may be the case where
// we learn of a key from a remote party for a contract, but don't know the
// precise details of its derivation (as we don't know the private key!).
func (k KeyLocator) IsEmpty() bool {
return k.Family == 0 && k.Index == 0

@ -13,6 +13,7 @@ import (
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/wallet"
"github.com/btcsuite/btcwallet/walletdb"
"github.com/davecgh/go-spew/spew"
_ "github.com/btcsuite/btcwallet/walletdb/bdb" // Required in order to create the default database.
)
@ -91,6 +92,14 @@ func createTestBtcWallet(coinType uint32) (func(), *wallet.Wallet, error) {
return cleanUp, baseWallet, nil
}
func assertEqualKeyLocator(t *testing.T, a, b KeyLocator) {
t.Helper()
if a != b {
t.Fatalf("mismatched key locators: expected %v, "+
"got %v", spew.Sdump(a), spew.Sdump(b))
}
}
// secretKeyRingConstructor is a function signature that's used as a generic
// constructor for various implementations of the KeyRing interface. A string
// naming the returned interface, a function closure that cleans up any
@ -141,6 +150,8 @@ func TestKeyRingDerivation(t *testing.T) {
},
}
const numKeysToDerive = 10
// For each implementation constructor registered above, we'll execute
// an identical set of tests in order to ensure that the interface
// adheres to our nominal specification.
@ -163,10 +174,16 @@ func TestKeyRingDerivation(t *testing.T) {
t.Fatalf("unable to derive next for "+
"keyFam=%v: %v", keyFam, err)
}
assertEqualKeyLocator(t,
KeyLocator{
Family: keyFam,
Index: 0,
}, keyDesc.KeyLocator,
)
// If we now try to manually derive the *first*
// key, then we should get an identical public
// key back.
// We'll now re-derive that key to ensure that
// we're able to properly access the key via
// the random access derivation methods.
keyLoc := KeyLocator{
Family: keyFam,
Index: 0,
@ -176,13 +193,41 @@ func TestKeyRingDerivation(t *testing.T) {
t.Fatalf("unable to derive first key for "+
"keyFam=%v: %v", keyFam, err)
}
if !keyDesc.PubKey.IsEqual(firstKeyDesc.PubKey) {
t.Fatalf("mismatched keys: expected %v, "+
t.Fatalf("mismatched keys: expected %x, "+
"got %x",
keyDesc.PubKey.SerializeCompressed(),
firstKeyDesc.PubKey.SerializeCompressed())
}
assertEqualKeyLocator(t,
KeyLocator{
Family: keyFam,
Index: 0,
}, firstKeyDesc.KeyLocator,
)
// If we now try to manually derive the next 10
// keys (including the original key), then we
// should get an identical public key back and
// their KeyLocator information
// should be set properly.
for i := 0; i < numKeysToDerive+1; i++ {
keyLoc := KeyLocator{
Family: keyFam,
Index: uint32(i),
}
keyDesc, err := keyRing.DeriveKey(keyLoc)
if err != nil {
t.Fatalf("unable to derive first key for "+
"keyFam=%v: %v", keyFam, err)
}
// Ensure that the key locator matches
// up as well.
assertEqualKeyLocator(
t, keyLoc, keyDesc.KeyLocator,
)
}
// If this succeeds, then we'll also try to
// derive a random index within the range.
@ -191,12 +236,15 @@ func TestKeyRingDerivation(t *testing.T) {
Family: keyFam,
Index: randKeyIndex,
}
_, err = keyRing.DeriveKey(keyLoc)
keyDesc, err = keyRing.DeriveKey(keyLoc)
if err != nil {
t.Fatalf("unable to derive key_index=%v "+
"for keyFam=%v: %v",
randKeyIndex, keyFam, err)
}
assertEqualKeyLocator(
t, keyLoc, keyDesc.KeyLocator,
)
}
})
if !success {

@ -5094,15 +5094,18 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer Signer,
}
closeSummary := channeldb.ChannelCloseSummary{
ChanPoint: chanState.FundingOutpoint,
ChainHash: chanState.ChainHash,
ClosingTXID: *commitSpend.SpenderTxHash,
CloseHeight: uint32(commitSpend.SpendingHeight),
RemotePub: chanState.IdentityPub,
Capacity: chanState.Capacity,
SettledBalance: btcutil.Amount(localBalance),
CloseType: channeldb.RemoteForceClose,
IsPending: true,
ChanPoint: chanState.FundingOutpoint,
ChainHash: chanState.ChainHash,
ClosingTXID: *commitSpend.SpenderTxHash,
CloseHeight: uint32(commitSpend.SpendingHeight),
RemotePub: chanState.IdentityPub,
Capacity: chanState.Capacity,
SettledBalance: btcutil.Amount(localBalance),
CloseType: channeldb.RemoteForceClose,
IsPending: true,
RemoteCurrentRevocation: chanState.RemoteCurrentRevocation,
RemoteNextRevocation: chanState.RemoteNextRevocation,
LocalChanConfig: chanState.LocalChanCfg,
}
return &UnilateralCloseSummary{