mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-02-20 13:34:32 +01:00
channeldb+lnwallet: add ShutdownInfo with read and write methods
ShutdownInfo contains any info about a previous Shutdown message that we have sent. This commit adds this type along with read and write methods for it in the channel db. The existence of the ShutdownInfo on disk represents the fact that we have previously sent the Shutdown message and hence that we should resend it on re-establish.
This commit is contained in:
parent
987604effb
commit
dc25b425c0
5 changed files with 225 additions and 0 deletions
|
@ -20,6 +20,7 @@ import (
|
|||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcwallet/walletdb"
|
||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/htlcswitch/hop"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
|
@ -121,6 +122,12 @@ var (
|
|||
// broadcasted when moving the channel to state CoopBroadcasted.
|
||||
coopCloseTxKey = []byte("coop-closing-tx-key")
|
||||
|
||||
// shutdownInfoKey points to the serialised shutdown info that has been
|
||||
// persisted for a channel. The existence of this info means that we
|
||||
// have sent the Shutdown message before and so should re-initiate the
|
||||
// shutdown on re-establish.
|
||||
shutdownInfoKey = []byte("shutdown-info-key")
|
||||
|
||||
// commitDiffKey stores the current pending commitment state we've
|
||||
// extended to the remote party (if any). Each time we propose a new
|
||||
// state, we store the information necessary to reconstruct this state
|
||||
|
@ -188,6 +195,10 @@ var (
|
|||
// in the state CommitBroadcasted.
|
||||
ErrNoCloseTx = fmt.Errorf("no closing tx found")
|
||||
|
||||
// ErrNoShutdownInfo is returned when no shutdown info has been
|
||||
// persisted for a channel.
|
||||
ErrNoShutdownInfo = errors.New("no shutdown info")
|
||||
|
||||
// ErrNoRestoredChannelMutation is returned when a caller attempts to
|
||||
// mutate a channel that's been recovered.
|
||||
ErrNoRestoredChannelMutation = fmt.Errorf("cannot mutate restored " +
|
||||
|
@ -1575,6 +1586,79 @@ func (c *OpenChannel) ChanSyncMsg() (*lnwire.ChannelReestablish, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
// MarkShutdownSent serialises and persist the given ShutdownInfo for this
|
||||
// channel. Persisting this info represents the fact that we have sent the
|
||||
// Shutdown message to the remote side and hence that we should re-transmit the
|
||||
// same Shutdown message on re-establish.
|
||||
func (c *OpenChannel) MarkShutdownSent(info *ShutdownInfo) error {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
return c.storeShutdownInfo(info)
|
||||
}
|
||||
|
||||
// storeShutdownInfo serialises the ShutdownInfo and persists it under the
|
||||
// shutdownInfoKey.
|
||||
func (c *OpenChannel) storeShutdownInfo(info *ShutdownInfo) error {
|
||||
var b bytes.Buffer
|
||||
err := info.encode(&b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return 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
|
||||
}
|
||||
|
||||
return chanBucket.Put(shutdownInfoKey, b.Bytes())
|
||||
}, func() {})
|
||||
}
|
||||
|
||||
// ShutdownInfo decodes the shutdown info stored for this channel and returns
|
||||
// the result. If no shutdown info has been persisted for this channel then the
|
||||
// ErrNoShutdownInfo error is returned.
|
||||
func (c *OpenChannel) ShutdownInfo() (fn.Option[ShutdownInfo], error) {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
var shutdownInfo *ShutdownInfo
|
||||
err := kvdb.View(c.Db.backend, func(tx kvdb.RTx) error {
|
||||
chanBucket, err := fetchChanBucket(
|
||||
tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash,
|
||||
)
|
||||
switch {
|
||||
case err == nil:
|
||||
case errors.Is(err, ErrNoChanDBExists),
|
||||
errors.Is(err, ErrNoActiveChannels),
|
||||
errors.Is(err, ErrChannelNotFound):
|
||||
|
||||
return ErrNoShutdownInfo
|
||||
default:
|
||||
return err
|
||||
}
|
||||
|
||||
shutdownInfoBytes := chanBucket.Get(shutdownInfoKey)
|
||||
if shutdownInfoBytes == nil {
|
||||
return ErrNoShutdownInfo
|
||||
}
|
||||
|
||||
shutdownInfo, err = decodeShutdownInfo(shutdownInfoBytes)
|
||||
|
||||
return err
|
||||
}, func() {
|
||||
shutdownInfo = nil
|
||||
})
|
||||
if err != nil {
|
||||
return fn.None[ShutdownInfo](), err
|
||||
}
|
||||
|
||||
return fn.Some[ShutdownInfo](*shutdownInfo), nil
|
||||
}
|
||||
|
||||
// isBorked returns true if the channel has been marked as borked in the
|
||||
// database. This requires an existing database transaction to already be
|
||||
// active.
|
||||
|
@ -4294,3 +4378,59 @@ func MakeScidRecord(typ tlv.Type, scid *lnwire.ShortChannelID) tlv.Record {
|
|||
typ, scid, 8, lnwire.EShortChannelID, lnwire.DShortChannelID,
|
||||
)
|
||||
}
|
||||
|
||||
// ShutdownInfo contains various info about the shutdown initiation of a
|
||||
// channel.
|
||||
type ShutdownInfo struct {
|
||||
// DeliveryScript is the address that we have included in any previous
|
||||
// Shutdown message for a particular channel and so should include in
|
||||
// any future re-sends of the Shutdown message.
|
||||
DeliveryScript tlv.RecordT[tlv.TlvType0, lnwire.DeliveryAddress]
|
||||
|
||||
// LocalInitiator is true if we sent a Shutdown message before ever
|
||||
// receiving a Shutdown message from the remote peer.
|
||||
LocalInitiator tlv.RecordT[tlv.TlvType1, bool]
|
||||
}
|
||||
|
||||
// NewShutdownInfo constructs a new ShutdownInfo object.
|
||||
func NewShutdownInfo(deliveryScript lnwire.DeliveryAddress,
|
||||
locallyInitiated bool) *ShutdownInfo {
|
||||
|
||||
return &ShutdownInfo{
|
||||
DeliveryScript: tlv.NewRecordT[tlv.TlvType0](deliveryScript),
|
||||
LocalInitiator: tlv.NewPrimitiveRecord[tlv.TlvType1](
|
||||
locallyInitiated,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// encode serialises the ShutdownInfo to the given io.Writer.
|
||||
func (s *ShutdownInfo) encode(w io.Writer) error {
|
||||
records := []tlv.Record{
|
||||
s.DeliveryScript.Record(),
|
||||
s.LocalInitiator.Record(),
|
||||
}
|
||||
|
||||
stream, err := tlv.NewStream(records...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return stream.Encode(w)
|
||||
}
|
||||
|
||||
// decodeShutdownInfo constructs a ShutdownInfo struct by decoding the given
|
||||
// byte slice.
|
||||
func decodeShutdownInfo(b []byte) (*ShutdownInfo, error) {
|
||||
tlvStream := lnwire.ExtraOpaqueData(b)
|
||||
|
||||
var info ShutdownInfo
|
||||
records := []tlv.RecordProducer{
|
||||
&info.DeliveryScript,
|
||||
&info.LocalInitiator,
|
||||
}
|
||||
|
||||
_, err := tlvStream.ExtractRecords(records...)
|
||||
|
||||
return &info, err
|
||||
}
|
||||
|
|
|
@ -1158,6 +1158,70 @@ func TestFetchWaitingCloseChannels(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestShutdownInfo tests that a channel's shutdown info can correctly be
|
||||
// persisted and retrieved.
|
||||
func TestShutdownInfo(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
localInit bool
|
||||
}{
|
||||
{
|
||||
name: "local node initiated",
|
||||
localInit: true,
|
||||
},
|
||||
{
|
||||
name: "remote node initiated",
|
||||
localInit: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testShutdownInfo(t, test.localInit)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testShutdownInfo(t *testing.T, locallyInitiated bool) {
|
||||
fullDB, err := MakeTestDB(t)
|
||||
require.NoError(t, err, "unable to make test database")
|
||||
|
||||
cdb := fullDB.ChannelStateDB()
|
||||
|
||||
// First a test channel.
|
||||
channel := createTestChannel(t, cdb)
|
||||
|
||||
// We haven't persisted any shutdown info for this channel yet.
|
||||
_, err = channel.ShutdownInfo()
|
||||
require.Error(t, err, ErrNoShutdownInfo)
|
||||
|
||||
// Construct a new delivery script and create a new ShutdownInfo object.
|
||||
script := []byte{1, 3, 4, 5}
|
||||
|
||||
// Create a ShutdownInfo struct.
|
||||
shutdownInfo := NewShutdownInfo(script, locallyInitiated)
|
||||
|
||||
// Persist the shutdown info.
|
||||
require.NoError(t, channel.MarkShutdownSent(shutdownInfo))
|
||||
|
||||
// We should now be able to retrieve the shutdown info.
|
||||
info, err := channel.ShutdownInfo()
|
||||
require.NoError(t, err)
|
||||
require.True(t, info.IsSome())
|
||||
|
||||
// Assert that the decoded values of the shutdown info are correct.
|
||||
info.WhenSome(func(info ShutdownInfo) {
|
||||
require.EqualValues(t, script, info.DeliveryScript.Val)
|
||||
require.Equal(t, locallyInitiated, info.LocalInitiator.Val)
|
||||
})
|
||||
}
|
||||
|
||||
// TestRefresh asserts that Refresh updates the in-memory state of another
|
||||
// OpenChannel to reflect a preceding call to MarkOpen on a different
|
||||
// OpenChannel.
|
||||
|
|
|
@ -154,6 +154,10 @@ func (m *mockChannel) MarkCoopBroadcasted(*wire.MsgTx, bool) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (m *mockChannel) MarkShutdownSent(*channeldb.ShutdownInfo) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockChannel) IsInitiator() bool {
|
||||
return m.initiator
|
||||
}
|
||||
|
|
|
@ -35,6 +35,11 @@ type Channel interface { //nolint:interfacebloat
|
|||
// transaction has been broadcast.
|
||||
MarkCoopBroadcasted(*wire.MsgTx, bool) error
|
||||
|
||||
// MarkShutdownSent persists the given ShutdownInfo. The existence of
|
||||
// the ShutdownInfo represents the fact that the Shutdown message has
|
||||
// been sent by us and so should be re-sent on re-establish.
|
||||
MarkShutdownSent(info *channeldb.ShutdownInfo) error
|
||||
|
||||
// IsInitiator returns true we are the initiator of the channel.
|
||||
IsInitiator() bool
|
||||
|
||||
|
|
|
@ -8823,6 +8823,18 @@ func (lc *LightningChannel) MarkCoopBroadcasted(tx *wire.MsgTx,
|
|||
return lc.channelState.MarkCoopBroadcasted(tx, localInitiated)
|
||||
}
|
||||
|
||||
// MarkShutdownSent persists the given ShutdownInfo. The existence of the
|
||||
// ShutdownInfo represents the fact that the Shutdown message has been sent by
|
||||
// us and so should be re-sent on re-establish.
|
||||
func (lc *LightningChannel) MarkShutdownSent(
|
||||
info *channeldb.ShutdownInfo) error {
|
||||
|
||||
lc.Lock()
|
||||
defer lc.Unlock()
|
||||
|
||||
return lc.channelState.MarkShutdownSent(info)
|
||||
}
|
||||
|
||||
// MarkDataLoss marks sets the channel status to LocalDataLoss and stores the
|
||||
// passed commitPoint for use to retrieve funds in case the remote force closes
|
||||
// the channel.
|
||||
|
|
Loading…
Add table
Reference in a new issue