Skip to content

Commit afce928

Browse files
authored
Merge pull request #81 from iron-fish/feat-example-download-script
feat: add example script for downloading chain data
2 parents b966e8d + dbc2e54 commit afce928

File tree

6 files changed

+327
-136
lines changed

6 files changed

+327
-136
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@ node_modules/
99
*testdb*
1010
*yarn-error.log
1111
test/cache/
12-
blocks*
12+
*blocks*
13+
*byteRanges*
1314
manifest.json

example/.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
SPENDING_KEY=""
2-
WALLET_SERVER_HOST="walletserver.ironfish.network:50051"
2+
WALLET_SERVER_HOST="https://walletserver.ironfish.network"
33
BLOCK_PROCESSOR_BATCH_SIZE=99

example/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
"main": "index.js",
66
"scripts": {
77
"dev": "ts-node src/index.ts",
8+
"download": "ts-node src/downloadChain.ts",
9+
"download-api-only": "LATEST_FINALIZED_SEQUENCE=422949 ts-node src/downloadChain.ts",
810
"gen-api": "yarn swagger-typescript-api -p ../src/swagger/swagger.json -o src/api",
911
"test": "echo \"Error: no test specified\" && exit 1"
1012
},
@@ -19,5 +21,8 @@
1921
"swagger-typescript-api": "^13.0.3",
2022
"ts-node": "^10.9.1",
2123
"typescript": "^5.1.6"
24+
},
25+
"dependencies": {
26+
"axios": "^1.7.2"
2227
}
2328
}

example/src/Client/utils/BlockProcessor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ export class BlockProcessor {
136136

137137
for (const transaction of block.transactions) {
138138
for (const output of transaction.outputs) {
139-
notes.push(Buffer.from(output.note, "hex"));
139+
notes.push(output.note);
140140
}
141141
}
142142

example/src/downloadChain.ts

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import { config } from "dotenv";
2+
config();
3+
4+
import axios from "axios";
5+
import zlib from "zlib";
6+
import fs from "fs";
7+
import { promisify } from "util";
8+
import { LightBlock } from "../../src/models/lightstreamer";
9+
import { ManifestFile } from "../../src/upload";
10+
import { Api } from "./api/Api";
11+
12+
// Define the URL for the manifest
13+
const BLOCKS_URL = "https://testnet.lightblocks.ironfish.network/";
14+
const MANIFEST_PATH = BLOCKS_URL + "manifest.json";
15+
16+
// Function to fetch JSON data from a URL
17+
async function fetchJson<T>(url: string): Promise<T> {
18+
const response = await axios.get(url);
19+
return response.data as T;
20+
}
21+
22+
// Function to download a file from a URL and save it locally
23+
async function downloadFile(url: string, dest: string): Promise<void> {
24+
const response = await axios.get(url, { responseType: "stream" });
25+
const writer = fs.createWriteStream(dest);
26+
27+
response.data.pipe(writer);
28+
29+
return new Promise((resolve, reject) => {
30+
writer.on("finish", resolve);
31+
writer.on("error", reject);
32+
});
33+
}
34+
35+
const gunzip = promisify(zlib.gunzip);
36+
37+
async function readGzippedFile(byteRangesFile: string) {
38+
const compressedData = fs.readFileSync(byteRangesFile);
39+
const decompressedData = await gunzip(compressedData);
40+
const byteRanges = decompressedData
41+
.toString("utf-8")
42+
.split("\n")
43+
.filter((line) => line.trim() !== "");
44+
return byteRanges;
45+
}
46+
47+
// Function to download finalized blocks and convert them to LightBlock
48+
async function downloadFinalizedBlocks(): Promise<number> {
49+
const latestFinalizedBlockSequence = 0;
50+
try {
51+
// Fetch the manifest file
52+
const manifest = await fetchJson<ManifestFile>(MANIFEST_PATH);
53+
54+
for (const chunk of manifest.chunks) {
55+
if (chunk.finalized) {
56+
// Download the block binary file
57+
const blocksUrl = `${BLOCKS_URL}${chunk.blocks}`;
58+
const blocksFile = `blocks_${chunk.range.start}_${chunk.range.end}`;
59+
await downloadFile(blocksUrl, blocksFile);
60+
61+
// Download the byte ranges file
62+
const byteRangesUrl = `${BLOCKS_URL}${chunk.byteRangesFile}`;
63+
const byteRangesFile = `byteRanges_${chunk.range.start}_${chunk.range.end}.csv.gz`;
64+
await downloadFile(byteRangesUrl, byteRangesFile);
65+
66+
// Read the byte ranges file and convert each block
67+
const byteRanges = await readGzippedFile(byteRangesFile);
68+
const blockBuffer = fs.readFileSync(blocksFile);
69+
for (const range of byteRanges) {
70+
const [_, start, end] = range.split(",").map(Number);
71+
const blockBufferSub = blockBuffer.subarray(start, end + 1);
72+
const lightBlock = LightBlock.decode(blockBufferSub);
73+
// Process the LightBlock as needed
74+
console.log(
75+
`Downloaded LightBlock sequence: ${
76+
lightBlock.sequence
77+
}, hash: ${lightBlock.hash.toString("hex")}`,
78+
);
79+
}
80+
}
81+
}
82+
} catch (error) {
83+
console.error(`Failed to download finalized blocks: ${error}`);
84+
}
85+
return latestFinalizedBlockSequence;
86+
}
87+
88+
async function main() {
89+
if (!process.env["WALLET_SERVER_HOST"]) {
90+
console.error("WALLET_SERVER_HOST environment variable is required");
91+
process.exit(1);
92+
}
93+
94+
// Allows skipping download and only download unfinalized blocks
95+
let latestFinalizedSequence;
96+
if (process.env["LATEST_FINALIZED_SEQUENCE"]) {
97+
latestFinalizedSequence = Number(process.env["LATEST_FINALIZED_SEQUENCE"]);
98+
} else {
99+
// Download finalized blocks and returns the latest finalized sequence
100+
latestFinalizedSequence = await downloadFinalizedBlocks();
101+
}
102+
const api = new Api({ baseUrl: process.env["WALLET_SERVER_HOST"] });
103+
const latestReponse = await api.latestBlock.getLatestBlock();
104+
const latestBlockSequence = latestReponse.data.sequence;
105+
106+
const chunkSize = 100;
107+
for (
108+
let i = latestFinalizedSequence + 1;
109+
i < latestBlockSequence;
110+
i += chunkSize
111+
) {
112+
const start = i;
113+
const end = Math.min(i + chunkSize - 1, latestBlockSequence);
114+
console.log(`Downloading blocks from ${start} to ${end}`);
115+
const blocksResponse = await api.blockRange.getBlockRange({ start, end });
116+
for (const block of blocksResponse.data) {
117+
const lightBlock = LightBlock.decode(Buffer.from(block, "hex"));
118+
// Process the LightBlock as needed
119+
console.log(
120+
`API Downloaded LightBlock sequence: ${
121+
lightBlock.sequence
122+
}, hash: ${lightBlock.hash.toString("hex")}`,
123+
);
124+
}
125+
}
126+
}
127+
128+
main();

0 commit comments

Comments
 (0)