Merge pull request #6347 from yyforyongyu/5388-rm-revc-log
lnwallet+channeldb: store minimal info in revocation log bucket
This commit is contained in:
33 files changed
+3635
-562
No files matched your search
+59
-130
@@ -117,21 +117,14 @@ var (
|
||||
// TODO(roasbeef): rename to commit chain?
|
||||
commitDiffKey = []byte("commit-diff-key")
|
||||
|
||||
// revocationLogBucket is dedicated for storing the necessary delta
|
||||
// state between channel updates required to re-construct a past state
|
||||
// in order to punish a counterparty attempting a non-cooperative
|
||||
// channel closure. This key should be accessed from within the
|
||||
// sub-bucket of a target channel, identified by its channel point.
|
||||
revocationLogBucket = []byte("revocation-log-key")
|
||||
|
||||
// frozenChanKey is the key where we store the information for any
|
||||
// active "frozen" channels. This key is present only in the leaf
|
||||
// bucket for a given channel.
|
||||
frozenChanKey = []byte("frozen-chans")
|
||||
|
||||
// lastWasRevokeKey is a key that stores true when the last update we sent
|
||||
// was a revocation and false when it was a commitment signature. This is
|
||||
// nil in the case of new channels with no updates exchanged.
|
||||
// lastWasRevokeKey is a key that stores true when the last update we
|
||||
// sent was a revocation and false when it was a commitment signature.
|
||||
// This is nil in the case of new channels with no updates exchanged.
|
||||
lastWasRevokeKey = []byte("last-was-revoke")
|
||||
)
|
||||
|
||||
@@ -176,18 +169,9 @@ var (
|
||||
// channel.
|
||||
ErrChanBorked = fmt.Errorf("cannot mutate borked channel")
|
||||
|
||||
// ErrLogEntryNotFound is returned when we cannot find a log entry at
|
||||
// the height requested in the revocation log.
|
||||
ErrLogEntryNotFound = fmt.Errorf("log entry not found")
|
||||
|
||||
// ErrMissingIndexEntry is returned when a caller attempts to close a
|
||||
// channel and the outpoint is missing from the index.
|
||||
ErrMissingIndexEntry = fmt.Errorf("missing outpoint from index")
|
||||
|
||||
// errHeightNotFound is returned when a query for channel balances at
|
||||
// a height that we have not reached yet is made.
|
||||
errHeightNotReached = fmt.Errorf("height requested greater than " +
|
||||
"current commit height")
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -657,6 +641,15 @@ type OpenChannel struct {
|
||||
// received within this channel.
|
||||
TotalMSatReceived lnwire.MilliSatoshi
|
||||
|
||||
// InitialLocalBalance is the balance we have during the channel
|
||||
// opening. When we are not the initiator, this value represents the
|
||||
// push amount.
|
||||
InitialLocalBalance lnwire.MilliSatoshi
|
||||
|
||||
// InitialRemoteBalance is the balance they have during the channel
|
||||
// opening.
|
||||
InitialRemoteBalance lnwire.MilliSatoshi
|
||||
|
||||
// LocalChanCfg is the channel configuration for the local node.
|
||||
LocalChanCfg ChannelConfig
|
||||
|
||||
@@ -1644,44 +1637,6 @@ func (c *OpenChannel) UpdateCommitment(newCommitment *ChannelCommitment,
|
||||
return nil
|
||||
}
|
||||
|
||||
// BalancesAtHeight returns the local and remote balances on our commitment
|
||||
// transactions as of a given height.
|
||||
//
|
||||
// NOTE: these are our balances *after* subtracting the commitment fee and
|
||||
// anchor outputs.
|
||||
func (c *OpenChannel) BalancesAtHeight(height uint64) (lnwire.MilliSatoshi,
|
||||
lnwire.MilliSatoshi, error) {
|
||||
|
||||
if height > c.LocalCommitment.CommitHeight &&
|
||||
height > c.RemoteCommitment.CommitHeight {
|
||||
|
||||
return 0, 0, errHeightNotReached
|
||||
}
|
||||
|
||||
// If our current commit is as the desired height, we can return our
|
||||
// current balances.
|
||||
if c.LocalCommitment.CommitHeight == height {
|
||||
return c.LocalCommitment.LocalBalance,
|
||||
c.LocalCommitment.RemoteBalance, nil
|
||||
}
|
||||
|
||||
// If our current remote commit is at the desired height, we can return
|
||||
// the current balances.
|
||||
if c.RemoteCommitment.CommitHeight == height {
|
||||
return c.RemoteCommitment.LocalBalance,
|
||||
c.RemoteCommitment.RemoteBalance, nil
|
||||
}
|
||||
|
||||
// If we are not currently on the height requested, we need to look up
|
||||
// the previous height to obtain our balances at the given height.
|
||||
commit, err := c.FindPreviousState(height)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
return commit.LocalBalance, commit.RemoteBalance, nil
|
||||
}
|
||||
|
||||
// ActiveHtlcs returns a slice of HTLC's which are currently active on *both*
|
||||
// commitment transactions.
|
||||
func (c *OpenChannel) ActiveHtlcs() []HTLC {
|
||||
@@ -1718,6 +1673,8 @@ func (c *OpenChannel) ActiveHtlcs() []HTLC {
|
||||
//
|
||||
// TODO(roasbeef): save space by using smaller ints at tail end?
|
||||
type HTLC struct {
|
||||
// TODO(yy): can embed an HTLCEntry here.
|
||||
|
||||
// Signature is the signature for the second level covenant transaction
|
||||
// for this HTLC. The second level transaction is a timeout tx in the
|
||||
// case that this is an outgoing HTLC, and a success tx in the case
|
||||
@@ -2349,7 +2306,7 @@ func (c *OpenChannel) InsertNextRevocation(revKey *btcec.PublicKey) error {
|
||||
// set of local updates that the peer still needs to send us a signature for.
|
||||
// We store this set of updates in case we go down.
|
||||
func (c *OpenChannel) AdvanceCommitChainTail(fwdPkg *FwdPkg,
|
||||
updates []LogUpdate) error {
|
||||
updates []LogUpdate, ourOutputIndex, theirOutputIndex uint32) error {
|
||||
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
@@ -2422,9 +2379,10 @@ func (c *OpenChannel) AdvanceCommitChainTail(fwdPkg *FwdPkg,
|
||||
|
||||
// With the commitment pointer swapped, we can now add the
|
||||
// revoked (prior) state to the revocation log.
|
||||
//
|
||||
// TODO(roasbeef): store less
|
||||
err = appendChannelLogEntry(logBucket, &c.RemoteCommitment)
|
||||
err = putRevocationLog(
|
||||
logBucket, &c.RemoteCommitment,
|
||||
ourOutputIndex, theirOutputIndex,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -2608,22 +2566,24 @@ func (c *OpenChannel) RemoveFwdPkgs(heights ...uint64) error {
|
||||
}, func() {})
|
||||
}
|
||||
|
||||
// RevocationLogTail returns the "tail", or the end of the current revocation
|
||||
// log. This entry represents the last previous state for the remote node's
|
||||
// commitment chain. The ChannelDelta returned by this method will always lag
|
||||
// one state behind the most current (unrevoked) state of the remote node's
|
||||
// commitment chain.
|
||||
func (c *OpenChannel) RevocationLogTail() (*ChannelCommitment, error) {
|
||||
// revocationLogTailCommitHeight returns the commit height at the end of the
|
||||
// revocation log. This entry represents the last previous state for the remote
|
||||
// node's commitment chain. The ChannelDelta returned by this method will
|
||||
// always lag one state behind the most current (unrevoked) state of the remote
|
||||
// node's commitment chain.
|
||||
// NOTE: used in unit test only.
|
||||
func (c *OpenChannel) revocationLogTailCommitHeight() (uint64, error) {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
var height uint64
|
||||
|
||||
// If we haven't created any state updates yet, then we'll exit early as
|
||||
// there's nothing to be found on disk in the revocation bucket.
|
||||
if c.RemoteCommitment.CommitHeight == 0 {
|
||||
return nil, nil
|
||||
return height, nil
|
||||
}
|
||||
|
||||
var commit ChannelCommitment
|
||||
if err := kvdb.View(c.Db.backend, func(tx kvdb.RTx) error {
|
||||
chanBucket, err := fetchChanBucket(
|
||||
tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash,
|
||||
@@ -2632,33 +2592,25 @@ func (c *OpenChannel) RevocationLogTail() (*ChannelCommitment, error) {
|
||||
return err
|
||||
}
|
||||
|
||||
logBucket := chanBucket.NestedReadBucket(revocationLogBucket)
|
||||
if logBucket == nil {
|
||||
return ErrNoPastDeltas
|
||||
logBucket, err := fetchLogBucket(chanBucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Once we have the bucket that stores the revocation log from
|
||||
// this channel, we'll jump to the _last_ key in bucket. As we
|
||||
// store the update number on disk in a big-endian format,
|
||||
// this will retrieve the latest entry.
|
||||
// this channel, we'll jump to the _last_ key in bucket. Since
|
||||
// the key is the commit height, we'll decode the bytes and
|
||||
// return it.
|
||||
cursor := logBucket.ReadCursor()
|
||||
_, tailLogEntry := cursor.Last()
|
||||
logEntryReader := bytes.NewReader(tailLogEntry)
|
||||
|
||||
// Once we have the entry, we'll decode it into the channel
|
||||
// delta pointer we created above.
|
||||
var dbErr error
|
||||
commit, dbErr = deserializeChanCommit(logEntryReader)
|
||||
if dbErr != nil {
|
||||
return dbErr
|
||||
}
|
||||
rawHeight, _ := cursor.Last()
|
||||
height = byteOrder.Uint64(rawHeight)
|
||||
|
||||
return nil
|
||||
}, func() {}); err != nil {
|
||||
return nil, err
|
||||
return height, err
|
||||
}
|
||||
|
||||
return &commit, nil
|
||||
return height, nil
|
||||
}
|
||||
|
||||
// CommitmentHeight returns the current commitment height. The commitment
|
||||
@@ -2703,11 +2655,15 @@ func (c *OpenChannel) CommitmentHeight() (uint64, error) {
|
||||
// intended to be used for obtaining the relevant data needed to claim all
|
||||
// funds rightfully spendable in the case of an on-chain broadcast of the
|
||||
// commitment transaction.
|
||||
func (c *OpenChannel) FindPreviousState(updateNum uint64) (*ChannelCommitment, error) {
|
||||
func (c *OpenChannel) FindPreviousState(
|
||||
updateNum uint64) (*RevocationLog, *ChannelCommitment, error) {
|
||||
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
var commit ChannelCommitment
|
||||
commit := &ChannelCommitment{}
|
||||
rl := &RevocationLog{}
|
||||
|
||||
err := kvdb.View(c.Db.backend, func(tx kvdb.RTx) error {
|
||||
chanBucket, err := fetchChanBucket(
|
||||
tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash,
|
||||
@@ -2716,24 +2672,24 @@ func (c *OpenChannel) FindPreviousState(updateNum uint64) (*ChannelCommitment, e
|
||||
return err
|
||||
}
|
||||
|
||||
logBucket := chanBucket.NestedReadBucket(revocationLogBucket)
|
||||
if logBucket == nil {
|
||||
return ErrNoPastDeltas
|
||||
}
|
||||
|
||||
c, err := fetchChannelLogEntry(logBucket, updateNum)
|
||||
// Find the revocation log from both the new and the old
|
||||
// bucket.
|
||||
r, c, err := fetchRevocationLogCompatible(chanBucket, updateNum)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rl = r
|
||||
commit = c
|
||||
return nil
|
||||
}, func() {})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return &commit, nil
|
||||
// Either the `rl` or the `commit` is nil here. We return them as-is
|
||||
// and leave it to the caller to decide its following action.
|
||||
return rl, commit, nil
|
||||
}
|
||||
|
||||
// ClosureType is an enum like structure that details exactly _how_ a channel
|
||||
@@ -2930,12 +2886,8 @@ func (c *OpenChannel) CloseChannel(summary *ChannelCloseSummary,
|
||||
|
||||
// With the base channel data deleted, attempt to delete the
|
||||
// information stored within the revocation log.
|
||||
logBucket := chanBucket.NestedReadWriteBucket(revocationLogBucket)
|
||||
if logBucket != nil {
|
||||
err = chanBucket.DeleteNestedBucket(revocationLogBucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := deleteLogBucket(chanBucket); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = chainBucket.DeleteNestedBucket(chanPointBuf.Bytes())
|
||||
@@ -3328,7 +3280,8 @@ func putChanInfo(chanBucket kvdb.RwBucket, channel *OpenChannel) error {
|
||||
channel.chanStatus, channel.FundingBroadcastHeight,
|
||||
channel.NumConfsRequired, channel.ChannelFlags,
|
||||
channel.IdentityPub, channel.Capacity, channel.TotalMSatSent,
|
||||
channel.TotalMSatReceived,
|
||||
channel.TotalMSatReceived, channel.InitialLocalBalance,
|
||||
channel.InitialRemoteBalance,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -3515,7 +3468,8 @@ func fetchChanInfo(chanBucket kvdb.RBucket, channel *OpenChannel) error {
|
||||
&channel.chanStatus, &channel.FundingBroadcastHeight,
|
||||
&channel.NumConfsRequired, &channel.ChannelFlags,
|
||||
&channel.IdentityPub, &channel.Capacity, &channel.TotalMSatSent,
|
||||
&channel.TotalMSatReceived,
|
||||
&channel.TotalMSatReceived, &channel.InitialLocalBalance,
|
||||
&channel.InitialRemoteBalance,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -3690,31 +3644,6 @@ func makeLogKey(updateNum uint64) [8]byte {
|
||||
return key
|
||||
}
|
||||
|
||||
func appendChannelLogEntry(log kvdb.RwBucket,
|
||||
commit *ChannelCommitment) error {
|
||||
|
||||
var b bytes.Buffer
|
||||
if err := serializeChanCommit(&b, commit); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logEntrykey := makeLogKey(commit.CommitHeight)
|
||||
return log.Put(logEntrykey[:], b.Bytes())
|
||||
}
|
||||
|
||||
func fetchChannelLogEntry(log kvdb.RBucket,
|
||||
updateNum uint64) (ChannelCommitment, error) {
|
||||
|
||||
logEntrykey := makeLogKey(updateNum)
|
||||
commitBytes := log.Get(logEntrykey[:])
|
||||
if commitBytes == nil {
|
||||
return ChannelCommitment{}, ErrLogEntryNotFound
|
||||
}
|
||||
|
||||
commitReader := bytes.NewReader(commitBytes)
|
||||
return deserializeChanCommit(commitReader)
|
||||
}
|
||||
|
||||
func fetchThawHeight(chanBucket kvdb.RBucket) (uint32, error) {
|
||||
var height uint32
|
||||
|
||||
|
||||
+74
-199
@@ -52,8 +52,17 @@ var (
|
||||
Port: 18555,
|
||||
}
|
||||
|
||||
// keyLocIndex is the KeyLocator Index we use for TestKeyLocatorEncoding.
|
||||
// keyLocIndex is the KeyLocator Index we use for
|
||||
// TestKeyLocatorEncoding.
|
||||
keyLocIndex = uint32(2049)
|
||||
|
||||
// dummyLocalOutputIndex specifics a default value for our output index
|
||||
// in this test.
|
||||
dummyLocalOutputIndex = uint32(0)
|
||||
|
||||
// dummyRemoteOutIndex specifics a default value for their output index
|
||||
// in this test.
|
||||
dummyRemoteOutIndex = uint32(1)
|
||||
)
|
||||
|
||||
// testChannelParams is a struct which details the specifics of how a channel
|
||||
@@ -78,25 +87,6 @@ type testChannelParams struct {
|
||||
// default channel that is creates for testing.
|
||||
type testChannelOption func(params *testChannelParams)
|
||||
|
||||
// channelCommitmentOption is an option which allows overwriting of the default
|
||||
// commitment height and balances. The local boolean can be used to set these
|
||||
// balances on the local or remote commit.
|
||||
func channelCommitmentOption(height uint64, localBalance,
|
||||
remoteBalance lnwire.MilliSatoshi, local bool) testChannelOption {
|
||||
|
||||
return func(params *testChannelParams) {
|
||||
if local {
|
||||
params.channel.LocalCommitment.CommitHeight = height
|
||||
params.channel.LocalCommitment.LocalBalance = localBalance
|
||||
params.channel.LocalCommitment.RemoteBalance = remoteBalance
|
||||
} else {
|
||||
params.channel.RemoteCommitment.CommitHeight = height
|
||||
params.channel.RemoteCommitment.LocalBalance = localBalance
|
||||
params.channel.RemoteCommitment.RemoteBalance = remoteBalance
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pendingHeightOption is an option which can be used to set the height the
|
||||
// channel is marked as pending at.
|
||||
func pendingHeightOption(height uint32) testChannelOption {
|
||||
@@ -352,6 +342,8 @@ func createTestChannelState(t *testing.T, cdb *ChannelStateDB) *OpenChannel {
|
||||
Packager: NewChannelPackager(chanID),
|
||||
FundingTxn: channels.TestFundingTx,
|
||||
ThawHeight: uint32(defaultPendingHeight),
|
||||
InitialLocalBalance: lnwire.MilliSatoshi(9000),
|
||||
InitialRemoteBalance: lnwire.MilliSatoshi(3000),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -565,6 +557,32 @@ func assertCommitmentEqual(t *testing.T, a, b *ChannelCommitment) {
|
||||
}
|
||||
}
|
||||
|
||||
// assertRevocationLogEntryEqual asserts that, for all the fields of a given
|
||||
// revocation log entry, their values match those on a given ChannelCommitment.
|
||||
func assertRevocationLogEntryEqual(t *testing.T, c *ChannelCommitment,
|
||||
r *RevocationLog) {
|
||||
|
||||
// Check the common fields.
|
||||
require.EqualValues(
|
||||
t, r.CommitTxHash, c.CommitTx.TxHash(), "CommitTx mismatch",
|
||||
)
|
||||
|
||||
// Now check the common fields from the HTLCs.
|
||||
require.Equal(t, len(r.HTLCEntries), len(c.Htlcs), "HTLCs len mismatch")
|
||||
for i, rHtlc := range r.HTLCEntries {
|
||||
cHtlc := c.Htlcs[i]
|
||||
require.Equal(t, rHtlc.RHash, cHtlc.RHash, "RHash mismatch")
|
||||
require.Equal(t, rHtlc.Amt, cHtlc.Amt.ToSatoshis(),
|
||||
"Amt mismatch")
|
||||
require.Equal(t, rHtlc.RefundTimeout, cHtlc.RefundTimeout,
|
||||
"RefundTimeout mismatch")
|
||||
require.EqualValues(t, rHtlc.OutputIndex, cHtlc.OutputIndex,
|
||||
"OutputIndex mismatch")
|
||||
require.Equal(t, rHtlc.Incoming, cHtlc.Incoming,
|
||||
"Incoming mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestChannelStateTransition(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -765,7 +783,9 @@ func TestChannelStateTransition(t *testing.T) {
|
||||
fwdPkg := NewFwdPkg(channel.ShortChanID(), oldRemoteCommit.CommitHeight,
|
||||
diskCommitDiff.LogUpdates, nil)
|
||||
|
||||
err = channel.AdvanceCommitChainTail(fwdPkg, nil)
|
||||
err = channel.AdvanceCommitChainTail(
|
||||
fwdPkg, nil, dummyLocalOutputIndex, dummyRemoteOutIndex,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to append to revocation log: %v", err)
|
||||
}
|
||||
@@ -778,24 +798,32 @@ func TestChannelStateTransition(t *testing.T) {
|
||||
|
||||
// We should be able to fetch the channel delta created above by its
|
||||
// update number with all the state properly reconstructed.
|
||||
diskPrevCommit, err := channel.FindPreviousState(
|
||||
diskPrevCommit, _, err := channel.FindPreviousState(
|
||||
oldRemoteCommit.CommitHeight,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to fetch past delta: %v", err)
|
||||
}
|
||||
|
||||
// Check the output indexes are saved as expected.
|
||||
require.EqualValues(
|
||||
t, dummyLocalOutputIndex, diskPrevCommit.OurOutputIndex,
|
||||
)
|
||||
require.EqualValues(
|
||||
t, dummyRemoteOutIndex, diskPrevCommit.TheirOutputIndex,
|
||||
)
|
||||
|
||||
// The two deltas (the original vs the on-disk version) should
|
||||
// identical, and all HTLC data should properly be retained.
|
||||
assertCommitmentEqual(t, &oldRemoteCommit, diskPrevCommit)
|
||||
assertRevocationLogEntryEqual(t, &oldRemoteCommit, diskPrevCommit)
|
||||
|
||||
// The state number recovered from the tail of the revocation log
|
||||
// should be identical to this current state.
|
||||
logTail, err := channel.RevocationLogTail()
|
||||
logTailHeight, err := channel.revocationLogTailCommitHeight()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to retrieve log: %v", err)
|
||||
}
|
||||
if logTail.CommitHeight != oldRemoteCommit.CommitHeight {
|
||||
if logTailHeight != oldRemoteCommit.CommitHeight {
|
||||
t.Fatal("update number doesn't match")
|
||||
}
|
||||
|
||||
@@ -813,25 +841,38 @@ func TestChannelStateTransition(t *testing.T) {
|
||||
|
||||
fwdPkg = NewFwdPkg(channel.ShortChanID(), oldRemoteCommit.CommitHeight, nil, nil)
|
||||
|
||||
err = channel.AdvanceCommitChainTail(fwdPkg, nil)
|
||||
err = channel.AdvanceCommitChainTail(
|
||||
fwdPkg, nil, dummyLocalOutputIndex, dummyRemoteOutIndex,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to append to revocation log: %v", err)
|
||||
}
|
||||
|
||||
// Once again, fetch the state and ensure it has been properly updated.
|
||||
prevCommit, err := channel.FindPreviousState(oldRemoteCommit.CommitHeight)
|
||||
prevCommit, _, err := channel.FindPreviousState(
|
||||
oldRemoteCommit.CommitHeight,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to fetch past delta: %v", err)
|
||||
}
|
||||
assertCommitmentEqual(t, &oldRemoteCommit, prevCommit)
|
||||
|
||||
// Check the output indexes are saved as expected.
|
||||
require.EqualValues(
|
||||
t, dummyLocalOutputIndex, diskPrevCommit.OurOutputIndex,
|
||||
)
|
||||
require.EqualValues(
|
||||
t, dummyRemoteOutIndex, diskPrevCommit.TheirOutputIndex,
|
||||
)
|
||||
|
||||
assertRevocationLogEntryEqual(t, &oldRemoteCommit, prevCommit)
|
||||
|
||||
// Once again, state number recovered from the tail of the revocation
|
||||
// log should be identical to this current state.
|
||||
logTail, err = channel.RevocationLogTail()
|
||||
logTailHeight, err = channel.revocationLogTailCommitHeight()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to retrieve log: %v", err)
|
||||
}
|
||||
if logTail.CommitHeight != oldRemoteCommit.CommitHeight {
|
||||
if logTailHeight != oldRemoteCommit.CommitHeight {
|
||||
t.Fatal("update number doesn't match")
|
||||
}
|
||||
|
||||
@@ -877,7 +918,9 @@ func TestChannelStateTransition(t *testing.T) {
|
||||
|
||||
// Attempting to find previous states on the channel should fail as the
|
||||
// revocation log has been deleted.
|
||||
_, err = updatedChannel[0].FindPreviousState(oldRemoteCommit.CommitHeight)
|
||||
_, _, err = updatedChannel[0].FindPreviousState(
|
||||
oldRemoteCommit.CommitHeight,
|
||||
)
|
||||
if err == nil {
|
||||
t.Fatal("revocation log search should have failed")
|
||||
}
|
||||
@@ -1413,174 +1456,6 @@ func TestCloseChannelStatus(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestBalanceAtHeight tests lookup of our local and remote balance at a given
|
||||
// height.
|
||||
func TestBalanceAtHeight(t *testing.T) {
|
||||
const (
|
||||
// Values that will be set on our current local commit in
|
||||
// memory.
|
||||
localHeight = 2
|
||||
localLocalBalance = 1000
|
||||
localRemoteBalance = 1500
|
||||
|
||||
// Values that will be set on our current remote commit in
|
||||
// memory.
|
||||
remoteHeight = 3
|
||||
remoteLocalBalance = 2000
|
||||
remoteRemoteBalance = 2500
|
||||
|
||||
// Values that will be written to disk in the revocation log.
|
||||
oldHeight = 0
|
||||
oldLocalBalance = 200
|
||||
oldRemoteBalance = 300
|
||||
|
||||
// Heights to test error cases.
|
||||
unknownHeight = 1
|
||||
unreachedHeight = 4
|
||||
)
|
||||
|
||||
// putRevokedState is a helper function used to put commitments is
|
||||
// the revocation log bucket to test lookup of balances at heights that
|
||||
// are not our current height.
|
||||
putRevokedState := func(c *OpenChannel, height uint64, local,
|
||||
remote lnwire.MilliSatoshi) error {
|
||||
|
||||
err := kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error {
|
||||
chanBucket, err := fetchChanBucketRw(
|
||||
tx, c.IdentityPub, &c.FundingOutpoint,
|
||||
c.ChainHash,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logKey := revocationLogBucket
|
||||
logBucket, err := chanBucket.CreateBucketIfNotExists(
|
||||
logKey,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Make a copy of our current commitment so we do not
|
||||
// need to re-fill all the required fields and copy in
|
||||
// our new desired values.
|
||||
commit := c.LocalCommitment
|
||||
commit.CommitHeight = height
|
||||
commit.LocalBalance = local
|
||||
commit.RemoteBalance = remote
|
||||
|
||||
return appendChannelLogEntry(logBucket, &commit)
|
||||
}, func() {})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
targetHeight uint64
|
||||
expectedLocalBalance lnwire.MilliSatoshi
|
||||
expectedRemoteBalance lnwire.MilliSatoshi
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "target is current local height",
|
||||
targetHeight: localHeight,
|
||||
expectedLocalBalance: localLocalBalance,
|
||||
expectedRemoteBalance: localRemoteBalance,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "target is current remote height",
|
||||
targetHeight: remoteHeight,
|
||||
expectedLocalBalance: remoteLocalBalance,
|
||||
expectedRemoteBalance: remoteRemoteBalance,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "need to lookup commit",
|
||||
targetHeight: oldHeight,
|
||||
expectedLocalBalance: oldLocalBalance,
|
||||
expectedRemoteBalance: oldRemoteBalance,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "height not found",
|
||||
targetHeight: unknownHeight,
|
||||
expectedLocalBalance: 0,
|
||||
expectedRemoteBalance: 0,
|
||||
expectedError: ErrLogEntryNotFound,
|
||||
},
|
||||
{
|
||||
name: "height not reached",
|
||||
targetHeight: unreachedHeight,
|
||||
expectedLocalBalance: 0,
|
||||
expectedRemoteBalance: 0,
|
||||
expectedError: errHeightNotReached,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fullDB, cleanUp, err := MakeTestDB()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to make test database: %v",
|
||||
err)
|
||||
}
|
||||
defer cleanUp()
|
||||
|
||||
cdb := fullDB.ChannelStateDB()
|
||||
|
||||
// Create options to set the heights and balances of
|
||||
// our local and remote commitments.
|
||||
localCommitOpt := channelCommitmentOption(
|
||||
localHeight, localLocalBalance,
|
||||
localRemoteBalance, true,
|
||||
)
|
||||
|
||||
remoteCommitOpt := channelCommitmentOption(
|
||||
remoteHeight, remoteLocalBalance,
|
||||
remoteRemoteBalance, false,
|
||||
)
|
||||
|
||||
// Create an open channel.
|
||||
channel := createTestChannel(
|
||||
t, cdb, openChannelOption(),
|
||||
localCommitOpt, remoteCommitOpt,
|
||||
)
|
||||
|
||||
// Write an older commit to disk.
|
||||
err = putRevokedState(channel, oldHeight,
|
||||
oldLocalBalance, oldRemoteBalance)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
local, remote, err := channel.BalancesAtHeight(
|
||||
test.targetHeight,
|
||||
)
|
||||
if err != test.expectedError {
|
||||
t.Fatalf("expected: %v, got: %v",
|
||||
test.expectedError, err)
|
||||
}
|
||||
|
||||
if local != test.expectedLocalBalance {
|
||||
t.Fatalf("expected local: %v, got: %v",
|
||||
test.expectedLocalBalance, local)
|
||||
}
|
||||
|
||||
if remote != test.expectedRemoteBalance {
|
||||
t.Fatalf("expected remote: %v, got: %v",
|
||||
test.expectedRemoteBalance, remote)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestHasChanStatus asserts the behavior of HasChanStatus by checking the
|
||||
// behavior of various status flags in addition to the special case of
|
||||
// ChanStatusDefault which is treated like a flag in the code base even though
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/lightningnetwork/lnd/channeldb/migration21"
|
||||
"github.com/lightningnetwork/lnd/channeldb/migration23"
|
||||
"github.com/lightningnetwork/lnd/channeldb/migration24"
|
||||
"github.com/lightningnetwork/lnd/channeldb/migration25"
|
||||
"github.com/lightningnetwork/lnd/channeldb/migration_01_to_11"
|
||||
"github.com/lightningnetwork/lnd/clock"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
@@ -205,6 +206,12 @@ var (
|
||||
number: 24,
|
||||
migration: migration24.MigrateFwdPkgCleanup,
|
||||
},
|
||||
{
|
||||
// Save the initial local/remote balances in channel
|
||||
// info.
|
||||
number: 25,
|
||||
migration: migration25.MigrateInitialBalances,
|
||||
},
|
||||
}
|
||||
|
||||
// Big endian is the preferred byte order, due to cursor scans over
|
||||
|
||||
@@ -409,7 +409,9 @@ func TestRestoreChannelShells(t *testing.T) {
|
||||
if err != ErrNoRestoredChannelMutation {
|
||||
t.Fatalf("able to mutate restored channel")
|
||||
}
|
||||
err = channel.AdvanceCommitChainTail(nil, nil)
|
||||
err = channel.AdvanceCommitChainTail(
|
||||
nil, nil, dummyLocalOutputIndex, dummyRemoteOutIndex,
|
||||
)
|
||||
if err != ErrNoRestoredChannelMutation {
|
||||
t.Fatalf("able to mutate restored channel")
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ func MigrateFwdPkgCleanup(tx kvdb.RwTx) error {
|
||||
|
||||
// Iterate over all close channels and remove their forwarding packages.
|
||||
for _, summery := range chanSummaries {
|
||||
sourceBytes := makeLogKey(summery.ShortChanID.ToUint64())
|
||||
sourceBytes := MakeLogKey(summery.ShortChanID.ToUint64())
|
||||
|
||||
// First, we will try to find the corresponding bucket. If there
|
||||
// is not a nested bucket matching the ShortChanID, we will skip
|
||||
@@ -112,7 +112,7 @@ func deserializeCloseChannelSummary(
|
||||
}
|
||||
|
||||
// makeLogKey converts a uint64 into an 8 byte array.
|
||||
func makeLogKey(updateNum uint64) [8]byte {
|
||||
func MakeLogKey(updateNum uint64) [8]byte {
|
||||
var key [8]byte
|
||||
binary.BigEndian.PutUint64(key[:], updateNum)
|
||||
return key
|
||||
|
||||
@@ -187,7 +187,7 @@ func genAfterMigration(deleted, untouched []int) func(kvdb.RwTx) error {
|
||||
// Reading deleted buckets should return nil
|
||||
for _, id := range deleted {
|
||||
chanID := lnwire.NewShortChanIDFromInt(uint64(id))
|
||||
sourceKey := makeLogKey(chanID.ToUint64())
|
||||
sourceKey := MakeLogKey(chanID.ToUint64())
|
||||
sourceBkt := fwdPkgBkt.NestedReadBucket(sourceKey[:])
|
||||
if sourceBkt != nil {
|
||||
return fmt.Errorf(
|
||||
@@ -200,7 +200,7 @@ func genAfterMigration(deleted, untouched []int) func(kvdb.RwTx) error {
|
||||
// Reading untouched buckets should return not nil
|
||||
for _, id := range untouched {
|
||||
chanID := lnwire.NewShortChanIDFromInt(uint64(id))
|
||||
sourceKey := makeLogKey(chanID.ToUint64())
|
||||
sourceKey := MakeLogKey(chanID.ToUint64())
|
||||
sourceBkt := fwdPkgBkt.NestedReadBucket(sourceKey[:])
|
||||
if sourceBkt == nil {
|
||||
return fmt.Errorf(
|
||||
@@ -259,7 +259,7 @@ func createTestFwdPkgBucket(tx kvdb.RwTx, chanID lnwire.ShortChannelID) error {
|
||||
return err
|
||||
}
|
||||
|
||||
source := makeLogKey(chanID.ToUint64())
|
||||
source := MakeLogKey(chanID.ToUint64())
|
||||
if _, err := fwdPkgBkt.CreateBucketIfNotExists(source[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,722 @@
|
||||
package migration25
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
|
||||
mig24 "github.com/lightningnetwork/lnd/channeldb/migration24"
|
||||
mig "github.com/lightningnetwork/lnd/channeldb/migration_01_to_11"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
)
|
||||
|
||||
const (
|
||||
// A tlv type definition used to serialize and deserialize a KeyLocator
|
||||
// from the database.
|
||||
keyLocType tlv.Type = 1
|
||||
)
|
||||
|
||||
var (
|
||||
// chanCommitmentKey can be accessed within the sub-bucket for a
|
||||
// particular channel. This key stores the up to date commitment state
|
||||
// for a particular channel party. Appending a 0 to the end of this key
|
||||
// indicates it's the commitment for the local party, and appending a 1
|
||||
// to the end of this key indicates it's the commitment for the remote
|
||||
// party.
|
||||
chanCommitmentKey = []byte("chan-commitment-key")
|
||||
|
||||
// revocationLogBucketLegacy is the legacy bucket where we store the
|
||||
// revocation log in old format.
|
||||
revocationLogBucketLegacy = []byte("revocation-log-key")
|
||||
|
||||
// localUpfrontShutdownKey can be accessed within the bucket for a
|
||||
// channel (identified by its chanPoint). This key stores an optional
|
||||
// upfront shutdown script for the local peer.
|
||||
localUpfrontShutdownKey = []byte("local-upfront-shutdown-key")
|
||||
|
||||
// remoteUpfrontShutdownKey can be accessed within the bucket for a
|
||||
// channel (identified by its chanPoint). This key stores an optional
|
||||
// upfront shutdown script for the remote peer.
|
||||
remoteUpfrontShutdownKey = []byte("remote-upfront-shutdown-key")
|
||||
|
||||
// lastWasRevokeKey is a key that stores true when the last update we
|
||||
// sent was a revocation and false when it was a commitment signature.
|
||||
// This is nil in the case of new channels with no updates exchanged.
|
||||
lastWasRevokeKey = []byte("last-was-revoke")
|
||||
|
||||
// ErrNoChanInfoFound is returned when a particular channel does not
|
||||
// have any channels state.
|
||||
ErrNoChanInfoFound = fmt.Errorf("no chan info found")
|
||||
|
||||
// ErrNoPastDeltas is returned when the channel delta bucket hasn't been
|
||||
// created.
|
||||
ErrNoPastDeltas = fmt.Errorf("channel has no recorded deltas")
|
||||
|
||||
// ErrLogEntryNotFound is returned when we cannot find a log entry at
|
||||
// the height requested in the revocation log.
|
||||
ErrLogEntryNotFound = fmt.Errorf("log entry not found")
|
||||
|
||||
// ErrNoCommitmentsFound is returned when a channel has not set
|
||||
// commitment states.
|
||||
ErrNoCommitmentsFound = fmt.Errorf("no commitments found")
|
||||
)
|
||||
|
||||
// ChannelType is an enum-like type that describes one of several possible
|
||||
// channel types. Each open channel is associated with a particular type as the
|
||||
// channel type may determine how higher level operations are conducted such as
|
||||
// fee negotiation, channel closing, the format of HTLCs, etc. Structure-wise,
|
||||
// a ChannelType is a bit field, with each bit denoting a modification from the
|
||||
// base channel type of single funder.
|
||||
type ChannelType uint8
|
||||
|
||||
const (
|
||||
// NOTE: iota isn't used here for this enum needs to be stable
|
||||
// long-term as it will be persisted to the database.
|
||||
|
||||
// SingleFunderBit represents a channel wherein one party solely funds
|
||||
// the entire capacity of the channel.
|
||||
SingleFunderBit ChannelType = 0
|
||||
|
||||
// DualFunderBit represents a channel wherein both parties contribute
|
||||
// funds towards the total capacity of the channel. The channel may be
|
||||
// funded symmetrically or asymmetrically.
|
||||
DualFunderBit ChannelType = 1 << 0
|
||||
|
||||
// SingleFunderTweaklessBit is similar to the basic SingleFunder channel
|
||||
// type, but it omits the tweak for one's key in the commitment
|
||||
// transaction of the remote party.
|
||||
SingleFunderTweaklessBit ChannelType = 1 << 1
|
||||
|
||||
// NoFundingTxBit denotes if we have the funding transaction locally on
|
||||
// disk. This bit may be on if the funding transaction was crafted by a
|
||||
// wallet external to the primary daemon.
|
||||
NoFundingTxBit ChannelType = 1 << 2
|
||||
|
||||
// AnchorOutputsBit indicates that the channel makes use of anchor
|
||||
// outputs to bump the commitment transaction's effective feerate. This
|
||||
// channel type also uses a delayed to_remote output script.
|
||||
AnchorOutputsBit ChannelType = 1 << 3
|
||||
|
||||
// FrozenBit indicates that the channel is a frozen channel, meaning
|
||||
// that only the responder can decide to cooperatively close the
|
||||
// channel.
|
||||
FrozenBit ChannelType = 1 << 4
|
||||
|
||||
// ZeroHtlcTxFeeBit indicates that the channel should use zero-fee
|
||||
// second-level HTLC transactions.
|
||||
ZeroHtlcTxFeeBit ChannelType = 1 << 5
|
||||
|
||||
// LeaseExpirationBit indicates that the channel has been leased for a
|
||||
// period of time, constraining every output that pays to the channel
|
||||
// initiator with an additional CLTV of the lease maturity.
|
||||
LeaseExpirationBit ChannelType = 1 << 6
|
||||
)
|
||||
|
||||
// IsSingleFunder returns true if the channel type if one of the known single
|
||||
// funder variants.
|
||||
func (c ChannelType) IsSingleFunder() bool {
|
||||
return c&DualFunderBit == 0
|
||||
}
|
||||
|
||||
// IsDualFunder returns true if the ChannelType has the DualFunderBit set.
|
||||
func (c ChannelType) IsDualFunder() bool {
|
||||
return c&DualFunderBit == DualFunderBit
|
||||
}
|
||||
|
||||
// IsTweakless returns true if the target channel uses a commitment that
|
||||
// doesn't tweak the key for the remote party.
|
||||
func (c ChannelType) IsTweakless() bool {
|
||||
return c&SingleFunderTweaklessBit == SingleFunderTweaklessBit
|
||||
}
|
||||
|
||||
// HasFundingTx returns true if this channel type is one that has a funding
|
||||
// transaction stored locally.
|
||||
func (c ChannelType) HasFundingTx() bool {
|
||||
return c&NoFundingTxBit == 0
|
||||
}
|
||||
|
||||
// HasAnchors returns true if this channel type has anchor outputs on its
|
||||
// commitment.
|
||||
func (c ChannelType) HasAnchors() bool {
|
||||
return c&AnchorOutputsBit == AnchorOutputsBit
|
||||
}
|
||||
|
||||
// ZeroHtlcTxFee returns true if this channel type uses second-level HTLC
|
||||
// transactions signed with zero-fee.
|
||||
func (c ChannelType) ZeroHtlcTxFee() bool {
|
||||
return c&ZeroHtlcTxFeeBit == ZeroHtlcTxFeeBit
|
||||
}
|
||||
|
||||
// IsFrozen returns true if the channel is considered to be "frozen". A frozen
|
||||
// channel means that only the responder can initiate a cooperative channel
|
||||
// closure.
|
||||
func (c ChannelType) IsFrozen() bool {
|
||||
return c&FrozenBit == FrozenBit
|
||||
}
|
||||
|
||||
// HasLeaseExpiration returns true if the channel originated from a lease.
|
||||
func (c ChannelType) HasLeaseExpiration() bool {
|
||||
return c&LeaseExpirationBit == LeaseExpirationBit
|
||||
}
|
||||
|
||||
// ChannelStatus is a bit vector used to indicate whether an OpenChannel is in
|
||||
// the default usable state, or a state where it shouldn't be used.
|
||||
type ChannelStatus uint8
|
||||
|
||||
var (
|
||||
// ChanStatusDefault is the normal state of an open channel.
|
||||
ChanStatusDefault ChannelStatus
|
||||
|
||||
// ChanStatusBorked indicates that the channel has entered an
|
||||
// irreconcilable state, triggered by a state desynchronization or
|
||||
// channel breach. Channels in this state should never be added to the
|
||||
// htlc switch.
|
||||
ChanStatusBorked ChannelStatus = 1
|
||||
|
||||
// ChanStatusCommitBroadcasted indicates that a commitment for this
|
||||
// channel has been broadcasted.
|
||||
ChanStatusCommitBroadcasted ChannelStatus = 1 << 1
|
||||
|
||||
// ChanStatusLocalDataLoss indicates that we have lost channel state
|
||||
// for this channel, and broadcasting our latest commitment might be
|
||||
// considered a breach.
|
||||
//
|
||||
// TODO(halseh): actually enforce that we are not force closing such a
|
||||
// channel.
|
||||
ChanStatusLocalDataLoss ChannelStatus = 1 << 2
|
||||
|
||||
// ChanStatusRestored is a status flag that signals that the channel
|
||||
// has been restored, and doesn't have all the fields a typical channel
|
||||
// will have.
|
||||
ChanStatusRestored ChannelStatus = 1 << 3
|
||||
|
||||
// ChanStatusCoopBroadcasted indicates that a cooperative close for
|
||||
// this channel has been broadcasted. Older cooperatively closed
|
||||
// channels will only have this status set. Newer ones will also have
|
||||
// close initiator information stored using the local/remote initiator
|
||||
// status. This status is set in conjunction with the initiator status
|
||||
// so that we do not need to check multiple channel statues for
|
||||
// cooperative closes.
|
||||
ChanStatusCoopBroadcasted ChannelStatus = 1 << 4
|
||||
|
||||
// ChanStatusLocalCloseInitiator indicates that we initiated closing
|
||||
// the channel.
|
||||
ChanStatusLocalCloseInitiator ChannelStatus = 1 << 5
|
||||
|
||||
// ChanStatusRemoteCloseInitiator indicates that the remote node
|
||||
// initiated closing the channel.
|
||||
ChanStatusRemoteCloseInitiator ChannelStatus = 1 << 6
|
||||
)
|
||||
|
||||
// chanStatusStrings maps a ChannelStatus to a human friendly string that
|
||||
// describes that status.
|
||||
var chanStatusStrings = map[ChannelStatus]string{
|
||||
ChanStatusDefault: "ChanStatusDefault",
|
||||
ChanStatusBorked: "ChanStatusBorked",
|
||||
ChanStatusCommitBroadcasted: "ChanStatusCommitBroadcasted",
|
||||
ChanStatusLocalDataLoss: "ChanStatusLocalDataLoss",
|
||||
ChanStatusRestored: "ChanStatusRestored",
|
||||
ChanStatusCoopBroadcasted: "ChanStatusCoopBroadcasted",
|
||||
ChanStatusLocalCloseInitiator: "ChanStatusLocalCloseInitiator",
|
||||
ChanStatusRemoteCloseInitiator: "ChanStatusRemoteCloseInitiator",
|
||||
}
|
||||
|
||||
// orderedChanStatusFlags is an in-order list of all that channel status flags.
|
||||
var orderedChanStatusFlags = []ChannelStatus{
|
||||
ChanStatusBorked,
|
||||
ChanStatusCommitBroadcasted,
|
||||
ChanStatusLocalDataLoss,
|
||||
ChanStatusRestored,
|
||||
ChanStatusCoopBroadcasted,
|
||||
ChanStatusLocalCloseInitiator,
|
||||
ChanStatusRemoteCloseInitiator,
|
||||
}
|
||||
|
||||
// String returns a human-readable representation of the ChannelStatus.
|
||||
func (c ChannelStatus) String() string {
|
||||
// If no flags are set, then this is the default case.
|
||||
if c == ChanStatusDefault {
|
||||
return chanStatusStrings[ChanStatusDefault]
|
||||
}
|
||||
|
||||
// Add individual bit flags.
|
||||
statusStr := ""
|
||||
for _, flag := range orderedChanStatusFlags {
|
||||
if c&flag == flag {
|
||||
statusStr += chanStatusStrings[flag] + "|"
|
||||
c -= flag
|
||||
}
|
||||
}
|
||||
|
||||
// Remove anything to the right of the final bar, including it as well.
|
||||
statusStr = strings.TrimRight(statusStr, "|")
|
||||
|
||||
// Add any remaining flags which aren't accounted for as hex.
|
||||
if c != 0 {
|
||||
statusStr += "|0x" + strconv.FormatUint(uint64(c), 16)
|
||||
}
|
||||
|
||||
// If this was purely an unknown flag, then remove the extra bar at the
|
||||
// start of the string.
|
||||
statusStr = strings.TrimLeft(statusStr, "|")
|
||||
|
||||
return statusStr
|
||||
}
|
||||
|
||||
// OpenChannel embeds a mig.OpenChannel with the extra update-to-date fields.
|
||||
//
|
||||
// NOTE: doesn't have the Packager field as it's not used in current migration.
|
||||
type OpenChannel struct {
|
||||
mig.OpenChannel
|
||||
|
||||
// ChanType denotes which type of channel this is.
|
||||
ChanType ChannelType
|
||||
|
||||
// chanStatus is the current status of this channel. If it is not in
|
||||
// the state Default, it should not be used for forwarding payments.
|
||||
chanStatus ChannelStatus
|
||||
|
||||
// InitialLocalBalance is the balance we have during the channel
|
||||
// opening. When we are not the initiator, this value represents the
|
||||
// push amount.
|
||||
InitialLocalBalance lnwire.MilliSatoshi
|
||||
|
||||
// InitialRemoteBalance is the balance they have during the channel
|
||||
// opening.
|
||||
InitialRemoteBalance lnwire.MilliSatoshi
|
||||
|
||||
// LocalShutdownScript is set to a pre-set script if the channel was
|
||||
// opened by the local node with option_upfront_shutdown_script set. If
|
||||
// the option was not set, the field is empty.
|
||||
LocalShutdownScript lnwire.DeliveryAddress
|
||||
|
||||
// RemoteShutdownScript is set to a pre-set script if the channel was
|
||||
// opened by the remote node with option_upfront_shutdown_script set.
|
||||
// If the option was not set, the field is empty.
|
||||
RemoteShutdownScript lnwire.DeliveryAddress
|
||||
|
||||
// ThawHeight is the height when a frozen channel once again becomes a
|
||||
// normal channel. If this is zero, then there're no restrictions on
|
||||
// this channel. If the value is lower than 500,000, then it's
|
||||
// interpreted as a relative height, or an absolute height otherwise.
|
||||
ThawHeight uint32
|
||||
|
||||
// LastWasRevoke is a boolean that determines if the last update we
|
||||
// sent was a revocation (true) or a commitment signature (false).
|
||||
LastWasRevoke bool
|
||||
|
||||
// RevocationKeyLocator stores the KeyLocator information that we will
|
||||
// need to derive the shachain root for this channel. This allows us to
|
||||
// have private key isolation from lnd.
|
||||
RevocationKeyLocator keychain.KeyLocator
|
||||
}
|
||||
|
||||
func (c *OpenChannel) hasChanStatus(status ChannelStatus) bool {
|
||||
// Special case ChanStatusDefualt since it isn't actually flag, but a
|
||||
// particular combination (or lack-there-of) of flags.
|
||||
if status == ChanStatusDefault {
|
||||
return c.chanStatus == ChanStatusDefault
|
||||
}
|
||||
|
||||
return c.chanStatus&status == status
|
||||
}
|
||||
|
||||
// FundingTxPresent returns true if expect the funding transcation to be found
|
||||
// on disk or already populated within the passed open channel struct.
|
||||
func (c *OpenChannel) FundingTxPresent() bool {
|
||||
chanType := c.ChanType
|
||||
|
||||
return chanType.IsSingleFunder() && chanType.HasFundingTx() &&
|
||||
c.IsInitiator &&
|
||||
!c.hasChanStatus(ChanStatusRestored)
|
||||
}
|
||||
|
||||
// fetchChanInfo deserializes the channel info based on the legacy boolean.
|
||||
func fetchChanInfo(chanBucket kvdb.RBucket, c *OpenChannel, legacy bool) error {
|
||||
infoBytes := chanBucket.Get(chanInfoKey)
|
||||
if infoBytes == nil {
|
||||
return ErrNoChanInfoFound
|
||||
}
|
||||
r := bytes.NewReader(infoBytes)
|
||||
|
||||
var (
|
||||
chanType mig.ChannelType
|
||||
chanStatus mig.ChannelStatus
|
||||
)
|
||||
|
||||
if err := mig.ReadElements(r,
|
||||
&chanType, &c.ChainHash, &c.FundingOutpoint,
|
||||
&c.ShortChannelID, &c.IsPending, &c.IsInitiator,
|
||||
&chanStatus, &c.FundingBroadcastHeight,
|
||||
&c.NumConfsRequired, &c.ChannelFlags,
|
||||
&c.IdentityPub, &c.Capacity, &c.TotalMSatSent,
|
||||
&c.TotalMSatReceived,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.ChanType = ChannelType(chanType)
|
||||
c.chanStatus = ChannelStatus(chanStatus)
|
||||
|
||||
// If this is not the legacy format, we need to read the extra two new
|
||||
// fields.
|
||||
if !legacy {
|
||||
if err := mig.ReadElements(r,
|
||||
&c.InitialLocalBalance, &c.InitialRemoteBalance,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// For single funder channels that we initiated and have the funding
|
||||
// transaction to, read the funding txn.
|
||||
if c.FundingTxPresent() {
|
||||
if err := mig.ReadElement(r, &c.FundingTxn); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := mig.ReadChanConfig(r, &c.LocalChanCfg); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := mig.ReadChanConfig(r, &c.RemoteChanCfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Retrieve the boolean stored under lastWasRevokeKey.
|
||||
lastWasRevokeBytes := chanBucket.Get(lastWasRevokeKey)
|
||||
if lastWasRevokeBytes == nil {
|
||||
// If nothing has been stored under this key, we store false in
|
||||
// the OpenChannel struct.
|
||||
c.LastWasRevoke = false
|
||||
} else {
|
||||
// Otherwise, read the value into the LastWasRevoke field.
|
||||
revokeReader := bytes.NewReader(lastWasRevokeBytes)
|
||||
err := mig.ReadElements(revokeReader, &c.LastWasRevoke)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
keyLocRecord := MakeKeyLocRecord(keyLocType, &c.RevocationKeyLocator)
|
||||
tlvStream, err := tlv.NewStream(keyLocRecord)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tlvStream.Decode(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Finally, read the optional shutdown scripts.
|
||||
if err := GetOptionalUpfrontShutdownScript(
|
||||
chanBucket, localUpfrontShutdownKey, &c.LocalShutdownScript,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return GetOptionalUpfrontShutdownScript(
|
||||
chanBucket, remoteUpfrontShutdownKey, &c.RemoteShutdownScript,
|
||||
)
|
||||
}
|
||||
|
||||
// fetchChanInfo serializes the channel info based on the legacy boolean and
|
||||
// saves it to disk.
|
||||
func putChanInfo(chanBucket kvdb.RwBucket, c *OpenChannel, legacy bool) error {
|
||||
var w bytes.Buffer
|
||||
if err := mig.WriteElements(&w,
|
||||
mig.ChannelType(c.ChanType), c.ChainHash, c.FundingOutpoint,
|
||||
c.ShortChannelID, c.IsPending, c.IsInitiator,
|
||||
mig.ChannelStatus(c.chanStatus), c.FundingBroadcastHeight,
|
||||
c.NumConfsRequired, c.ChannelFlags,
|
||||
c.IdentityPub, c.Capacity, c.TotalMSatSent,
|
||||
c.TotalMSatReceived,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If this is not legacy format, we need to write the extra two fields.
|
||||
if !legacy {
|
||||
if err := mig.WriteElements(&w,
|
||||
c.InitialLocalBalance, c.InitialRemoteBalance,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// For single funder channels that we initiated, and we have the
|
||||
// funding transaction, then write the funding txn.
|
||||
if c.FundingTxPresent() {
|
||||
if err := mig.WriteElement(&w, c.FundingTxn); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := mig.WriteChanConfig(&w, &c.LocalChanCfg); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := mig.WriteChanConfig(&w, &c.RemoteChanCfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write the RevocationKeyLocator as the first entry in a tlv stream.
|
||||
keyLocRecord := MakeKeyLocRecord(
|
||||
keyLocType, &c.RevocationKeyLocator,
|
||||
)
|
||||
|
||||
tlvStream, err := tlv.NewStream(keyLocRecord)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tlvStream.Encode(&w); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := chanBucket.Put(chanInfoKey, w.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Finally, add optional shutdown scripts for the local and remote peer
|
||||
// if they are present.
|
||||
if err := PutOptionalUpfrontShutdownScript(
|
||||
chanBucket, localUpfrontShutdownKey, c.LocalShutdownScript,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return PutOptionalUpfrontShutdownScript(
|
||||
chanBucket, remoteUpfrontShutdownKey, c.RemoteShutdownScript,
|
||||
)
|
||||
}
|
||||
|
||||
// EKeyLocator is an encoder for keychain.KeyLocator.
|
||||
func EKeyLocator(w io.Writer, val interface{}, buf *[8]byte) error {
|
||||
if v, ok := val.(*keychain.KeyLocator); ok {
|
||||
err := tlv.EUint32T(w, uint32(v.Family), buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tlv.EUint32T(w, v.Index, buf)
|
||||
}
|
||||
return tlv.NewTypeForEncodingErr(val, "keychain.KeyLocator")
|
||||
}
|
||||
|
||||
// DKeyLocator is a decoder for keychain.KeyLocator.
|
||||
func DKeyLocator(r io.Reader, val interface{}, buf *[8]byte, l uint64) error {
|
||||
if v, ok := val.(*keychain.KeyLocator); ok {
|
||||
var family uint32
|
||||
err := tlv.DUint32(r, &family, buf, 4)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Family = keychain.KeyFamily(family)
|
||||
|
||||
return tlv.DUint32(r, &v.Index, buf, 4)
|
||||
}
|
||||
return tlv.NewTypeForDecodingErr(val, "keychain.KeyLocator", l, 8)
|
||||
}
|
||||
|
||||
// MakeKeyLocRecord creates a Record out of a KeyLocator using the passed
|
||||
// Type and the EKeyLocator and DKeyLocator functions. The size will always be
|
||||
// 8 as KeyFamily is uint32 and the Index is uint32.
|
||||
func MakeKeyLocRecord(typ tlv.Type, keyLoc *keychain.KeyLocator) tlv.Record {
|
||||
return tlv.MakeStaticRecord(typ, keyLoc, 8, EKeyLocator, DKeyLocator)
|
||||
}
|
||||
|
||||
// PutOptionalUpfrontShutdownScript adds a shutdown script under the key
|
||||
// provided if it has a non-zero length.
|
||||
func PutOptionalUpfrontShutdownScript(chanBucket kvdb.RwBucket, key []byte,
|
||||
script []byte) error {
|
||||
// If the script is empty, we do not need to add anything.
|
||||
if len(script) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var w bytes.Buffer
|
||||
if err := mig.WriteElement(&w, script); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return chanBucket.Put(key, w.Bytes())
|
||||
}
|
||||
|
||||
// GetOptionalUpfrontShutdownScript reads the shutdown script stored under the
|
||||
// key provided if it is present. Upfront shutdown scripts are optional, so the
|
||||
// function returns with no error if the key is not present.
|
||||
func GetOptionalUpfrontShutdownScript(chanBucket kvdb.RBucket, key []byte,
|
||||
script *lnwire.DeliveryAddress) error {
|
||||
|
||||
// Return early if the bucket does not exit, a shutdown script was not
|
||||
// set.
|
||||
bs := chanBucket.Get(key)
|
||||
if bs == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var tempScript []byte
|
||||
r := bytes.NewReader(bs)
|
||||
if err := mig.ReadElement(r, &tempScript); err != nil {
|
||||
return err
|
||||
}
|
||||
*script = tempScript
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FetchChanCommitments fetches both the local and remote commitments. This
|
||||
// function is exported so it can be used by later migrations.
|
||||
func FetchChanCommitments(chanBucket kvdb.RBucket, channel *OpenChannel) error {
|
||||
var err error
|
||||
|
||||
// If this is a restored channel, then we don't have any commitments to
|
||||
// read.
|
||||
if channel.hasChanStatus(ChanStatusRestored) {
|
||||
return nil
|
||||
}
|
||||
|
||||
channel.LocalCommitment, err = FetchChanCommitment(chanBucket, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
channel.RemoteCommitment, err = FetchChanCommitment(chanBucket, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FetchChanCommitment fetches a channel commitment. This function is exported
|
||||
// so it can be used by later migrations.
|
||||
func FetchChanCommitment(chanBucket kvdb.RBucket,
|
||||
local bool) (mig.ChannelCommitment, error) {
|
||||
|
||||
commitKey := chanCommitmentKey
|
||||
if local {
|
||||
commitKey = append(commitKey, byte(0x00))
|
||||
} else {
|
||||
commitKey = append(commitKey, byte(0x01))
|
||||
}
|
||||
|
||||
commitBytes := chanBucket.Get(commitKey)
|
||||
if commitBytes == nil {
|
||||
return mig.ChannelCommitment{}, ErrNoCommitmentsFound
|
||||
}
|
||||
|
||||
r := bytes.NewReader(commitBytes)
|
||||
return mig.DeserializeChanCommit(r)
|
||||
}
|
||||
|
||||
func PutChanCommitment(chanBucket kvdb.RwBucket, c *mig.ChannelCommitment,
|
||||
local bool) error {
|
||||
|
||||
commitKey := chanCommitmentKey
|
||||
if local {
|
||||
commitKey = append(commitKey, byte(0x00))
|
||||
} else {
|
||||
commitKey = append(commitKey, byte(0x01))
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
if err := mig.SerializeChanCommit(&b, c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return chanBucket.Put(commitKey, b.Bytes())
|
||||
}
|
||||
|
||||
func PutChanCommitments(chanBucket kvdb.RwBucket, channel *OpenChannel) error {
|
||||
// If this is a restored channel, then we don't have any commitments to
|
||||
// write.
|
||||
if channel.hasChanStatus(ChanStatusRestored) {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := PutChanCommitment(
|
||||
chanBucket, &channel.LocalCommitment, true,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return PutChanCommitment(
|
||||
chanBucket, &channel.RemoteCommitment, false,
|
||||
)
|
||||
}
|
||||
|
||||
// balancesAtHeight returns the local and remote balances on our commitment
|
||||
// transactions as of a given height. This function is not exported as it's
|
||||
// deprecated.
|
||||
//
|
||||
// NOTE: these are our balances *after* subtracting the commitment fee and
|
||||
// anchor outputs.
|
||||
func (c *OpenChannel) balancesAtHeight(chanBucket kvdb.RBucket,
|
||||
height uint64) (lnwire.MilliSatoshi, lnwire.MilliSatoshi, error) {
|
||||
|
||||
// If our current commit is as the desired height, we can return our
|
||||
// current balances.
|
||||
if c.LocalCommitment.CommitHeight == height {
|
||||
return c.LocalCommitment.LocalBalance,
|
||||
c.LocalCommitment.RemoteBalance, nil
|
||||
}
|
||||
|
||||
// If our current remote commit is at the desired height, we can return
|
||||
// the current balances.
|
||||
if c.RemoteCommitment.CommitHeight == height {
|
||||
return c.RemoteCommitment.LocalBalance,
|
||||
c.RemoteCommitment.RemoteBalance, nil
|
||||
}
|
||||
|
||||
// If we are not currently on the height requested, we need to look up
|
||||
// the previous height to obtain our balances at the given height.
|
||||
commit, err := c.FindPreviousStateLegacy(chanBucket, height)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
return commit.LocalBalance, commit.RemoteBalance, nil
|
||||
}
|
||||
|
||||
// FindPreviousStateLegacy scans through the append-only log in an attempt to
|
||||
// recover the previous channel state indicated by the update number. This
|
||||
// method is intended to be used for obtaining the relevant data needed to
|
||||
// claim all funds rightfully spendable in the case of an on-chain broadcast of
|
||||
// the commitment transaction.
|
||||
func (c *OpenChannel) FindPreviousStateLegacy(chanBucket kvdb.RBucket,
|
||||
updateNum uint64) (*mig.ChannelCommitment, error) {
|
||||
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
logBucket := chanBucket.NestedReadBucket(revocationLogBucketLegacy)
|
||||
if logBucket == nil {
|
||||
return nil, ErrNoPastDeltas
|
||||
}
|
||||
|
||||
commit, err := fetchChannelLogEntry(logBucket, updateNum)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &commit, nil
|
||||
}
|
||||
|
||||
func fetchChannelLogEntry(log kvdb.RBucket,
|
||||
updateNum uint64) (mig.ChannelCommitment, error) {
|
||||
|
||||
logEntrykey := mig24.MakeLogKey(updateNum)
|
||||
commitBytes := log.Get(logEntrykey[:])
|
||||
if commitBytes == nil {
|
||||
return mig.ChannelCommitment{}, ErrLogEntryNotFound
|
||||
}
|
||||
|
||||
commitReader := bytes.NewReader(commitBytes)
|
||||
return mig.DeserializeChanCommit(commitReader)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package migration25
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btclog"
|
||||
)
|
||||
|
||||
// log is a logger that is initialized as disabled. This means the package will
|
||||
// not perform any logging by default until a logger is set.
|
||||
var log = btclog.Disabled
|
||||
|
||||
// UseLogger uses a specified Logger to output package logging info.
|
||||
func UseLogger(logger btclog.Logger) {
|
||||
log = logger
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
package migration25
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
mig "github.com/lightningnetwork/lnd/channeldb/migration_01_to_11"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
)
|
||||
|
||||
var (
|
||||
// openChanBucket stores all the currently open channels. This bucket
|
||||
// has a second, nested bucket which is keyed by a node's ID. Within
|
||||
// that node ID bucket, all attributes required to track, update, and
|
||||
// close a channel are stored.
|
||||
openChannelBucket = []byte("open-chan-bucket")
|
||||
|
||||
// chanInfoKey can be accessed within the bucket for a channel
|
||||
// (identified by its chanPoint). This key stores all the static
|
||||
// information for a channel which is decided at the end of the
|
||||
// funding flow.
|
||||
chanInfoKey = []byte("chan-info-key")
|
||||
|
||||
// ErrNoChanDBExists is returned when a channel bucket hasn't been
|
||||
// created.
|
||||
ErrNoChanDBExists = fmt.Errorf("channel db has not yet been created")
|
||||
|
||||
// ErrNoActiveChannels is returned when there is no active (open)
|
||||
// channels within the database.
|
||||
ErrNoActiveChannels = fmt.Errorf("no active channels exist")
|
||||
|
||||
// ErrChannelNotFound is returned when we attempt to locate a channel
|
||||
// for a specific chain, but it is not found.
|
||||
ErrChannelNotFound = fmt.Errorf("channel not found")
|
||||
)
|
||||
|
||||
// MigrateInitialBalances patches the two new fields, InitialLocalBalance and
|
||||
// InitialRemoteBalance, for all the open channels. It does so by reading the
|
||||
// revocation log at height 0 to learn the initial balances and then updates
|
||||
// the channel's info.
|
||||
// The channel info is saved in the nested bucket which is accessible via
|
||||
// nodePub:chainHash:chanPoint. If any of the sub-buckets turns out to be nil,
|
||||
// we will log the error and continue to process the rest.
|
||||
func MigrateInitialBalances(tx kvdb.RwTx) error {
|
||||
log.Infof("Migrating initial local and remote balances...")
|
||||
|
||||
openChanBucket := tx.ReadWriteBucket(openChannelBucket)
|
||||
|
||||
// If no bucket is found, we can exit early.
|
||||
if openChanBucket == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read a list of open channels.
|
||||
channels, err := findOpenChannels(openChanBucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Migrate the balances.
|
||||
for _, c := range channels {
|
||||
if err := migrateBalances(tx, c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// findOpenChannels finds all open channels.
|
||||
func findOpenChannels(openChanBucket kvdb.RBucket) ([]*OpenChannel, error) {
|
||||
channels := []*OpenChannel{}
|
||||
|
||||
// readChannel is a helper closure that reads the channel info from the
|
||||
// channel bucket.
|
||||
readChannel := func(chainBucket kvdb.RBucket, cp []byte) error {
|
||||
c := &OpenChannel{}
|
||||
|
||||
// Read the sub-bucket level 3.
|
||||
chanBucket := chainBucket.NestedReadBucket(
|
||||
cp,
|
||||
)
|
||||
if chanBucket == nil {
|
||||
log.Errorf("unable to read bucket for chanPoint=%x", cp)
|
||||
return nil
|
||||
}
|
||||
// Get the old channel info.
|
||||
if err := fetchChanInfo(chanBucket, c, true); err != nil {
|
||||
return fmt.Errorf("unable to fetch chan info: %v", err)
|
||||
}
|
||||
|
||||
// Fetch the channel commitments, which are useful for freshly
|
||||
// open channels as they don't have any revocation logs and
|
||||
// their current commitments reflect the initial balances.
|
||||
if err := FetchChanCommitments(chanBucket, c); err != nil {
|
||||
return fmt.Errorf("unable to fetch chan commits: %v",
|
||||
err)
|
||||
}
|
||||
|
||||
channels = append(channels, c)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Iterate the root bucket.
|
||||
err := openChanBucket.ForEach(func(nodePub, v []byte) error {
|
||||
// Ensure that this is a key the same size as a pubkey, and
|
||||
// also that it leads directly to a bucket.
|
||||
if len(nodePub) != 33 || v != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read the sub-bucket level 1.
|
||||
nodeChanBucket := openChanBucket.NestedReadBucket(nodePub)
|
||||
if nodeChanBucket == nil {
|
||||
log.Errorf("no bucket for node %x", nodePub)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Iterate the bucket.
|
||||
return nodeChanBucket.ForEach(func(chainHash, _ []byte) error {
|
||||
// Read the sub-bucket level 2.
|
||||
chainBucket := nodeChanBucket.NestedReadBucket(
|
||||
chainHash,
|
||||
)
|
||||
if chainBucket == nil {
|
||||
log.Errorf("unable to read bucket for chain=%x",
|
||||
chainHash)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Iterate the bucket.
|
||||
return chainBucket.ForEach(func(cp, _ []byte) error {
|
||||
return readChannel(chainBucket, cp)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return channels, nil
|
||||
}
|
||||
|
||||
// migrateBalances queries the revocation log at height 0 to find the initial
|
||||
// balances and save them to the channel info.
|
||||
func migrateBalances(tx kvdb.RwTx, c *OpenChannel) error {
|
||||
// Get the bucket.
|
||||
chanBucket, err := fetchChanBucket(tx, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Get the initial balances.
|
||||
localAmt, remoteAmt, err := c.balancesAtHeight(chanBucket, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get initial balances: %v", err)
|
||||
}
|
||||
|
||||
c.InitialLocalBalance = localAmt
|
||||
c.InitialRemoteBalance = remoteAmt
|
||||
|
||||
// Update the channel info.
|
||||
if err := putChanInfo(chanBucket, c, false); err != nil {
|
||||
return fmt.Errorf("unable to put chan info: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// fetchChanBucket is a helper function that returns the bucket where a
|
||||
// channel's data resides in given: the public key for the node, the outpoint,
|
||||
// and the chainhash that the channel resides on.
|
||||
func fetchChanBucket(tx kvdb.RwTx, c *OpenChannel) (kvdb.RwBucket, error) {
|
||||
// First fetch the top level bucket which stores all data related to
|
||||
// current, active channels.
|
||||
openChanBucket := tx.ReadWriteBucket(openChannelBucket)
|
||||
if openChanBucket == nil {
|
||||
return nil, ErrNoChanDBExists
|
||||
}
|
||||
|
||||
// Within this top level bucket, fetch the bucket dedicated to storing
|
||||
// open channel data specific to the remote node.
|
||||
nodePub := c.IdentityPub.SerializeCompressed()
|
||||
nodeChanBucket := openChanBucket.NestedReadWriteBucket(nodePub)
|
||||
if nodeChanBucket == nil {
|
||||
return nil, ErrNoActiveChannels
|
||||
}
|
||||
|
||||
// We'll then recurse down an additional layer in order to fetch the
|
||||
// bucket for this particular chain.
|
||||
chainBucket := nodeChanBucket.NestedReadWriteBucket(c.ChainHash[:])
|
||||
if chainBucket == nil {
|
||||
return nil, ErrNoActiveChannels
|
||||
}
|
||||
|
||||
// With the bucket for the node and chain fetched, we can now go down
|
||||
// another level, for this channel itself.
|
||||
var chanPointBuf bytes.Buffer
|
||||
err := mig.WriteOutpoint(&chanPointBuf, &c.FundingOutpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
chanBucket := chainBucket.NestedReadWriteBucket(chanPointBuf.Bytes())
|
||||
if chanBucket == nil {
|
||||
return nil, ErrChannelNotFound
|
||||
}
|
||||
|
||||
return chanBucket, nil
|
||||
}
|
||||
@@ -0,0 +1,391 @@
|
||||
package migration25
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
|
||||
lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
|
||||
mig24 "github.com/lightningnetwork/lnd/channeldb/migration24"
|
||||
mig "github.com/lightningnetwork/lnd/channeldb/migration_01_to_11"
|
||||
"github.com/lightningnetwork/lnd/channeldb/migtest"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
)
|
||||
|
||||
var (
|
||||
// Create dummy values to be stored in db.
|
||||
dummyPrivKey, _ = btcec.NewPrivateKey()
|
||||
dummyPubKey = dummyPrivKey.PubKey()
|
||||
dummySig = []byte{1, 2, 3}
|
||||
dummyTx = &wire.MsgTx{
|
||||
Version: 1,
|
||||
TxIn: []*wire.TxIn{
|
||||
{
|
||||
PreviousOutPoint: wire.OutPoint{
|
||||
Hash: chainhash.Hash{},
|
||||
Index: 0xffffffff,
|
||||
},
|
||||
Sequence: 0xffffffff,
|
||||
},
|
||||
},
|
||||
TxOut: []*wire.TxOut{
|
||||
{
|
||||
Value: 5000000000,
|
||||
},
|
||||
},
|
||||
LockTime: 5,
|
||||
}
|
||||
|
||||
dummyOp = wire.OutPoint{
|
||||
Hash: chainhash.Hash{},
|
||||
Index: 9,
|
||||
}
|
||||
dummyHTLC = mig.HTLC{
|
||||
Signature: dummySig,
|
||||
RHash: [32]byte{},
|
||||
Amt: 100_000,
|
||||
RefundTimeout: 731583,
|
||||
OutputIndex: 1,
|
||||
Incoming: true,
|
||||
HtlcIndex: 1,
|
||||
LogIndex: 1,
|
||||
}
|
||||
|
||||
// ourAmt and theirAmt are the initial balances found in the local
|
||||
// channel commitment at height 0.
|
||||
ourAmt = lnwire.MilliSatoshi(500_000)
|
||||
theirAmt = lnwire.MilliSatoshi(1000_000)
|
||||
|
||||
// ourAmtRevoke and theirAmtRevoke are the initial balances found in
|
||||
// the revocation log at height 0.
|
||||
//
|
||||
// NOTE: they are made differently such that we can easily check the
|
||||
// source when patching the balances.
|
||||
ourAmtRevoke = lnwire.MilliSatoshi(501_000)
|
||||
theirAmtRevoke = lnwire.MilliSatoshi(1001_000)
|
||||
|
||||
// remoteCommit0 is the channel commitment at commit height 0. This is
|
||||
// also the revocation log we will use to patch the initial balances.
|
||||
remoteCommit0 = mig.ChannelCommitment{
|
||||
LocalBalance: ourAmtRevoke,
|
||||
RemoteBalance: theirAmtRevoke,
|
||||
CommitFee: btcutil.Amount(1),
|
||||
FeePerKw: btcutil.Amount(1),
|
||||
CommitTx: dummyTx,
|
||||
CommitSig: dummySig,
|
||||
Htlcs: []mig.HTLC{},
|
||||
}
|
||||
|
||||
// localCommit0 is the channel commitment at commit height 0. This is
|
||||
// the channel commitment we will use to patch the initial balances
|
||||
// when there are no revocation logs.
|
||||
localCommit0 = mig.ChannelCommitment{
|
||||
LocalBalance: ourAmt,
|
||||
RemoteBalance: theirAmt,
|
||||
CommitFee: btcutil.Amount(1),
|
||||
FeePerKw: btcutil.Amount(1),
|
||||
CommitTx: dummyTx,
|
||||
CommitSig: dummySig,
|
||||
Htlcs: []mig.HTLC{},
|
||||
}
|
||||
|
||||
// remoteCommit1 and localCommit1 are the channel commitment at commit
|
||||
// height 1.
|
||||
remoteCommit1 = mig.ChannelCommitment{
|
||||
CommitHeight: 1,
|
||||
LocalLogIndex: 1,
|
||||
LocalHtlcIndex: 1,
|
||||
RemoteLogIndex: 1,
|
||||
RemoteHtlcIndex: 1,
|
||||
LocalBalance: ourAmt - dummyHTLC.Amt,
|
||||
RemoteBalance: theirAmt + dummyHTLC.Amt,
|
||||
CommitFee: btcutil.Amount(1),
|
||||
FeePerKw: btcutil.Amount(1),
|
||||
CommitTx: dummyTx,
|
||||
CommitSig: dummySig,
|
||||
Htlcs: []mig.HTLC{dummyHTLC},
|
||||
}
|
||||
localCommit1 = mig.ChannelCommitment{
|
||||
CommitHeight: 1,
|
||||
LocalLogIndex: 1,
|
||||
LocalHtlcIndex: 1,
|
||||
RemoteLogIndex: 1,
|
||||
RemoteHtlcIndex: 1,
|
||||
LocalBalance: ourAmt - dummyHTLC.Amt,
|
||||
RemoteBalance: theirAmt + dummyHTLC.Amt,
|
||||
CommitFee: btcutil.Amount(1),
|
||||
FeePerKw: btcutil.Amount(1),
|
||||
CommitTx: dummyTx,
|
||||
CommitSig: dummySig,
|
||||
Htlcs: []mig.HTLC{dummyHTLC},
|
||||
}
|
||||
|
||||
// openChannel0 is the OpenChannel at commit height 0. When this
|
||||
// variable is used, we expect to patch the initial balances from its
|
||||
// commitments.
|
||||
openChannel0 = &OpenChannel{
|
||||
OpenChannel: mig.OpenChannel{
|
||||
IdentityPub: dummyPubKey,
|
||||
FundingOutpoint: dummyOp,
|
||||
LocalCommitment: localCommit0,
|
||||
RemoteCommitment: remoteCommit0,
|
||||
},
|
||||
}
|
||||
|
||||
// openChannel1 is the OpenChannel at commit height 1. When this
|
||||
// variable is used, we expect to patch the initial balances from the
|
||||
// remote commitment at height 0.
|
||||
openChannel1 = &OpenChannel{
|
||||
OpenChannel: mig.OpenChannel{
|
||||
IdentityPub: dummyPubKey,
|
||||
FundingOutpoint: dummyOp,
|
||||
LocalCommitment: localCommit1,
|
||||
RemoteCommitment: remoteCommit1,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// TestMigrateInitialBalances checks that the proper initial balances are
|
||||
// patched to the channel info.
|
||||
func TestMigrateInitialBalances(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
beforeMigrationFunc func(kvdb.RwTx) error
|
||||
afterMigrationFunc func(kvdb.RwTx) error
|
||||
shouldFail bool
|
||||
}{
|
||||
{
|
||||
// Test that we patch the initial balances using the
|
||||
// revocation log.
|
||||
name: "patch balance from revoke log",
|
||||
beforeMigrationFunc: genBeforeMigration(
|
||||
openChannel1, &remoteCommit0,
|
||||
),
|
||||
afterMigrationFunc: genAfterMigration(
|
||||
ourAmtRevoke, theirAmtRevoke, openChannel1,
|
||||
),
|
||||
},
|
||||
{
|
||||
// Test that we patch the initial balances using the
|
||||
// channel's local commitment since at height 0,
|
||||
// balances found in LocalCommitment reflect the
|
||||
// initial balances.
|
||||
name: "patch balance from local commit",
|
||||
beforeMigrationFunc: genBeforeMigration(
|
||||
openChannel0, nil,
|
||||
),
|
||||
afterMigrationFunc: genAfterMigration(
|
||||
ourAmt, theirAmt, openChannel0,
|
||||
),
|
||||
},
|
||||
{
|
||||
// Test that we patch the initial balances using the
|
||||
// channel's local commitment even when there is a
|
||||
// revocation log available.
|
||||
name: "patch balance from local commit only",
|
||||
beforeMigrationFunc: genBeforeMigration(
|
||||
openChannel0, &remoteCommit0,
|
||||
),
|
||||
afterMigrationFunc: genAfterMigration(
|
||||
ourAmt, theirAmt, openChannel0,
|
||||
),
|
||||
},
|
||||
{
|
||||
// Test that when there is no revocation log the
|
||||
// migration would fail.
|
||||
name: "patch balance error on no revoke log",
|
||||
beforeMigrationFunc: genBeforeMigration(
|
||||
// Use nil to specify no revocation log will be
|
||||
// created.
|
||||
openChannel1, nil,
|
||||
),
|
||||
afterMigrationFunc: genAfterMigration(
|
||||
// Use nil to specify skipping the
|
||||
// afterMigrationFunc.
|
||||
0, 0, nil,
|
||||
),
|
||||
shouldFail: true,
|
||||
},
|
||||
{
|
||||
// Test that when the saved revocation log is not what
|
||||
// we want the migration would fail.
|
||||
name: "patch balance error on wrong revoke log",
|
||||
beforeMigrationFunc: genBeforeMigration(
|
||||
// Use the revocation log with the wrong
|
||||
// height.
|
||||
openChannel1, &remoteCommit1,
|
||||
),
|
||||
afterMigrationFunc: genAfterMigration(
|
||||
// Use nil to specify skipping the
|
||||
// afterMigrationFunc.
|
||||
0, 0, nil,
|
||||
),
|
||||
shouldFail: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
migtest.ApplyMigration(
|
||||
t,
|
||||
tc.beforeMigrationFunc,
|
||||
tc.afterMigrationFunc,
|
||||
MigrateInitialBalances,
|
||||
tc.shouldFail,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func genBeforeMigration(c *OpenChannel,
|
||||
commit *mig.ChannelCommitment) func(kvdb.RwTx) error {
|
||||
|
||||
return func(tx kvdb.RwTx) error {
|
||||
if c.InitialLocalBalance != 0 {
|
||||
return fmt.Errorf("non zero initial local amount")
|
||||
}
|
||||
|
||||
if c.InitialRemoteBalance != 0 {
|
||||
return fmt.Errorf("non zero initial local amount")
|
||||
}
|
||||
|
||||
// Create the channel bucket.
|
||||
chanBucket, err := createChanBucket(tx, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Save the channel info using legacy format.
|
||||
if err := putChanInfo(chanBucket, c, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Save the channel commitments.
|
||||
if err := PutChanCommitments(chanBucket, c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If we have a remote commitment, save it as our revocation
|
||||
// log.
|
||||
if commit != nil {
|
||||
err := putChannelLogEntryLegacy(chanBucket, commit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func genAfterMigration(ourAmt, theirAmt lnwire.MilliSatoshi,
|
||||
c *OpenChannel) func(kvdb.RwTx) error {
|
||||
|
||||
return func(tx kvdb.RwTx) error {
|
||||
// If the passed OpenChannel is nil, we will skip the
|
||||
// afterMigrationFunc as it indicates an error is expected
|
||||
// during the migration.
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
chanBucket, err := fetchChanBucket(tx, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newChan := &OpenChannel{}
|
||||
|
||||
// Fetch the channel info using the new format.
|
||||
err = fetchChanInfo(chanBucket, newChan, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check our initial amount is correct.
|
||||
if newChan.InitialLocalBalance != ourAmt {
|
||||
return fmt.Errorf("wrong local balance, got %d, "+
|
||||
"want %d", newChan.InitialLocalBalance, ourAmt)
|
||||
}
|
||||
|
||||
// Check their initial amount is correct.
|
||||
if newChan.InitialRemoteBalance != theirAmt {
|
||||
return fmt.Errorf("wrong remote balance, got %d, "+
|
||||
"want %d", newChan.InitialRemoteBalance,
|
||||
theirAmt)
|
||||
}
|
||||
|
||||
// We also check the relevant channel info fields stay the
|
||||
// same.
|
||||
if !newChan.IdentityPub.IsEqual(c.IdentityPub) {
|
||||
return fmt.Errorf("wrong IdentityPub")
|
||||
}
|
||||
if newChan.FundingOutpoint != c.FundingOutpoint {
|
||||
return fmt.Errorf("wrong FundingOutpoint")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func createChanBucket(tx kvdb.RwTx, c *OpenChannel) (kvdb.RwBucket, error) {
|
||||
// First fetch the top level bucket which stores all data related to
|
||||
// current, active channels.
|
||||
openChanBucket, err := tx.CreateTopLevelBucket(openChannelBucket)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Within this top level bucket, fetch the bucket dedicated to storing
|
||||
// open channel data specific to the remote node.
|
||||
nodePub := c.IdentityPub.SerializeCompressed()
|
||||
nodeChanBucket, err := openChanBucket.CreateBucketIfNotExists(nodePub)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We'll then recurse down an additional layer in order to fetch the
|
||||
// bucket for this particular chain.
|
||||
chainBucket, err := nodeChanBucket.CreateBucketIfNotExists(
|
||||
c.ChainHash[:],
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var chanPointBuf bytes.Buffer
|
||||
err = mig.WriteOutpoint(&chanPointBuf, &c.FundingOutpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// With the bucket for the node fetched, we can now go down another
|
||||
// level, creating the bucket for this channel itself.
|
||||
return chainBucket.CreateBucketIfNotExists(chanPointBuf.Bytes())
|
||||
}
|
||||
|
||||
// putChannelLogEntryLegacy saves an old format revocation log to the bucket.
|
||||
func putChannelLogEntryLegacy(chanBucket kvdb.RwBucket,
|
||||
commit *mig.ChannelCommitment) error {
|
||||
|
||||
logBucket, err := chanBucket.CreateBucketIfNotExists(
|
||||
revocationLogBucketLegacy,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
if err := mig.SerializeChanCommit(&b, commit); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logEntrykey := mig24.MakeLogKey(commit.CommitHeight)
|
||||
return logBucket.Put(logEntrykey[:], b.Bytes())
|
||||
}
|
||||
@@ -616,7 +616,7 @@ func serializeChannelCloseSummary(w io.Writer, cs *ChannelCloseSummary) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := writeChanConfig(w, &cs.LocalChanConfig); err != nil {
|
||||
if err := WriteChanConfig(w, &cs.LocalChanConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -680,7 +680,7 @@ func deserializeCloseChannelSummary(r io.Reader) (*ChannelCloseSummary, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := readChanConfig(r, &c.LocalChanConfig); err != nil {
|
||||
if err := ReadChanConfig(r, &c.LocalChanConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -731,7 +731,7 @@ func deserializeCloseChannelSummary(r io.Reader) (*ChannelCloseSummary, error) {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func writeChanConfig(b io.Writer, c *ChannelConfig) error {
|
||||
func WriteChanConfig(b io.Writer, c *ChannelConfig) error {
|
||||
return WriteElements(b,
|
||||
c.DustLimit, c.MaxPendingAmount, c.ChanReserve, c.MinHTLC,
|
||||
c.MaxAcceptedHtlcs, c.CsvDelay, c.MultiSigKey,
|
||||
@@ -740,7 +740,7 @@ func writeChanConfig(b io.Writer, c *ChannelConfig) error {
|
||||
)
|
||||
}
|
||||
|
||||
func readChanConfig(b io.Reader, c *ChannelConfig) error {
|
||||
func ReadChanConfig(b io.Reader, c *ChannelConfig) error {
|
||||
return ReadElements(b,
|
||||
&c.DustLimit, &c.MaxPendingAmount, &c.ChanReserve,
|
||||
&c.MinHTLC, &c.MaxAcceptedHtlcs, &c.CsvDelay,
|
||||
@@ -749,3 +749,92 @@ func readChanConfig(b io.Reader, c *ChannelConfig) error {
|
||||
&c.HtlcBasePoint,
|
||||
)
|
||||
}
|
||||
|
||||
func DeserializeChanCommit(r io.Reader) (ChannelCommitment, error) {
|
||||
var c ChannelCommitment
|
||||
|
||||
err := ReadElements(r,
|
||||
&c.CommitHeight, &c.LocalLogIndex, &c.LocalHtlcIndex, &c.RemoteLogIndex,
|
||||
&c.RemoteHtlcIndex, &c.LocalBalance, &c.RemoteBalance,
|
||||
&c.CommitFee, &c.FeePerKw, &c.CommitTx, &c.CommitSig,
|
||||
)
|
||||
if err != nil {
|
||||
return c, err
|
||||
}
|
||||
|
||||
c.Htlcs, err = DeserializeHtlcs(r)
|
||||
if err != nil {
|
||||
return c, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// DeserializeHtlcs attempts to read out a slice of HTLC's from the passed
|
||||
// io.Reader. The bytes within the passed reader MUST have been previously
|
||||
// written to using the SerializeHtlcs function.
|
||||
//
|
||||
// NOTE: This API is NOT stable, the on-disk format will likely change in the
|
||||
// future.
|
||||
func DeserializeHtlcs(r io.Reader) ([]HTLC, error) {
|
||||
var numHtlcs uint16
|
||||
if err := ReadElement(r, &numHtlcs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var htlcs []HTLC
|
||||
if numHtlcs == 0 {
|
||||
return htlcs, nil
|
||||
}
|
||||
|
||||
htlcs = make([]HTLC, numHtlcs)
|
||||
for i := uint16(0); i < numHtlcs; i++ {
|
||||
if err := ReadElements(r,
|
||||
&htlcs[i].Signature, &htlcs[i].RHash, &htlcs[i].Amt,
|
||||
&htlcs[i].RefundTimeout, &htlcs[i].OutputIndex,
|
||||
&htlcs[i].Incoming, &htlcs[i].OnionBlob,
|
||||
&htlcs[i].HtlcIndex, &htlcs[i].LogIndex,
|
||||
); err != nil {
|
||||
return htlcs, err
|
||||
}
|
||||
}
|
||||
|
||||
return htlcs, nil
|
||||
}
|
||||
|
||||
func SerializeChanCommit(w io.Writer, c *ChannelCommitment) error {
|
||||
if err := WriteElements(w,
|
||||
c.CommitHeight, c.LocalLogIndex, c.LocalHtlcIndex,
|
||||
c.RemoteLogIndex, c.RemoteHtlcIndex, c.LocalBalance,
|
||||
c.RemoteBalance, c.CommitFee, c.FeePerKw, c.CommitTx,
|
||||
c.CommitSig,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return SerializeHtlcs(w, c.Htlcs...)
|
||||
}
|
||||
|
||||
// SerializeHtlcs writes out the passed set of HTLC's into the passed writer
|
||||
// using the current default on-disk serialization format.
|
||||
//
|
||||
// NOTE: This API is NOT stable, the on-disk format will likely change in the
|
||||
// future.
|
||||
func SerializeHtlcs(b io.Writer, htlcs ...HTLC) error {
|
||||
numHtlcs := uint16(len(htlcs))
|
||||
if err := WriteElement(b, numHtlcs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, htlc := range htlcs {
|
||||
if err := WriteElements(b,
|
||||
htlc.Signature, htlc.RHash, htlc.Amt, htlc.RefundTimeout,
|
||||
htlc.OutputIndex, htlc.Incoming, htlc.OnionBlob[:],
|
||||
htlc.HtlcIndex, htlc.LogIndex,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -15,9 +15,9 @@ import (
|
||||
"github.com/lightningnetwork/lnd/shachain"
|
||||
)
|
||||
|
||||
// writeOutpoint writes an outpoint to the passed writer using the minimal
|
||||
// WriteOutpoint writes an outpoint to the passed writer using the minimal
|
||||
// amount of bytes possible.
|
||||
func writeOutpoint(w io.Writer, o *wire.OutPoint) error {
|
||||
func WriteOutpoint(w io.Writer, o *wire.OutPoint) error {
|
||||
if _, err := w.Write(o.Hash[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -28,9 +28,9 @@ func writeOutpoint(w io.Writer, o *wire.OutPoint) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// readOutpoint reads an outpoint from the passed reader that was previously
|
||||
// ReadOutpoint reads an outpoint from the passed reader that was previously
|
||||
// written using the writeOutpoint struct.
|
||||
func readOutpoint(r io.Reader, o *wire.OutPoint) error {
|
||||
func ReadOutpoint(r io.Reader, o *wire.OutPoint) error {
|
||||
if _, err := io.ReadFull(r, o.Hash[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -88,7 +88,7 @@ func WriteElement(w io.Writer, element interface{}) error {
|
||||
}
|
||||
|
||||
case wire.OutPoint:
|
||||
return writeOutpoint(w, &e)
|
||||
return WriteOutpoint(w, &e)
|
||||
|
||||
case lnwire.ShortChannelID:
|
||||
if err := binary.Write(w, byteOrder, e.ToUint64()); err != nil {
|
||||
@@ -258,7 +258,7 @@ func ReadElement(r io.Reader, element interface{}) error {
|
||||
}
|
||||
|
||||
case *wire.OutPoint:
|
||||
return readOutpoint(r, e)
|
||||
return ReadOutpoint(r, e)
|
||||
|
||||
case *lnwire.ShortChannelID:
|
||||
var a uint64
|
||||
|
||||
@@ -836,7 +836,7 @@ func deserializeChanEdgeInfo(r io.Reader) (ChannelEdgeInfo, error) {
|
||||
}
|
||||
|
||||
edgeInfo.ChannelPoint = wire.OutPoint{}
|
||||
if err := readOutpoint(r, &edgeInfo.ChannelPoint); err != nil {
|
||||
if err := ReadOutpoint(r, &edgeInfo.ChannelPoint); err != nil {
|
||||
return ChannelEdgeInfo{}, err
|
||||
}
|
||||
if err := binary.Read(r, byteOrder, &edgeInfo.Capacity); err != nil {
|
||||
|
||||
@@ -34,7 +34,7 @@ func deserializeCloseChannelSummaryV6(r io.Reader) (*ChannelCloseSummary, error)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := readChanConfig(r, &c.LocalChanConfig); err != nil {
|
||||
if err := ReadChanConfig(r, &c.LocalChanConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -203,7 +203,7 @@ func TestMigrateOptionalChannelCloseSummaryFields(t *testing.T) {
|
||||
}
|
||||
|
||||
var chanPointBuf bytes.Buffer
|
||||
err = writeOutpoint(&chanPointBuf, &chanState.FundingOutpoint)
|
||||
err = WriteOutpoint(&chanPointBuf, &chanState.FundingOutpoint)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to write outpoint: %v", err)
|
||||
}
|
||||
@@ -303,7 +303,7 @@ func TestMigrateOptionalChannelCloseSummaryFields(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = writeChanConfig(&buf, &cs.LocalChanConfig)
|
||||
err = WriteChanConfig(&buf, &cs.LocalChanConfig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -354,7 +354,7 @@ func TestMigrateOptionalChannelCloseSummaryFields(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = writeChanConfig(&buf, &cs.LocalChanConfig)
|
||||
err = WriteChanConfig(&buf, &cs.LocalChanConfig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,547 @@
|
||||
package channeldb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"math"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
)
|
||||
|
||||
// OutputIndexEmpty is used when the output index doesn't exist.
|
||||
const OutputIndexEmpty = math.MaxUint16
|
||||
|
||||
var (
|
||||
// revocationLogBucketDeprecated is dedicated for storing the necessary
|
||||
// delta state between channel updates required to re-construct a past
|
||||
// state in order to punish a counterparty attempting a non-cooperative
|
||||
// channel closure. This key should be accessed from within the
|
||||
// sub-bucket of a target channel, identified by its channel point.
|
||||
//
|
||||
// Deprecated: This bucket is kept for read-only in case the user
|
||||
// choose not to migrate the old data.
|
||||
revocationLogBucketDeprecated = []byte("revocation-log-key")
|
||||
|
||||
// revocationLogBucket is a sub-bucket under openChannelBucket. This
|
||||
// sub-bucket is dedicated for storing the minimal info required to
|
||||
// re-construct a past state in order to punish a counterparty
|
||||
// attempting a non-cooperative channel closure.
|
||||
revocationLogBucket = []byte("revocation-log")
|
||||
|
||||
// ErrLogEntryNotFound is returned when we cannot find a log entry at
|
||||
// the height requested in the revocation log.
|
||||
ErrLogEntryNotFound = errors.New("log entry not found")
|
||||
|
||||
// ErrOutputIndexTooBig is returned when the output index is greater
|
||||
// than uint16.
|
||||
ErrOutputIndexTooBig = errors.New("output index is over uint16")
|
||||
)
|
||||
|
||||
// HTLCEntry specifies the minimal info needed to be stored on disk for ALL the
|
||||
// historical HTLCs, which is useful for constructing RevocationLog when a
|
||||
// breach is detected.
|
||||
// The actual size of each HTLCEntry varies based on its RHash and Amt(sat),
|
||||
// summarized as follows,
|
||||
//
|
||||
// | RHash empty | Amt<=252 | Amt<=65,535 | Amt<=4,294,967,295 | otherwise |
|
||||
// |:-----------:|:--------:|:-----------:|:------------------:|:---------:|
|
||||
// | true | 19 | 21 | 23 | 26 |
|
||||
// | false | 51 | 53 | 55 | 58 |
|
||||
//
|
||||
// So the size varies from 19 bytes to 58 bytes, where most likely to be 23 or
|
||||
// 55 bytes.
|
||||
//
|
||||
// NOTE: all the fields saved to disk use the primitive go types so they can be
|
||||
// made into tlv records without further conversion.
|
||||
type HTLCEntry struct {
|
||||
// RHash is the payment hash of the HTLC.
|
||||
RHash [32]byte
|
||||
|
||||
// RefundTimeout is the absolute timeout on the HTLC that the sender
|
||||
// must wait before reclaiming the funds in limbo.
|
||||
RefundTimeout uint32
|
||||
|
||||
// OutputIndex is the output index for this particular HTLC output
|
||||
// within the commitment transaction.
|
||||
//
|
||||
// NOTE: we use uint16 instead of int32 here to save us 2 bytes, which
|
||||
// gives us a max number of HTLCs of 65K.
|
||||
OutputIndex uint16
|
||||
|
||||
// Incoming denotes whether we're the receiver or the sender of this
|
||||
// HTLC.
|
||||
//
|
||||
// NOTE: this field is the memory representation of the field
|
||||
// incomingUint.
|
||||
Incoming bool
|
||||
|
||||
// Amt is the amount of satoshis this HTLC escrows.
|
||||
//
|
||||
// NOTE: this field is the memory representation of the field amtUint.
|
||||
Amt btcutil.Amount
|
||||
|
||||
// amtTlv is the uint64 format of Amt. This field is created so we can
|
||||
// easily make it into a tlv record and save it to disk.
|
||||
//
|
||||
// NOTE: we keep this field for accounting purpose only. If the disk
|
||||
// space becomes an issue, we could delete this field to save us extra
|
||||
// 8 bytes.
|
||||
amtTlv uint64
|
||||
|
||||
// incomingTlv is the uint8 format of Incoming. This field is created
|
||||
// so we can easily make it into a tlv record and save it to disk.
|
||||
incomingTlv uint8
|
||||
}
|
||||
|
||||
// RHashLen is used by MakeDynamicRecord to return the size of the RHash.
|
||||
//
|
||||
// NOTE: for zero hash, we return a length 0.
|
||||
func (h *HTLCEntry) RHashLen() uint64 {
|
||||
if h.RHash == lntypes.ZeroHash {
|
||||
return 0
|
||||
}
|
||||
return 32
|
||||
}
|
||||
|
||||
// RHashEncoder is the customized encoder which skips encoding the empty hash.
|
||||
func RHashEncoder(w io.Writer, val interface{}, buf *[8]byte) error {
|
||||
v, ok := val.(*[32]byte)
|
||||
if !ok {
|
||||
return tlv.NewTypeForEncodingErr(val, "RHash")
|
||||
}
|
||||
|
||||
// If the value is an empty hash, we will skip encoding it.
|
||||
if *v == lntypes.ZeroHash {
|
||||
return nil
|
||||
}
|
||||
|
||||
return tlv.EBytes32(w, v, buf)
|
||||
}
|
||||
|
||||
// RHashDecoder is the customized decoder which skips decoding the empty hash.
|
||||
func RHashDecoder(r io.Reader, val interface{}, buf *[8]byte, l uint64) error {
|
||||
v, ok := val.(*[32]byte)
|
||||
if !ok {
|
||||
return tlv.NewTypeForEncodingErr(val, "RHash")
|
||||
}
|
||||
|
||||
// If the length is zero, we will skip encoding the empty hash.
|
||||
if l == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return tlv.DBytes32(r, v, buf, 32)
|
||||
}
|
||||
|
||||
// toTlvStream converts an HTLCEntry record into a tlv representation.
|
||||
func (h *HTLCEntry) toTlvStream() (*tlv.Stream, error) {
|
||||
const (
|
||||
// A set of tlv type definitions used to serialize htlc entries
|
||||
// to the database. We define it here instead of the head of
|
||||
// the file to avoid naming conflicts.
|
||||
//
|
||||
// NOTE: A migration should be added whenever this list
|
||||
// changes.
|
||||
rHashType tlv.Type = 0
|
||||
refundTimeoutType tlv.Type = 1
|
||||
outputIndexType tlv.Type = 2
|
||||
incomingType tlv.Type = 3
|
||||
amtType tlv.Type = 4
|
||||
)
|
||||
|
||||
return tlv.NewStream(
|
||||
tlv.MakeDynamicRecord(
|
||||
rHashType, &h.RHash, h.RHashLen,
|
||||
RHashEncoder, RHashDecoder,
|
||||
),
|
||||
tlv.MakePrimitiveRecord(
|
||||
refundTimeoutType, &h.RefundTimeout,
|
||||
),
|
||||
tlv.MakePrimitiveRecord(
|
||||
outputIndexType, &h.OutputIndex,
|
||||
),
|
||||
tlv.MakePrimitiveRecord(incomingType, &h.incomingTlv),
|
||||
// We will save 3 bytes if the amount is less or equal to
|
||||
// 4,294,967,295 msat, or roughly 0.043 bitcoin.
|
||||
tlv.MakeBigSizeRecord(amtType, &h.amtTlv),
|
||||
)
|
||||
}
|
||||
|
||||
// RevocationLog stores the info needed to construct a breach retribution. Its
|
||||
// fields can be viewed as a subset of a ChannelCommitment's. In the database,
|
||||
// all historical versions of the RevocationLog are saved using the
|
||||
// CommitHeight as the key.
|
||||
//
|
||||
// NOTE: all the fields use the primitive go types so they can be made into tlv
|
||||
// records without further conversion.
|
||||
type RevocationLog struct {
|
||||
// OurOutputIndex specifies our output index in this commitment. In a
|
||||
// remote commitment transaction, this is the to remote output index.
|
||||
OurOutputIndex uint16
|
||||
|
||||
// TheirOutputIndex specifies their output index in this commitment. In
|
||||
// a remote commitment transaction, this is the to local output index.
|
||||
TheirOutputIndex uint16
|
||||
|
||||
// CommitTxHash is the hash of the latest version of the commitment
|
||||
// state, broadcast able by us.
|
||||
CommitTxHash [32]byte
|
||||
|
||||
// HTLCEntries is the set of HTLCEntry's that are pending at this
|
||||
// particular commitment height.
|
||||
HTLCEntries []*HTLCEntry
|
||||
}
|
||||
|
||||
// toTlvStream converts an RevocationLog record into a tlv representation.
|
||||
func (rl *RevocationLog) toTlvStream() (*tlv.Stream, error) {
|
||||
const (
|
||||
// A set of tlv type definitions used to serialize the body of
|
||||
// revocation logs to the database. We define it here instead
|
||||
// of the head of the file to avoid naming conflicts.
|
||||
//
|
||||
// NOTE: A migration should be added whenever this list
|
||||
// changes.
|
||||
ourOutputIndexType tlv.Type = 0
|
||||
theirOutputIndexType tlv.Type = 1
|
||||
commitTxHashType tlv.Type = 2
|
||||
)
|
||||
|
||||
return tlv.NewStream(
|
||||
tlv.MakePrimitiveRecord(ourOutputIndexType, &rl.OurOutputIndex),
|
||||
tlv.MakePrimitiveRecord(
|
||||
theirOutputIndexType, &rl.TheirOutputIndex,
|
||||
),
|
||||
tlv.MakePrimitiveRecord(commitTxHashType, &rl.CommitTxHash),
|
||||
)
|
||||
}
|
||||
|
||||
// putRevocationLog uses the fields `CommitTx` and `Htlcs` from a
|
||||
// ChannelCommitment to construct a revocation log entry and saves them to
|
||||
// disk. It also saves our output index and their output index, which are
|
||||
// useful when creating breach retribution.
|
||||
func putRevocationLog(bucket kvdb.RwBucket, commit *ChannelCommitment,
|
||||
ourOutputIndex, theirOutputIndex uint32) error {
|
||||
|
||||
// Sanity check that the output indexes can be safely converted.
|
||||
if ourOutputIndex > math.MaxUint16 {
|
||||
return ErrOutputIndexTooBig
|
||||
}
|
||||
if theirOutputIndex > math.MaxUint16 {
|
||||
return ErrOutputIndexTooBig
|
||||
}
|
||||
|
||||
rl := &RevocationLog{
|
||||
OurOutputIndex: uint16(ourOutputIndex),
|
||||
TheirOutputIndex: uint16(theirOutputIndex),
|
||||
CommitTxHash: commit.CommitTx.TxHash(),
|
||||
HTLCEntries: make([]*HTLCEntry, 0, len(commit.Htlcs)),
|
||||
}
|
||||
|
||||
for _, htlc := range commit.Htlcs {
|
||||
// Skip dust HTLCs.
|
||||
if htlc.OutputIndex < 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Sanity check that the output indexes can be safely
|
||||
// converted.
|
||||
if htlc.OutputIndex > math.MaxUint16 {
|
||||
return ErrOutputIndexTooBig
|
||||
}
|
||||
|
||||
entry := &HTLCEntry{
|
||||
RHash: htlc.RHash,
|
||||
RefundTimeout: htlc.RefundTimeout,
|
||||
Incoming: htlc.Incoming,
|
||||
OutputIndex: uint16(htlc.OutputIndex),
|
||||
Amt: htlc.Amt.ToSatoshis(),
|
||||
}
|
||||
rl.HTLCEntries = append(rl.HTLCEntries, entry)
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
err := serializeRevocationLog(&b, rl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logEntrykey := makeLogKey(commit.CommitHeight)
|
||||
return bucket.Put(logEntrykey[:], b.Bytes())
|
||||
}
|
||||
|
||||
// fetchRevocationLog queries the revocation log bucket to find an log entry.
|
||||
// Return an error if not found.
|
||||
func fetchRevocationLog(log kvdb.RBucket,
|
||||
updateNum uint64) (RevocationLog, error) {
|
||||
|
||||
logEntrykey := makeLogKey(updateNum)
|
||||
commitBytes := log.Get(logEntrykey[:])
|
||||
if commitBytes == nil {
|
||||
return RevocationLog{}, ErrLogEntryNotFound
|
||||
}
|
||||
|
||||
commitReader := bytes.NewReader(commitBytes)
|
||||
|
||||
return deserializeRevocationLog(commitReader)
|
||||
}
|
||||
|
||||
// serializeRevocationLog serializes a RevocationLog record based on tlv
|
||||
// format.
|
||||
func serializeRevocationLog(w io.Writer, rl *RevocationLog) error {
|
||||
// Create the tlv stream.
|
||||
tlvStream, err := rl.toTlvStream()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write the tlv stream.
|
||||
if err := writeTlvStream(w, tlvStream); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write the HTLCs.
|
||||
return serializeHTLCEntries(w, rl.HTLCEntries)
|
||||
}
|
||||
|
||||
// serializeHTLCEntries serializes a list of HTLCEntry records based on tlv
|
||||
// format.
|
||||
func serializeHTLCEntries(w io.Writer, htlcs []*HTLCEntry) error {
|
||||
for _, htlc := range htlcs {
|
||||
// Patch the incomingTlv field.
|
||||
if htlc.Incoming {
|
||||
htlc.incomingTlv = 1
|
||||
}
|
||||
|
||||
// Patch the amtTlv field.
|
||||
htlc.amtTlv = uint64(htlc.Amt)
|
||||
|
||||
// Create the tlv stream.
|
||||
tlvStream, err := htlc.toTlvStream()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write the tlv stream.
|
||||
if err := writeTlvStream(w, tlvStream); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// deserializeRevocationLog deserializes a RevocationLog based on tlv format.
|
||||
func deserializeRevocationLog(r io.Reader) (RevocationLog, error) {
|
||||
var rl RevocationLog
|
||||
|
||||
// Create the tlv stream.
|
||||
tlvStream, err := rl.toTlvStream()
|
||||
if err != nil {
|
||||
return rl, err
|
||||
}
|
||||
|
||||
// Read the tlv stream.
|
||||
if err := readTlvStream(r, tlvStream); err != nil {
|
||||
return rl, err
|
||||
}
|
||||
|
||||
// Read the HTLC entries.
|
||||
rl.HTLCEntries, err = deserializeHTLCEntries(r)
|
||||
|
||||
return rl, err
|
||||
}
|
||||
|
||||
// deserializeHTLCEntries deserializes a list of HTLC entries based on tlv
|
||||
// format.
|
||||
func deserializeHTLCEntries(r io.Reader) ([]*HTLCEntry, error) {
|
||||
var htlcs []*HTLCEntry
|
||||
|
||||
for {
|
||||
var htlc HTLCEntry
|
||||
|
||||
// Create the tlv stream.
|
||||
tlvStream, err := htlc.toTlvStream()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Read the HTLC entry.
|
||||
if err := readTlvStream(r, tlvStream); err != nil {
|
||||
// We've reached the end when hitting an EOF.
|
||||
if err == io.ErrUnexpectedEOF {
|
||||
break
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Patch the Incoming field.
|
||||
if htlc.incomingTlv == 1 {
|
||||
htlc.Incoming = true
|
||||
}
|
||||
|
||||
// Patch the Amt field.
|
||||
htlc.Amt = btcutil.Amount(htlc.amtTlv)
|
||||
|
||||
// Append the entry.
|
||||
htlcs = append(htlcs, &htlc)
|
||||
}
|
||||
|
||||
return htlcs, nil
|
||||
}
|
||||
|
||||
// writeTlvStream is a helper function that encodes the tlv stream into the
|
||||
// writer.
|
||||
func writeTlvStream(w io.Writer, s *tlv.Stream) error {
|
||||
var b bytes.Buffer
|
||||
if err := s.Encode(&b); err != nil {
|
||||
return err
|
||||
}
|
||||
// Write the stream's length as a varint.
|
||||
err := tlv.WriteVarInt(w, uint64(b.Len()), &[8]byte{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = w.Write(b.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// readTlvStream is a helper function that decodes the tlv stream from the
|
||||
// reader.
|
||||
func readTlvStream(r io.Reader, s *tlv.Stream) error {
|
||||
var bodyLen uint64
|
||||
|
||||
// Read the stream's length.
|
||||
bodyLen, err := tlv.ReadVarInt(r, &[8]byte{})
|
||||
switch {
|
||||
// We'll convert any EOFs to ErrUnexpectedEOF, since this results in an
|
||||
// invalid record.
|
||||
case err == io.EOF:
|
||||
return io.ErrUnexpectedEOF
|
||||
|
||||
// Other unexpected errors.
|
||||
case err != nil:
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO(yy): add overflow check.
|
||||
lr := io.LimitReader(r, int64(bodyLen))
|
||||
return s.Decode(lr)
|
||||
}
|
||||
|
||||
// fetchOldRevocationLog finds the revocation log from the deprecated
|
||||
// sub-bucket.
|
||||
func fetchOldRevocationLog(log kvdb.RBucket,
|
||||
updateNum uint64) (ChannelCommitment, error) {
|
||||
|
||||
logEntrykey := makeLogKey(updateNum)
|
||||
commitBytes := log.Get(logEntrykey[:])
|
||||
if commitBytes == nil {
|
||||
return ChannelCommitment{}, ErrLogEntryNotFound
|
||||
}
|
||||
|
||||
commitReader := bytes.NewReader(commitBytes)
|
||||
return deserializeChanCommit(commitReader)
|
||||
}
|
||||
|
||||
// fetchRevocationLogCompatible finds the revocation log from both the
|
||||
// revocationLogBucket and revocationLogBucketDeprecated for compatibility
|
||||
// concern. It returns three values,
|
||||
// - RevocationLog, if this is non-nil, it means we've found the log in the
|
||||
// new bucket.
|
||||
// - ChannelCommitment, if this is non-nil, it means we've found the log in the
|
||||
// old bucket.
|
||||
// - error, this can happen if the log cannot be found in neither buckets.
|
||||
func fetchRevocationLogCompatible(chanBucket kvdb.RBucket,
|
||||
updateNum uint64) (*RevocationLog, *ChannelCommitment, error) {
|
||||
|
||||
// Look into the new bucket first.
|
||||
logBucket := chanBucket.NestedReadBucket(revocationLogBucket)
|
||||
if logBucket != nil {
|
||||
rl, err := fetchRevocationLog(logBucket, updateNum)
|
||||
// We've found the record, no need to visit the old bucket.
|
||||
if err == nil {
|
||||
return &rl, nil, nil
|
||||
}
|
||||
|
||||
// Return the error if it doesn't say the log cannot be found.
|
||||
if err != ErrLogEntryNotFound {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, look into the old bucket and try to find the log there.
|
||||
oldBucket := chanBucket.NestedReadBucket(revocationLogBucketDeprecated)
|
||||
if oldBucket != nil {
|
||||
c, err := fetchOldRevocationLog(oldBucket, updateNum)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Found an old record and return it.
|
||||
return nil, &c, nil
|
||||
}
|
||||
|
||||
// If both the buckets are nil, then the sub-buckets haven't been
|
||||
// created yet.
|
||||
if logBucket == nil && oldBucket == nil {
|
||||
return nil, nil, ErrNoPastDeltas
|
||||
}
|
||||
|
||||
// Otherwise, we've tried to query the new bucket but the log cannot be
|
||||
// found.
|
||||
return nil, nil, ErrLogEntryNotFound
|
||||
}
|
||||
|
||||
// fetchLogBucket returns a read bucket by visiting both the new and the old
|
||||
// bucket.
|
||||
func fetchLogBucket(chanBucket kvdb.RBucket) (kvdb.RBucket, error) {
|
||||
logBucket := chanBucket.NestedReadBucket(revocationLogBucket)
|
||||
if logBucket == nil {
|
||||
logBucket = chanBucket.NestedReadBucket(
|
||||
revocationLogBucketDeprecated,
|
||||
)
|
||||
if logBucket == nil {
|
||||
return nil, ErrNoPastDeltas
|
||||
}
|
||||
}
|
||||
|
||||
return logBucket, nil
|
||||
}
|
||||
|
||||
// deleteLogBucket deletes the both the new and old revocation log buckets.
|
||||
func deleteLogBucket(chanBucket kvdb.RwBucket) error {
|
||||
// Check if the bucket exists and delete it.
|
||||
logBucket := chanBucket.NestedReadWriteBucket(
|
||||
revocationLogBucket,
|
||||
)
|
||||
if logBucket != nil {
|
||||
err := chanBucket.DeleteNestedBucket(revocationLogBucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// We also check whether the old revocation log bucket exists
|
||||
// and delete it if so.
|
||||
oldLogBucket := chanBucket.NestedReadWriteBucket(
|
||||
revocationLogBucketDeprecated,
|
||||
)
|
||||
if oldLogBucket != nil {
|
||||
err := chanBucket.DeleteNestedBucket(
|
||||
revocationLogBucketDeprecated,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,622 @@
|
||||
package channeldb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"math"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
"github.com/lightningnetwork/lnd/lntest/channels"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
// testType is used for creating a testing tlv record type. We use 10
|
||||
// here so it's easier to be recognized by its hex value, 0xa.
|
||||
testType tlv.Type = 10
|
||||
)
|
||||
|
||||
var (
|
||||
// testValue is used for creating tlv record.
|
||||
testValue = uint8(255) // 0xff
|
||||
|
||||
// testValueBytes is the tlv encoded testValue.
|
||||
testValueBytes = []byte{
|
||||
0x3, // total length = 3
|
||||
0xa, // type = 10
|
||||
0x1, // length = 1
|
||||
0xff, // value = 255
|
||||
}
|
||||
|
||||
testHTLCEntry = HTLCEntry{
|
||||
RefundTimeout: 740_000,
|
||||
OutputIndex: 10,
|
||||
Incoming: true,
|
||||
Amt: 1000_000,
|
||||
amtTlv: 1000_000,
|
||||
incomingTlv: 1,
|
||||
}
|
||||
testHTLCEntryBytes = []byte{
|
||||
// Body length 23.
|
||||
0x16,
|
||||
// Rhash tlv.
|
||||
0x0, 0x0,
|
||||
// RefundTimeout tlv.
|
||||
0x1, 0x4, 0x0, 0xb, 0x4a, 0xa0,
|
||||
// OutputIndex tlv.
|
||||
0x2, 0x2, 0x0, 0xa,
|
||||
// Incoming tlv.
|
||||
0x3, 0x1, 0x1,
|
||||
// Amt tlv.
|
||||
0x4, 0x5, 0xfe, 0x0, 0xf, 0x42, 0x40,
|
||||
}
|
||||
|
||||
testChannelCommit = ChannelCommitment{
|
||||
CommitHeight: 999,
|
||||
LocalBalance: lnwire.MilliSatoshi(9000),
|
||||
RemoteBalance: lnwire.MilliSatoshi(3000),
|
||||
CommitFee: btcutil.Amount(rand.Int63()),
|
||||
FeePerKw: btcutil.Amount(5000),
|
||||
CommitTx: channels.TestFundingTx,
|
||||
CommitSig: bytes.Repeat([]byte{1}, 71),
|
||||
Htlcs: []HTLC{{
|
||||
RefundTimeout: testHTLCEntry.RefundTimeout,
|
||||
OutputIndex: int32(testHTLCEntry.OutputIndex),
|
||||
Incoming: testHTLCEntry.Incoming,
|
||||
Amt: lnwire.NewMSatFromSatoshis(
|
||||
testHTLCEntry.Amt,
|
||||
),
|
||||
}},
|
||||
}
|
||||
|
||||
testRevocationLog = RevocationLog{
|
||||
OurOutputIndex: 0,
|
||||
TheirOutputIndex: 1,
|
||||
CommitTxHash: testChannelCommit.CommitTx.TxHash(),
|
||||
HTLCEntries: []*HTLCEntry{&testHTLCEntry},
|
||||
}
|
||||
testRevocationLogBytes = []byte{
|
||||
// Body length 42.
|
||||
0x2a,
|
||||
// OurOutputIndex tlv.
|
||||
0x0, 0x2, 0x0, 0x0,
|
||||
// TheirOutputIndex tlv.
|
||||
0x1, 0x2, 0x0, 0x1,
|
||||
// CommitTxHash tlv.
|
||||
0x2, 0x20,
|
||||
0x28, 0x76, 0x2, 0x59, 0x1d, 0x9d, 0x64, 0x86,
|
||||
0x6e, 0x60, 0x29, 0x23, 0x1d, 0x5e, 0xc5, 0xe6,
|
||||
0xbd, 0xf7, 0xd3, 0x9b, 0x16, 0x7d, 0x0, 0xff,
|
||||
0xc8, 0x22, 0x51, 0xb1, 0x5b, 0xa0, 0xbf, 0xd,
|
||||
}
|
||||
)
|
||||
|
||||
func TestWriteTLVStream(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a dummy tlv stream for testing.
|
||||
ts, err := tlv.NewStream(
|
||||
tlv.MakePrimitiveRecord(testType, &testValue),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Write the tlv stream.
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
err = writeTlvStream(buf, ts)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check the bytes are written as expected.
|
||||
require.Equal(t, testValueBytes, buf.Bytes())
|
||||
}
|
||||
|
||||
func TestReadTLVStream(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var valueRead uint8
|
||||
|
||||
// Create a dummy tlv stream for testing.
|
||||
ts, err := tlv.NewStream(
|
||||
tlv.MakePrimitiveRecord(testType, &valueRead),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Read the tlv stream.
|
||||
buf := bytes.NewBuffer(testValueBytes)
|
||||
err = readTlvStream(buf, ts)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check the bytes are read as expected.
|
||||
require.Equal(t, testValue, valueRead)
|
||||
}
|
||||
|
||||
func TestReadTLVStreamErr(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var valueRead uint8
|
||||
|
||||
// Create a dummy tlv stream for testing.
|
||||
ts, err := tlv.NewStream(
|
||||
tlv.MakePrimitiveRecord(testType, &valueRead),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Use empty bytes to cause an EOF.
|
||||
b := []byte{}
|
||||
|
||||
// Read the tlv stream.
|
||||
buf := bytes.NewBuffer(b)
|
||||
err = readTlvStream(buf, ts)
|
||||
require.ErrorIs(t, err, io.ErrUnexpectedEOF)
|
||||
|
||||
// Check the bytes are not read.
|
||||
require.Zero(t, valueRead)
|
||||
}
|
||||
|
||||
func TestSerializeHTLCEntriesEmptyRHash(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Copy the testHTLCEntry.
|
||||
entry := testHTLCEntry
|
||||
|
||||
// Set the internal fields to empty values so we can test the bytes are
|
||||
// padded.
|
||||
entry.incomingTlv = 0
|
||||
entry.amtTlv = 0
|
||||
|
||||
// Write the tlv stream.
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
err := serializeHTLCEntries(buf, []*HTLCEntry{&entry})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check the bytes are read as expected.
|
||||
require.Equal(t, testHTLCEntryBytes, buf.Bytes())
|
||||
}
|
||||
|
||||
func TestSerializeHTLCEntries(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Copy the testHTLCEntry.
|
||||
entry := testHTLCEntry
|
||||
|
||||
// Create a fake rHash.
|
||||
rHashBytes := bytes.Repeat([]byte{10}, 32)
|
||||
copy(entry.RHash[:], rHashBytes)
|
||||
|
||||
// Construct the serialized bytes.
|
||||
//
|
||||
// Exclude the first 3 bytes, which are total length, RHash type and
|
||||
// RHash length(0).
|
||||
partialBytes := testHTLCEntryBytes[3:]
|
||||
|
||||
// Write the total length and RHash tlv.
|
||||
expectedBytes := []byte{0x36, 0x0, 0x20}
|
||||
expectedBytes = append(expectedBytes, rHashBytes...)
|
||||
|
||||
// Append the rest.
|
||||
expectedBytes = append(expectedBytes, partialBytes...)
|
||||
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
err := serializeHTLCEntries(buf, []*HTLCEntry{&entry})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check the bytes are read as expected.
|
||||
require.Equal(t, expectedBytes, buf.Bytes())
|
||||
}
|
||||
|
||||
func TestSerializeRevocationLog(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Copy the testRevocationLog.
|
||||
rl := testRevocationLog
|
||||
|
||||
// Write the tlv stream.
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
err := serializeRevocationLog(buf, &rl)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check the expected bytes on the body of the revocation log.
|
||||
bodyIndex := buf.Len() - len(testHTLCEntryBytes)
|
||||
require.Equal(t, testRevocationLogBytes, buf.Bytes()[:bodyIndex])
|
||||
}
|
||||
|
||||
func TestDerializeHTLCEntriesEmptyRHash(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Read the tlv stream.
|
||||
buf := bytes.NewBuffer(testHTLCEntryBytes)
|
||||
htlcs, err := deserializeHTLCEntries(buf)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check the bytes are read as expected.
|
||||
require.Len(t, htlcs, 1)
|
||||
require.Equal(t, &testHTLCEntry, htlcs[0])
|
||||
}
|
||||
|
||||
func TestDerializeHTLCEntries(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Copy the testHTLCEntry.
|
||||
entry := testHTLCEntry
|
||||
|
||||
// Create a fake rHash.
|
||||
rHashBytes := bytes.Repeat([]byte{10}, 32)
|
||||
copy(entry.RHash[:], rHashBytes)
|
||||
|
||||
// Construct the serialized bytes.
|
||||
//
|
||||
// Exclude the first 3 bytes, which are total length, RHash type and
|
||||
// RHash length(0).
|
||||
partialBytes := testHTLCEntryBytes[3:]
|
||||
|
||||
// Write the total length and RHash tlv.
|
||||
testBytes := append([]byte{0x36, 0x0, 0x20}, rHashBytes...)
|
||||
|
||||
// Append the rest.
|
||||
testBytes = append(testBytes, partialBytes...)
|
||||
|
||||
// Read the tlv stream.
|
||||
buf := bytes.NewBuffer(testBytes)
|
||||
htlcs, err := deserializeHTLCEntries(buf)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check the bytes are read as expected.
|
||||
require.Len(t, htlcs, 1)
|
||||
require.Equal(t, &entry, htlcs[0])
|
||||
}
|
||||
|
||||
func TestDerializeRevocationLog(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Construct the full bytes.
|
||||
b := testRevocationLogBytes
|
||||
b = append(b, testHTLCEntryBytes...)
|
||||
|
||||
// Read the tlv stream.
|
||||
buf := bytes.NewBuffer(b)
|
||||
rl, err := deserializeRevocationLog(buf)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check the bytes are read as expected.
|
||||
require.Len(t, rl.HTLCEntries, 1)
|
||||
require.Equal(t, testRevocationLog, rl)
|
||||
}
|
||||
|
||||
func TestFetchLogBucket(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fullDB, cleanUp, err := MakeTestDB()
|
||||
require.NoError(t, err)
|
||||
defer cleanUp()
|
||||
|
||||
backend := fullDB.ChannelStateDB().backend
|
||||
|
||||
// Test that when neither of the buckets exists, an error is returned.
|
||||
err = kvdb.Update(backend, func(tx kvdb.RwTx) error {
|
||||
chanBucket, err := tx.CreateTopLevelBucket(openChannelBucket)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check an error is returned when there's no sub bucket.
|
||||
_, err = fetchLogBucket(chanBucket)
|
||||
return err
|
||||
}, func() {})
|
||||
require.ErrorIs(t, err, ErrNoPastDeltas)
|
||||
|
||||
// Test a successful fetch.
|
||||
err = kvdb.Update(backend, func(tx kvdb.RwTx) error {
|
||||
chanBucket, err := tx.CreateTopLevelBucket(openChannelBucket)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = chanBucket.CreateBucket(revocationLogBucket)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check an error is returned when there's no sub bucket.
|
||||
_, err = fetchLogBucket(chanBucket)
|
||||
return err
|
||||
}, func() {})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestDeleteLogBucket(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fullDB, cleanUp, err := MakeTestDB()
|
||||
require.NoError(t, err)
|
||||
defer cleanUp()
|
||||
|
||||
backend := fullDB.ChannelStateDB().backend
|
||||
|
||||
err = kvdb.Update(backend, func(tx kvdb.RwTx) error {
|
||||
// Create the buckets.
|
||||
chanBucket, _, err := createTestRevocatoinLogBuckets(tx)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create the buckets again should give us an error.
|
||||
_, _, err = createTestRevocatoinLogBuckets(tx)
|
||||
require.ErrorIs(t, err, kvdb.ErrBucketExists)
|
||||
|
||||
// Delete both buckets.
|
||||
err = deleteLogBucket(chanBucket)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create the buckets again should give us NO error.
|
||||
_, _, err = createTestRevocatoinLogBuckets(tx)
|
||||
return err
|
||||
}, func() {})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestPutRevocationLog(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a test commit that has a large htlc output index.
|
||||
testHtlc := HTLC{OutputIndex: math.MaxUint16 + 1}
|
||||
testCommit := testChannelCommit
|
||||
testCommit.Htlcs = []HTLC{testHtlc}
|
||||
|
||||
// Create a test commit that has a dust HTLC.
|
||||
testHtlcDust := HTLC{OutputIndex: -1}
|
||||
testCommitDust := testChannelCommit
|
||||
testCommitDust.Htlcs = append(testCommitDust.Htlcs, testHtlcDust)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
commit ChannelCommitment
|
||||
ourIndex uint32
|
||||
theirIndex uint32
|
||||
expectedErr error
|
||||
expectedLog RevocationLog
|
||||
}{
|
||||
{
|
||||
// Test a normal put operation.
|
||||
name: "successful put",
|
||||
commit: testChannelCommit,
|
||||
ourIndex: 0,
|
||||
theirIndex: 1,
|
||||
expectedErr: nil,
|
||||
expectedLog: testRevocationLog,
|
||||
},
|
||||
{
|
||||
// Test our index too big.
|
||||
name: "our index too big",
|
||||
commit: testChannelCommit,
|
||||
ourIndex: math.MaxUint16 + 1,
|
||||
theirIndex: 1,
|
||||
expectedErr: ErrOutputIndexTooBig,
|
||||
expectedLog: RevocationLog{},
|
||||
},
|
||||
{
|
||||
// Test their index too big.
|
||||
name: "their index too big",
|
||||
commit: testChannelCommit,
|
||||
ourIndex: 0,
|
||||
theirIndex: math.MaxUint16 + 1,
|
||||
expectedErr: ErrOutputIndexTooBig,
|
||||
expectedLog: RevocationLog{},
|
||||
},
|
||||
{
|
||||
// Test htlc output index too big.
|
||||
name: "htlc index too big",
|
||||
commit: testCommit,
|
||||
ourIndex: 0,
|
||||
theirIndex: 1,
|
||||
expectedErr: ErrOutputIndexTooBig,
|
||||
expectedLog: RevocationLog{},
|
||||
},
|
||||
{
|
||||
// Test dust htlc is not saved.
|
||||
name: "dust htlc not saved",
|
||||
commit: testCommitDust,
|
||||
ourIndex: 0,
|
||||
theirIndex: 1,
|
||||
expectedErr: nil,
|
||||
expectedLog: testRevocationLog,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
|
||||
fullDB, cleanUp, err := MakeTestDB()
|
||||
require.NoError(t, err)
|
||||
defer cleanUp()
|
||||
|
||||
backend := fullDB.ChannelStateDB().backend
|
||||
|
||||
// Construct the testing db transaction.
|
||||
dbTx := func(tx kvdb.RwTx) (RevocationLog, error) {
|
||||
// Create the buckets.
|
||||
_, bucket, err := createTestRevocatoinLogBuckets(tx)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Save the log.
|
||||
err = putRevocationLog(
|
||||
bucket, &tc.commit, tc.ourIndex, tc.theirIndex,
|
||||
)
|
||||
if err != nil {
|
||||
return RevocationLog{}, err
|
||||
}
|
||||
|
||||
// Read the saved log.
|
||||
return fetchRevocationLog(
|
||||
bucket, tc.commit.CommitHeight,
|
||||
)
|
||||
}
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var rl RevocationLog
|
||||
err := kvdb.Update(backend, func(tx kvdb.RwTx) error {
|
||||
record, err := dbTx(tx)
|
||||
rl = record
|
||||
return err
|
||||
}, func() {})
|
||||
|
||||
require.Equal(t, tc.expectedErr, err)
|
||||
require.Equal(t, tc.expectedLog, rl)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchRevocationLogCompatible(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
knownHeight := testChannelCommit.CommitHeight
|
||||
unknownHeight := knownHeight + 1
|
||||
logKey := makeLogKey(knownHeight)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
updateNum uint64
|
||||
expectedErr error
|
||||
createRl bool
|
||||
createCommit bool
|
||||
expectRl bool
|
||||
expectCommit bool
|
||||
}{
|
||||
{
|
||||
// Test we can fetch the new log.
|
||||
name: "fetch new log",
|
||||
updateNum: knownHeight,
|
||||
expectedErr: nil,
|
||||
createRl: true,
|
||||
expectRl: true,
|
||||
},
|
||||
{
|
||||
// Test we can fetch the legacy log.
|
||||
name: "fetch legacy log",
|
||||
updateNum: knownHeight,
|
||||
expectedErr: nil,
|
||||
createCommit: true,
|
||||
expectCommit: true,
|
||||
},
|
||||
{
|
||||
// Test we only fetch the new log when both logs exist.
|
||||
name: "fetch new log only",
|
||||
updateNum: knownHeight,
|
||||
expectedErr: nil,
|
||||
createRl: true,
|
||||
createCommit: true,
|
||||
expectRl: true,
|
||||
},
|
||||
{
|
||||
// Test no past deltas when the buckets do not exist.
|
||||
name: "no buckets created",
|
||||
updateNum: unknownHeight,
|
||||
expectedErr: ErrNoPastDeltas,
|
||||
},
|
||||
{
|
||||
// Test no logs found when the height is unknown.
|
||||
name: "no log found",
|
||||
updateNum: unknownHeight,
|
||||
expectedErr: ErrLogEntryNotFound,
|
||||
createRl: true,
|
||||
createCommit: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
|
||||
fullDB, cleanUp, err := MakeTestDB()
|
||||
require.NoError(t, err)
|
||||
defer cleanUp()
|
||||
|
||||
backend := fullDB.ChannelStateDB().backend
|
||||
|
||||
var (
|
||||
rl *RevocationLog
|
||||
commit *ChannelCommitment
|
||||
)
|
||||
|
||||
// Setup the buckets and fill the test data if specified.
|
||||
err = kvdb.Update(backend, func(tx kvdb.RwTx) error {
|
||||
// Create the root bucket.
|
||||
cb, err := tx.CreateTopLevelBucket(openChannelBucket)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create the revocation log if specified.
|
||||
if tc.createRl {
|
||||
lb, err := cb.CreateBucket(revocationLogBucket)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = putRevocationLog(
|
||||
lb, &testChannelCommit, 0, 1,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// Create the channel commit if specified.
|
||||
if tc.createCommit {
|
||||
legacyBucket, err := cb.CreateBucket(
|
||||
revocationLogBucketDeprecated,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
err = serializeChanCommit(
|
||||
buf, &testChannelCommit,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = legacyBucket.Put(logKey[:], buf.Bytes())
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}, func() {})
|
||||
|
||||
// Construct the testing db transaction.
|
||||
dbTx := func(tx kvdb.RTx) error {
|
||||
cb := tx.ReadBucket(openChannelBucket)
|
||||
|
||||
rl, commit, err = fetchRevocationLogCompatible(
|
||||
cb, tc.updateNum,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := kvdb.View(backend, dbTx, func() {})
|
||||
require.Equal(t, tc.expectedErr, err)
|
||||
|
||||
// Check the expected revocation log is returned.
|
||||
if tc.expectRl {
|
||||
require.NotNil(t, rl)
|
||||
} else {
|
||||
require.Nil(t, rl)
|
||||
}
|
||||
|
||||
// Check the expected channel commit is returned.
|
||||
if tc.expectCommit {
|
||||
require.NotNil(t, commit)
|
||||
} else {
|
||||
require.Nil(t, commit)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func createTestRevocatoinLogBuckets(tx kvdb.RwTx) (kvdb.RwBucket,
|
||||
kvdb.RwBucket, error) {
|
||||
|
||||
chanBucket, err := tx.CreateTopLevelBucket(openChannelBucket)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
logBucket, err := chanBucket.CreateBucket(revocationLogBucket)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
_, err = chanBucket.CreateBucket(revocationLogBucketDeprecated)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return chanBucket, logBucket, nil
|
||||
}
|
||||
Reference in new issue
Block a user