From 0d933bef2e24f42b5613f91529fe15195800786f Mon Sep 17 00:00:00 2001 From: George Tsagkarelis Date: Sat, 8 Jun 2024 15:35:54 +0200 Subject: [PATCH 01/10] mod: update lndclient to latest --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c14bc825c..82852c4e3 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( github.com/lib/pq v1.10.9 github.com/lightninglabs/aperture v0.1.21-beta.0.20230705004936-87bb996a4030 github.com/lightninglabs/lightning-node-connect/hashmailrpc v1.0.2 - github.com/lightninglabs/lndclient v1.0.1-0.20240604150101-6b56ac87a4fa + github.com/lightninglabs/lndclient v1.0.1-0.20240607082608-4ce52a1a3f27 github.com/lightninglabs/neutrino/cache v1.1.2 github.com/lightningnetwork/lnd v0.18.0-beta.rc3.0.20240604145823-53dbd1ee66d0 github.com/lightningnetwork/lnd/cert v1.2.2 diff --git a/go.sum b/go.sum index fb8def519..625c19f17 100644 --- a/go.sum +++ b/go.sum @@ -480,8 +480,8 @@ github.com/lightninglabs/lightning-node-connect v0.2.5-alpha h1:ZRVChwczFXK0CEbx github.com/lightninglabs/lightning-node-connect v0.2.5-alpha/go.mod h1:A9Pof9fETkH+F67BnOmrBDThPKstqp73wlImWOZvTXQ= github.com/lightninglabs/lightning-node-connect/hashmailrpc v1.0.2 h1:Er1miPZD2XZwcfE4xoS5AILqP1mj7kqnhbBSxW9BDxY= github.com/lightninglabs/lightning-node-connect/hashmailrpc v1.0.2/go.mod h1:antQGRDRJiuyQF6l+k6NECCSImgCpwaZapATth2Chv4= -github.com/lightninglabs/lndclient v1.0.1-0.20240604150101-6b56ac87a4fa h1:O4ruYmvqRNm/1/8Crl4Y2fnGWSLmy43nxU12iuOnGBE= -github.com/lightninglabs/lndclient v1.0.1-0.20240604150101-6b56ac87a4fa/go.mod h1:bxd2a15cIaW8KKcmOf9nNDI/GTxxj0upEYs1EIkttqw= +github.com/lightninglabs/lndclient v1.0.1-0.20240607082608-4ce52a1a3f27 h1:vm8a13EzH2Qe6j4eZx+tHPeEVoNhJ7coihFPX6K2kco= +github.com/lightninglabs/lndclient v1.0.1-0.20240607082608-4ce52a1a3f27/go.mod h1:bxd2a15cIaW8KKcmOf9nNDI/GTxxj0upEYs1EIkttqw= github.com/lightninglabs/neutrino v0.16.1-0.20240425105051-602843d34ffd h1:D8aRocHpoCv43hL8egXEMYyPmyOiefFHZ66338KQB2s= github.com/lightninglabs/neutrino v0.16.1-0.20240425105051-602843d34ffd/go.mod h1:x3OmY2wsA18+Kc3TSV2QpSUewOCiscw2mKpXgZv2kZk= github.com/lightninglabs/neutrino/cache v1.1.2 h1:C9DY/DAPaPxbFC+xNNEI/z1SJY9GS3shmlu5hIQ798g= From 8d4de72e7e8c8b1ead02cf089db3810bb6c43893 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Sat, 8 Jun 2024 15:35:55 +0200 Subject: [PATCH 02/10] multi: add push amount to channel funding --- psbt_channel_funder.go | 4 +- rpcserver.go | 2 + tapchannel/aux_funding_controller.go | 11 ++ taprpc/tapchannelrpc/tapchannel.pb.go | 128 ++++++++++--------- taprpc/tapchannelrpc/tapchannel.proto | 7 + taprpc/tapchannelrpc/tapchannel.swagger.json | 8 ++ 6 files changed, 101 insertions(+), 59 deletions(-) diff --git a/psbt_channel_funder.go b/psbt_channel_funder.go index e359276ea..ad04894f7 100644 --- a/psbt_channel_funder.go +++ b/psbt_channel_funder.go @@ -92,8 +92,8 @@ func (l *LndPbstChannelFunder) OpenChannel(ctx context.Context, // taproot channel, that uses the PSBT funding flow. taprootCommitType := lnrpc.CommitmentType_SIMPLE_TAPROOT openChanStream, errChan, err := l.lnd.Client.OpenChannelStream( - ctx, route.NewVertex(&req.PeerPub), req.ChanAmt, 0, true, - lndclient.WithCommitmentType(&taprootCommitType), + ctx, route.NewVertex(&req.PeerPub), req.ChanAmt, req.PushAmt, + true, lndclient.WithCommitmentType(&taprootCommitType), lndclient.WithFundingShim(&lnrpc.FundingShim{ Shim: &lnrpc.FundingShim_PsbtShim{ PsbtShim: &lnrpc.PsbtShim{ diff --git a/rpcserver.go b/rpcserver.go index 2f6b4ac74..5a7b28068 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -17,6 +17,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil/psbt" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" @@ -6414,6 +6415,7 @@ func (r *rpcServer) FundChannel(ctx context.Context, PeerPub: *peerPub, AssetAmount: req.AssetAmount, FeeRate: chainfee.SatPerVByte(req.FeeRateSatPerVbyte), + PushAmount: btcutil.Amount(req.PushSat), } copy(fundReq.AssetID[:], req.AssetId) diff --git a/tapchannel/aux_funding_controller.go b/tapchannel/aux_funding_controller.go index 6b572fe5a..c60a054e7 100644 --- a/tapchannel/aux_funding_controller.go +++ b/tapchannel/aux_funding_controller.go @@ -72,6 +72,9 @@ type OpenChanReq struct { // CPFP with the channel anchor outputs. ChanAmt btcutil.Amount + // PushAmt is the amount of BTC to push to the remote peer. + PushAmt btcutil.Amount + // PeerPub is the identity public key of the remote peer we wish to // open the channel with. PeerPub btcec.PublicKey @@ -290,6 +293,8 @@ type pendingAssetFunding struct { amt uint64 + pushAmt btcutil.Amount + inputProofs []*proof.Proof feeRate chainfee.SatPerVByte @@ -946,6 +951,7 @@ func (f *FundingController) completeChannelFunding(ctx context.Context, // flow with lnd. fundingReq := OpenChanReq{ ChanAmt: 100_000, + PushAmt: fundingState.pushAmt, PeerPub: fundingState.peerPub, TempPID: fundingState.pid, } @@ -1173,6 +1179,7 @@ func (f *FundingController) chanFunder() { pid: tempPID, initiator: true, amt: fundReq.AssetAmount, + pushAmt: fundReq.PushAmount, feeRate: fundReq.FeeRate, fundingAckChan: make(chan bool, 1), fundingFinalizedSignal: make(chan struct{}), @@ -1602,6 +1609,10 @@ type FundReq struct { // FeeRate is the fee rate that we'll use to fund the channel. FeeRate chainfee.SatPerVByte + // PushAmount is the amount of satoshis that we'll push to the remote + // party. + PushAmount btcutil.Amount + ctx context.Context respChan chan *wire.OutPoint errChan chan error diff --git a/taprpc/tapchannelrpc/tapchannel.pb.go b/taprpc/tapchannelrpc/tapchannel.pb.go index 7c5defa2b..baee599d1 100644 --- a/taprpc/tapchannelrpc/tapchannel.pb.go +++ b/taprpc/tapchannelrpc/tapchannel.pb.go @@ -35,6 +35,12 @@ type FundChannelRequest struct { PeerPubkey []byte `protobuf:"bytes,3,opt,name=peer_pubkey,json=peerPubkey,proto3" json:"peer_pubkey,omitempty"` // The channel funding fee rate in sat/vByte. FeeRateSatPerVbyte uint32 `protobuf:"varint,4,opt,name=fee_rate_sat_per_vbyte,json=feeRateSatPerVbyte,proto3" json:"fee_rate_sat_per_vbyte,omitempty"` + // The number of satoshis to give the remote side as part of the initial + // commitment state. This is equivalent to first opening a channel and then + // sending the remote party funds, but all done in one step. Therefore, this + // is equivalent to a donation to the remote party, unless they reimburse + // the funds in another way (outside the protocol). + PushSat int64 `protobuf:"varint,5,opt,name=push_sat,json=pushSat,proto3" json:"push_sat,omitempty"` } func (x *FundChannelRequest) Reset() { @@ -97,6 +103,13 @@ func (x *FundChannelRequest) GetFeeRateSatPerVbyte() uint32 { return 0 } +func (x *FundChannelRequest) GetPushSat() int64 { + if x != nil { + return x.PushSat + } + return 0 +} + type FundChannelResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -335,7 +348,7 @@ var file_tapchannelrpc_tapchannel_proto_rawDesc = []byte{ 0x0a, 0x1e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2f, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x22, - 0xa7, 0x01, 0x0a, 0x12, 0x46, 0x75, 0x6e, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, + 0xc2, 0x01, 0x0a, 0x12, 0x46, 0x75, 0x6e, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x61, 0x73, 0x73, 0x65, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x73, 0x73, @@ -345,64 +358,65 @@ var file_tapchannelrpc_tapchannel_proto_rawDesc = []byte{ 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x32, 0x0a, 0x16, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, 0x66, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x53, 0x61, - 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x22, 0x4c, 0x0a, 0x13, 0x46, 0x75, 0x6e, - 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x74, 0x78, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x69, - 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x6f, 0x75, 0x74, 0x70, - 0x75, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0xcc, 0x01, 0x0a, 0x15, 0x52, 0x6f, 0x75, 0x74, - 0x65, 0x72, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x44, 0x61, 0x74, - 0x61, 0x12, 0x5b, 0x0a, 0x0d, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, - 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x53, - 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x44, 0x61, 0x74, 0x61, 0x2e, 0x41, - 0x73, 0x73, 0x65, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x0c, 0x61, 0x73, 0x73, 0x65, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x15, - 0x0a, 0x06, 0x72, 0x66, 0x71, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, - 0x72, 0x66, 0x71, 0x49, 0x64, 0x1a, 0x3f, 0x0a, 0x11, 0x41, 0x73, 0x73, 0x65, 0x74, 0x41, 0x6d, - 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x7d, 0x0a, 0x1a, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, - 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x56, 0x0a, 0x13, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x5f, 0x73, - 0x65, 0x6e, 0x64, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x24, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, - 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, - 0x65, 0x6e, 0x74, 0x44, 0x61, 0x74, 0x61, 0x48, 0x00, 0x52, 0x11, 0x72, 0x6f, 0x75, 0x74, 0x65, - 0x72, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x07, 0x0a, 0x05, - 0x69, 0x6e, 0x70, 0x75, 0x74, 0x22, 0xc5, 0x01, 0x0a, 0x1b, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, - 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x64, 0x0a, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, - 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3d, 0x2e, - 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x6e, - 0x63, 0x6f, 0x64, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, - 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x63, 0x75, - 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x1a, 0x40, 0x0a, 0x12, 0x43, - 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, - 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x32, 0xda, 0x01, - 0x0a, 0x14, 0x54, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x54, 0x0a, 0x0b, 0x46, 0x75, 0x6e, 0x64, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x21, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, - 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x43, 0x68, 0x61, - 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, - 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, - 0x72, 0x64, 0x73, 0x12, 0x29, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x72, 0x70, 0x63, 0x2e, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, - 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, + 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x75, 0x73, + 0x68, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x70, 0x75, 0x73, + 0x68, 0x53, 0x61, 0x74, 0x22, 0x4c, 0x0a, 0x13, 0x46, 0x75, 0x6e, 0x64, 0x43, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, + 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, + 0x21, 0x0a, 0x0c, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x6e, 0x64, + 0x65, 0x78, 0x22, 0xcc, 0x01, 0x0a, 0x15, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x53, 0x65, 0x6e, + 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x44, 0x61, 0x74, 0x61, 0x12, 0x5b, 0x0a, 0x0d, + 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x44, 0x61, 0x74, 0x61, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x41, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x61, 0x73, 0x73, + 0x65, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x15, 0x0a, 0x06, 0x72, 0x66, 0x71, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x72, 0x66, 0x71, 0x49, 0x64, + 0x1a, 0x3f, 0x0a, 0x11, 0x41, 0x73, 0x73, 0x65, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, + 0x01, 0x22, 0x7d, 0x0a, 0x1a, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, + 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x56, 0x0a, 0x13, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x70, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x74, + 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, + 0x74, 0x65, 0x72, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x44, 0x61, + 0x74, 0x61, 0x48, 0x00, 0x52, 0x11, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x53, 0x65, 0x6e, 0x64, + 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x07, 0x0a, 0x05, 0x69, 0x6e, 0x70, 0x75, 0x74, + 0x22, 0xc5, 0x01, 0x0a, 0x1b, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, + 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x64, 0x0a, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, + 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x43, + 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, + 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, + 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x1a, 0x40, 0x0a, 0x12, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, + 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x32, 0xda, 0x01, 0x0a, 0x14, 0x54, 0x61, 0x70, + 0x72, 0x6f, 0x6f, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x73, 0x12, 0x54, 0x0a, 0x0b, 0x46, 0x75, 0x6e, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x12, 0x21, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, + 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, 0x45, 0x6e, 0x63, 0x6f, 0x64, + 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x29, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, - 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x3e, 0x5a, 0x3c, 0x67, 0x69, - 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, - 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x2d, 0x61, - 0x73, 0x73, 0x65, 0x74, 0x73, 0x2f, 0x74, 0x61, 0x70, 0x72, 0x70, 0x63, 0x2f, 0x74, 0x61, 0x70, - 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x74, 0x61, 0x70, 0x63, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, + 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x3e, 0x5a, 0x3c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, + 0x73, 0x2f, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x2d, 0x61, 0x73, 0x73, 0x65, 0x74, 0x73, + 0x2f, 0x74, 0x61, 0x70, 0x72, 0x70, 0x63, 0x2f, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/taprpc/tapchannelrpc/tapchannel.proto b/taprpc/tapchannelrpc/tapchannel.proto index ec071f25e..d2871c074 100644 --- a/taprpc/tapchannelrpc/tapchannel.proto +++ b/taprpc/tapchannelrpc/tapchannel.proto @@ -36,6 +36,13 @@ message FundChannelRequest { // The channel funding fee rate in sat/vByte. uint32 fee_rate_sat_per_vbyte = 4; + + // The number of satoshis to give the remote side as part of the initial + // commitment state. This is equivalent to first opening a channel and then + // sending the remote party funds, but all done in one step. Therefore, this + // is equivalent to a donation to the remote party, unless they reimburse + // the funds in another way (outside the protocol). + int64 push_sat = 5; } message FundChannelResponse { diff --git a/taprpc/tapchannelrpc/tapchannel.swagger.json b/taprpc/tapchannelrpc/tapchannel.swagger.json index eef198030..d8f118ebb 100644 --- a/taprpc/tapchannelrpc/tapchannel.swagger.json +++ b/taprpc/tapchannelrpc/tapchannel.swagger.json @@ -99,6 +99,14 @@ "required": false, "type": "integer", "format": "int64" + }, + { + "name": "push_sat", + "description": "The number of satoshis to give the remote side as part of the initial\ncommitment state. This is equivalent to first opening a channel and then\nsending the remote party funds, but all done in one step. Therefore, this\nis equivalent to a donation to the remote party, unless they reimburse\nthe funds in another way (outside the protocol).", + "in": "query", + "required": false, + "type": "string", + "format": "int64" } ], "tags": [ From a30d1361698466ad1309b449dcc533a649ba73e8 Mon Sep 17 00:00:00 2001 From: George Tsagkarelis Date: Sat, 8 Jun 2024 15:35:56 +0200 Subject: [PATCH 03/10] tapd: use min chan reserve for custom channels --- psbt_channel_funder.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/psbt_channel_funder.go b/psbt_channel_funder.go index ad04894f7..922284b32 100644 --- a/psbt_channel_funder.go +++ b/psbt_channel_funder.go @@ -13,6 +13,12 @@ import ( "github.com/lightningnetwork/lnd/routing/route" ) +const ( + // CustomChannelRemoteReserve is the custom channel minimum remote + // reserve that we'll use for our channels. + CustomChannelRemoteReserve = 1062 +) + // LndPbstChannelFunder is an implementation of the tapchannel.ChannelFunder // interface that uses lnd to carry out the PSBT funding process. type LndPbstChannelFunder struct { @@ -103,6 +109,7 @@ func (l *LndPbstChannelFunder) OpenChannel(ctx context.Context, }, }, }), + lndclient.WithRemoteReserve(CustomChannelRemoteReserve), ) if err != nil { return nil, fmt.Errorf("unable to open channel with "+ From 20ee72022ce97e822f8429ca6cf6f08c765e25c0 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Sat, 8 Jun 2024 15:35:57 +0200 Subject: [PATCH 04/10] multi: gate channel functionality behind flag To avoid calling any channel functionality when tapd is running in standalone mode (not integrated into litd), we add a new flag that can be set by litd. --- cmd/tapd/main.go | 2 +- config.go | 5 +++++ itest/tapd_harness.go | 2 +- rpcserver.go | 8 ++++++++ tapcfg/server.go | 22 +++++++++++++--------- 5 files changed, 28 insertions(+), 11 deletions(-) diff --git a/cmd/tapd/main.go b/cmd/tapd/main.go index 67b54036b..4b89dd9e6 100644 --- a/cmd/tapd/main.go +++ b/cmd/tapd/main.go @@ -67,7 +67,7 @@ func main() { defer errQueue.Stop() server, err := tapcfg.CreateServerFromConfig( - cfg, cfgLogger, shutdownInterceptor, errQueue.ChanIn(), + cfg, cfgLogger, shutdownInterceptor, false, errQueue.ChanIn(), ) if err != nil { err := fmt.Errorf("error creating server: %w", err) diff --git a/config.go b/config.go index 08166d68c..4337a1ab7 100644 --- a/config.go +++ b/config.go @@ -147,6 +147,11 @@ type Config struct { // connecting to itself as a federation member. RuntimeID int64 + // EnableChannelFeatures indicates that tapd is running inside the + // Lightning Terminal daemon (litd) and can provide Taproot Asset + // channel functionality. + EnableChannelFeatures bool + ChainParams address.ChainParams Lnd *lndclient.LndServices diff --git a/itest/tapd_harness.go b/itest/tapd_harness.go index 85cf02dff..53ca06e01 100644 --- a/itest/tapd_harness.go +++ b/itest/tapd_harness.go @@ -309,7 +309,7 @@ func (hs *tapdHarness) start(expectErrExit bool) error { ) hs.server, err = tapcfg.CreateServerFromConfig( - hs.clientCfg, cfgLogger, hs.ht.interceptor, mainErrChan, + hs.clientCfg, cfgLogger, hs.ht.interceptor, false, mainErrChan, ) if err != nil { return fmt.Errorf("could not create tapd server: %w", err) diff --git a/rpcserver.go b/rpcserver.go index 5a7b28068..edbe2156f 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -6395,6 +6395,14 @@ func (r *rpcServer) FundChannel(ctx context.Context, req *tchrpc.FundChannelRequest) (*tchrpc.FundChannelResponse, error) { + // If we're not running inside litd, we cannot offer this functionality. + if !r.cfg.EnableChannelFeatures { + return nil, fmt.Errorf("the Taproot Asset channel " + + "functionality is only available when running inside " + + "Lightning Terminal daemon (litd), with lnd and tapd " + + "both running in 'integrated' mode") + } + peerPub, err := btcec.ParsePubKey(req.PeerPubkey) if err != nil { return nil, fmt.Errorf("error parsing peer pubkey: %w", err) diff --git a/tapcfg/server.go b/tapcfg/server.go index 7472860d7..082aebda2 100644 --- a/tapcfg/server.go +++ b/tapcfg/server.go @@ -38,7 +38,7 @@ type databaseBackend interface { // NOTE: The RPCConfig and SignalInterceptor fields must be set by the caller // after generating the server config. func genServerConfig(cfg *Config, cfgLogger btclog.Logger, - lndServices *lndclient.LndServices, + lndServices *lndclient.LndServices, enableChannelFeatures bool, mainErrChan chan<- error) (*tap.Config, error) { var err error @@ -452,10 +452,13 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger, } return &tap.Config{ - DebugLevel: cfg.DebugLevel, - RuntimeID: runtimeID, - Lnd: lndServices, - ChainParams: address.ParamsForChain(cfg.ActiveNetParams.Name), + DebugLevel: cfg.DebugLevel, + RuntimeID: runtimeID, + EnableChannelFeatures: enableChannelFeatures, + Lnd: lndServices, + ChainParams: address.ParamsForChain( + cfg.ActiveNetParams.Name, + ), ReOrgWatcher: reOrgWatcher, AssetMinter: tapgarden.NewChainPlanter(tapgarden.PlanterConfig{ GardenKit: tapgarden.GardenKit{ @@ -530,7 +533,7 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger, // CreateServerFromConfig creates a new Taproot Asset server from the given CLI // config. func CreateServerFromConfig(cfg *Config, cfgLogger btclog.Logger, - shutdownInterceptor signal.Interceptor, + shutdownInterceptor signal.Interceptor, enableChannelFeatures bool, mainErrChan chan<- error) (*tap.Server, error) { // Given the config above, grab the TLS config which includes the set @@ -556,7 +559,8 @@ func CreateServerFromConfig(cfg *Config, cfgLogger btclog.Logger, cfgLogger.Infof("lnd connection initialized") serverCfg, err := genServerConfig( - cfg, cfgLogger, &lndConn.LndServices, mainErrChan, + cfg, cfgLogger, &lndConn.LndServices, enableChannelFeatures, + mainErrChan, ) if err != nil { return nil, fmt.Errorf("unable to generate server config: %w", @@ -590,11 +594,11 @@ func CreateServerFromConfig(cfg *Config, cfgLogger btclog.Logger, // ConfigureSubServer updates a Taproot Asset server with the given CLI config. func ConfigureSubServer(srv *tap.Server, cfg *Config, cfgLogger btclog.Logger, - lndServices *lndclient.LndServices, + lndServices *lndclient.LndServices, litdIntegrated bool, mainErrChan chan<- error) error { serverCfg, err := genServerConfig( - cfg, cfgLogger, lndServices, mainErrChan, + cfg, cfgLogger, lndServices, litdIntegrated, mainErrChan, ) if err != nil { return fmt.Errorf("unable to generate server config: %w", err) From f796ddde2d8ea38752061981265ce7671b6d20f0 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Sat, 8 Jun 2024 15:35:58 +0200 Subject: [PATCH 05/10] vm+tapsend: fix copy bug, fix lint issue, fix error This commit mainly fixes a bug that when we copy an asset to create a new output, we didn't empty out the split commitment when we spent an output of a previous split in a full-value send output. This lead to a validation error in the VM. We also add some more context to the error that we were previously running into with this. And just because we can, we fix a package shadowing issue. --- tapsend/send.go | 12 ++++++++---- vm/vm.go | 4 +++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/tapsend/send.go b/tapsend/send.go index c17ff82e4..f0333380e 100644 --- a/tapsend/send.go +++ b/tapsend/send.go @@ -515,7 +515,7 @@ func PrepareOutputAssets(ctx context.Context, vPkt *tappsbt.VPacket) error { // Sum the total amount of the input assets. inputsAmountSum := uint64(0) for idx := range inputs { - input := inputs[idx] + in := inputs[idx] // At the moment, we need to ensure that all inputs have // the same asset ID. We've already checked that above, @@ -525,7 +525,7 @@ func PrepareOutputAssets(ctx context.Context, vPkt *tappsbt.VPacket) error { "must have the same asset ID") } - inputsAmountSum += input.Asset().Amount + inputsAmountSum += in.Asset().Amount } // At this point we know that each input has the same asset ID @@ -541,16 +541,20 @@ func PrepareOutputAssets(ctx context.Context, vPkt *tappsbt.VPacket) error { vOut.Asset.Amount = inputsAmountSum vOut.Asset.ScriptKey = vOut.ScriptKey + // We also need to clear the split commitment root to avoid + // state from previous splits being carried over. + vOut.Asset.SplitCommitmentRoot = nil + // Gather previous witnesses from the input assets. prevWitnesses := make([]asset.Witness, len(inputs)) for idx := range inputs { - input := inputs[idx] + in := inputs[idx] // Record the PrevID of the input asset in a Witness for // the new asset. This Witness still needs a valid // signature for the new asset to be valid. prevWitnesses[idx] = asset.Witness{ - PrevID: &input.PrevID, + PrevID: &in.PrevID, TxWitness: nil, SplitCommitment: nil, } diff --git a/vm/vm.go b/vm/vm.go index b319ce0ce..16eedcbdf 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -366,7 +366,9 @@ func (vm *Engine) validateStateTransition() error { if treeRoot.NodeSum() != uint64(virtualTx.TxOut[0].Value) { - return newErrKind(ErrAmountMismatch) + return newErrInner(ErrAmountMismatch, fmt.Errorf("expected "+ + "output value=%v, got=%v", treeRoot.NodeSum(), + virtualTx.TxOut[0].Value)) } for i, witness := range vm.newAsset.PrevWitnesses { From ea86288d50189681eba34165abde1b3fed6ee447 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Sat, 8 Jun 2024 15:35:59 +0200 Subject: [PATCH 06/10] tapchannel: increase level of limit spewer --- tapchannel/log.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tapchannel/log.go b/tapchannel/log.go index e7c6a2a30..c87da7dfd 100644 --- a/tapchannel/log.go +++ b/tapchannel/log.go @@ -17,7 +17,7 @@ var log = btclog.Disabled // to 4 levels, so it can safely be used for things that contain an MS-SMT tree. var limitSpewer = &spew.ConfigState{ Indent: " ", - MaxDepth: 4, + MaxDepth: 5, } // DisableLog disables all library log output. Logging output is disabled From 2dc1ba6400cc2e3153d52903ef8cc23c728f7aa7 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Sat, 8 Jun 2024 15:36:01 +0200 Subject: [PATCH 07/10] tapchannel: add proof delivery addr to allocation In order for us to send the proofs to the correct proof courier address, we need to be able to specify different delivery addresses for each allocation. --- tapchannel/allocation.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tapchannel/allocation.go b/tapchannel/allocation.go index a752112e3..16edea0d8 100644 --- a/tapchannel/allocation.go +++ b/tapchannel/allocation.go @@ -3,6 +3,7 @@ package tapchannel import ( "bytes" "fmt" + "net/url" "sort" "github.com/btcsuite/btcd/btcec/v2" @@ -144,6 +145,10 @@ type Allocation struct { // OutputCommitment is the taproot output commitment that is set after // fully distributing the coins and creating the asset and TAP trees. OutputCommitment *commitment.TapCommitment + + // ProofDeliveryAddress is the address the proof courier should use to + // upload the proof for this allocation. + ProofDeliveryAddress *url.URL } // tapscriptSibling returns the tapscript sibling preimage from the non-asset @@ -438,6 +443,7 @@ func DistributeCoins(inputs []*proof.Proof, allocations []*Allocation, return nil, err } + deliveryAddr := a.ProofDeliveryAddress vOut := &tappsbt.VOutput{ Amount: allocating, AssetVersion: a.AssetVersion, @@ -447,6 +453,7 @@ func DistributeCoins(inputs []*proof.Proof, allocations []*Allocation, AnchorOutputInternalKey: a.InternalKey, AnchorOutputTapscriptSibling: sibling, ScriptKey: a.ScriptKey, + ProofDeliveryAddress: deliveryAddr, } p.packet.Outputs = append(p.packet.Outputs, vOut) From 976f332f9bf224f3868781d4fb67d5754ec83142 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Sat, 8 Jun 2024 15:36:02 +0200 Subject: [PATCH 08/10] tapchannel+tapchannelmsg: add proof delivery addr to shutdown msg We want to be able to tell the initiator of the shutdown what proof delivery address we use, so we can fetch the proof of the co-op close correctly (only the initiator of the shutdown will create an asset transfer and hand it to the freighter, so if we're on the other side, we'll need to fetch the proof instead). --- tapchannel/aux_closer.go | 39 ++++++++++-------- tapchannelmsg/records.go | 75 +++++++++++++++++++++++++++++------ tapchannelmsg/records_test.go | 44 ++++++++++++++++---- 3 files changed, 121 insertions(+), 37 deletions(-) diff --git a/tapchannel/aux_closer.go b/tapchannel/aux_closer.go index affe412f4..06800b4d6 100644 --- a/tapchannel/aux_closer.go +++ b/tapchannel/aux_closer.go @@ -102,6 +102,19 @@ func createCloseAlloc(isLocal, isInitiator bool, closeAsset *asset.Asset, return nil, fmt.Errorf("no script key for asset %v", assetID) } + var proofDeliveryUrl *url.URL + err := lfn.MapOptionZ( + shutdownMsg.ProofDeliveryAddr.ValOpt(), func(u []byte) error { + var err error + proofDeliveryUrl, err = url.Parse(string(u)) + return err + }, + ) + if err != nil { + return nil, fmt.Errorf("unable to decode proof delivery "+ + "address: %w", err) + } + return &Allocation{ Type: func() AllocationType { if isLocal { @@ -110,13 +123,14 @@ func createCloseAlloc(isLocal, isInitiator bool, closeAsset *asset.Asset, return CommitAllocationToRemote }(), - SplitRoot: isInitiator, - InternalKey: shutdownMsg.AssetInternalKey.Val, - ScriptKey: asset.NewScriptKey(&scriptKey), - Amount: closeAsset.Amount, - AssetVersion: asset.V0, - BtcAmount: tapsend.DummyAmtSats, - SortTaprootKeyBytes: sortKeyBytes, + SplitRoot: isInitiator, + InternalKey: shutdownMsg.AssetInternalKey.Val, + ScriptKey: asset.NewScriptKey(&scriptKey), + Amount: closeAsset.Amount, + AssetVersion: asset.V0, + BtcAmount: tapsend.DummyAmtSats, + SortTaprootKeyBytes: sortKeyBytes, + ProofDeliveryAddress: proofDeliveryUrl, }, nil } @@ -325,14 +339,6 @@ func (a *AuxChanCloser) AuxCloseOutputs( return none, fmt.Errorf("unable to distribute coins: %w", err) } - // For each vPkt, we'll also go ahead and add the default proof courier - // addr to them. - for _, vPacket := range vPackets { - for _, vOut := range vPacket.Outputs { - vOut.ProofDeliveryAddress = a.cfg.DefaultCourierAddr - } - } - // With the vPackets created we'll now prepare all the split // information encoded in the vPackets. fundingScriptTree := NewFundingScriptTree() @@ -517,8 +523,9 @@ func (a *AuxChanCloser) ShutdownBlob( // can send to lnd to have included. shutdownRecord := tapchannelmsg.NewAuxShutdownMsg( &btcInternalKey, newInternalKey.PubKey, scriptKeys, + a.cfg.DefaultCourierAddr, ) - records, err := tlv.RecordsToMap(shutdownRecord.Records()) + records, err := tlv.RecordsToMap(shutdownRecord.EncodeRecords()) if err != nil { return none, err } diff --git a/tapchannelmsg/records.go b/tapchannelmsg/records.go index e536f07a7..e593c49bc 100644 --- a/tapchannelmsg/records.go +++ b/tapchannelmsg/records.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "io" + "net/url" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/txscript" @@ -69,6 +70,11 @@ type ( // ScriptKeysShutdownType is the type alias for the TLV type that is // used to encode the script keys of the shutdown record on the wire. ScriptKeysShutdownType = tlv.TlvType65541 + + // ProofDeliveryAddrShutdownType is the type alias for the TLV type that + // is used to encode the proof delivery address of the shutdown record + // on the wire. + ProofDeliveryAddrShutdownType = tlv.TlvType65542 ) // OpenChannel is a record that represents the capacity information related to @@ -1795,12 +1801,31 @@ type AuxShutdownMsg struct { // ScriptKeys maps asset IDs to script keys to be used to send the // assets to the sending party in the co-op close transaction. ScriptKeys tlv.RecordT[ScriptKeysShutdownType, ScriptKeyMap] + + // ProofDeliveryAddr is an optional type that contains the delivery + // address for the proofs of the co-op close outputs of the local node. + ProofDeliveryAddr tlv.OptionalRecordT[ + ProofDeliveryAddrShutdownType, []byte, + ] } // NewAuxShutdownMsg creates a new AuxShutdownMsg with the given internal key // and script key map. func NewAuxShutdownMsg(btcInternalKey, assetInternalKey *btcec.PublicKey, - scriptKeys ScriptKeyMap) *AuxShutdownMsg { + scriptKeys ScriptKeyMap, proofDeliveryAddr *url.URL) *AuxShutdownMsg { + + var deliveryAddr tlv.OptionalRecordT[ + ProofDeliveryAddrShutdownType, []byte, + ] + if proofDeliveryAddr != nil { + deliveryAddrBytes := []byte(proofDeliveryAddr.String()) + rec := tlv.NewPrimitiveRecord[ProofDeliveryAddrShutdownType]( + deliveryAddrBytes, + ) + deliveryAddr = tlv.SomeRecordT[ProofDeliveryAddrShutdownType]( + rec, + ) + } return &AuxShutdownMsg{ BtcInternalKey: tlv.NewPrimitiveRecord[BtcKeyShutdownType]( @@ -1812,12 +1837,31 @@ func NewAuxShutdownMsg(btcInternalKey, assetInternalKey *btcec.PublicKey, ScriptKeys: tlv.NewRecordT[ScriptKeysShutdownType]( scriptKeys, ), + ProofDeliveryAddr: deliveryAddr, + } +} + +// EncodeRecords returns the records that make up the AuxShutdownMsg for +// encoding. +func (a *AuxShutdownMsg) EncodeRecords() []tlv.Record { + records := []tlv.Record{ + a.BtcInternalKey.Record(), + a.AssetInternalKey.Record(), + a.ScriptKeys.Record(), } + + a.ProofDeliveryAddr.WhenSome( + func(r tlv.RecordT[ProofDeliveryAddrShutdownType, []byte]) { + records = append(records, r.Record()) + }, + ) + + return records } // Encode serializes the AuxShutdownMsg to the given io.Writer. func (a *AuxShutdownMsg) Encode(w io.Writer) error { - tlvStream, err := tlv.NewStream(a.Records()...) + tlvStream, err := tlv.NewStream(a.EncodeRecords()...) if err != nil { return err } @@ -1827,25 +1871,30 @@ func (a *AuxShutdownMsg) Encode(w io.Writer) error { // Decode deserializes the AuxShutdownMsg from the given io.Reader. func (a *AuxShutdownMsg) Decode(r io.Reader) error { - tlvStream, err := tlv.NewStream(a.Records()...) + deliveryAddr := a.ProofDeliveryAddr.Zero() + + records := []tlv.Record{ + a.BtcInternalKey.Record(), + a.AssetInternalKey.Record(), + a.ScriptKeys.Record(), + deliveryAddr.Record(), + } + + tlvStream, err := tlv.NewStream(records...) if err != nil { return err } - if _, err := tlvStream.DecodeWithParsedTypesP2P(r); err != nil { + tlvs, err := tlvStream.DecodeWithParsedTypesP2P(r) + if err != nil { return err } - return nil -} - -// Records returns a slice of tlv.Record that represents the AuxShutdownMsg. -func (a *AuxShutdownMsg) Records() []tlv.Record { - return []tlv.Record{ - a.BtcInternalKey.Record(), - a.AssetInternalKey.Record(), - a.ScriptKeys.Record(), + if _, ok := tlvs[deliveryAddr.TlvType()]; ok { + a.ProofDeliveryAddr = tlv.SomeRecordT(deliveryAddr) } + + return nil } // DecodeAuxShutdownMsg deserializes a AuxShutdownMsg from the given blob. diff --git a/tapchannelmsg/records_test.go b/tapchannelmsg/records_test.go index 234596000..e7989342d 100644 --- a/tapchannelmsg/records_test.go +++ b/tapchannelmsg/records_test.go @@ -3,6 +3,7 @@ package tapchannelmsg import ( "bytes" "encoding/hex" + "net/url" "os" "path/filepath" "strings" @@ -438,15 +439,42 @@ func TestAuxShutdownMsg(t *testing.T) { testScriptKeys[[32]byte{byte(i)}] = *test.RandPubKey(t) } - testShutdownMsg := NewAuxShutdownMsg( - testBtcInternalKey, testAssetInternalKey, testScriptKeys, - ) + dummyURL, err := url.Parse("https://example.com") + require.NoError(t, err) + + testCases := []struct { + name string + shutdown *AuxShutdownMsg + }{ + { + name: "AuxShutdownMsg with no URL", + shutdown: NewAuxShutdownMsg( + testBtcInternalKey, testAssetInternalKey, + testScriptKeys, nil, + ), + }, + { + name: "AuxShutdownMsg with URL", + shutdown: NewAuxShutdownMsg( + testBtcInternalKey, testAssetInternalKey, + testScriptKeys, dummyURL, + ), + }, + } - var shutdownBuffer bytes.Buffer - require.NoError(t, testShutdownMsg.Encode(&shutdownBuffer)) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Serialize the AuxShutdownMsg and then deserialize it + // again. + var b bytes.Buffer + err := tc.shutdown.Encode(&b) + require.NoError(t, err) - var newShutdownMsg AuxShutdownMsg - require.NoError(t, newShutdownMsg.Decode(&shutdownBuffer)) + newShutdownMsg := &AuxShutdownMsg{} + err = newShutdownMsg.Decode(&b) + require.NoError(t, err) - require.Equal(t, *testShutdownMsg, newShutdownMsg) + require.Equal(t, tc.shutdown, newShutdownMsg) + }) + } } From 6c92f4b5324b63b9ad49df36e5c98ef14c25a136 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Sat, 8 Jun 2024 15:36:03 +0200 Subject: [PATCH 09/10] tapchannel: import close output as address If we're not the initiator of the shutdown process, we won't be called back from lnd after the channel closing TX confirmed. So in order for us to pick up on the transfer and download the proof from the courier, we need to add our expected output as a TAP address, so the custodian will detect the confirmed output on chain. This is not optimal, as it will not work if there are multiple assets in that close output. But since we'll need to add a whole new callback in lnd that invokes a method in the aux closer after the channel confirmed, we solve it this way instead for now. Because the whole funding logic isn't ready to add multiple assets to a channel anyway. --- tapchannel/aux_closer.go | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/tapchannel/aux_closer.go b/tapchannel/aux_closer.go index 06800b4d6..8b36a8a4c 100644 --- a/tapchannel/aux_closer.go +++ b/tapchannel/aux_closer.go @@ -499,16 +499,16 @@ func (a *AuxChanCloser) ShutdownBlob( return none, err } - // Next, we'll collect all the asset IDs that we own in this channel. - assetIDs := lfn.Map(func(o *tapchannelmsg.AssetOutput) asset.ID { - return o.AssetID.Val - }, commitState.LocalAssets.Val.Outputs) + // Next, we'll collect all the assets that we own in this channel. + assets := commitState.LocalAssets.Val.Outputs // Now that we have all the asset IDs, we'll query for a new key for // each of them which we'll use as both the internal key and the script // key. scriptKeys := make(tapchannelmsg.ScriptKeyMap) - for _, assetID := range assetIDs { + for idx := range assets { + channelAsset := assets[idx] + newKey, err := a.cfg.AddrBook.NextScriptKey( ctx, asset.TaprootAssetsKeyFamily, ) @@ -516,7 +516,20 @@ func (a *AuxChanCloser) ShutdownBlob( return none, err } - scriptKeys[assetID] = *newKey.PubKey + // We now add the a + // TODO(guggero): This only works if there's only a single asset + // in the channel. We need to extend this to support multiple + // assets. + _, err = a.cfg.AddrBook.NewAddressWithKeys( + ctx, channelAsset.AssetID.Val, channelAsset.Amount.Val, + newKey, newInternalKey, nil, *a.cfg.DefaultCourierAddr, + ) + if err != nil { + return none, fmt.Errorf("error adding new address: %w", + err) + } + + scriptKeys[channelAsset.AssetID.Val] = *newKey.PubKey } // Finally, we'll map the extra shutdown info to a TLV record map we From d78472c982cb69356083e3900ca623c6a617eae6 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Sun, 9 Jun 2024 17:25:11 +0200 Subject: [PATCH 10/10] tapchannel: account for BTC only balances The allocation code incorrectly assumed that if there was no asset balance on one side, there also wouldn't be a BTC balance. With that incorrect assumptions, no commitment or anchor allocations were added if there for example was a BTC push amount added in the channel opening transaction. --- tapchannel/commitment.go | 62 ++++++++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/tapchannel/commitment.go b/tapchannel/commitment.go index cef85f788..9e07e6959 100644 --- a/tapchannel/commitment.go +++ b/tapchannel/commitment.go @@ -401,8 +401,16 @@ func SanityCheckAmounts(ourBalance, theirBalance btcutil.Amount, "anchors", chanType) } - wantLocalAnchor := ourAssetBalance > 0 || numHTLCs > 0 - wantRemoteAnchor := theirAssetBalance > 0 || numHTLCs > 0 + // Due to push amounts on channel open or pure BTC payments, we can have + // a BTC balance even if the asset balance is zero. + ourBtcNonDust := ourBalance >= dustLimit + theirBtcNonDust := theirBalance >= dustLimit + + // So we want an anchor if we either have assets or non-dust BTC or + // any in-flight HTLCs. + wantLocalAnchor := ourAssetBalance > 0 || ourBtcNonDust || numHTLCs > 0 + wantRemoteAnchor := theirAssetBalance > 0 || theirBtcNonDust || + numHTLCs > 0 return wantLocalAnchor, wantRemoteAnchor, nil } @@ -750,7 +758,7 @@ func addCommitmentOutputs(chanType channeldb.ChannelType, localChanCfg, // We've asserted that we have a non-dust BTC balance if we have an // asset balance before, so we can just check the asset balance here. - if ourAssetBalance > 0 { + if ourAssetBalance > 0 || ourBalance > 0 { toLocalScript, err := lnwallet.CommitScriptToSelf( chanType, initiator, keys.ToLocalKey, keys.RevocationKey, uint32(localChanCfg.CsvDelay), @@ -769,7 +777,7 @@ func addCommitmentOutputs(chanType channeldb.ChannelType, localChanCfg, "sibling: %w", err) } - addAllocation(&Allocation{ + allocation := &Allocation{ Type: CommitAllocationToLocal, Amount: ourAssetBalance, AssetVersion: asset.V1, @@ -783,10 +791,29 @@ func addCommitmentOutputs(chanType channeldb.ChannelType, localChanCfg, SortTaprootKeyBytes: schnorr.SerializePubKey( toLocalTree.TaprootKey, ), - }) + } + + // If there are no assets, only BTC (for example due to a push + // amount), the allocation looks simpler. + if ourAssetBalance == 0 { + allocation = &Allocation{ + Type: AllocationTypeNoAssets, + BtcAmount: ourBalance, + InternalKey: toLocalTree.InternalKey, + NonAssetLeaves: sibling, + ScriptKey: asset.NewScriptKey( + toLocalTree.TaprootKey, + ), + SortTaprootKeyBytes: schnorr.SerializePubKey( + toLocalTree.TaprootKey, + ), + } + } + + addAllocation(allocation) } - if theirAssetBalance > 0 { + if theirAssetBalance > 0 || theirBalance > 0 { toRemoteScript, _, err := lnwallet.CommitScriptToRemote( chanType, initiator, keys.ToRemoteKey, leaseExpiry, lfn.None[txscript.TapLeaf](), @@ -804,7 +831,7 @@ func addCommitmentOutputs(chanType channeldb.ChannelType, localChanCfg, "sibling: %w", err) } - addAllocation(&Allocation{ + allocation := &Allocation{ Type: CommitAllocationToRemote, Amount: theirAssetBalance, AssetVersion: asset.V1, @@ -818,7 +845,26 @@ func addCommitmentOutputs(chanType channeldb.ChannelType, localChanCfg, SortTaprootKeyBytes: schnorr.SerializePubKey( toRemoteTree.TaprootKey, ), - }) + } + + // If there are no assets, only BTC (for example due to a push + // amount), the allocation looks simpler. + if theirAssetBalance == 0 { + allocation = &Allocation{ + Type: AllocationTypeNoAssets, + BtcAmount: theirBalance, + InternalKey: toRemoteTree.InternalKey, + NonAssetLeaves: sibling, + ScriptKey: asset.NewScriptKey( + toRemoteTree.TaprootKey, + ), + SortTaprootKeyBytes: schnorr.SerializePubKey( + toRemoteTree.TaprootKey, + ), + } + } + + addAllocation(allocation) } return nil