diff --git a/channeldb/channel_test.go b/channeldb/channel_test.go index 445402bb..917e689e 100644 --- a/channeldb/channel_test.go +++ b/channeldb/channel_test.go @@ -3,11 +3,11 @@ package channeldb import ( "bytes" "io/ioutil" + "math/rand" "net" "os" "reflect" "testing" - "time" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/lnwire" @@ -15,7 +15,6 @@ import ( "github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/chaincfg" "github.com/roasbeef/btcd/chaincfg/chainhash" - "github.com/roasbeef/btcd/txscript" "github.com/roasbeef/btcd/wire" "github.com/roasbeef/btcutil" _ "github.com/roasbeef/btcwallet/walletdb/bdb" @@ -109,23 +108,13 @@ func makeTestDB() (*DB, func(), error) { } func createTestChannelState(cdb *DB) (*OpenChannel, error) { - addr, err := btcutil.NewAddressPubKey(pubKey.SerializeCompressed(), netParams) - if err != nil { - return nil, err - } - - script, err := txscript.MultiSigScript([]*btcutil.AddressPubKey{addr, addr}, 2) - if err != nil { - return nil, err - } - // Simulate 1000 channel updates. producer, err := shachain.NewRevocationProducerFromBytes(key[:]) if err != nil { return nil, err } store := shachain.NewRevocationStore() - for i := 0; i < 1000; i++ { + for i := 0; i < 1; i++ { preImage, err := producer.AtIndex(uint64(i)) if err != nil { return nil, err @@ -139,41 +128,63 @@ func createTestChannelState(cdb *DB) (*OpenChannel, error) { var obsfucator [6]byte copy(obsfucator[:], key[:]) + localCfg := ChannelConfig{ + ChannelConstraints: ChannelConstraints{ + DustLimit: btcutil.Amount(rand.Int63()), + MaxPendingAmount: btcutil.Amount(rand.Int63()), + ChanReserve: btcutil.Amount(rand.Int63()), + MinHTLC: btcutil.Amount(rand.Int63()), + MaxAcceptedHtlcs: uint16(rand.Int31()), + }, + CsvDelay: uint16(rand.Int31()), + MultiSigKey: privKey.PubKey(), + RevocationBasePoint: privKey.PubKey(), + PaymentBasePoint: privKey.PubKey(), + DelayBasePoint: privKey.PubKey(), + } + remoteCfg := ChannelConfig{ + ChannelConstraints: ChannelConstraints{ + DustLimit: btcutil.Amount(rand.Int63()), + MaxPendingAmount: btcutil.Amount(rand.Int63()), + ChanReserve: btcutil.Amount(rand.Int63()), + MinHTLC: btcutil.Amount(rand.Int63()), + MaxAcceptedHtlcs: uint16(rand.Int31()), + }, + CsvDelay: uint16(rand.Int31()), + MultiSigKey: privKey.PubKey(), + RevocationBasePoint: privKey.PubKey(), + PaymentBasePoint: privKey.PubKey(), + DelayBasePoint: privKey.PubKey(), + } + + chanID := lnwire.NewShortChanIDFromInt(uint64(rand.Int63())) + return &OpenChannel{ - IsInitiator: true, - IsPending: true, - ChanType: SingleFunder, - IdentityPub: pubKey, - ChanID: id, - FeePerKw: btcutil.Amount(5000), - TheirDustLimit: btcutil.Amount(200), - OurDustLimit: btcutil.Amount(200), - OurCommitKey: privKey.PubKey(), - TheirCommitKey: pubKey, - Capacity: btcutil.Amount(10000), - OurBalance: btcutil.Amount(3000), - TheirBalance: btcutil.Amount(9000), - OurCommitTx: testTx, - OurCommitSig: bytes.Repeat([]byte{1}, 71), - RevocationProducer: producer, - RevocationStore: store, - StateHintObsfucator: obsfucator, - FundingOutpoint: testOutpoint, - OurMultiSigKey: privKey.PubKey(), - TheirMultiSigKey: privKey.PubKey(), - FundingWitnessScript: script, - NumConfsRequired: 4, - TheirCurrentRevocation: privKey.PubKey(), - TheirCurrentRevocationHash: key, - OurDeliveryScript: script, - TheirDeliveryScript: script, - LocalCsvDelay: 5, - RemoteCsvDelay: 9, - NumUpdates: 0, - TotalSatoshisSent: 8, - TotalSatoshisReceived: 2, - CreationTime: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC), - Db: cdb, + ChanType: SingleFunder, + ChainHash: key, + FundingOutpoint: *testOutpoint, + ShortChanID: chanID, + IsInitiator: true, + IsPending: true, + IdentityPub: pubKey, + LocalChanCfg: localCfg, + RemoteChanCfg: remoteCfg, + CommitFee: btcutil.Amount(rand.Int63()), + FeePerKw: btcutil.Amount(5000), + Capacity: btcutil.Amount(10000), + LocalBalance: btcutil.Amount(3000), + RemoteBalance: btcutil.Amount(9000), + CommitTx: *testTx, + CommitSig: bytes.Repeat([]byte{1}, 71), + NumConfsRequired: 4, + RemoteCurrentRevocation: privKey.PubKey(), + RemoteNextRevocation: privKey.PubKey(), + RevocationProducer: producer, + RevocationStore: store, + NumUpdates: 0, + TotalSatoshisSent: 8, + TotalSatoshisReceived: 2, + Db: cdb, }, nil } @@ -194,6 +205,7 @@ func TestOpenChannelPutGetDelete(t *testing.T) { } state.Htlcs = []*HTLC{ { + Signature: testSig.Serialize(), Incoming: true, Amt: 10, RHash: key, @@ -214,161 +226,58 @@ func TestOpenChannelPutGetDelete(t *testing.T) { // The decoded channel state should be identical to what we stored // above. - if !state.IdentityPub.IsEqual(newState.IdentityPub) { - t.Fatal("their id doesn't match") - } - if !reflect.DeepEqual(state.ChanID, newState.ChanID) { - t.Fatal("chan id's don't match") - } - if state.FeePerKw != newState.FeePerKw { - t.Fatal("fee/kb doesn't match") - } - if state.TheirDustLimit != newState.TheirDustLimit { - t.Fatal("their dust limit doesn't match") - } - if state.OurDustLimit != newState.OurDustLimit { - t.Fatal("our dust limit doesn't match") - } - if state.IsInitiator != newState.IsInitiator { - t.Fatal("initiator status doesn't match") - } - if state.ChanType != newState.ChanType { - t.Fatal("channel type doesn't match") + if !reflect.DeepEqual(state, newState) { + state.LocalChanCfg.MultiSigKey.Curve = nil + state.LocalChanCfg.RevocationBasePoint.Curve = nil + state.LocalChanCfg.PaymentBasePoint.Curve = nil + state.LocalChanCfg.DelayBasePoint.Curve = nil + + state.RemoteChanCfg.MultiSigKey.Curve = nil + state.RemoteChanCfg.RevocationBasePoint.Curve = nil + state.RemoteChanCfg.PaymentBasePoint.Curve = nil + state.RemoteChanCfg.DelayBasePoint.Curve = nil + + state.IdentityPub.Curve = nil + state.RemoteNextRevocation.Curve = nil + state.RemoteCurrentRevocation.Curve = nil + + newState.LocalChanCfg.MultiSigKey.Curve = nil + newState.LocalChanCfg.RevocationBasePoint.Curve = nil + newState.LocalChanCfg.PaymentBasePoint.Curve = nil + newState.LocalChanCfg.DelayBasePoint.Curve = nil + + newState.RemoteChanCfg.MultiSigKey.Curve = nil + newState.RemoteChanCfg.RevocationBasePoint.Curve = nil + newState.RemoteChanCfg.PaymentBasePoint.Curve = nil + newState.RemoteChanCfg.DelayBasePoint.Curve = nil + + newState.IdentityPub.Curve = nil + newState.RemoteCurrentRevocation.Curve = nil + newState.RemoteNextRevocation.Curve = nil + t.Fatalf("channel state doesn't match:: %v vs %v", + spew.Sdump(state), spew.Sdump(newState)) } - if !bytes.Equal(state.OurCommitKey.SerializeCompressed(), - newState.OurCommitKey.SerializeCompressed()) { - t.Fatal("our commit key doesn't match") - } - if !bytes.Equal(state.TheirCommitKey.SerializeCompressed(), - newState.TheirCommitKey.SerializeCompressed()) { - t.Fatal("their commit key doesn't match") - } - - if state.Capacity != newState.Capacity { - t.Fatalf("capacity doesn't match: %v vs %v", state.Capacity, - newState.Capacity) - } - if state.OurBalance != newState.OurBalance { - t.Fatal("our balance doesn't match") - } - if state.TheirBalance != newState.TheirBalance { - t.Fatal("their balance doesn't match") - } - - var b1, b2 bytes.Buffer - if err := state.OurCommitTx.Serialize(&b1); err != nil { - t.Fatal("unable to serialize transaction") - } - if err := newState.OurCommitTx.Serialize(&b2); err != nil { - t.Fatal("unable to serialize transaction") - } - if !bytes.Equal(b1.Bytes(), b2.Bytes()) { - t.Fatal("ourCommitTx doesn't match") - } - if !bytes.Equal(newState.OurCommitSig, state.OurCommitSig) { - t.Fatal("commit sigs don't match") - } - - // TODO(roasbeef): replace with a single equal? - if !reflect.DeepEqual(state.FundingOutpoint, newState.FundingOutpoint) { - t.Fatal("funding outpoint doesn't match") - } - - if !bytes.Equal(state.OurMultiSigKey.SerializeCompressed(), - newState.OurMultiSigKey.SerializeCompressed()) { - t.Fatal("our multisig key doesn't match") - } - if !bytes.Equal(state.TheirMultiSigKey.SerializeCompressed(), - newState.TheirMultiSigKey.SerializeCompressed()) { - t.Fatal("their multisig key doesn't match") - } - if !bytes.Equal(state.FundingWitnessScript, newState.FundingWitnessScript) { - t.Fatal("redeem script doesn't match") - } - - // The local and remote delivery scripts should be identical. - if !bytes.Equal(state.OurDeliveryScript, newState.OurDeliveryScript) { - t.Fatal("our delivery address doesn't match") - } - if !bytes.Equal(state.TheirDeliveryScript, newState.TheirDeliveryScript) { - t.Fatal("their delivery address doesn't match") - } - - if state.NumUpdates != newState.NumUpdates { - t.Fatalf("num updates doesn't match: %v vs %v", - state.NumUpdates, newState.NumUpdates) - } - if state.RemoteCsvDelay != newState.RemoteCsvDelay { - t.Fatalf("csv delay doesn't match: %v vs %v", - state.RemoteCsvDelay, newState.RemoteCsvDelay) - } - if state.LocalCsvDelay != newState.LocalCsvDelay { - t.Fatalf("csv delay doesn't match: %v vs %v", - state.LocalCsvDelay, newState.LocalCsvDelay) - } - if state.TotalSatoshisSent != newState.TotalSatoshisSent { - t.Fatalf("satoshis sent doesn't match: %v vs %v", - state.TotalSatoshisSent, newState.TotalSatoshisSent) - } - if state.TotalSatoshisReceived != newState.TotalSatoshisReceived { - t.Fatal("satoshis received doesn't match") - } - if state.NumConfsRequired != newState.NumConfsRequired { - t.Fatalf("num confs required doesn't match: %v, vs. %v", - state.NumConfsRequired, newState.NumConfsRequired) - } - - if state.CreationTime.Unix() != newState.CreationTime.Unix() { - t.Fatal("creation time doesn't match") - } - - // The local and remote producers should be identical. - var old bytes.Buffer - err = state.RevocationProducer.Encode(&old) + // We'll also test that the channel is properly able to hot swap the + // next revocation for the state machine. This tests the initial + // post-funding revocation exchange. + nextRevKey, err := btcec.NewPrivateKey(btcec.S256()) if err != nil { - t.Fatalf("can't convert old revocation producer to bytes: %v", - err) + t.Fatalf("unable to create new private key: %v", err) + } + if err := state.InsertNextRevocation(nextRevKey.PubKey()); err != nil { + t.Fatalf("unable to update revocation: %v", err) } - var new bytes.Buffer - err = newState.RevocationProducer.Encode(&new) + openChannels, err = cdb.FetchOpenChannels(state.IdentityPub) if err != nil { - t.Fatalf("can't convert new revocation producer to bytes: %v", - err) + t.Fatalf("unable to fetch open channel: %v", err) } + updatedChan := openChannels[0] - if !bytes.Equal(old.Bytes(), new.Bytes()) { - t.Fatal("local producer don't match") - } - - old.Reset() - new.Reset() - - err = state.RevocationStore.Encode(&old) - if err != nil { - t.Fatalf("unable to serialize old remote store: %v", err) - } - err = newState.RevocationStore.Encode(&new) - if err != nil { - t.Fatalf("unable to serialize new remote store: %v", err) - } - if !bytes.Equal(old.Bytes(), new.Bytes()) { - t.Fatal("remote store don't match") - } - if !newState.TheirCurrentRevocation.IsEqual(state.TheirCurrentRevocation) { - t.Fatal("revocation keys don't match") - } - if !bytes.Equal(newState.TheirCurrentRevocationHash[:], state.TheirCurrentRevocationHash[:]) { - t.Fatal("revocation hashes don't match") - } - if !reflect.DeepEqual(state.Htlcs[0], newState.Htlcs[0]) { - t.Fatalf("htlcs don't match: %v vs %v", spew.Sdump(state.Htlcs[0]), - spew.Sdump(newState.Htlcs[0])) - } - if !bytes.Equal(state.StateHintObsfucator[:], - newState.StateHintObsfucator[:]) { - t.Fatal("obsfuctators don't match") + // Ensure that the revocation was set properly. + if !nextRevKey.PubKey().IsEqual(updatedChan.RemoteNextRevocation) { + t.Fatalf("next revocation wasn't updated") } // Finally to wrap up the test, delete the state of the channel within @@ -376,7 +285,7 @@ func TestOpenChannelPutGetDelete(t *testing.T) { // written state, and creates a small "summary" elsewhere within the // database. closeSummary := &ChannelCloseSummary{ - ChanPoint: *state.ChanID, + ChanPoint: state.FundingOutpoint, RemotePub: state.IdentityPub, SettledBalance: btcutil.Amount(500), TimeLockedBalance: btcutil.Amount(10000), @@ -439,12 +348,13 @@ func TestChannelStateTransition(t *testing.T) { incoming = true } htlc := &HTLC{ + Signature: testSig.Serialize(), Incoming: incoming, Amt: 10, RHash: key, RefundTimeout: i, RevocationDelay: i + 2, - OutputIndex: uint16(i * 3), + OutputIndex: int32(i * 3), } htlcs = append(htlcs, htlc) htlcAmt += htlc.Amt @@ -455,7 +365,7 @@ func TestChannelStateTransition(t *testing.T) { // Additionally, modify the signature and commitment transaction. newSequence := uint32(129498) newSig := bytes.Repeat([]byte{3}, 71) - newTx := channel.OurCommitTx.Copy() + newTx := channel.CommitTx.Copy() newTx.TxIn[0].Sequence = newSequence delta := &ChannelDelta{ LocalBalance: btcutil.Amount(1e8), @@ -476,21 +386,21 @@ func TestChannelStateTransition(t *testing.T) { if err != nil { t.Fatalf("unable to fetch updated channel: %v", err) } - if !bytes.Equal(updatedChannel[0].OurCommitSig, newSig) { + if !bytes.Equal(updatedChannel[0].CommitSig, newSig) { t.Fatalf("sigs don't match %x vs %x", - updatedChannel[0].OurCommitSig, newSig) + updatedChannel[0].CommitSig, newSig) } - if updatedChannel[0].OurCommitTx.TxIn[0].Sequence != newSequence { + if updatedChannel[0].CommitTx.TxIn[0].Sequence != newSequence { t.Fatalf("sequence numbers don't match: %v vs %v", - updatedChannel[0].OurCommitTx.TxIn[0].Sequence, newSequence) + updatedChannel[0].CommitTx.TxIn[0].Sequence, newSequence) } - if updatedChannel[0].OurBalance != delta.LocalBalance { + if updatedChannel[0].LocalBalance != delta.LocalBalance { t.Fatalf("local balances don't match: %v vs %v", - updatedChannel[0].OurBalance, delta.LocalBalance) + updatedChannel[0].LocalBalance, delta.LocalBalance) } - if updatedChannel[0].TheirBalance != delta.RemoteBalance { + if updatedChannel[0].RemoteBalance != delta.RemoteBalance { t.Fatalf("remote balances don't match: %v vs %v", - updatedChannel[0].TheirBalance, delta.RemoteBalance) + updatedChannel[0].RemoteBalance, delta.RemoteBalance) } if updatedChannel[0].NumUpdates != uint64(delta.UpdateNum) { t.Fatalf("update # doesn't match: %v vs %v", @@ -518,8 +428,12 @@ func TestChannelStateTransition(t *testing.T) { // needed to rectify any fishy behavior by the remote party. Modify the // current uncollapsed revocation state to simulate a state transition // by the remote party. - newRevocation := bytes.Repeat([]byte{9}, 32) - copy(channel.TheirCurrentRevocationHash[:], newRevocation) + channel.RemoteCurrentRevocation = channel.RemoteNextRevocation + newPriv, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Fatalf("unable to generate key: %v", err) + } + channel.RemoteNextRevocation = newPriv.PubKey() if err := channel.AppendToRevocationLog(delta); err != nil { t.Fatalf("unable to append to revocation log: %v", err) } @@ -604,14 +518,16 @@ func TestChannelStateTransition(t *testing.T) { if err != nil { t.Fatalf("unable to fetch updated channel: %v", err) } - if !bytes.Equal(updatedChannel[0].TheirCurrentRevocationHash[:], - newRevocation) { - t.Fatal("revocation state wasn't synced!") + if !channel.RemoteCurrentRevocation.IsEqual(updatedChannel[0].RemoteCurrentRevocation) { + t.Fatalf("revocation state was not synced") + } + if !channel.RemoteNextRevocation.IsEqual(updatedChannel[0].RemoteNextRevocation) { + t.Fatalf("revocation state was not synced") } // Now attempt to delete the channel from the database. closeSummary := &ChannelCloseSummary{ - ChanPoint: *channel.ChanID, + ChanPoint: channel.FundingOutpoint, RemotePub: channel.IdentityPub, SettledBalance: btcutil.Amount(500), TimeLockedBalance: btcutil.Amount(10000), @@ -689,7 +605,7 @@ func TestFetchPendingChannels(t *testing.T) { TxIndex: 10, TxPosition: 15, } - err = cdb.MarkChannelAsOpen(pendingChannels[0].ChanID, chanOpenLoc) + err = cdb.MarkChannelAsOpen(&pendingChannels[0].FundingOutpoint, chanOpenLoc) if err != nil { t.Fatalf("unable to mark channel as open: %v", err) } @@ -756,19 +672,20 @@ func TestFetchClosedChannels(t *testing.T) { TxIndex: 10, TxPosition: 15, } - if err := cdb.MarkChannelAsOpen(state.ChanID, chanOpenLoc); err != nil { + err = cdb.MarkChannelAsOpen(&state.FundingOutpoint, chanOpenLoc) + if err != nil { t.Fatalf("unable to mark channel as open: %v", err) } // Next, close the channel by including a close channel summary in the // database. summary := &ChannelCloseSummary{ - ChanPoint: *state.ChanID, + ChanPoint: state.FundingOutpoint, ClosingTXID: rev, RemotePub: state.IdentityPub, Capacity: state.Capacity, - SettledBalance: state.OurBalance, - TimeLockedBalance: state.OurBalance + 10000, + SettledBalance: state.LocalBalance, + TimeLockedBalance: state.LocalBalance + 10000, CloseType: ForceClose, IsPending: true, } @@ -805,7 +722,8 @@ func TestFetchClosedChannels(t *testing.T) { } // Mark the channel as fully closed - if err := cdb.MarkChanFullyClosed(state.ChanID); err != nil { + err = cdb.MarkChanFullyClosed(&state.FundingOutpoint) + if err != nil { t.Fatalf("failed fully closing channel: %v", err) }