channeldb: prep waiting proof store for taproot proofs

With a migration to migrate existing entries in the waiting proof store
to use a type byte prefix.
This commit is contained in:
Elle Mouton 2023-11-06 13:55:05 +02:00
parent 2a4f36397d
commit c0b66d76df
No known key found for this signature in database
GPG Key ID: D7D916376026F177
9 changed files with 528 additions and 44 deletions

View File

@ -26,6 +26,7 @@ import (
"github.com/lightningnetwork/lnd/channeldb/migration29"
"github.com/lightningnetwork/lnd/channeldb/migration30"
"github.com/lightningnetwork/lnd/channeldb/migration31"
"github.com/lightningnetwork/lnd/channeldb/migration32"
"github.com/lightningnetwork/lnd/channeldb/migration_01_to_11"
"github.com/lightningnetwork/lnd/clock"
"github.com/lightningnetwork/lnd/invoices"
@ -286,6 +287,10 @@ var (
number: 31,
migration: migration31.DeleteLastPublishedTxTLB,
},
{
number: 32,
migration: migration32.MigrateWaitingProofStore,
},
}
// optionalVersions stores all optional migrations that are applied

View File

@ -10,6 +10,7 @@ import (
"github.com/lightningnetwork/lnd/channeldb/migration24"
"github.com/lightningnetwork/lnd/channeldb/migration30"
"github.com/lightningnetwork/lnd/channeldb/migration31"
"github.com/lightningnetwork/lnd/channeldb/migration32"
"github.com/lightningnetwork/lnd/channeldb/migration_01_to_11"
"github.com/lightningnetwork/lnd/kvdb"
)
@ -42,5 +43,6 @@ func UseLogger(logger btclog.Logger) {
migration24.UseLogger(logger)
migration30.UseLogger(logger)
migration31.UseLogger(logger)
migration32.UseLogger(logger)
kvdb.UseLogger(logger)
}

View File

@ -0,0 +1,14 @@
package migration32
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
}

View File

@ -0,0 +1,55 @@
package migration32
import (
"bytes"
"fmt"
"github.com/lightningnetwork/lnd/kvdb"
)
// waitingProofsBucketKey byte string name of the waiting proofs store.
var waitingProofsBucketKey = []byte("waitingproofs")
// MigrateWaitingProofStore migrates the waiting proof store so that all entries
// are prefixed with a type byte.
func MigrateWaitingProofStore(tx kvdb.RwTx) error {
log.Infof("Migrating waiting proof store")
bucket := tx.ReadWriteBucket(waitingProofsBucketKey)
// If the bucket does not exist yet, then there are no entries to
// migrate.
if bucket == nil {
return nil
}
return bucket.ForEach(func(k, v []byte) error {
// Skip buckets fields.
if v == nil {
return nil
}
// Read in the waiting proof using the legacy decoding method.
var proof WaitingProof
if err := proof.LegacyDecode(bytes.NewReader(v)); err != nil {
return err
}
// Do sanity check to ensure that the proof key is the same as
// the key used to store the proof.
key := proof.Key()
if !bytes.Equal(key[:], k) {
return fmt.Errorf("proof key (%x) does not match "+
"the key used to store the proof: %x", key, k)
}
// Re-encode the proof using the new, type-prefixed encoding.
var b bytes.Buffer
err := proof.UpdatedEncode(&b)
if err != nil {
return err
}
return bucket.Put(k, b.Bytes())
})
}

View File

@ -0,0 +1,100 @@
package migration32
import (
"bytes"
"encoding/hex"
"testing"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
"github.com/lightningnetwork/lnd/channeldb/migtest"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/stretchr/testify/require"
)
var (
testRBytes, _ = hex.DecodeString("8ce2bc69281ce27da07e6683571" +
"319d18e949ddfa2965fb6caa1bf0314f882d7")
testSBytes, _ = hex.DecodeString("299105481d63e0f4bc2a" +
"88121167221b6700d72a0ead154c03be696a292d24ae")
testRScalar = new(btcec.ModNScalar)
testSScalar = new(btcec.ModNScalar)
_ = testRScalar.SetByteSlice(testRBytes)
_ = testSScalar.SetByteSlice(testSBytes)
testSig = ecdsa.NewSignature(testRScalar, testSScalar)
sig, _ = lnwire.NewSigFromSignature(testSig)
wp1 = &WaitingProof{
AnnounceSignatures: &lnwire.AnnounceSignatures{
ChannelID: lnwire.ChannelID{1},
ShortChannelID: lnwire.NewShortChanIDFromInt(1),
NodeSignature: sig,
BitcoinSignature: sig,
ExtraOpaqueData: []byte{1, 2, 3, 4},
},
isRemote: false,
}
wp2 = &WaitingProof{
AnnounceSignatures: &lnwire.AnnounceSignatures{
ChannelID: lnwire.ChannelID{2},
ShortChannelID: lnwire.NewShortChanIDFromInt(2),
NodeSignature: sig,
BitcoinSignature: sig,
},
isRemote: true,
}
)
// TestMigrationWaitingProofStore tests that the MigrateWaitingProofStore
// function works as expected.
func TestMigrateWaitingProofStore(t *testing.T) {
var (
key1 = wp1.Key()
key2 = wp2.Key()
wp1BytesBefore bytes.Buffer
wp2BytesBefore bytes.Buffer
wp1BytesAfter bytes.Buffer
wp2BytesAfter bytes.Buffer
)
err := wp1.LegacyEncode(&wp1BytesBefore)
require.NoError(t, err)
err = wp2.LegacyEncode(&wp2BytesBefore)
require.NoError(t, err)
wpStoreBefore := map[string]interface{}{
string(key1[:]): wp1BytesBefore.String(),
string(key2[:]): wp2BytesBefore.String(),
}
err = wp1.UpdatedEncode(&wp1BytesAfter)
require.NoError(t, err)
err = wp2.UpdatedEncode(&wp2BytesAfter)
require.NoError(t, err)
wpStoreAfter := map[string]interface{}{
string(key1[:]): wp1BytesAfter.String(),
string(key2[:]): wp2BytesAfter.String(),
}
before := func(tx kvdb.RwTx) error {
return migtest.RestoreDB(
tx, waitingProofsBucketKey, wpStoreBefore,
)
}
after := func(tx kvdb.RwTx) error {
return migtest.VerifyDB(
tx, waitingProofsBucketKey, wpStoreAfter,
)
}
migtest.ApplyMigration(
t, before, after, MigrateWaitingProofStore, false,
)
}

View File

@ -0,0 +1,100 @@
package migration32
import (
"bytes"
"encoding/binary"
"fmt"
"io"
lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
)
var byteOrder = binary.BigEndian
// WaitingProofType represents the type of the encoded waiting proof.
type WaitingProofType uint8
const (
// WaitingProofTypeLegacy represents a waiting proof for legacy P2WSH
// channels.
WaitingProofTypeLegacy WaitingProofType = 0
)
// WaitingProofKey is the proof key which uniquely identifies the waiting
// proof object. The goal of this key is distinguish the local and remote
// proof for the same channel id.
type WaitingProofKey [9]byte
// WaitingProof is the storable object, which encapsulate the half proof and
// the information about from which side this proof came. This structure is
// needed to make channel proof exchange persistent, so that after client
// restart we may receive remote/local half proof and process it.
type WaitingProof struct {
*lnwire.AnnounceSignatures
isRemote bool
}
// Key returns the key which uniquely identifies waiting proof.
func (p *WaitingProof) Key() WaitingProofKey {
var key [9]byte
binary.BigEndian.PutUint64(key[:8], p.ShortChannelID.ToUint64())
if p.isRemote {
key[8] = 1
}
return key
}
// UpdatedEncode writes the internal representation of waiting proof in byte
// stream using the new format that is prefixed with a type byte.
func (p *WaitingProof) UpdatedEncode(w io.Writer) error {
// Write the type byte.
err := binary.Write(w, byteOrder, WaitingProofTypeLegacy)
if err != nil {
return err
}
if err := binary.Write(w, byteOrder, p.isRemote); err != nil {
return err
}
buf, ok := w.(*bytes.Buffer)
if !ok {
return fmt.Errorf("expect io.Writer to be *bytes.Buffer")
}
return p.AnnounceSignatures.Encode(buf, 0)
}
// LegacyEncode writes the internal representation of waiting proof in byte
// stream using the legacy format.
func (p *WaitingProof) LegacyEncode(w io.Writer) error {
if err := binary.Write(w, byteOrder, p.isRemote); err != nil {
return err
}
buf, ok := w.(*bytes.Buffer)
if !ok {
return fmt.Errorf("expect io.Writer to be *bytes.Buffer")
}
return p.AnnounceSignatures.Encode(buf, 0)
}
// LegacyDecode reads the data from the byte stream and initializes the
// waiting proof object with it.
func (p *WaitingProof) LegacyDecode(r io.Reader) error {
if err := binary.Read(r, byteOrder, &p.isRemote); err != nil {
return err
}
msg := &lnwire.AnnounceSignatures{}
if err := msg.Decode(r, 0); err != nil {
return err
}
p.AnnounceSignatures = msg
return nil
}

View File

@ -7,6 +7,7 @@ import (
"io"
"sync"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnwire"
@ -20,11 +21,6 @@ var (
// found by db.
ErrWaitingProofNotFound = errors.New("waiting proofs haven't been " +
"found")
// ErrWaitingProofAlreadyExist is returned if waiting proofs haven't been
// found by db.
ErrWaitingProofAlreadyExist = errors.New("waiting proof with such " +
"key already exist")
)
// WaitingProofStore is the bold db map-like storage for half announcement
@ -186,29 +182,191 @@ func (s *WaitingProofStore) Get(key WaitingProofKey) (*WaitingProof, error) {
// proof for the same channel id.
type WaitingProofKey [9]byte
// WaitingProofType represents the type of the encoded waiting proof.
type WaitingProofType uint8
const (
// WaitingProofTypeLegacy represents a waiting proof for legacy P2WSH
// channels.
WaitingProofTypeLegacy WaitingProofType = 0
// WaitingProofTypeTaproot represents a waiting proof for taproot
// channels.
WaitingProofTypeTaproot WaitingProofType = 1
)
// typeToWaitingProofType is a map from WaitingProofType to an empty
// instantiation of the associated type.
func typeToWaitingProof(pt WaitingProofType) (WaitingProofInterface, bool) {
switch pt {
case WaitingProofTypeLegacy:
return &LegacyWaitingProof{}, true
case WaitingProofTypeTaproot:
return &TaprootWaitingProof{}, true
default:
return nil, false
}
}
// WaitingProofInterface is an interface that must be implemented by any waiting
// proof to be stored in the waiting proof DB.
type WaitingProofInterface interface {
// SCID returns the short channel ID of the channel that the waiting
// proof is for.
SCID() lnwire.ShortChannelID
// Encode encodes the waiting proof to the given buffer.
Encode(w *bytes.Buffer, pver uint32) error
// Decode parses the bytes from the given reader to reconstruct the
// waiting proof.
Decode(r io.Reader, pver uint32) error
// Type returns the waiting proof type.
Type() WaitingProofType
}
// LegacyWaitingProof is an implementation of the WaitingProofInterface to be
// used for legacy, P2WSH channels.
type LegacyWaitingProof struct {
lnwire.AnnounceSignatures1
}
// SCID returns the short channel ID of the channel that the waiting
// proof is for.
//
// NOTE: this is part of the WaitingProofInterface.
func (l *LegacyWaitingProof) SCID() lnwire.ShortChannelID {
return l.ShortChannelID
}
// Type returns the waiting proof type.
//
// NOTE: this is part of the WaitingProofInterface.
func (l *LegacyWaitingProof) Type() WaitingProofType {
return WaitingProofTypeLegacy
}
var _ WaitingProofInterface = (*LegacyWaitingProof)(nil)
// TaprootWaitingProof is an implementation of the WaitingProofInterface to be
// used for taproot channels.
type TaprootWaitingProof struct {
lnwire.AnnounceSignatures2
// AggNonce is the aggregate nonce used to construct the partial
// signatures. It will be used as the R value in the final signature.
AggNonce *btcec.PublicKey
}
// SCID returns the short channel ID of the channel that the waiting
// proof is for.
//
// NOTE: this is part of the WaitingProofInterface.
func (t *TaprootWaitingProof) SCID() lnwire.ShortChannelID {
return t.ShortChannelID
}
// Decode parses the bytes from the given reader to reconstruct the
// waiting proof.
//
// NOTE: this is part of the WaitingProofInterface.
func (t *TaprootWaitingProof) Decode(r io.Reader, pver uint32) error {
// Read byte to see if agg nonce is present.
var aggNoncePresent bool
if err := binary.Read(r, byteOrder, &aggNoncePresent); err != nil {
return err
}
// If agg nonce is present, read it in.
if aggNoncePresent {
var nonceBytes [btcec.PubKeyBytesLenCompressed]byte
if err := binary.Read(r, byteOrder, &nonceBytes); err != nil {
return err
}
nonce, err := btcec.ParsePubKey(nonceBytes[:])
if err != nil {
return err
}
t.AggNonce = nonce
}
return t.AnnounceSignatures2.Decode(r, pver)
}
// Encode encodes the waiting proof to the given buffer.
//
// NOTE: this is part of the WaitingProofInterface.
func (t *TaprootWaitingProof) Encode(w *bytes.Buffer, pver uint32) error {
// If agg nonce is present, write a signaling byte for that.
aggNoncePresent := t.AggNonce != nil
if err := binary.Write(w, byteOrder, aggNoncePresent); err != nil {
return err
}
// Now follow with the actual nonce if present.
if aggNoncePresent {
err := binary.Write(
w, byteOrder, t.AggNonce.SerializeCompressed(),
)
if err != nil {
return err
}
}
return t.AnnounceSignatures2.Encode(w, pver)
}
// Type returns the waiting proof type.
//
// NOTE: this is part of the WaitingProofInterface.
func (t *TaprootWaitingProof) Type() WaitingProofType {
return WaitingProofTypeTaproot
}
var _ WaitingProofInterface = (*TaprootWaitingProof)(nil)
// WaitingProof is the storable object, which encapsulate the half proof and
// the information about from which side this proof came. This structure is
// needed to make channel proof exchange persistent, so that after client
// restart we may receive remote/local half proof and process it.
type WaitingProof struct {
*lnwire.AnnounceSignatures1
WaitingProofInterface
isRemote bool
}
// NewWaitingProof constructs a new waiting prof instance.
func NewWaitingProof(isRemote bool,
// NewLegacyWaitingProof constructs a new waiting prof instance for a legacy,
// P2WSH channel.
func NewLegacyWaitingProof(isRemote bool,
proof *lnwire.AnnounceSignatures1) *WaitingProof {
return &WaitingProof{
AnnounceSignatures1: proof,
isRemote: isRemote,
WaitingProofInterface: &LegacyWaitingProof{*proof},
isRemote: isRemote,
}
}
// NewTaprootWaitingProof constructs a new waiting prof instance for a taproot
// channel.
func NewTaprootWaitingProof(isRemote bool, proof *lnwire.AnnounceSignatures2,
aggNonce *btcec.PublicKey) *WaitingProof {
return &WaitingProof{
WaitingProofInterface: &TaprootWaitingProof{
AnnounceSignatures2: *proof,
AggNonce: aggNonce,
},
isRemote: isRemote,
}
}
// OppositeKey returns the key which uniquely identifies opposite waiting proof.
func (p *WaitingProof) OppositeKey() WaitingProofKey {
var key [9]byte
binary.BigEndian.PutUint64(key[:8], p.ShortChannelID.ToUint64())
binary.BigEndian.PutUint64(key[:8], p.SCID().ToUint64())
if !p.isRemote {
key[8] = 1
@ -219,7 +377,7 @@ func (p *WaitingProof) OppositeKey() WaitingProofKey {
// Key returns the key which uniquely identifies waiting proof.
func (p *WaitingProof) Key() WaitingProofKey {
var key [9]byte
binary.BigEndian.PutUint64(key[:8], p.ShortChannelID.ToUint64())
binary.BigEndian.PutUint64(key[:8], p.SCID().ToUint64())
if p.isRemote {
key[8] = 1
@ -229,6 +387,11 @@ func (p *WaitingProof) Key() WaitingProofKey {
// Encode writes the internal representation of waiting proof in byte stream.
func (p *WaitingProof) Encode(w io.Writer) error {
// Write the type byte.
if err := binary.Write(w, byteOrder, p.Type()); err != nil {
return err
}
if err := binary.Write(w, byteOrder, p.isRemote); err != nil {
return err
}
@ -240,26 +403,31 @@ func (p *WaitingProof) Encode(w io.Writer) error {
return fmt.Errorf("expect io.Writer to be *bytes.Buffer")
}
if err := p.AnnounceSignatures1.Encode(buf, 0); err != nil {
return err
}
return nil
return p.WaitingProofInterface.Encode(buf, 0)
}
// Decode reads the data from the byte stream and initializes the
// waiting proof object with it.
func (p *WaitingProof) Decode(r io.Reader) error {
var proofType WaitingProofType
if err := binary.Read(r, byteOrder, &proofType); err != nil {
return err
}
if err := binary.Read(r, byteOrder, &p.isRemote); err != nil {
return err
}
msg := &lnwire.AnnounceSignatures1{}
if err := msg.Decode(r, 0); err != nil {
proof, ok := typeToWaitingProof(proofType)
if !ok {
return fmt.Errorf("unknown proof type")
}
if err := proof.Decode(r, 0); err != nil {
return err
}
p.AnnounceSignatures1 = msg
p.WaitingProofInterface = proof
return nil
}

View File

@ -1,10 +1,10 @@
package channeldb
import (
"reflect"
"math/rand"
"testing"
"github.com/davecgh/go-spew/spew"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/stretchr/testify/require"
@ -18,35 +18,54 @@ func TestWaitingProofStore(t *testing.T) {
db, err := MakeTestDB(t)
require.NoError(t, err, "failed to make test database")
proof1 := NewWaitingProof(true, &lnwire.AnnounceSignatures1{
proof1 := NewLegacyWaitingProof(true, &lnwire.AnnounceSignatures1{
NodeSignature: wireSig,
BitcoinSignature: wireSig,
ExtraOpaqueData: make([]byte, 0),
})
// No agg nonce.
proof2 := NewTaprootWaitingProof(true, &lnwire.AnnounceSignatures2{
ShortChannelID: lnwire.ShortChannelID{
BlockHeight: 2000,
},
PartialSignature: *randPartialSig(t),
ExtraOpaqueData: make([]byte, 0),
}, nil)
// With agg nonce.
priv, err := btcec.NewPrivateKey()
require.NoError(t, err)
proof3 := NewTaprootWaitingProof(true, &lnwire.AnnounceSignatures2{
ShortChannelID: lnwire.ShortChannelID{
BlockHeight: 2000,
},
PartialSignature: *randPartialSig(t),
ExtraOpaqueData: make([]byte, 0),
}, priv.PubKey())
proofs := []*WaitingProof{
proof1,
proof2,
proof3,
}
store, err := NewWaitingProofStore(db)
if err != nil {
t.Fatalf("unable to create the waiting proofs storage: %v",
err)
}
require.NoError(t, err)
if err := store.Add(proof1); err != nil {
t.Fatalf("unable add proof to storage: %v", err)
}
for _, proof := range proofs {
require.NoError(t, store.Add(proof))
proof2, err := store.Get(proof1.Key())
require.NoError(t, err, "unable retrieve proof from storage")
if !reflect.DeepEqual(proof1, proof2) {
t.Fatalf("wrong proof retrieved: expected %v, got %v",
spew.Sdump(proof1), spew.Sdump(proof2))
}
p2, err := store.Get(proof.Key())
require.NoError(t, err, "unable retrieve proof from storage")
require.Equal(t, proof, p2)
if _, err := store.Get(proof1.OppositeKey()); err != ErrWaitingProofNotFound {
t.Fatalf("proof shouldn't be found: %v", err)
}
_, err = store.Get(proof.OppositeKey())
require.ErrorIs(t, err, ErrWaitingProofNotFound)
if err := store.Remove(proof1.Key()); err != nil {
t.Fatalf("unable remove proof from storage: %v", err)
err = store.Remove(proof.Key())
require.NoError(t, err)
}
if err := store.ForAll(func(proof *WaitingProof) error {
@ -55,3 +74,16 @@ func TestWaitingProofStore(t *testing.T) {
t.Fatal(err)
}
}
func randPartialSig(t *testing.T) *lnwire.PartialSig {
var sigBytes [32]byte
_, err := rand.Read(sigBytes[:])
require.NoError(t, err)
var s btcec.ModNScalar
s.SetByteSlice(sigBytes[:])
return &lnwire.PartialSig{
Sig: s,
}
}

View File

@ -3347,7 +3347,7 @@ func (d *AuthenticatedGossiper) handleAnnSig(nMsg *networkMsg,
return nil, false
}
proof := channeldb.NewWaitingProof(nMsg.isRemote, ann)
proof := channeldb.NewLegacyWaitingProof(nMsg.isRemote, ann)
err := d.cfg.WaitingProofStore.Add(proof)
if err != nil {
err := fmt.Errorf("unable to store the proof for "+
@ -3459,8 +3459,8 @@ func (d *AuthenticatedGossiper) handleAnnSig(nMsg *networkMsg,
// announcement. If we didn't receive the opposite half of the proof
// then we should store this one, and wait for the opposite to be
// received.
proof := channeldb.NewWaitingProof(nMsg.isRemote, ann)
oppProof, err := d.cfg.WaitingProofStore.Get(proof.OppositeKey())
proof := channeldb.NewLegacyWaitingProof(nMsg.isRemote, ann)
oppositeProof, err := d.cfg.WaitingProofStore.Get(proof.OppositeKey())
if err != nil && err != channeldb.ErrWaitingProofNotFound {
err := fmt.Errorf("unable to get the opposite proof for "+
"short_chan_id=%v: %v", shortChanID, err)
@ -3487,6 +3487,14 @@ func (d *AuthenticatedGossiper) handleAnnSig(nMsg *networkMsg,
return nil, false
}
oppProof, ok := oppositeProof.
WaitingProofInterface.(*channeldb.LegacyWaitingProof)
if !ok {
nMsg.err <- fmt.Errorf("got wrong waiting proof type")
return nil, false
}
// We now have both halves of the channel announcement proof, then
// we'll reconstruct the initial announcement so we can validate it
// shortly below.