Skip to content

Commit b9a64f4

Browse files
committed
itest: test min relay fee bump
The `testMinRelayFeeBump` itest checks that the minting transaction and a basic send obtain a fee bump when the min relay fee is increased to a value that is higher than the fee estimation. fix
1 parent 81ee925 commit b9a64f4

File tree

4 files changed

+261
-11
lines changed

4 files changed

+261
-11
lines changed

itest/addrs_test.go

Lines changed: 69 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1078,24 +1078,70 @@ func sendProofUniRPC(t *harnessTest, src, dst *tapdHarness, scriptKey []byte,
10781078
return importResp
10791079
}
10801080

1081-
// sendAssetsToAddr spends the given input asset and sends the amount specified
1081+
// sendOptions is a struct that holds a SendAssetRequest and an
1082+
// optional error string that should be tested against.
1083+
type sendOptions struct {
1084+
sendAssetRequest taprpc.SendAssetRequest
1085+
errText string
1086+
}
1087+
1088+
// sendOption is a functional option for configuring the sendAssets call.
1089+
type sendOption func(*sendOptions)
1090+
1091+
// withReceiverAddresses is an option to specify the receiver addresses for the
1092+
// send.
1093+
func withReceiverAddresses(addrs ...*taprpc.Addr) sendOption {
1094+
return func(options *sendOptions) {
1095+
encodedAddrs := make([]string, len(addrs))
1096+
for i, addr := range addrs {
1097+
encodedAddrs[i] = addr.Encoded
1098+
}
1099+
options.sendAssetRequest.TapAddrs = encodedAddrs
1100+
}
1101+
}
1102+
1103+
// withFeeRate is an option to specify the fee rate for the send.
1104+
func withFeeRate(feeRate uint32) sendOption {
1105+
return func(options *sendOptions) {
1106+
options.sendAssetRequest.FeeRate = feeRate
1107+
}
1108+
}
1109+
1110+
// withError is an option to specify the string that is expected in the error
1111+
// returned by the SendAsset call.
1112+
func withError(errorText string) sendOption {
1113+
return func(options *sendOptions) {
1114+
options.errText = errorText
1115+
}
1116+
}
1117+
1118+
// sendAsset spends the given input asset and sends the amount specified
10821119
// in the address to the Taproot output derived from the address.
1083-
func sendAssetsToAddr(t *harnessTest, sender *tapdHarness,
1084-
receiverAddrs ...*taprpc.Addr) (*taprpc.SendAssetResponse,
1120+
func sendAsset(t *harnessTest, sender *tapdHarness,
1121+
opts ...sendOption) (*taprpc.SendAssetResponse,
10851122
*EventSubscription[*taprpc.SendEvent]) {
10861123

10871124
ctxb := context.Background()
10881125
ctxt, cancel := context.WithTimeout(ctxb, defaultWaitTimeout)
10891126
defer cancel()
10901127

1091-
require.NotEmpty(t.t, receiverAddrs)
1092-
scriptKey := receiverAddrs[0].ScriptKey
1128+
// Create base request that will be modified by options.
1129+
options := &sendOptions{}
10931130

1094-
encodedAddrs := make([]string, len(receiverAddrs))
1095-
for i, addr := range receiverAddrs {
1096-
encodedAddrs[i] = addr.Encoded
1131+
// Apply all the functional options.
1132+
for _, opt := range opts {
1133+
opt(options)
10971134
}
10981135

1136+
require.NotEmpty(t.t, options.sendAssetRequest.TapAddrs)
1137+
1138+
// We need the first address's scriptkey to subscribe to events.
1139+
firstAddr, err := address.DecodeAddress(
1140+
options.sendAssetRequest.TapAddrs[0], &address.RegressionNetTap,
1141+
)
1142+
require.NoError(t.t, err)
1143+
scriptKey := firstAddr.ScriptKey.SerializeCompressed()
1144+
10991145
ctxc, streamCancel := context.WithCancel(ctxb)
11001146
stream, err := sender.SubscribeSendEvents(
11011147
ctxc, &taprpc.SubscribeSendEventsRequest{
@@ -1108,9 +1154,12 @@ func sendAssetsToAddr(t *harnessTest, sender *tapdHarness,
11081154
Cancel: streamCancel,
11091155
}
11101156

1111-
resp, err := sender.SendAsset(ctxt, &taprpc.SendAssetRequest{
1112-
TapAddrs: encodedAddrs,
1113-
})
1157+
resp, err := sender.SendAsset(ctxt, &options.sendAssetRequest)
1158+
if options.errText != "" {
1159+
require.ErrorContains(t.t, err, options.errText)
1160+
return nil, nil
1161+
}
1162+
11141163
require.NoError(t.t, err)
11151164

11161165
// We'll get events up to the point where we broadcast the transaction.
@@ -1123,6 +1172,15 @@ func sendAssetsToAddr(t *harnessTest, sender *tapdHarness,
11231172
return resp, sub
11241173
}
11251174

1175+
// sendAssetsToAddr is a variadic wrapper around sendAsset that enables passsing
1176+
// a multitude of addresses.
1177+
func sendAssetsToAddr(t *harnessTest, sender *tapdHarness,
1178+
receiverAddrs ...*taprpc.Addr) (*taprpc.SendAssetResponse,
1179+
*EventSubscription[*taprpc.SendEvent]) {
1180+
1181+
return sendAsset(t, sender, withReceiverAddresses(receiverAddrs...))
1182+
}
1183+
11261184
// fundAddressSendPacket asks the wallet to fund a new virtual packet with the
11271185
// given address as the single receiver.
11281186
func fundAddressSendPacket(t *harnessTest, tapd *tapdHarness,

itest/send_test.go

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
"testing"
1010
"time"
1111

12+
"github.com/btcsuite/btcd/btcutil"
13+
"github.com/btcsuite/btcd/wire"
1214
"github.com/lightninglabs/taproot-assets/internal/test"
1315
"github.com/lightninglabs/taproot-assets/proof"
1416
"github.com/lightninglabs/taproot-assets/tapfreighter"
@@ -18,7 +20,9 @@ import (
1820
"github.com/lightninglabs/taproot-assets/taprpc/tapdevrpc"
1921
unirpc "github.com/lightninglabs/taproot-assets/taprpc/universerpc"
2022
"github.com/lightninglabs/taproot-assets/tapsend"
23+
"github.com/lightningnetwork/lnd/lnrpc"
2124
"github.com/lightningnetwork/lnd/lntest/wait"
25+
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
2226
"github.com/stretchr/testify/require"
2327
)
2428

@@ -140,6 +144,144 @@ func testBasicSendUnidirectional(t *harnessTest) {
140144
wg.Wait()
141145
}
142146

147+
// testMinRelayFeeBump tests that if the fee estimation is below the min relay
148+
// fee the feerate is bumped to the min relay fee for both the minting
149+
// transaction and a basic asset send.
150+
func testMinRelayFeeBump(t *harnessTest) {
151+
var ctxb = context.Background()
152+
153+
const numUnits = 10
154+
155+
// Subscribe to receive assent send events from primary tapd node.
156+
events := SubscribeSendEvents(t.t, t.tapd)
157+
158+
// We will mint assets using the first output and then use the second
159+
// output for the transfer. This ensures a valid fee calculation.
160+
initialUTXOs := []*UTXORequest{
161+
{
162+
Type: lnrpc.AddressType_NESTED_PUBKEY_HASH,
163+
Amount: 1_000_000,
164+
},
165+
{
166+
Type: lnrpc.AddressType_NESTED_PUBKEY_HASH,
167+
Amount: 999_990,
168+
},
169+
}
170+
171+
// Set the initial state of the wallet of the first node. The wallet
172+
// state will reset at the end of this test.
173+
SetNodeUTXOs(t, t.lndHarness.Alice, btcutil.Amount(1), initialUTXOs)
174+
defer ResetNodeWallet(t, t.lndHarness.Alice)
175+
176+
// Set the min relay fee to a higher value than the fee rate that will
177+
// be returned by the fee estimation.
178+
lowFeeRate := chainfee.SatPerVByte(1).FeePerKWeight()
179+
highMinRelayFeeRate := chainfee.SatPerVByte(2).FeePerKVByte()
180+
defaultMinRelayFeeRate := chainfee.SatPerVByte(1).FeePerKVByte()
181+
defaultFeeRate := chainfee.SatPerKWeight(3125)
182+
t.lndHarness.SetFeeEstimateWithConf(lowFeeRate, 6)
183+
t.lndHarness.SetMinRelayFeerate(highMinRelayFeeRate)
184+
185+
// Reset all fee rates to their default value at the end of this test.
186+
defer t.lndHarness.SetMinRelayFeerate(defaultMinRelayFeeRate)
187+
defer t.lndHarness.SetFeeEstimateWithConf(defaultFeeRate, 6)
188+
189+
// First, we'll make a normal assets with enough units to allow us to
190+
// send it around a few times.
191+
MintAssetsConfirmBatch(
192+
t.t, t.lndHarness.Miner().Client, t.tapd,
193+
[]*mintrpc.MintAssetRequest{issuableAssets[0]},
194+
WithFeeRate(uint32(lowFeeRate)),
195+
WithError("manual fee rate below floor"),
196+
)
197+
198+
MintAssetsConfirmBatch(
199+
t.t, t.lndHarness.Miner().Client, t.tapd,
200+
[]*mintrpc.MintAssetRequest{issuableAssets[0]},
201+
WithFeeRate(uint32(lowFeeRate)+10),
202+
WithError("feerate does not meet minrelayfee"),
203+
)
204+
205+
rpcAssets := MintAssetsConfirmBatch(
206+
t.t, t.lndHarness.Miner().Client, t.tapd,
207+
[]*mintrpc.MintAssetRequest{issuableAssets[0]},
208+
)
209+
210+
genInfo := rpcAssets[0].AssetGenesis
211+
212+
// Check the final fee rate of the mint TX.
213+
rpcMintOutpoint := rpcAssets[0].ChainAnchor.AnchorOutpoint
214+
mintOutpoint, err := wire.NewOutPointFromString(rpcMintOutpoint)
215+
require.NoError(t.t, err)
216+
217+
// We check whether the minting TX is bumped to the min relay fee.
218+
AssertFeeRate(
219+
t.t, t.lndHarness.Miner().Client, initialUTXOs[0].Amount,
220+
&mintOutpoint.Hash, highMinRelayFeeRate.FeePerKWeight(),
221+
)
222+
223+
// Now that we have the asset created, we'll make a new node that'll
224+
// serve as the node which'll receive the assets. The existing tapd
225+
// node will be used to synchronize universe state.
226+
secondTapd := setupTapdHarness(
227+
t.t, t, t.lndHarness.Bob, t.universeServer,
228+
)
229+
defer func() {
230+
require.NoError(t.t, secondTapd.stop(!*noDelete))
231+
}()
232+
233+
// Next, we'll attempt to complete two transfers with distinct
234+
// addresses from our main node to Bob.
235+
currentUnits := issuableAssets[0].Asset.Amount
236+
237+
// Issue a single address which will be reused for each send.
238+
bobAddr, err := secondTapd.NewAddr(ctxb, &taprpc.NewAddrRequest{
239+
AssetId: genInfo.AssetId,
240+
Amt: numUnits,
241+
AssetVersion: rpcAssets[0].Version,
242+
})
243+
require.NoError(t.t, err)
244+
245+
// Deduct what we sent from the expected current number of
246+
// units.
247+
currentUnits -= numUnits
248+
249+
AssertAddrCreated(t.t, secondTapd, rpcAssets[0], bobAddr)
250+
251+
sendAsset(
252+
t, t.tapd, withReceiverAddresses(bobAddr),
253+
withFeeRate(uint32(lowFeeRate)),
254+
withError("manual fee rate below floor"),
255+
)
256+
257+
sendAsset(
258+
t, t.tapd, withReceiverAddresses(bobAddr),
259+
withFeeRate(uint32(lowFeeRate)+10),
260+
withError("feerate does not meet minrelayfee"),
261+
)
262+
263+
sendResp, sendEvents := sendAssetsToAddr(t, t.tapd, bobAddr)
264+
265+
ConfirmAndAssertOutboundTransfer(
266+
t.t, t.lndHarness.Miner().Client, t.tapd, sendResp,
267+
genInfo.AssetId,
268+
[]uint64{currentUnits, numUnits}, 0, 1,
269+
)
270+
271+
sendInputAmt := initialUTXOs[1].Amount + 1000
272+
AssertTransferFeeRate(
273+
t.t, t.lndHarness.Miner().Client, sendResp, sendInputAmt,
274+
highMinRelayFeeRate.FeePerKWeight(),
275+
)
276+
277+
AssertNonInteractiveRecvComplete(t.t, secondTapd, 1)
278+
AssertSendEventsComplete(t.t, bobAddr.ScriptKey, sendEvents)
279+
280+
// Close event stream.
281+
err = events.CloseSend()
282+
require.NoError(t.t, err)
283+
}
284+
143285
// testRestartReceiver tests that the receiver node's asset balance after a
144286
// single asset transfer does not change if the receiver node restarts.
145287
// Before the addition of this test, after restarting the receiver node

itest/test_list_on_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ var testCases = []*testCase{
8181
name: "basic send unidirectional",
8282
test: testBasicSendUnidirectional,
8383
},
84+
{
85+
name: "min relay fee bump",
86+
test: testMinRelayFeeBump,
87+
},
8488
{
8589
name: "restart receiver check balance",
8690
test: testRestartReceiverCheckBalance,

itest/utils.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,8 @@ type MintOptions struct {
266266
mintingTimeout time.Duration
267267
siblingBranch *mintrpc.FinalizeBatchRequest_Branch
268268
siblingFullTree *mintrpc.FinalizeBatchRequest_FullTree
269+
feeRate uint32
270+
errText string
269271
}
270272

271273
func DefaultMintOptions() *MintOptions {
@@ -292,6 +294,20 @@ func WithSiblingTree(tree mintrpc.FinalizeBatchRequest_FullTree) MintOption {
292294
}
293295
}
294296

297+
func WithFeeRate(feeRate uint32) MintOption {
298+
return func(options *MintOptions) {
299+
options.feeRate = feeRate
300+
}
301+
}
302+
303+
// WithError is an option to specify the string that is expected in the error
304+
// returned by the FinalizeBatch call.
305+
func WithError(errorText string) MintOption {
306+
return func(options *MintOptions) {
307+
options.errText = errorText
308+
}
309+
}
310+
295311
func BuildMintingBatch(t *testing.T, tapClient TapdClient,
296312
assetRequests []*mintrpc.MintAssetRequest, opts ...MintOption) {
297313

@@ -334,9 +350,27 @@ func FinalizeBatchUnconfirmed(t *testing.T, minerClient *rpcclient.Client,
334350
if options.siblingFullTree != nil {
335351
finalizeReq.BatchSibling = options.siblingFullTree
336352
}
353+
if options.feeRate > 0 {
354+
finalizeReq.FeeRate = options.feeRate
355+
}
337356

338357
// Instruct the daemon to finalize the batch.
339358
batchResp, err := tapClient.FinalizeBatch(ctxt, finalizeReq)
359+
360+
// If we expect an error, check for it and cancel the batch if it's
361+
// found.
362+
if options.errText != "" {
363+
require.ErrorContains(t, err, options.errText)
364+
cancelBatchKey, err := tapClient.CancelBatch(
365+
ctxt, &mintrpc.CancelBatchRequest{},
366+
)
367+
require.NoError(t, err)
368+
require.NotEmpty(t, cancelBatchKey.BatchKey)
369+
return chainhash.Hash{}, nil
370+
}
371+
372+
// If we don't expect an error, we confirm that the batch has been
373+
// broadcast.
340374
require.NoError(t, err)
341375
require.NotEmpty(t, batchResp.Batch)
342376
require.Len(t, batchResp.Batch.Assets, len(assetRequests))
@@ -443,6 +477,11 @@ func MintAssetsConfirmBatch(t *testing.T, minerClient *rpcclient.Client,
443477
tapClient TapdClient, assetRequests []*mintrpc.MintAssetRequest,
444478
opts ...MintOption) []*taprpc.Asset {
445479

480+
options := DefaultMintOptions()
481+
for _, opt := range opts {
482+
opt(options)
483+
}
484+
446485
ctxc, streamCancel := context.WithCancel(context.Background())
447486
stream, err := tapClient.SubscribeMintEvents(
448487
ctxc, &mintrpc.SubscribeMintEventsRequest{},
@@ -457,6 +496,13 @@ func MintAssetsConfirmBatch(t *testing.T, minerClient *rpcclient.Client,
457496
t, minerClient, tapClient, assetRequests, opts...,
458497
)
459498

499+
// If we expect an error, we know that the error has successfully
500+
// occurred during MintAssetUnconfirmed so we don't need to confirm the
501+
// batch and can return here.
502+
if options.errText != "" {
503+
return nil
504+
}
505+
460506
return ConfirmBatch(
461507
t, minerClient, tapClient, assetRequests, sub, mintTXID,
462508
batchKey, opts...,

0 commit comments

Comments
 (0)