Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

multi+server.go: add initial permissions for some peers #9458

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
30 changes: 30 additions & 0 deletions channeldb/channel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ type testChannelParams struct {
// openChannel is set to true if the channel should be fully marked as
// open if this is false, the channel will be left in pending state.
openChannel bool

// closedChannel is set to true if the channel should be marked as
// closed after opening it.
closedChannel bool
}

// testChannelOption is a functional option which can be used to alter the
Expand All @@ -129,6 +133,21 @@ func openChannelOption() testChannelOption {
}
}

// closedChannelOption is an option which can be used to create a test channel
// that is closed.
func closedChannelOption() testChannelOption {
return func(params *testChannelParams) {
params.closedChannel = true
}
}

// pubKeyOption is an option which can be used to set the remote's pubkey.
func pubKeyOption(pubKey *btcec.PublicKey) testChannelOption {
return func(params *testChannelParams) {
params.channel.IdentityPub = pubKey
}
}

// localHtlcsOption is an option which allows setting of htlcs on the local
// commitment.
func localHtlcsOption(htlcs []HTLC) testChannelOption {
Expand Down Expand Up @@ -231,6 +250,17 @@ func createTestChannel(t *testing.T, cdb *ChannelStateDB,
err = params.channel.MarkAsOpen(params.channel.ShortChannelID)
require.NoError(t, err, "unable to mark channel open")

if params.closedChannel {
// Set the other public keys so that serialization doesn't
// panic.
err = params.channel.CloseChannel(&ChannelCloseSummary{
RemotePub: params.channel.IdentityPub,
RemoteCurrentRevocation: params.channel.IdentityPub,
RemoteNextRevocation: params.channel.IdentityPub,
})
require.NoError(t, err, "unable to close channel")
}

return params.channel
}

Expand Down
188 changes: 188 additions & 0 deletions channeldb/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,194 @@ func (c *ChannelStateDB) FetchChannelByID(tx kvdb.RTx, id lnwire.ChannelID) (
return c.channelScanner(tx, selector)
}

// ChanCount is used by the server in determining access control.
type ChanCount struct {
HasOpenOrClosedChan bool
PendingOpenCount uint64
}

// FetchPermAndTempPeers returns a map where the key is the remote node's
// public key and the value is a struct that has a tally of the pending-open
// channels and whether the peer has an open or closed channel with us.
func (c *ChannelStateDB) FetchPermAndTempPeers(
chainHash []byte) (map[string]ChanCount, error) {

peerCounts := make(map[string]ChanCount)

err := kvdb.View(c.backend, func(tx kvdb.RTx) error {
openChanBucket := tx.ReadBucket(openChannelBucket)
if openChanBucket == nil {
return ErrNoChanDBExists
}

openChanErr := openChanBucket.ForEach(func(nodePub,
v []byte) error {

// If there is a value, this is not a bucket.
if v != nil {
return nil
}

nodeChanBucket := openChanBucket.NestedReadBucket(
nodePub,
)
if nodeChanBucket == nil {
return nil
}

chainBucket := nodeChanBucket.NestedReadBucket(
chainHash,
)
if chainBucket == nil {
return fmt.Errorf("no chain bucket exists")
}

var isPermPeer bool
var pendingOpenCount uint64

internalErr := chainBucket.ForEach(func(chanPoint,
val []byte) error {

// If there is a value, this is not a bucket.
if val != nil {
return nil
}

chanBucket := chainBucket.NestedReadBucket(
chanPoint,
)
if chanBucket == nil {
return nil
}

var op wire.OutPoint
readErr := graphdb.ReadOutpoint(
bytes.NewReader(chanPoint), &op,
)
if readErr != nil {
return readErr
}

// We need to go through each channel and look
// at the IsPending status.
openChan, err := fetchOpenChannel(
chanBucket, &op,
)
if err != nil {
return err
}

if openChan.IsPending {
// Add to the pending-open count since
// this is a temp peer.
pendingOpenCount++
return nil
}

// Since IsPending is false, this is a perm
// peer.
isPermPeer = true

return nil
})
if internalErr != nil {
return internalErr
}

peerCount := ChanCount{
HasOpenOrClosedChan: isPermPeer,
PendingOpenCount: pendingOpenCount,
}
peerCounts[string(nodePub)] = peerCount

return nil
})
if openChanErr != nil {
return openChanErr
}

// Now check the closed channel bucket.
historicalChanBucket := tx.ReadBucket(historicalChannelBucket)
if historicalChanBucket == nil {
return ErrNoHistoricalBucket
}

historicalErr := historicalChanBucket.ForEach(func(chanPoint,
v []byte) error {
// Parse each nested bucket and the chanInfoKey to get
// the IsPending bool. This determines whether the
// peer is protected or not.
if v != nil {
// This is not a bucket. This is currently not
// possible.
return nil
}

chanBucket := historicalChanBucket.NestedReadBucket(
chanPoint,
)
if chanBucket == nil {
// This is not possible.
return fmt.Errorf("no historical channel " +
"bucket exists")
}

var op wire.OutPoint
readErr := graphdb.ReadOutpoint(
bytes.NewReader(chanPoint), &op,
)
if readErr != nil {
return readErr
}

// This channel is closed, but the structure of the
// historical bucket is the same. This is by design,
// which means we can call fetchOpenChannel.
channel, fetchErr := fetchOpenChannel(chanBucket, &op)
if fetchErr != nil {
return fetchErr
}

// Only include this peer in the protected class if
// the closing transaction confirmed. Note that
// CloseChannel can be called in the funding manager
// while IsPending is true which is why we need this
// special-casing to not count premature funding
// manager calls to CloseChannel.
if !channel.IsPending {
// Fetch the public key of the remote node. We
// need to use the string-ified serialized,
// compressed bytes as the key.
remotePub := channel.IdentityPub
remoteSer := remotePub.SerializeCompressed()
remoteKey := string(remoteSer)

count, exists := peerCounts[remoteKey]
if exists {
count.HasOpenOrClosedChan = true
peerCounts[remoteKey] = count
} else {
peerCount := ChanCount{
HasOpenOrClosedChan: true,
}
peerCounts[remoteKey] = peerCount
}
}

return nil
})
if historicalErr != nil {
return historicalErr
}

return nil
}, func() {
clear(peerCounts)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we want to have isPermPeer = false here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice catch

})

return peerCounts, err
}

// channelSelector describes a function that takes a chain-hash bucket from
// within the open-channel DB and returns the wanted channel point bytes, and
// channel point. It must return the ErrChannelNotFound error if the wanted
Expand Down
85 changes: 85 additions & 0 deletions channeldb/db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,91 @@ func TestFetchHistoricalChannel(t *testing.T) {
}
}

// TestFetchPermTempPeer tests that we're able to call FetchPermAndTempPeers
// successfully.
func TestFetchPermTempPeer(t *testing.T) {
t.Parallel()

fullDB, err := MakeTestDB(t)
require.NoError(t, err, "unable to make test database")

cdb := fullDB.ChannelStateDB()

// Create an open channel.
privKey1, err := btcec.NewPrivateKey()
require.NoError(t, err, "unable to generate new private key")

pubKey1 := privKey1.PubKey()

channelState1 := createTestChannel(
t, cdb, openChannelOption(), pubKeyOption(pubKey1),
)

// Next, assert that the channel exists in the database.
_, err = cdb.FetchChannel(channelState1.FundingOutpoint)
require.NoError(t, err, "unable to fetch channel")

// Create a pending channel.
privKey2, err := btcec.NewPrivateKey()
require.NoError(t, err, "unable to generate private key")

pubKey2 := privKey2.PubKey()
channelState2 := createTestChannel(t, cdb, pubKeyOption(pubKey2))

// Assert that the channel exists in the database.
_, err = cdb.FetchChannel(channelState2.FundingOutpoint)
require.NoError(t, err, "unable to fetch channel")

// Create a closed channel.
privKey3, err := btcec.NewPrivateKey()
require.NoError(t, err, "unable to generate new private key")

pubKey3 := privKey3.PubKey()

_ = createTestChannel(
t, cdb, pubKeyOption(pubKey3), openChannelOption(),
closedChannelOption(),
)

// Fetch the ChanCount for our peers.
peerCounts, err := cdb.FetchPermAndTempPeers(key[:])
require.NoError(t, err, "unable to fetch perm and temp peers")

// There should only be three entries.
require.Len(t, peerCounts, 3)

// The first entry should have OpenClosed set to true and Pending set
// to 0.
count1, found := peerCounts[string(pubKey1.SerializeCompressed())]
require.True(t, found, "unable to find peer 1 in peerCounts")
require.True(
t, count1.HasOpenOrClosedChan,
"couldn't find peer 1's channels",
)
require.Zero(
t, count1.PendingOpenCount,
"peer 1 doesn't have 0 pending-open",
)

count2, found := peerCounts[string(pubKey2.SerializeCompressed())]
require.True(t, found, "unable to find peer 2 in peerCounts")
require.False(
t, count2.HasOpenOrClosedChan, "found erroneous channels",
)
require.Equal(t, uint64(1), count2.PendingOpenCount)

count3, found := peerCounts[string(pubKey3.SerializeCompressed())]
require.True(t, found, "unable to find peer 3 in peerCounts")
require.True(
t, count3.HasOpenOrClosedChan,
"couldn't find peer 3's channels",
)
require.Zero(
t, count3.PendingOpenCount,
"peer 3 doesn't have 0 pending-open",
)
}

func createLightningNode(priv *btcec.PrivateKey) *models.LightningNode {
updateTime := rand.Int63()

Expand Down