Merge pull request #10182 from GeorgeTsagk/aux-feature-bits

Aux feature bits
This commit is contained in:
Olaoluwa Osuntokun authored and GitHub committed 2025-09-24 18:26:35 -07:00
commit df46d18626
11 files changed
+227 -6

No files matched your search

+5
View File
@@ -213,6 +213,11 @@ type AuxComponents struct {
// AuxContractResolver is an optional interface that can be used to
// modify the way contracts are resolved.
AuxContractResolver fn.Option[lnwallet.AuxContractResolver]
// AuxChannelNegotiator is an optional interface that allows aux channel
// implementations to inject and process custom records over channel
// related wire messages.
AuxChannelNegotiator fn.Option[lnwallet.AuxChannelNegotiator]
}
// DefaultWalletImpl is the default implementation of our normal, btcwallet
@@ -76,6 +76,11 @@ a certain amount of msats.
[allow](https://github.com/lightningnetwork/lnd/pull/10087)
`conf_target=1`. Previously they required `conf_target >= 2`.
* A new AuxComponent was added named AuxChannelNegotiator. This component aids
with custom data communication for aux channels, by injecting and handling
data in channel related wire messages. See
[PR](https://github.com/lightningnetwork/lnd/pull/10182) for more info.
## RPC Additions
* When querying [`ForwardingEvents`](https://github.com/lightningnetwork/lnd/pull/9813)
logs, the response now include the incoming and outgoing htlc indices of the payment
+13
View File
@@ -568,6 +568,11 @@ type Config struct {
// AuxResolver is an optional interface that can be used to modify the
// way contracts are resolved.
AuxResolver fn.Option[lnwallet.AuxContractResolver]
// AuxChannelNegotiator is an optional interface that allows aux channel
// implementations to inject and process custom records over channel
// related wire messages.
AuxChannelNegotiator fn.Option[lnwallet.AuxChannelNegotiator]
}
// Manager acts as an orchestrator/bridge between the wallet's
@@ -4019,6 +4024,14 @@ func (f *Manager) handleChannelReady(peer lnpeer.Peer, //nolint:funlen
defer f.wg.Done()
// Notify the aux hook that the specified peer just established a
// channel with us, identified by the given channel ID.
f.cfg.AuxChannelNegotiator.WhenSome(
func(acn lnwallet.AuxChannelNegotiator) {
acn.ProcessChannelReady(msg.ChanID, peer.PubKey())
},
)
// If we are in development mode, we'll wait for specified duration
// before processing the channel ready message.
if f.cfg.Dev != nil {
+21
View File
@@ -298,6 +298,11 @@ type ChannelLinkConfig struct {
// used to manage the bandwidth of the link.
AuxTrafficShaper fn.Option[AuxTrafficShaper]
// AuxChannelNegotiator is an optional interface that allows aux channel
// implementations to inject and process custom records over channel
// related wire messages.
AuxChannelNegotiator fn.Option[lnwallet.AuxChannelNegotiator]
// QuiescenceTimeout is the max duration that the channel can be
// quiesced. Any dependent protocols (dynamic commitments, splicing,
// etc.) must finish their operations under this timeout value,
@@ -987,6 +992,22 @@ func (l *channelLink) syncChanStates(ctx context.Context) error {
// In any case, we'll then process their ChanSync message.
l.log.Info("received re-establishment message from remote side")
// If we have an AuxChannelNegotiator we notify any external
// component for this message. This serves as a notification
// that the reestablish message was received.
l.cfg.AuxChannelNegotiator.WhenSome(
func(acn lnwallet.AuxChannelNegotiator) {
fundingPoint := l.channel.ChannelPoint()
cid := lnwire.NewChanIDFromOutPoint(
fundingPoint,
)
acn.ProcessReestablish(
cid, l.cfg.Peer.PubKey(),
)
},
)
var (
openedCircuits []CircuitKey
closedCircuits []CircuitKey
+8
View File
@@ -9,6 +9,7 @@ import (
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/lightningnetwork/lnd/tlv"
)
@@ -97,6 +98,10 @@ type AuxChanState struct {
// funding output.
TapscriptRoot fn.Option[chainhash.Hash]
// PeerPubKey is the peer pub key of the peer we've established this
// channel with.
PeerPubKey route.Vertex
// CustomBlob is an optional blob that can be used to store information
// specific to a custom channel type. This information is only created
// at channel funding time, and after wards is to be considered
@@ -106,6 +111,8 @@ type AuxChanState struct {
// NewAuxChanState creates a new AuxChanState from the given channel state.
func NewAuxChanState(chanState *channeldb.OpenChannel) AuxChanState {
peerPub := chanState.IdentityPub.SerializeCompressed()
return AuxChanState{
ChanType: chanState.ChanType,
FundingOutpoint: chanState.FundingOutpoint,
@@ -116,6 +123,7 @@ func NewAuxChanState(chanState *channeldb.OpenChannel) AuxChanState {
RemoteChanCfg: chanState.RemoteChanCfg,
ThawHeight: chanState.ThawHeight,
TapscriptRoot: chanState.TapscriptRoot,
PeerPubKey: route.Vertex(peerPub),
CustomBlob: chanState.CustomBlob,
}
}
+34
View File
@@ -0,0 +1,34 @@
package lnwallet
import (
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
)
// AuxChannelNegotiator is an interface that allows aux channel implementations
// to inject or handle custom records in the init message that is used when
// establishing a connection with a peer. It may also notify the aux channel
// implementation for the channel ready or channel reestablish events, which
// mark the channel as ready to use.
type AuxChannelNegotiator interface {
// GetInitRecords is called when sending an init message to a peer.
// It returns custom records to include in the init message TLVs. The
// implementation can decide which records to include based on the peer
// identity.
GetInitRecords(peer route.Vertex) (lnwire.CustomRecords, error)
// ProcessInitRecords handles received init records from a peer. The
// implementation can store state internally to affect future
// channel operations with this peer.
ProcessInitRecords(peer route.Vertex,
customRecords lnwire.CustomRecords) error
// ProcessChannelReady handles the event of marking a channel identified
// by its channel ID as ready to use. We also provide the peer the
// channel was established with.
ProcessChannelReady(cid lnwire.ChannelID, peer route.Vertex)
// ProcessReestablish handles the received channel_reestablish message
// which marks a channel identified by its cid as ready to use again.
ProcessReestablish(cid lnwire.ChannelID, peer route.Vertex)
}
+29 -4
View File
@@ -24,6 +24,10 @@ type Init struct {
// Features field.
Features *RawFeatureVector
// CustomRecords maps TLV types to byte slices, storing arbitrary data
// intended for inclusion in the ExtraData field of the init message.
CustomRecords CustomRecords
// ExtraData is the set of data that was appended to this message to
// fill out the full maximum transport message size. These fields can
// be used to specify optional data such as custom TLV fields.
@@ -35,7 +39,6 @@ func NewInitMessage(gf *RawFeatureVector, f *RawFeatureVector) *Init {
return &Init{
GlobalFeatures: gf,
Features: f,
ExtraData: make([]byte, 0),
}
}
@@ -52,11 +55,28 @@ var _ SizeableMessage = (*Init)(nil)
//
// This is part of the lnwire.Message interface.
func (msg *Init) Decode(r io.Reader, pver uint32) error {
return ReadElements(r,
var msgExtraData ExtraOpaqueData
err := ReadElements(r,
&msg.GlobalFeatures,
&msg.Features,
&msg.ExtraData,
&msgExtraData,
)
if err != nil {
return err
}
customRecords, _, extraDData, err := ParseAndExtractCustomRecords(
msgExtraData,
)
if err != nil {
return err
}
msg.CustomRecords = customRecords
msg.ExtraData = extraDData
return nil
}
// Encode serializes the target Init into the passed io.Writer observing
@@ -72,7 +92,12 @@ func (msg *Init) Encode(w *bytes.Buffer, pver uint32) error {
return err
}
return WriteBytes(w, msg.ExtraData)
extraData, err := MergeAndEncode(nil, msg.ExtraData, msg.CustomRecords)
if err != nil {
return err
}
return WriteBytes(w, extraData)
}
// MsgType returns the integer uniquely identifying this message type on the
+61
View File
@@ -0,0 +1,61 @@
package lnwire
import (
"bytes"
"testing"
"github.com/stretchr/testify/require"
)
// TestInitEncodeDecode checks that we can encode and decode an Init message
// to and from a byte stream.
func TestInitEncodeDecode(t *testing.T) {
t.Parallel()
// These are the raw bytes that we expect to be generated from the
// sample Init message.
rawBytes := []byte{
// GlobalFeatures
0x00, 0x01, 0xc0,
// Features
0x00, 0x01, 0xc0,
// ExtraData - unknown odd-type TLV record.
0x6f, // type (111)
0x02, // length
0x79, 0x79, // value
// ExtraData - custom TLV record.
// TLV record for type 67676
0xfe, 0x00, 0x01, 0x08, 0x6c, // type (67676)
0x05, // length
0x01, 0x02, 0x03, 0x04, 0x05, // value
// ExtraData - custom TLV record.
// TLV record for type 67777
0xfe, 0x00, 0x01, 0x08, 0xc1, // type (67777)
0x03, // length
0x01, 0x02, 0x03, // value
}
// Create a new empty message and decode the raw bytes into it.
msg := &Init{}
r := bytes.NewReader(rawBytes)
err := msg.Decode(r, 0)
require.NoError(t, err)
require.NotNil(t, msg.GlobalFeatures)
require.NotNil(t, msg.Features)
require.NotNil(t, msg.CustomRecords)
require.NotNil(t, msg.ExtraData)
// Next, encode the message back into a new byte buffer.
var b bytes.Buffer
err = msg.Encode(&b, 0)
require.NoError(t, err)
// The re-encoded bytes should be exactly the same as the original raw
// bytes.
require.Equal(t, rawBytes, b.Bytes())
}
+4
View File
@@ -1188,6 +1188,10 @@ func (msg *Init) RandTestMessage(t *rapid.T) Message {
local.Set(bit)
}
ignoreRecords := fn.NewSet[uint64]()
msg.ExtraData = RandExtraOpaqueData(t, ignoreRecords)
return NewInitMessage(global, local)
}
+45 -2
View File
@@ -457,6 +457,11 @@ type Config struct {
// used to modify the way the co-op close transaction is constructed.
AuxChanCloser fn.Option[chancloser.AuxChanCloser]
// AuxChannelNegotiator is an optional interface that allows aux channel
// implementations to inject and process custom records over channel
// related wire messages.
AuxChannelNegotiator fn.Option[lnwallet.AuxChannelNegotiator]
// ShouldFwdExpEndorsement is a closure that indicates whether
// experimental endorsement signals should be set.
ShouldFwdExpEndorsement func() bool
@@ -1455,8 +1460,9 @@ func (p *Brontide) addLink(chanPoint *wire.OutPoint,
ShouldFwdExpEndorsement: p.cfg.ShouldFwdExpEndorsement,
DisallowQuiescence: p.cfg.DisallowQuiescence ||
!p.remoteFeatures.HasFeature(lnwire.QuiescenceOptional),
AuxTrafficShaper: p.cfg.AuxTrafficShaper,
QuiescenceTimeout: p.cfg.QuiescenceTimeout,
AuxTrafficShaper: p.cfg.AuxTrafficShaper,
AuxChannelNegotiator: p.cfg.AuxChannelNegotiator,
QuiescenceTimeout: p.cfg.QuiescenceTimeout,
}
// Before adding our new link, purge the switch of any pending or live
@@ -4545,6 +4551,19 @@ func (p *Brontide) handleInitMsg(msg *lnwire.Init) error {
return fmt.Errorf("data loss protection required")
}
// If we have an AuxChannelNegotiator and the peer sent aux features,
// process them.
p.cfg.AuxChannelNegotiator.WhenSome(
func(acn lnwallet.AuxChannelNegotiator) {
err = acn.ProcessInitRecords(
p.cfg.PubKeyBytes, msg.CustomRecords.Copy(),
)
},
)
if err != nil {
return fmt.Errorf("could not process init records: %w", err)
}
return nil
}
@@ -4605,6 +4624,30 @@ func (p *Brontide) sendInitMsg(legacyChan bool) error {
features.RawFeatureVector,
)
var err error
// If we have an AuxChannelNegotiator, get custom feature bits to
// include in the init message.
p.cfg.AuxChannelNegotiator.WhenSome(
func(negotiator lnwallet.AuxChannelNegotiator) {
var auxRecords lnwire.CustomRecords
auxRecords, err = negotiator.GetInitRecords(
p.cfg.PubKeyBytes,
)
if err != nil {
p.log.Warnf("Failed to get aux init features: "+
"%v", err)
return
}
mergedRecs := msg.CustomRecords.MergedCopy(auxRecords)
msg.CustomRecords = mergedRecs
},
)
if err != nil {
return err
}
return p.writeMessage(msg)
}
+2
View File
@@ -1628,6 +1628,7 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr,
AuxFundingController: implCfg.AuxFundingController,
AuxSigner: implCfg.AuxSigner,
AuxResolver: implCfg.AuxContractResolver,
AuxChannelNegotiator: implCfg.AuxChannelNegotiator,
})
if err != nil {
return nil, err
@@ -4431,6 +4432,7 @@ func (s *server) peerConnected(conn net.Conn, connReq *connmgr.ConnReq,
AuxChanCloser: s.implCfg.AuxChanCloser,
AuxResolver: s.implCfg.AuxContractResolver,
AuxTrafficShaper: s.implCfg.TrafficShaper,
AuxChannelNegotiator: s.implCfg.AuxChannelNegotiator,
ShouldFwdExpEndorsement: func() bool {
if s.cfg.ProtocolOptions.NoExperimentalEndorsement() {
return false