-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathauthorize_and_store_papi_smoldot.js
More file actions
190 lines (161 loc) · 7.25 KB
/
authorize_and_store_papi_smoldot.js
File metadata and controls
190 lines (161 loc) · 7.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
import assert from "assert";
import * as smoldot from 'smoldot';
import { readFileSync } from 'fs';
import { createClient } from 'polkadot-api';
import { getSmProvider } from 'polkadot-api/sm-provider';
import { cryptoWaitReady } from '@polkadot/util-crypto';
import { authorizeAccount, fetchCid, store, TX_MODE_FINALIZED_BLOCK } from './api.js';
import { setupKeyringAndSigners, waitForChainReady, waitForBlockProduction, DEFAULT_IPFS_GATEWAY_URL } from './common.js';
import { logHeader, logConfig, logSuccess, logError, logTestResult } from './logger.js';
import { cidFromBytes } from "./cid_dag_metadata.js";
import { bulletin } from './.papi/descriptors/dist/index.js';
// Constants
// Increased sync time for parachain mode where smoldot needs more time to sync relay + para
const SYNC_WAIT_SEC = 30;
const SMOLDOT_LOG_LEVEL = 3; // 0=off, 1=error, 2=warn, 3=info, 4=debug, 5=trace
const TCP_BOOTNODE_REGEX = /^(\/ip[46]\/[^/]+)\/tcp\/(\d+)\/p2p\/(.+)$/;
const WS_BOOTNODE_REGEX = /\/tcp\/\d+\/ws\/p2p\//;
/**
* Converts a TCP bootnode to WebSocket format for smoldot compatibility.
* If already a WS address (zombienet default), returns it unchanged.
* For plain TCP bootnodes, uses convention: WebSocket port = TCP p2p_port + 1.
*/
function convertBootNodeToWebSocket(addr) {
// Already a WebSocket address
if (WS_BOOTNODE_REGEX.test(addr)) {
console.log(` ✅ Already WebSocket: ${addr.substring(0, 50)}...`);
return addr;
}
const match = addr.match(TCP_BOOTNODE_REGEX);
if (match) {
const [, hostPart, portStr, peerId] = match;
const wsPort = parseInt(portStr, 10) + 1;
console.log(` 📡 Converted: tcp/${portStr} -> tcp/${wsPort}/ws`);
return `${hostPart}/tcp/${wsPort}/ws/p2p/${peerId}`;
}
return null;
}
function readChainSpec(chainspecPath) {
const chainSpecObj = JSON.parse(readFileSync(chainspecPath, 'utf8'));
chainSpecObj.protocolId = null;
const bootNodes = chainSpecObj.bootNodes || [];
if (bootNodes.length === 0) {
console.log(`⚠️ No bootnodes found in chain spec: ${chainspecPath}`);
return JSON.stringify(chainSpecObj);
}
console.log(`🔄 Converting ${bootNodes.length} bootnode(s) to WebSocket for smoldot...`);
const wsBootNodes = bootNodes.map(convertBootNodeToWebSocket).filter(Boolean);
if (wsBootNodes.length > 0) {
chainSpecObj.bootNodes = wsBootNodes;
console.log(`✅ Using ${wsBootNodes.length} WebSocket bootnode(s)`);
} else {
console.log(`⚠️ No bootnodes could be converted to WebSocket`);
}
return JSON.stringify(chainSpecObj);
}
function initSmoldot() {
return smoldot.start({
maxLogLevel: SMOLDOT_LOG_LEVEL,
logCallback: (level, target, message) => {
const levelName = ['ERROR', 'WARN', 'INFO', 'DEBUG', 'TRACE'][level - 1] || 'UNKNOWN';
console.log(`[smoldot:${levelName}] ${target}: ${message}`);
}
});
}
async function createSmoldotClient(chainSpecPath, parachainSpecPath = null) {
const sd = initSmoldot();
const mainChainSpec = readChainSpec(chainSpecPath);
const parachainSpec = parachainSpecPath ? readChainSpec(parachainSpecPath) : null;
const provider = getSmProvider(async () => {
const mainChain = await sd.addChain({ chainSpec: mainChainSpec });
console.log(`✅ Added main chain: ${chainSpecPath}`);
if (parachainSpec) {
const parachain = await sd.addChain({
chainSpec: parachainSpec,
potentialRelayChains: [mainChain]
});
console.log(`✅ Added parachain: ${parachainSpecPath}`);
return parachain;
}
return mainChain;
});
return { client: createClient(provider), sd };
}
async function main() {
await cryptoWaitReady();
logHeader('AUTHORIZE AND STORE TEST (Smoldot Light Client)');
// Get chainspec path from command line argument (required - main chain: relay for para, or solo)
const chainSpecPath = process.argv[2];
if (!chainSpecPath) {
logError('Chain spec path is required as first argument');
console.error('Usage: node authorize_and_store_papi_smoldot.js <chain-spec-path> [parachain-spec-path] [ipfs-api-url]');
console.error(' For parachains: <relay-chain-spec-path> <parachain-spec-path> [ipfs-api-url]');
console.error(' For solochains: <solo-chain-spec-path> [ipfs-api-url]');
process.exit(1);
}
// Optional parachain chainspec path (only needed for parachains)
const parachainSpecPath = process.argv[3] || null;
// Optional IPFS API URL
const HTTP_IPFS_API = process.argv[4] || DEFAULT_IPFS_GATEWAY_URL;
logConfig({
'Mode': 'Smoldot Light Client',
'Chain Spec': chainSpecPath,
'Parachain Spec': parachainSpecPath || 'N/A (solochain)',
'IPFS API': HTTP_IPFS_API
});
let sd, client, resultCode;
try {
// Init Smoldot PAPI client and typed api.
({ client, sd } = await createSmoldotClient(chainSpecPath, parachainSpecPath));
console.log(`⏭️ Waiting ${SYNC_WAIT_SEC} seconds for smoldot to sync...`);
// TODO: check better way, when smoldot is synced, maybe some RPC/runtime api that checks best vs finalized block?
await new Promise(resolve => setTimeout(resolve, SYNC_WAIT_SEC * 1000));
console.log('🔍 Checking if chain is ready...');
const bulletinAPI = client.getTypedApi(bulletin);
await waitForChainReady(bulletinAPI);
await waitForBlockProduction(bulletinAPI);
// Signers: Use Bob for the account being authorized to avoid nonce conflicts
// when running after ws test (which uses Alice) on the same chain.
const { authorizationSigner, whoSigner, whoAddress } = setupKeyringAndSigners('//Alice', '//Papismoldosigner');
// Data to store.
const dataToStore = "Hello, Bulletin with PAPI + Smoldot - " + new Date().toString();
let expectedCid = await cidFromBytes(dataToStore);
// Authorize an account.
await authorizeAccount(
bulletinAPI,
authorizationSigner,
whoAddress,
100,
BigInt(100 * 1024 * 1024), // 100 MiB
TX_MODE_FINALIZED_BLOCK,
);
// Store data.
const { cid } = await store(bulletinAPI, whoSigner, dataToStore);
logSuccess(`Data stored successfully with CID: ${cid}`);
// Read back from IPFS
let downloadedContent = await fetchCid(HTTP_IPFS_API, cid);
logSuccess(`Downloaded content: ${downloadedContent.toString()}`);
assert.deepStrictEqual(
cid,
expectedCid,
'❌ expectedCid does not match cid!'
);
assert.deepStrictEqual(
dataToStore,
downloadedContent.toString(),
'❌ dataToStore does not match downloadedContent!'
);
logSuccess('Verified content!');
logTestResult(true, 'Authorize and Store Test (Smoldot)');
resultCode = 0;
} catch (error) {
logError(`Error: ${error.message}`);
console.error(error);
resultCode = 1;
} finally {
if (client) client.destroy();
if (sd) sd.terminate();
process.exit(resultCode);
}
}
await main();