Skip to content

Commit 60d99fc

Browse files
committed
tapchannel+tapchannelmsg: add group key to funding blob
To be able to detect certain asset related routing conditions, we'll want to commit the group key of assets into the funding blob of the channel.
1 parent f2acd9f commit 60d99fc

File tree

6 files changed

+143
-17
lines changed

6 files changed

+143
-17
lines changed

rfqmsg/custom_channel_data.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ type JsonAssetChannel struct {
3737
OutgoingHtlcs []JsonAssetTranche `json:"outgoing_htlcs"`
3838
IncomingHtlcs []JsonAssetTranche `json:"incoming_htlcs"`
3939
Capacity uint64 `json:"capacity"`
40+
GroupKey string `json:"group_key,omitempty"`
4041
LocalBalance uint64 `json:"local_balance"`
4142
RemoteBalance uint64 `json:"remote_balance"`
4243
OutgoingHtlcBalance uint64 `json:"outgoing_htlc_balance"`

tapchannel/aux_funding_controller.go

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -576,15 +576,18 @@ func newCommitBlobAndLeaves(pendingFunding *pendingAssetFunding,
576576
// both sides are able to construct the funding output, and will be able to
577577
// store the appropriate funding blobs.
578578
func (p *pendingAssetFunding) toAuxFundingDesc(req *bindFundingReq,
579-
decimalDisplay uint8) (*lnwallet.AuxFundingDesc, error) {
579+
decimalDisplay uint8,
580+
groupKey *btcec.PublicKey) (*lnwallet.AuxFundingDesc, error) {
580581

581582
// First, we'll map all the assets into asset outputs that'll be stored
582583
// in the open channel struct on the lnd side.
583584
assetOutputs := p.assetOutputs()
584585

585586
// With all the outputs assembled, we'll now map that to the open
586587
// channel wrapper that'll go in the set of TLV blobs.
587-
openChanDesc := cmsg.NewOpenChannel(assetOutputs, decimalDisplay)
588+
openChanDesc := cmsg.NewOpenChannel(
589+
assetOutputs, decimalDisplay, groupKey,
590+
)
588591

589592
// Now we'll encode the 3 TLV blobs that lnd will store: the main one
590593
// for the funding details, and then the blobs for the local and remote
@@ -1933,8 +1936,20 @@ func (f *FundingController) chanFunder() {
19331936
continue
19341937
}
19351938

1939+
groupKey, err := f.fundingAssetGroupKey(
1940+
ctxc, fundingFlow.assetOutputs(),
1941+
)
1942+
if err != nil {
1943+
fErr := fmt.Errorf("unable to determine group "+
1944+
"key: %w", err)
1945+
f.cfg.ErrReporter.ReportError(
1946+
ctxc, fundingFlow.peerPub, pid, fErr,
1947+
)
1948+
continue
1949+
}
1950+
19361951
fundingDesc, err := fundingFlow.toAuxFundingDesc(
1937-
req, decimalDisplay,
1952+
req, decimalDisplay, groupKey,
19381953
)
19391954
if err != nil {
19401955
fErr := fmt.Errorf("unable to create aux "+
@@ -2029,6 +2044,65 @@ func (f *FundingController) fundingAssetDecimalDisplay(ctx context.Context,
20292044
return decimalDisplay, nil
20302045
}
20312046

2047+
// fundingAssetGroupKey determines the group key of the funding asset(s). If no
2048+
// group key was used to fund the channel, then nil is returned.
2049+
func (f *FundingController) fundingAssetGroupKey(ctx context.Context,
2050+
assetOutputs []*cmsg.AssetOutput) (*btcec.PublicKey, error) {
2051+
2052+
// We now check the group key of each funding asset, to make sure we
2053+
// know the meta information for each asset. And we also verify that
2054+
// each asset tranche has the same group key.
2055+
var groupKey *btcec.PublicKey
2056+
for _, a := range assetOutputs {
2057+
info, err := f.cfg.AssetSyncer.QueryAssetInfo(
2058+
ctx, a.AssetID.Val,
2059+
)
2060+
switch {
2061+
// If the asset isn't a grouped asset (or we don't know the
2062+
// asset), then we just continue.
2063+
case errors.Is(err, address.ErrAssetGroupUnknown):
2064+
continue
2065+
2066+
case err != nil:
2067+
return nil, fmt.Errorf("unable to fetch group info: %w",
2068+
err)
2069+
}
2070+
2071+
switch {
2072+
// We haven't set the group key before and have found one now,
2073+
// perfect. Let's assume that's our group key we'll use.
2074+
case groupKey == nil && info.GroupKey != nil:
2075+
groupKey = &info.GroupKey.GroupPubKey
2076+
2077+
// If we already have a group key, then we need to verify that
2078+
// the group key of this asset matches the one we already have.
2079+
case groupKey != nil && info.GroupKey != nil:
2080+
if !groupKey.IsEqual(&info.GroupKey.GroupPubKey) {
2081+
return nil, fmt.Errorf("group key mismatch: "+
2082+
"expected %x, got %x",
2083+
groupKey.SerializeCompressed(),
2084+
info.GroupPubKey.SerializeCompressed())
2085+
}
2086+
2087+
// If a previous asset resulted in a group key, every following
2088+
// one must also result in the same one. If we can't find one
2089+
// now, it means we either don't know about the asset (not
2090+
// synced) or it's not a grouped asset.
2091+
case groupKey != nil && info.GroupKey == nil:
2092+
return nil, fmt.Errorf("group key mismatch: "+
2093+
"expected %x, got nil",
2094+
groupKey.SerializeCompressed())
2095+
2096+
// If we don't have a group key yet, and the asset isn't a
2097+
// grouped asset, then we just continue.
2098+
case groupKey == nil && info.GroupKey == nil:
2099+
continue
2100+
}
2101+
}
2102+
2103+
return groupKey, nil
2104+
}
2105+
20322106
// channelAcceptor is a callback that's called by the lnd client when a new
20332107
// channel is proposed. This function is responsible for deciding whether to
20342108
// accept the channel based on the channel parameters, and to also set some

tapchannelmsg/custom_channel_data.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"io"
99

10+
"github.com/btcsuite/btcd/btcec/v2"
1011
"github.com/btcsuite/btcd/wire"
1112
"github.com/lightninglabs/taproot-assets/rfqmsg"
1213
"github.com/lightningnetwork/lnd/lnrpc"
@@ -84,6 +85,14 @@ func (c *ChannelCustomData) AsJson() ([]byte, error) {
8485
IncomingHtlcBalance: c.LocalCommit.IncomingHtlcAssets.Val.Sum(),
8586
}
8687

88+
c.OpenChan.GroupKey.ValOpt().WhenSome(func(key *btcec.PublicKey) {
89+
if key != nil {
90+
resp.GroupKey = hex.EncodeToString(
91+
key.SerializeCompressed(),
92+
)
93+
}
94+
})
95+
8796
// First, we encode the funding state, which lists all assets committed
8897
// to the channel at the time of channel opening.
8998
for _, output := range c.OpenChan.Assets() {

tapchannelmsg/custom_channel_data_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ func TestReadChannelCustomData(t *testing.T) {
3939
output3 := NewAssetOutput(assetID3, 3000, proof3)
4040
output4 := NewAssetOutput(assetID4, 4000, proof4)
4141

42-
fundingState := NewOpenChannel([]*AssetOutput{output1, output2}, 11)
42+
fundingState := NewOpenChannel(
43+
[]*AssetOutput{output1, output2}, 11, nil,
44+
)
4345
commitState := NewCommitment(
4446
[]*AssetOutput{output1}, []*AssetOutput{output2},
4547
map[input.HtlcIndex][]*AssetOutput{

tapchannelmsg/records.go

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,23 @@ type OpenChannel struct {
9494
// this value needs to be the same for all assets. Otherwise, they would
9595
// not be fungible.
9696
DecimalDisplay tlv.RecordT[tlv.TlvType1, uint8]
97+
98+
// GroupKey is the optional group key used to fund this channel.
99+
GroupKey tlv.OptionalRecordT[tlv.TlvType2, *btcec.PublicKey]
97100
}
98101

99102
// NewOpenChannel creates a new OpenChannel record with the given funded assets.
100-
func NewOpenChannel(fundedAssets []*AssetOutput,
101-
decimalDisplay uint8) *OpenChannel {
103+
func NewOpenChannel(fundedAssets []*AssetOutput, decimalDisplay uint8,
104+
groupKey *btcec.PublicKey) *OpenChannel {
105+
106+
var optGroupRecord tlv.OptionalRecordT[tlv.TlvType2, *btcec.PublicKey]
107+
if groupKey != nil {
108+
optGroupRecord = tlv.SomeRecordT[tlv.TlvType2](
109+
tlv.NewPrimitiveRecord[tlv.TlvType2](
110+
groupKey,
111+
),
112+
)
113+
}
102114

103115
return &OpenChannel{
104116
FundedAssets: tlv.NewRecordT[tlv.TlvType0](
@@ -109,6 +121,7 @@ func NewOpenChannel(fundedAssets []*AssetOutput,
109121
DecimalDisplay: tlv.NewPrimitiveRecord[tlv.TlvType1](
110122
decimalDisplay,
111123
),
124+
GroupKey: optGroupRecord,
112125
}
113126
}
114127

@@ -118,17 +131,18 @@ func (o *OpenChannel) Assets() []*AssetOutput {
118131
return o.FundedAssets.Val.Outputs
119132
}
120133

121-
// records returns the records that make up the OpenChannel.
122-
func (o *OpenChannel) records() []tlv.Record {
123-
return []tlv.Record{
134+
// Encode serializes the OpenChannel to the given io.Writer.
135+
func (o *OpenChannel) Encode(w io.Writer) error {
136+
tlvRecords := []tlv.Record{
124137
o.FundedAssets.Record(),
125138
o.DecimalDisplay.Record(),
126139
}
127-
}
128140

129-
// Encode serializes the OpenChannel to the given io.Writer.
130-
func (o *OpenChannel) Encode(w io.Writer) error {
131-
tlvRecords := o.records()
141+
o.GroupKey.WhenSome(
142+
func(r tlv.RecordT[tlv.TlvType2, *btcec.PublicKey]) {
143+
tlvRecords = append(tlvRecords, r.Record())
144+
},
145+
)
132146

133147
// Create the tlv stream.
134148
tlvStream, err := tlv.NewStream(tlvRecords...)
@@ -141,13 +155,30 @@ func (o *OpenChannel) Encode(w io.Writer) error {
141155

142156
// Decode deserializes the OpenChannel from the given io.Reader.
143157
func (o *OpenChannel) Decode(r io.Reader) error {
158+
groupKey := o.GroupKey.Zero()
159+
160+
tlvRecords := []tlv.Record{
161+
o.FundedAssets.Record(),
162+
o.DecimalDisplay.Record(),
163+
groupKey.Record(),
164+
}
165+
144166
// Create the tlv stream.
145-
tlvStream, err := tlv.NewStream(o.records()...)
167+
tlvStream, err := tlv.NewStream(tlvRecords...)
146168
if err != nil {
147169
return err
148170
}
149171

150-
return tlvStream.Decode(r)
172+
tlvs, err := tlvStream.DecodeWithParsedTypes(r)
173+
if err != nil {
174+
return err
175+
}
176+
177+
if _, ok := tlvs[groupKey.TlvType()]; ok {
178+
o.GroupKey = tlv.SomeRecordT(groupKey)
179+
}
180+
181+
return nil
151182
}
152183

153184
// Bytes returns the serialized OpenChannel record.

tapchannelmsg/records_test.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ func TestOpenChannel(t *testing.T) {
6565
require.NoError(t, err)
6666
randProof, err := proof.Decode(proofBytes)
6767
require.NoError(t, err)
68+
randGroupKey := test.RandPubKey(t)
6869

6970
testCases := []struct {
7071
name string
@@ -78,14 +79,22 @@ func TestOpenChannel(t *testing.T) {
7879
name: "channel with funded asset",
7980
channel: NewOpenChannel([]*AssetOutput{
8081
NewAssetOutput([32]byte{1}, 1000, *randProof),
81-
}, 0),
82+
}, 0, nil),
8283
},
8384
{
8485
name: "channel with multiple funded assets",
8586
channel: NewOpenChannel([]*AssetOutput{
8687
NewAssetOutput([32]byte{1}, 1000, *randProof),
8788
NewAssetOutput([32]byte{2}, 2000, *randProof),
88-
}, 11),
89+
}, 11, nil),
90+
},
91+
{
92+
name: "channel with multiple funded assets and group " +
93+
"key",
94+
channel: NewOpenChannel([]*AssetOutput{
95+
NewAssetOutput([32]byte{1}, 1000, *randProof),
96+
NewAssetOutput([32]byte{2}, 2000, *randProof),
97+
}, 11, randGroupKey),
8998
},
9099
}
91100

0 commit comments

Comments
 (0)