channeldb: add balance at height lookup

Add a balance at height lookup function which can be used to
obtain local/remote balance at a given height. The current in memory
commits and revocation log are used to source this information.
This commit is contained in:
carla 2020-03-19 11:00:53 +02:00
parent 4897b34050
commit 41355756a1
No known key found for this signature in database
GPG Key ID: 4CA7FE54A6213C91
3 changed files with 236 additions and 1 deletions

@ -144,6 +144,15 @@ var (
// ErrChanBorked is returned when a caller attempts to mutate a borked
// 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")
// 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")
)
// ChannelType is an enum-like type that describes one of several possible
@ -1391,6 +1400,44 @@ 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
}
// HTLC is the on-disk representation of a hash time-locked contract. HTLCs are
// contained within ChannelDeltas which encode the current state of the
// commitment between state updates.
@ -3160,7 +3207,7 @@ func fetchChannelLogEntry(log kvdb.ReadBucket,
logEntrykey := makeLogKey(updateNum)
commitBytes := log.Get(logEntrykey[:])
if commitBytes == nil {
return ChannelCommitment{}, fmt.Errorf("log entry not found")
return ChannelCommitment{}, errLogEntryNotFound
}
commitReader := bytes.NewReader(commitBytes)

@ -10,6 +10,8 @@ import (
"runtime"
"testing"
"github.com/lightningnetwork/lnd/channeldb/kvdb"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
@ -131,6 +133,25 @@ 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 {
@ -1393,3 +1414,169 @@ func TestCloseChannelStatus(t *testing.T) {
t.Fatalf("channel should have status")
}
}
// 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, 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)
})
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()
cdb, cleanUp, err := makeTestDB()
if err != nil {
t.Fatalf("unable to make test database: %v",
err)
}
defer cleanUp()
// 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)
}
})
}
}

1
go.mod

@ -14,6 +14,7 @@ require (
github.com/btcsuite/btcwallet/walletdb v1.2.0
github.com/btcsuite/btcwallet/wtxmgr v1.0.0
github.com/btcsuite/fastsha256 v0.0.0-20160815193821-637e65642941
github.com/coreos/bbolt v1.3.3
github.com/davecgh/go-spew v1.1.1
github.com/go-errors/errors v1.0.1
github.com/golang/protobuf v1.3.1