Skip to content

Commit 1d32c37

Browse files
author
ffranr
authored
Merge pull request #1470 from lightninglabs/send-validate-proof-courier-connection
Add preliminary proof courier connection check to ChainPorter
2 parents 56f9a5b + 665a37d commit 1d32c37

File tree

10 files changed

+619
-401
lines changed

10 files changed

+619
-401
lines changed

cmd/commands/assets.go

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -38,31 +38,32 @@ var assetsCommands = []cli.Command{
3838
}
3939

4040
var (
41-
assetTypeName = "type"
42-
assetTagName = "name"
43-
assetSupplyName = "supply"
44-
assetMetaBytesName = "meta_bytes"
45-
assetMetaFilePathName = "meta_file_path"
46-
assetMetaTypeName = "meta_type"
47-
assetDecimalDisplayName = "decimal_display"
48-
assetNewGroupedAssetName = "new_grouped_asset"
49-
assetGroupedAssetName = "grouped_asset"
50-
assetShowWitnessName = "show_witness"
51-
assetShowSpentName = "show_spent"
52-
assetShowLeasedName = "show_leased"
53-
assetIncludeLeasedName = "include_leased"
54-
assetShowUnconfMintsName = "show_unconfirmed_mints"
55-
assetGroupKeyName = "group_key"
56-
assetGroupAnchorName = "group_anchor"
57-
anchorTxidName = "anchor_txid"
58-
batchKeyName = "batch_key"
59-
groupByGroupName = "by_group"
60-
assetIDName = "asset_id"
61-
shortResponseName = "short"
62-
universeCommitmentsName = "universe_commitments"
63-
feeRateName = "sat_per_vbyte"
64-
assetAmountName = "amount"
65-
burnOverrideConfirmationName = "override_confirmation_destroy_assets"
41+
assetTypeName = "type"
42+
assetTagName = "name"
43+
assetSupplyName = "supply"
44+
assetMetaBytesName = "meta_bytes"
45+
assetMetaFilePathName = "meta_file_path"
46+
assetMetaTypeName = "meta_type"
47+
assetDecimalDisplayName = "decimal_display"
48+
assetNewGroupedAssetName = "new_grouped_asset"
49+
assetGroupedAssetName = "grouped_asset"
50+
assetShowWitnessName = "show_witness"
51+
assetShowSpentName = "show_spent"
52+
assetShowLeasedName = "show_leased"
53+
assetIncludeLeasedName = "include_leased"
54+
assetShowUnconfMintsName = "show_unconfirmed_mints"
55+
assetGroupKeyName = "group_key"
56+
assetGroupAnchorName = "group_anchor"
57+
anchorTxidName = "anchor_txid"
58+
batchKeyName = "batch_key"
59+
groupByGroupName = "by_group"
60+
assetIDName = "asset_id"
61+
shortResponseName = "short"
62+
universeCommitmentsName = "universe_commitments"
63+
feeRateName = "sat_per_vbyte"
64+
skipProofCourierPingCheckName = "skip-proof-courier-ping-check"
65+
assetAmountName = "amount"
66+
burnOverrideConfirmationName = "override_confirmation_destroy_assets"
6667
)
6768

6869
var mintAssetCommand = cli.Command{
@@ -824,6 +825,10 @@ var sendAssetsCommand = cli.Command{
824825
Usage: "if set, the fee rate in sat/vB to use for " +
825826
"the anchor transaction",
826827
},
828+
cli.BoolFlag{
829+
Name: skipProofCourierPingCheckName,
830+
Usage: "if set, skip the proof courier ping check",
831+
},
827832
// TODO(roasbeef): add arg for file name to write sender proof
828833
// blob
829834
},
@@ -848,6 +853,9 @@ func sendAssets(ctx *cli.Context) error {
848853
resp, err := client.SendAsset(ctxc, &taprpc.SendAssetRequest{
849854
TapAddrs: addrs,
850855
FeeRate: feeRate,
856+
SkipProofCourierPingCheck: ctx.Bool(
857+
skipProofCourierPingCheckName,
858+
),
851859
})
852860
if err != nil {
853861
return fmt.Errorf("unable to send assets: %w", err)

itest/addrs_test.go

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -912,8 +912,9 @@ func transferProofNormalExportUniInsert(t *harnessTest, src, dst *tapdHarness,
912912
// sendOptions is a struct that holds a SendAssetRequest and an
913913
// optional error string that should be tested against.
914914
type sendOptions struct {
915-
sendAssetRequest taprpc.SendAssetRequest
916-
errText string
915+
sendAssetRequest taprpc.SendAssetRequest
916+
skipProofCourierPingCheck bool
917+
errText string
917918
}
918919

919920
// sendOption is a functional option for configuring the sendAssets call.
@@ -931,6 +932,14 @@ func withReceiverAddresses(addrs ...*taprpc.Addr) sendOption {
931932
}
932933
}
933934

935+
// withSkipProofCourierPingCheck is an option to skip the proof courier ping
936+
// check. This is useful for testing purposes.
937+
func withSkipProofCourierPingCheck() sendOption {
938+
return func(options *sendOptions) {
939+
options.skipProofCourierPingCheck = true
940+
}
941+
}
942+
934943
// withFeeRate is an option to specify the fee rate for the send.
935944
func withFeeRate(feeRate uint32) sendOption {
936945
return func(options *sendOptions) {
@@ -999,6 +1008,11 @@ func sendAsset(t *harnessTest, sender *tapdHarness,
9991008
},
10001009
}
10011010

1011+
// Apply the skip proof courier ping check option if set.
1012+
if options.skipProofCourierPingCheck {
1013+
options.sendAssetRequest.SkipProofCourierPingCheck = true
1014+
}
1015+
10021016
// Kick off the send asset request.
10031017
resp, err := sender.SendAsset(ctxt, &options.sendAssetRequest)
10041018
if options.errText != "" {
@@ -1015,7 +1029,7 @@ func sendAsset(t *harnessTest, sender *tapdHarness,
10151029
var targetScriptKey []byte = nil
10161030
AssertSendEvents(
10171031
t.t, targetScriptKey, sub,
1018-
tapfreighter.SendStateVirtualCommitmentSelect,
1032+
tapfreighter.SendStateStartHandleAddrParcel,
10191033
tapfreighter.SendStateBroadcast,
10201034
)
10211035

itest/assertions.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,7 +1010,7 @@ func AssertSendEvents(t *testing.T, targetScriptKey []byte,
10101010

10111011
success := make(chan struct{})
10121012
timeout := time.After(defaultWaitTimeout)
1013-
startStatus := from
1013+
expectedStatus := from
10141014

10151015
// By default, if the target script key is not given we will accept all
10161016
// send events.
@@ -1056,7 +1056,7 @@ func AssertSendEvents(t *testing.T, targetScriptKey []byte,
10561056
require.Emptyf(
10571057
t, event.Error, "send event error: %x", event,
10581058
)
1059-
require.Equal(t, startStatus.String(), event.SendState)
1059+
require.Equal(t, expectedStatus.String(), event.SendState)
10601060

10611061
// Fully close the stream once we definitely no longer need the
10621062
// stream.
@@ -1069,7 +1069,7 @@ func AssertSendEvents(t *testing.T, targetScriptKey []byte,
10691069
return
10701070
}
10711071

1072-
startStatus++
1072+
expectedStatus++
10731073
}
10741074
}
10751075

itest/send_test.go

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -738,7 +738,13 @@ func testReattemptFailedSendHashmailCourier(t *harnessTest) {
738738
require.NoError(t.t, t.tapd.stop(false))
739739

740740
// Send asset and then mine to confirm the associated on-chain tx.
741-
sendAssetsToAddr(t, sendTapd, recvAddr)
741+
//
742+
// We skip the proof courier ping check so that we can ensure that proof
743+
// transfer is reattempted.
744+
sendAsset(
745+
t, sendTapd, withReceiverAddresses(recvAddr),
746+
withSkipProofCourierPingCheck(),
747+
)
742748
_ = MineBlocks(t.t, t.lndHarness.Miner().Client, 1, 1)
743749

744750
// Define a target event selector to match the backoff wait
@@ -842,7 +848,10 @@ func testReattemptProofTransferOnTapdRestart(t *harnessTest) {
842848
// Start asset transfer and then mine to confirm the associated on-chain
843849
// tx. The on-chain tx should be mined successfully, but we expect the
844850
// asset proof transfer to be unsuccessful.
845-
sendResp, _ := sendAssetsToAddr(t, sendTapd, recvAddr)
851+
sendResp, _ := sendAsset(
852+
t, sendTapd, withReceiverAddresses(recvAddr),
853+
withSkipProofCourierPingCheck(),
854+
)
846855
MineBlocks(t.t, t.lndHarness.Miner().Client, 1, 1)
847856

848857
// Define a target event selector to match the backoff wait
@@ -997,7 +1006,13 @@ func testReattemptFailedSendUniCourier(t *harnessTest) {
9971006
require.NoError(t.t, t.proofCourier.Stop())
9981007

9991008
// Send asset and then mine to confirm the associated on-chain tx.
1000-
sendAssetsToAddr(t, sendTapd, recvAddr)
1009+
//
1010+
// We skip the proof courier ping check so that we can ensure that proof
1011+
// transfer is reattempted.
1012+
sendAsset(
1013+
t, sendTapd, withReceiverAddresses(recvAddr),
1014+
withSkipProofCourierPingCheck(),
1015+
)
10011016
_ = MineBlocks(t.t, t.lndHarness.Miner().Client, 1, 1)
10021017

10031018
// Define a target event selector to match the backoff wait
@@ -1009,7 +1024,7 @@ func testReattemptFailedSendUniCourier(t *harnessTest) {
10091024
// Expected number of events is one less than the number of
10101025
// tries because the first attempt does not count as a backoff
10111026
// event.
1012-
nodeBackoffCfg := sendTapd.clientCfg.HashMailCourier.BackoffCfg
1027+
nodeBackoffCfg := sendTapd.clientCfg.UniverseRpcCourier.BackoffCfg
10131028
expectedEventCount := nodeBackoffCfg.NumTries - 1
10141029

10151030
// Context timeout scales with expected number of events.
@@ -1104,7 +1119,10 @@ func testSpendChangeOutputWhenProofTransferFail(t *harnessTest) {
11041119
// Start asset transfer and then mine to confirm the associated on-chain
11051120
// tx. The on-chain tx should be mined successfully, but we expect the
11061121
// asset proof transfer to be unsuccessful.
1107-
sendAssetsToAddr(t, sendTapd, recvAddr)
1122+
sendAsset(
1123+
t, sendTapd, withReceiverAddresses(recvAddr),
1124+
withSkipProofCourierPingCheck(),
1125+
)
11081126
MineBlocks(t.t, t.lndHarness.Miner().Client, 1, 1)
11091127

11101128
// There may be a delay between mining the anchoring transaction and
@@ -1178,7 +1196,10 @@ func testSpendChangeOutputWhenProofTransferFail(t *harnessTest) {
11781196
require.NoError(t.t, err)
11791197
AssertAddrCreated(t.t, recvTapd, rpcAssets[0], recvAddr)
11801198

1181-
sendAssetsToAddr(t, sendTapd, recvAddr)
1199+
sendAsset(
1200+
t, sendTapd, withReceiverAddresses(recvAddr),
1201+
withSkipProofCourierPingCheck(),
1202+
)
11821203
MineBlocks(t.t, t.lndHarness.Miner().Client, 1, 1)
11831204

11841205
// There may be a delay between mining the anchoring transaction and

rpcserver.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3375,7 +3375,10 @@ func (r *rpcServer) SendAsset(ctx context.Context,
33753375
}
33763376

33773377
resp, err := r.cfg.ChainPorter.RequestShipment(
3378-
tapfreighter.NewAddressParcel(feeRate, req.Label, tapAddrs...),
3378+
tapfreighter.NewAddressParcel(
3379+
feeRate, req.Label, req.SkipProofCourierPingCheck,
3380+
tapAddrs...,
3381+
),
33793382
)
33803383
if err != nil {
33813384
return nil, err

tapfreighter/chain_porter.go

Lines changed: 129 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"context"
66
"errors"
77
"fmt"
8+
"net/url"
89
"strings"
910
"sync"
1011
"time"
@@ -1093,13 +1094,138 @@ func (p *ChainPorter) importLocalAddresses(ctx context.Context,
10931094
return nil
10941095
}
10951096

1097+
// pingCourier attempts to establish a connection to the given proof courier
1098+
// address. If the connection is successful, the courier is closed and the
1099+
// function returns nil. If the connection fails, an error is returned.
1100+
// This function is blocking.
1101+
func (p *ChainPorter) pingCourier(ctx context.Context, addr url.URL) error {
1102+
log.Debugf("Attempting to ping proof courier (addr=%s)", addr.String())
1103+
1104+
// Connect to the proof courier service with an eager (non-lazy)
1105+
// connection attempt, blocking until the connection either succeeds,
1106+
// fails, or times out.
1107+
courier, err := p.cfg.ProofCourierDispatcher.NewCourier(
1108+
ctx, &addr, false,
1109+
)
1110+
if err != nil {
1111+
return fmt.Errorf("unable to initiate proof courier "+
1112+
"service handle (addr=%s): %w", addr.String(), err)
1113+
}
1114+
1115+
return courier.Close()
1116+
}
1117+
1118+
// pingProofCouriers performs a blocking connectivity check for each applicable
1119+
// proof courier.
1120+
func (p *ChainPorter) pingProofCouriers(proofCourierAddrs []url.URL) error {
1121+
// Construct minimal set of unique proof couriers to ping.
1122+
var couriers []url.URL
1123+
1124+
for idx := range proofCourierAddrs {
1125+
addr := proofCourierAddrs[idx]
1126+
1127+
// Check if the address is a duplicate (already in the list of
1128+
// couriers).
1129+
for i := range couriers {
1130+
if addr.String() == couriers[i].String() {
1131+
// Skip duplicate addresses.
1132+
continue
1133+
}
1134+
}
1135+
1136+
couriers = append(couriers, addr)
1137+
}
1138+
1139+
// Ping each proof courier in parallel to ensure they are reachable.
1140+
ctx, cancel := p.WithCtxQuit()
1141+
defer cancel()
1142+
instanceErrors, err := fn.ParSliceErrCollect(
1143+
ctx, couriers, p.pingCourier,
1144+
)
1145+
if err != nil {
1146+
return fmt.Errorf("failed execute proof courier(s) parallel "+
1147+
"ping: %w", err)
1148+
}
1149+
1150+
// If any errors occurred while pinging proof couriers, log them all
1151+
// here.
1152+
for idx := range instanceErrors {
1153+
addr := couriers[idx]
1154+
instanceErr := instanceErrors[idx]
1155+
1156+
log.Errorf("Failed to pinging proof courier (addr=%s): %v",
1157+
addr.String(), instanceErr)
1158+
}
1159+
1160+
// If any errors occurred while pinging proof couriers, return an error.
1161+
if len(instanceErrors) > 0 {
1162+
return fmt.Errorf("failed to ping proof courier(s) "+
1163+
"(error_count=%d)", len(instanceErrors))
1164+
}
1165+
1166+
return nil
1167+
}
1168+
1169+
// prelimCheckAddrParcel performs preliminary validation on the given address
1170+
// parcel. These early checks run before any coin locking or transaction
1171+
// broadcasting occurs.
1172+
func (p *ChainPorter) prelimCheckAddrParcel(addrParcel AddressParcel) error {
1173+
// Currently, the only preliminary check is to ensure that the proof
1174+
// couriers are reachable. If the skip flag is set, we skip this
1175+
// check and exit early.
1176+
if addrParcel.skipProofCourierPingCheck {
1177+
log.Debugf("Flag skipProofCourierPingCheck activated. " +
1178+
"Skipping check. ")
1179+
return nil
1180+
}
1181+
1182+
// Ping the proof couriers to verify that they are reachable.
1183+
// This early check ensures a proof can be reliably delivered
1184+
// to the counterparty before broadcasting a transaction or
1185+
// locking local funds.
1186+
var proofCourierAddrs []url.URL
1187+
for idx := range addrParcel.destAddrs {
1188+
tapAddr := addrParcel.destAddrs[idx]
1189+
1190+
proofCourierAddrs = append(
1191+
proofCourierAddrs, tapAddr.ProofCourierAddr,
1192+
)
1193+
}
1194+
1195+
err := p.pingProofCouriers(proofCourierAddrs)
1196+
if err != nil {
1197+
return fmt.Errorf("failed proof courier(s) connection "+
1198+
"check: %w", err)
1199+
}
1200+
1201+
return nil
1202+
}
1203+
10961204
// stateStep attempts to step through the state machine to complete a Taproot
10971205
// Asset transfer.
10981206
func (p *ChainPorter) stateStep(currentPkg sendPackage) (*sendPackage, error) {
10991207
switch currentPkg.SendState {
1100-
// At this point we have the initial package information populated, so
1101-
// we'll perform coin selection to see if the send request is even
1102-
// possible at all.
1208+
// The initial state entered when the state machine begins processing a
1209+
// new address parcel. In this state, basic validation is performed,
1210+
// such as verifying connectivity to any required proof courier service.
1211+
case SendStateStartHandleAddrParcel:
1212+
// Ensure that the parcel is a valid address parcel.
1213+
addrParcel, ok := currentPkg.Parcel.(*AddressParcel)
1214+
if !ok {
1215+
return nil, fmt.Errorf("unable to cast parcel to " +
1216+
"address parcel")
1217+
}
1218+
1219+
err := p.prelimCheckAddrParcel(*addrParcel)
1220+
if err != nil {
1221+
return nil, fmt.Errorf("failed to perform prelim "+
1222+
"checks on address parcel: %w", err)
1223+
}
1224+
1225+
currentPkg.SendState = SendStateVirtualCommitmentSelect
1226+
return &currentPkg, nil
1227+
1228+
// Perform coin selection for the address parcel.
11031229
case SendStateVirtualCommitmentSelect:
11041230
ctx, cancel := p.WithCtxQuitNoTimeout()
11051231
defer cancel()

0 commit comments

Comments
 (0)