@fibscope/agent 0.1.5 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/index.mjs +61 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -74,7 +74,7 @@ The agent pushes to `POST /agent/v1/report` with:
|
|
|
74
74
|
|
|
75
75
|
```json
|
|
76
76
|
{
|
|
77
|
-
"agentVersion": "0.1.
|
|
77
|
+
"agentVersion": "0.1.6",
|
|
78
78
|
"chain": "testnet",
|
|
79
79
|
"collectedAt": 1710000000000,
|
|
80
80
|
"publicIp": "54.179.226.154",
|
|
@@ -102,7 +102,7 @@ The command result includes provider and receiver balance views before payment,
|
|
|
102
102
|
|
|
103
103
|
## Owner N2N Test Lifecycle
|
|
104
104
|
|
|
105
|
-
`N2N_PAYMENT` is an owner-authorized test command. It never confirms or charges an API test-wallet deposit. The selected source node checks its own funding wallet and existing routes, connects the receiving peer, opens a direct channel
|
|
105
|
+
`N2N_PAYMENT` is an owner-authorized test command. It never confirms or charges an API test-wallet deposit. The selected source node checks its own funding wallet and existing routes, connects the receiving peer, opens a private one-way direct channel when needed, sends over Fiber, and optionally closes only the channel created by that test. FNN v0.8.1 uses dual-funded CKB channel construction, so the receiving node wallet must still provide its configured reserved capacity (commonly about 99 CKB) when a new channel is opened.
|
|
106
106
|
|
|
107
107
|
When both FNN instances publish the same public IPv4 address, the agent dials the receiving P2P port through loopback so same-VPS nodes can complete the Fiber handshake. Command results include structured source selection, peer connection, channel creation, balance movement, Fiber transfer, optional close, and partial failure steps.
|
|
108
108
|
|
package/index.mjs
CHANGED
|
@@ -33,7 +33,7 @@ import { virtualizePayment } from "./lib/route/route.mjs";
|
|
|
33
33
|
import { chooseFnnInstance, describeFnnInstance, discoverFnnInstances, rpcResponds } from "./lib/fnn-discovery.mjs";
|
|
34
34
|
import { buildObservedPeerAddress, discoverPublicIpv4, publishablePeerAddresses } from "./lib/public-peer-address.mjs";
|
|
35
35
|
|
|
36
|
-
const AGENT_VERSION = "0.1.
|
|
36
|
+
const AGENT_VERSION = "0.1.6";
|
|
37
37
|
const DEFAULT_CONFIG_PATH = ".fiber/agent.json";
|
|
38
38
|
const DEFAULT_STATUS_PATH = ".fiber/agent-status.json";
|
|
39
39
|
const DEFAULT_PUSH_INTERVAL_MS = 15_000;
|
|
@@ -1599,11 +1599,14 @@ async function acceptApiNodePayment(runtime, params, options = {}) {
|
|
|
1599
1599
|
peerPubkey: targetPubkey,
|
|
1600
1600
|
peerAddress,
|
|
1601
1601
|
fundingAmount: firstTextValue(params.channelFundingAmount, params.totalDebit, params.amountShannons, params.amount),
|
|
1602
|
+
oneWay: ownerFunded ? true : params.oneWay,
|
|
1602
1603
|
connectFirst: true,
|
|
1603
1604
|
waitUntilReady: true,
|
|
1604
1605
|
waitTimeoutMs: positiveNumber(params.channelReadyTimeoutMs, 10 * 60_000),
|
|
1605
1606
|
pollMs: positiveNumber(params.channelPollMs, 5_000),
|
|
1606
|
-
public
|
|
1607
|
+
// FNN v0.8.1 rejects public one-way channels. N2N tests only need a
|
|
1608
|
+
// direct source-to-receiver path, so keep their temporary channel private.
|
|
1609
|
+
public: ownerFunded ? false : true,
|
|
1607
1610
|
});
|
|
1608
1611
|
paymentChannel = channelOpen.channel ?? await findReadyChannelForPeer(config.fiberRpcUrl, targetPubkey, config.timeoutMs);
|
|
1609
1612
|
}
|
|
@@ -1616,14 +1619,28 @@ async function acceptApiNodePayment(runtime, params, options = {}) {
|
|
|
1616
1619
|
});
|
|
1617
1620
|
} catch (error) {
|
|
1618
1621
|
const partialOpen = error?.commandResult && typeof error.commandResult === "object" ? error.commandResult : undefined;
|
|
1622
|
+
if (!channelOpen && partialOpen) channelOpen = partialOpen;
|
|
1623
|
+
if (!paymentChannel && partialOpen?.channel) paymentChannel = partialOpen.channel;
|
|
1619
1624
|
if (ownerFunded) {
|
|
1620
1625
|
let recovery;
|
|
1621
1626
|
let recoveryError;
|
|
1622
1627
|
try {
|
|
1623
1628
|
const openedChannelId = channelIdOf(paymentChannel);
|
|
1624
1629
|
if (channelOpen && openedChannelId) {
|
|
1625
|
-
|
|
1626
|
-
|
|
1630
|
+
const openedState = channelStateName(paymentChannel);
|
|
1631
|
+
if (isChannelReadyState(openedState)) {
|
|
1632
|
+
recovery = await settleFiberChannel(runtime, { channelId: openedChannelId, force: false, feeRate: "1000" });
|
|
1633
|
+
await waitForChannelClosed(config.fiberRpcUrl, openedChannelId, 10 * 60_000, 5_000);
|
|
1634
|
+
} else {
|
|
1635
|
+
const result = await rpcCall(config.fiberRpcUrl, "abandon_channel", [{ channel_id: openedChannelId }], config.timeoutMs);
|
|
1636
|
+
recovery = {
|
|
1637
|
+
ok: true,
|
|
1638
|
+
action: "ABANDON_CHANNEL",
|
|
1639
|
+
channelId: openedChannelId,
|
|
1640
|
+
previousState: openedState || undefined,
|
|
1641
|
+
result,
|
|
1642
|
+
};
|
|
1643
|
+
}
|
|
1627
1644
|
}
|
|
1628
1645
|
} catch (failure) {
|
|
1629
1646
|
recoveryError = message(failure);
|
|
@@ -1791,12 +1808,17 @@ async function acceptApiNodePayment(runtime, params, options = {}) {
|
|
|
1791
1808
|
export function nodePaymentLifecycle(input) {
|
|
1792
1809
|
const channelOpen = input.channelOpen && typeof input.channelOpen === "object" ? input.channelOpen : undefined;
|
|
1793
1810
|
const connectResult = channelOpen?.connectResult;
|
|
1794
|
-
const
|
|
1811
|
+
const observedChannel = input.paymentChannel ?? channelOpen?.channel;
|
|
1812
|
+
const channelId = input.paymentChannelId || channelIdOf(observedChannel);
|
|
1813
|
+
const observedChannelState = channelStateName(observedChannel);
|
|
1795
1814
|
const existingRoute = Boolean(input.existingChannel) || input.routeProbe?.status === "success";
|
|
1796
1815
|
const peerStatus = existingRoute ? "skipped" : connectResult ? "completed" : input.failure ? "failed" : "pending";
|
|
1797
|
-
const channelStatus = existingRoute ? "skipped" :
|
|
1816
|
+
const channelStatus = existingRoute ? "skipped" : isChannelReadyState(observedChannelState) ? "completed" : input.failure ? "failed" : "pending";
|
|
1798
1817
|
const paymentStatus = input.fiberPayment ? "completed" : input.failure ? "failed" : "pending";
|
|
1799
|
-
const
|
|
1818
|
+
const failedChannelAbandoned = input.recovery?.action === "ABANDON_CHANNEL" && input.recovery?.ok !== false;
|
|
1819
|
+
const closeStatus = failedChannelAbandoned
|
|
1820
|
+
? "completed"
|
|
1821
|
+
: !input.closeChannelAfterPayment
|
|
1800
1822
|
? "skipped"
|
|
1801
1823
|
: input.channelClosed ? "completed"
|
|
1802
1824
|
: input.channelClose?.status === "failed" || input.failure ? "failed" : "pending";
|
|
@@ -1835,9 +1857,13 @@ export function nodePaymentLifecycle(input) {
|
|
|
1835
1857
|
steps: [
|
|
1836
1858
|
{ key: "source_selected", label: "Source node selected", status: "completed", detail: input.source?.displayName || shortText(input.source?.pubkey, 10, 8) },
|
|
1837
1859
|
{ key: "peer_connect", label: "Receiving peer connected", status: peerStatus, detail: existingRoute ? "Existing route does not require a new direct peer connection." : input.failure && !connectResult ? input.failure : input.peerAddress },
|
|
1838
|
-
{ key: "channel_open", label: "Channel ready", status: channelStatus, detail: existingRoute ? "Existing Fiber liquidity selected." : channelId ? `Channel ${shortText(channelId, 10, 8)}` : input.failure },
|
|
1860
|
+
{ key: "channel_open", label: "Channel ready", status: channelStatus, detail: existingRoute ? "Existing Fiber liquidity selected." : channelStatus === "completed" && channelId ? `Channel ${shortText(channelId, 10, 8)}` : input.failure },
|
|
1839
1861
|
{ key: "fiber_transfer", label: "Funds moved over Fiber", status: paymentStatus, detail: input.fiberPayment ? `Payment ${shortText(input.fiberPayment.paymentHash ?? input.fiberPayment.payment_hash ?? input.fiberPayment.targetPubkey, 10, 8)}` : input.failure },
|
|
1840
|
-
...(
|
|
1862
|
+
...(failedChannelAbandoned
|
|
1863
|
+
? [{ key: "channel_cleanup", label: "Failed channel abandoned", status: closeStatus, detail: `Released pending channel ${shortText(channelId, 10, 8)}` }]
|
|
1864
|
+
: input.closeChannelAfterPayment
|
|
1865
|
+
? [{ key: "channel_close", label: "Provisioned channel closed", status: closeStatus, detail: input.channelClose?.error ?? input.channelClosed?.state ?? (input.failure ? "Not attempted because the channel never became ready." : undefined) }]
|
|
1866
|
+
: []),
|
|
1841
1867
|
],
|
|
1842
1868
|
});
|
|
1843
1869
|
}
|
|
@@ -2201,6 +2227,9 @@ async function openFiberChannel(config, params) {
|
|
|
2201
2227
|
public: boolValue(params.public ?? params.publicChannel, true),
|
|
2202
2228
|
};
|
|
2203
2229
|
if (params.oneWay !== undefined || params.one_way !== undefined) openRequest.one_way = boolValue(params.oneWay ?? params.one_way, false);
|
|
2230
|
+
if (openRequest.one_way && openRequest.public) {
|
|
2231
|
+
throw new Error("FNN does not allow a channel to be both public and one-way.");
|
|
2232
|
+
}
|
|
2204
2233
|
if (params.fundingUdtTypeScript ?? params.funding_udt_type_script) openRequest.funding_udt_type_script = params.fundingUdtTypeScript ?? params.funding_udt_type_script;
|
|
2205
2234
|
if (params.commitmentFeeRate ?? params.commitment_fee_rate) openRequest.commitment_fee_rate = quantity(params.commitmentFeeRate ?? params.commitment_fee_rate);
|
|
2206
2235
|
if (params.fundingFeeRate ?? params.funding_fee_rate) openRequest.funding_fee_rate = quantity(params.fundingFeeRate ?? params.funding_fee_rate);
|
|
@@ -2234,6 +2263,7 @@ async function openFiberChannel(config, params) {
|
|
|
2234
2263
|
peerAddress: peer.address,
|
|
2235
2264
|
fundingAmount: openRequest.funding_amount,
|
|
2236
2265
|
public: openRequest.public,
|
|
2266
|
+
oneWay: openRequest.one_way === true,
|
|
2237
2267
|
fundingCheck,
|
|
2238
2268
|
connectResult,
|
|
2239
2269
|
openResult,
|
|
@@ -2244,6 +2274,7 @@ async function openFiberChannel(config, params) {
|
|
|
2244
2274
|
}
|
|
2245
2275
|
return result;
|
|
2246
2276
|
} catch (error) {
|
|
2277
|
+
const observed = error?.commandResult && typeof error.commandResult === "object" ? error.commandResult : undefined;
|
|
2247
2278
|
throw commandResultError(message(error), {
|
|
2248
2279
|
ok: false,
|
|
2249
2280
|
action: "OPEN_CHANNEL",
|
|
@@ -2252,10 +2283,12 @@ async function openFiberChannel(config, params) {
|
|
|
2252
2283
|
peerAddress: peer.address,
|
|
2253
2284
|
fundingAmount: openRequest.funding_amount,
|
|
2254
2285
|
public: openRequest.public,
|
|
2286
|
+
oneWay: openRequest.one_way === true,
|
|
2255
2287
|
fundingCheck,
|
|
2256
2288
|
connectResult,
|
|
2257
2289
|
openResult,
|
|
2258
2290
|
channelWatch,
|
|
2291
|
+
channel: observed?.channel,
|
|
2259
2292
|
failure: message(error),
|
|
2260
2293
|
});
|
|
2261
2294
|
}
|
|
@@ -3159,7 +3192,25 @@ async function waitForChannel(rpcUrl, pubkey, timeoutMs, pollMs) {
|
|
|
3159
3192
|
if (failed) throw new Error(describeChannelTerminalFailure(failed));
|
|
3160
3193
|
await sleep(pollMs);
|
|
3161
3194
|
}
|
|
3162
|
-
|
|
3195
|
+
const fundingHint = isAwaitingRemoteFundingCollaboration(lastChannel)
|
|
3196
|
+
? " The receiving FNN did not complete funding collaboration. A CKB channel acceptor must provide its configured reserved capacity (commonly about 99 CKB), even for a private one-way channel. Fund the receiving node wallet and inspect its FNN logs."
|
|
3197
|
+
: "";
|
|
3198
|
+
throw commandResultError(`Timed out waiting for channel readiness.${fundingHint} Last channel: ${JSON.stringify(lastChannel ?? null)}`, {
|
|
3199
|
+
channel: lastChannel,
|
|
3200
|
+
channelId: channelIdOf(lastChannel) || undefined,
|
|
3201
|
+
});
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
function isAwaitingRemoteFundingCollaboration(channel) {
|
|
3205
|
+
const state = channel?.state && typeof channel.state === "object" ? channel.state : {};
|
|
3206
|
+
const name = textValue(state.state_name ?? state.stateName ?? channel?.state_name ?? channel?.stateName).toLowerCase();
|
|
3207
|
+
const flags = textValue(state.state_flags ?? state.stateFlags ?? channel?.state_flags ?? channel?.stateFlags).toLowerCase();
|
|
3208
|
+
return name === "collaboratingfundingtx" && flags.includes("awaiting_remote_tx_collaboration_msg");
|
|
3209
|
+
}
|
|
3210
|
+
|
|
3211
|
+
function isChannelReadyState(state) {
|
|
3212
|
+
const normalized = textValue(state).toLowerCase();
|
|
3213
|
+
return normalized === "channelready" || normalized === "open";
|
|
3163
3214
|
}
|
|
3164
3215
|
|
|
3165
3216
|
async function watchForImmediateChannelFailure(rpcUrl, pubkey, timeoutMs, pollMs) {
|