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/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= 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/psbt_channel_funder.go b/psbt_channel_funder.go index e359276ea..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 { @@ -92,8 +98,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{ @@ -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 "+ diff --git a/rpcserver.go b/rpcserver.go index 2f6b4ac74..edbe2156f 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" @@ -6394,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) @@ -6414,6 +6423,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/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) 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) diff --git a/tapchannel/aux_closer.go b/tapchannel/aux_closer.go index affe412f4..8b36a8a4c 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() @@ -493,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, ) @@ -510,15 +516,29 @@ 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 // 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/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/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 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 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) + }) + } } 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": [ 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 {