Skip to content

Commit b489d04

Browse files
authored
Merge pull request #595 from lightninglabs/manual_fee_rate_support
Manual fee rate support
2 parents 6eb255c + 96c8cc3 commit b489d04

File tree

16 files changed

+491
-307
lines changed

16 files changed

+491
-307
lines changed

cmd/tapcli/assets.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
import (
44
"encoding/hex"
55
"fmt"
6+
"math"
67
"os"
78

89
taprootassets "github.com/lightninglabs/taproot-assets"
@@ -48,6 +49,7 @@ var (
4849
groupByGroupName = "by_group"
4950
assetIDName = "asset_id"
5051
shortResponseName = "short"
52+
feeRateName = "fee_rate"
5153
assetAmountName = "amount"
5254
burnOverrideConfirmationName = "override_confirmation_destroy_assets"
5355
)
@@ -250,17 +252,41 @@ var finalizeBatchCommand = cli.Command{
250252
"in order to avoid printing a large amount " +
251253
"of data in case of large batches",
252254
},
255+
cli.Uint64Flag{
256+
Name: feeRateName,
257+
Usage: "if set, the fee rate in sat/kw to use for the" +
258+
"minting transaction",
259+
},
253260
},
254261
Action: finalizeBatch,
255262
}
256263

264+
func parseFeeRate(ctx *cli.Context) (uint32, error) {
265+
if ctx.IsSet(feeRateName) {
266+
feeRate := ctx.Uint64(feeRateName)
267+
if feeRate > math.MaxUint32 {
268+
return 0, fmt.Errorf("fee rate exceeds 2^32")
269+
}
270+
271+
return uint32(feeRate), nil
272+
}
273+
274+
return uint32(0), nil
275+
}
276+
257277
func finalizeBatch(ctx *cli.Context) error {
258278
ctxc := getContext()
259279
client, cleanUp := getMintClient(ctx)
260280
defer cleanUp()
261281

282+
feeRate, err := parseFeeRate(ctx)
283+
if err != nil {
284+
return err
285+
}
286+
262287
resp, err := client.FinalizeBatch(ctxc, &mintrpc.FinalizeBatchRequest{
263288
ShortResponse: ctx.Bool(shortResponseName),
289+
FeeRate: feeRate,
264290
})
265291
if err != nil {
266292
return fmt.Errorf("unable to finalize batch: %w", err)
@@ -496,6 +522,11 @@ var sendAssetsCommand = cli.Command{
496522
Usage: "addr to send to; can be specified multiple " +
497523
"times to send to multiple addresses at once",
498524
},
525+
cli.Uint64Flag{
526+
Name: feeRateName,
527+
Usage: "if set, the fee rate in sat/kw to use for the" +
528+
"anchor transaction",
529+
},
499530
// TODO(roasbeef): add arg for file name to write sender proof
500531
// blob
501532
},
@@ -512,8 +543,14 @@ func sendAssets(ctx *cli.Context) error {
512543
client, cleanUp := getClient(ctx)
513544
defer cleanUp()
514545

546+
feeRate, err := parseFeeRate(ctx)
547+
if err != nil {
548+
return err
549+
}
550+
515551
resp, err := client.SendAsset(ctxc, &taprpc.SendAssetRequest{
516552
TapAddrs: addrs,
553+
FeeRate: feeRate,
517554
})
518555
if err != nil {
519556
return fmt.Errorf("unable to send assets: %w", err)

rpcserver.go

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import (
4242
"github.com/lightninglabs/taproot-assets/universe"
4343
"github.com/lightningnetwork/lnd/build"
4444
"github.com/lightningnetwork/lnd/keychain"
45+
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
4546
"github.com/lightningnetwork/lnd/signal"
4647
"google.golang.org/grpc"
4748
)
@@ -443,12 +444,39 @@ func (r *rpcServer) MintAsset(ctx context.Context,
443444
}
444445
}
445446

447+
// checkFeeRateSanity ensures that the provided fee rate is above the same
448+
// minimum fee used as a floor in the fee estimator.
449+
func checkFeeRateSanity(rpcFeeRate uint32) (*chainfee.SatPerKWeight, error) {
450+
var feeRate *chainfee.SatPerKWeight
451+
switch {
452+
// No manual fee rate was set, which is the default.
453+
case rpcFeeRate == 0:
454+
455+
// A manual fee was set but is below a reasonable floor.
456+
case rpcFeeRate < uint32(chainfee.FeePerKwFloor):
457+
return nil, fmt.Errorf("manual fee rate %d below floor of %d",
458+
rpcFeeRate, uint32(chainfee.FeePerKwFloor))
459+
460+
default:
461+
// Set the fee rate for this transaction.
462+
manualFeeRate := chainfee.SatPerKWeight(rpcFeeRate)
463+
feeRate = &manualFeeRate
464+
}
465+
466+
return feeRate, nil
467+
}
468+
446469
// FinalizeBatch attempts to finalize the current pending batch.
447470
func (r *rpcServer) FinalizeBatch(_ context.Context,
448471
req *mintrpc.FinalizeBatchRequest) (*mintrpc.FinalizeBatchResponse,
449472
error) {
450473

451-
batch, err := r.cfg.AssetMinter.FinalizeBatch()
474+
feeRate, err := checkFeeRateSanity(req.FeeRate)
475+
if err != nil {
476+
return nil, err
477+
}
478+
479+
batch, err := r.cfg.AssetMinter.FinalizeBatch(feeRate)
452480
if err != nil {
453481
return nil, fmt.Errorf("unable to finalize batch: %w", err)
454482
}
@@ -1961,8 +1989,13 @@ func (r *rpcServer) SendAsset(_ context.Context,
19611989
}
19621990
}
19631991

1992+
feeRate, err := checkFeeRateSanity(req.FeeRate)
1993+
if err != nil {
1994+
return nil, err
1995+
}
1996+
19641997
resp, err := r.cfg.ChainPorter.RequestShipment(
1965-
tapfreighter.NewAddressParcel(tapAddrs...),
1998+
tapfreighter.NewAddressParcel(feeRate, tapAddrs...),
19661999
)
19672000
if err != nil {
19682001
return nil, err

tapfreighter/chain_porter.go

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"github.com/lightninglabs/taproot-assets/tappsbt"
2121
"github.com/lightninglabs/taproot-assets/tapscript"
2222
"github.com/lightningnetwork/lnd/chainntnfs"
23+
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
2324
)
2425

2526
// ChainPorterConfig is the main config for the chain porter.
@@ -880,14 +881,32 @@ func (p *ChainPorter) stateStep(currentPkg sendPackage) (*sendPackage, error) {
880881
// Submit the template PSBT to the wallet for funding.
881882
//
882883
// TODO(roasbeef): unlock the input UTXOs of things fail
883-
feeRate, err := p.cfg.ChainBridge.EstimateFee(
884-
ctx, tapscript.SendConfTarget,
884+
var (
885+
feeRate chainfee.SatPerKWeight
886+
err error
885887
)
886-
if err != nil {
887-
return nil, fmt.Errorf("unable to estimate fee: %w",
888-
err)
888+
889+
// First, use a manual fee rate if specified by the parcel.
890+
// TODO(jhb): Support PSBT flow / PreSignedParcels
891+
addrParcel, ok := currentPkg.Parcel.(*AddressParcel)
892+
switch {
893+
case ok && addrParcel.transferFeeRate != nil:
894+
feeRate = *addrParcel.transferFeeRate
895+
log.Infof("sending with manual fee rate")
896+
897+
default:
898+
feeRate, err = p.cfg.ChainBridge.EstimateFee(
899+
ctx, tapscript.SendConfTarget,
900+
)
901+
if err != nil {
902+
return nil, fmt.Errorf("unable to estimate "+
903+
"fee: %w", err)
904+
}
889905
}
890906

907+
readableFeeRate := feeRate.FeePerKVByte().String()
908+
log.Infof("sending with fee rate: %v", readableFeeRate)
909+
891910
vPacket := currentPkg.VirtualPacket
892911
firstRecipient, err := vPacket.FirstNonSplitRootOutput()
893912
if err != nil {

tapfreighter/parcel.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"github.com/lightninglabs/taproot-assets/tappsbt"
1818
"github.com/lightningnetwork/lnd/chainntnfs"
1919
"github.com/lightningnetwork/lnd/keychain"
20+
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
2021
)
2122

2223
// SendState is an enum that describes the current state of a pending outbound
@@ -130,20 +131,27 @@ type AddressParcel struct {
130131
// destAddrs is the list of address that should be used to satisfy the
131132
// transfer.
132133
destAddrs []*address.Tap
134+
135+
// transferFeeRate is an optional manually-set feerate specified when
136+
// requesting an asset transfer.
137+
transferFeeRate *chainfee.SatPerKWeight
133138
}
134139

135140
// A compile-time assertion to ensure AddressParcel implements the parcel
136141
// interface.
137142
var _ Parcel = (*AddressParcel)(nil)
138143

139144
// NewAddressParcel creates a new AddressParcel.
140-
func NewAddressParcel(destAddrs ...*address.Tap) *AddressParcel {
145+
func NewAddressParcel(feeRate *chainfee.SatPerKWeight,
146+
destAddrs ...*address.Tap) *AddressParcel {
147+
141148
return &AddressParcel{
142149
parcelKit: &parcelKit{
143150
respChan: make(chan *OutboundParcel, 1),
144151
errChan: make(chan error, 1),
145152
},
146-
destAddrs: destAddrs,
153+
destAddrs: destAddrs,
154+
transferFeeRate: feeRate,
147155
}
148156
}
149157

tapfreighter/wallet.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1442,6 +1442,7 @@ func (f *AssetWallet) AnchorVirtualTransactions(ctx context.Context,
14421442
return nil, fmt.Errorf("unable to get on-chain fees for psbt: "+
14431443
"%w", err)
14441444
}
1445+
log.Infof("PSBT absolute fee: %d sats", chainFees)
14451446

14461447
err = psbt.MaybeFinalizeAll(signedPsbt)
14471448
if err != nil {

tapgarden/caretaker.go

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,20 @@ import (
2121
"github.com/lightninglabs/taproot-assets/commitment"
2222
"github.com/lightninglabs/taproot-assets/fn"
2323
"github.com/lightninglabs/taproot-assets/proof"
24+
"github.com/lightninglabs/taproot-assets/tapscript"
2425
"github.com/lightninglabs/taproot-assets/universe"
2526
"github.com/lightningnetwork/lnd/chainntnfs"
27+
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
2628
"golang.org/x/exp/maps"
2729
"golang.org/x/sync/errgroup"
2830
)
2931

3032
var (
31-
// GenesisDummyScript is a dummy script that we'll use to fund the
32-
// initial PSBT packet that'll create initial set of assets. It's the
33-
// same size as a encoded P2TR output.
34-
GenesisDummyScript [34]byte
3533

3634
// DummyGenesisTxOut is the dummy TxOut we'll place in the PSBt funding
3735
// request to make sure we leave enough room for change and fees.
3836
DummyGenesisTxOut = wire.TxOut{
39-
PkScript: GenesisDummyScript[:],
37+
PkScript: tapscript.GenesisDummyScript[:],
4038
Value: int64(GenesisAmtSats),
4139
}
4240

@@ -72,6 +70,10 @@ type BatchCaretakerConfig struct {
7270
// Batch is the minting batch that this caretaker is responsible for?
7371
Batch *MintingBatch
7472

73+
// BatchFeeRate is an optional manually-set feerate specified when
74+
// finalizing a batch.
75+
BatchFeeRate *chainfee.SatPerKWeight
76+
7577
GardenKit
7678

7779
// BroadcastCompleteChan is used to signal back to the caller that the
@@ -386,13 +388,26 @@ func (b *BatchCaretaker) fundGenesisPsbt(ctx context.Context) (*FundedPsbt, erro
386388
log.Infof("BatchCaretaker(%x): creating skeleton PSBT", b.batchKey[:])
387389
log.Tracef("PSBT: %v", spew.Sdump(genesisPkt))
388390

389-
feeRate, err := b.cfg.ChainBridge.EstimateFee(
390-
ctx, GenesisConfTarget,
391-
)
392-
if err != nil {
393-
return nil, fmt.Errorf("unable to estimate fee: %w", err)
391+
// If a fee rate was manually assigned for this batch, use that instead
392+
// of a fee rate estimate.
393+
var feeRate chainfee.SatPerKWeight
394+
switch {
395+
case b.cfg.BatchFeeRate != nil:
396+
feeRate = *b.cfg.BatchFeeRate
397+
log.Infof("BatchCaretaker(%x): using manual fee rate")
398+
399+
default:
400+
feeRate, err = b.cfg.ChainBridge.EstimateFee(
401+
ctx, GenesisConfTarget,
402+
)
403+
if err != nil {
404+
return nil, fmt.Errorf("unable to estimate fee: %w", err)
405+
}
394406
}
395407

408+
log.Infof("BatchCaretaker(%x): using fee rate: %v",
409+
feeRate.FeePerKVByte().String())
410+
396411
fundedGenesisPkt, err := b.cfg.Wallet.FundPsbt(
397412
ctx, genesisPkt, 1, feeRate,
398413
)
@@ -723,6 +738,8 @@ func (b *BatchCaretaker) stateStep(currentState BatchState) (BatchState, error)
723738
}
724739
b.cfg.Batch.GenesisPacket.ChainFees = chainFees
725740

741+
log.Infof("BatchCaretaker(%x): GenesisPacket absolute fee: "+
742+
"%d sats", chainFees)
726743
log.Infof("BatchCaretaker(%x): GenesisPacket finalized",
727744
b.batchKey[:])
728745
log.Tracef("GenesisPacket: %v", spew.Sdump(signedPkt))

tapgarden/interface.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ type Planter interface {
4444

4545
// FinalizeBatch signals that the asset minter should finalize
4646
// the current batch, if one exists.
47-
FinalizeBatch() (*MintingBatch, error)
47+
FinalizeBatch(feeRate *chainfee.SatPerKWeight) (*MintingBatch, error)
4848

4949
// CancelBatch signals that the asset minter should cancel the
5050
// current batch, if one exists.

0 commit comments

Comments
 (0)