Skip to content

Commit 4af0898

Browse files
author
web3blind
committed
Add archive event block endpoint
1 parent fc86fac commit 4af0898

8 files changed

Lines changed: 124 additions & 6 deletions

File tree

app/js/blockchain/history-source.js

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,44 @@ var HistorySource = (function() {
3434
return pattern.replace(/\/$/, '') + '/' + encodeURIComponent(String(blockNum)) + '.json';
3535
}
3636

37+
function _eventsUrl(mirror, blockNum) {
38+
var pattern = mirror.eventsUrl || mirror.blockEventsUrl || '';
39+
if (!pattern && mirror.apiBase) {
40+
pattern = String(mirror.apiBase).replace(/\/$/, '') + '/v1/events/block/{block}.json';
41+
}
42+
if (!pattern) return '';
43+
if (pattern.indexOf('{block}') !== -1) {
44+
return pattern.replace('{block}', encodeURIComponent(String(blockNum)));
45+
}
46+
return pattern.replace(/\/$/, '') + '/' + encodeURIComponent(String(blockNum)) + '.json';
47+
}
48+
49+
function _eventsPayloadToThinBlock(payload) {
50+
if (!payload || !payload.events) return null;
51+
var events = payload.events || [];
52+
var block = {
53+
previous: payload.previous || '',
54+
timestamp: payload.timestamp || '',
55+
block_id: payload.block_id || payload.blockId || '',
56+
transactions: []
57+
};
58+
var byTx = {};
59+
for (var i = 0; i < events.length; i++) {
60+
var ev = events[i] || {};
61+
var txIndex = ev.txIndex || ev.tx_index || 0;
62+
if (!byTx[txIndex]) {
63+
byTx[txIndex] = { operations: [] };
64+
block.transactions.push(byTx[txIndex]);
65+
}
66+
if (ev.opType === 'custom' || ev.op_type === 'custom') {
67+
byTx[txIndex].operations.push(['custom', ev.raw || {}]);
68+
} else if (ev.opType === 'award' || ev.op_type === 'award') {
69+
byTx[txIndex].operations.push(['award', ev.raw || {}]);
70+
}
71+
}
72+
return block;
73+
}
74+
3775
function _extractBlockFromMirrorPayload(payload) {
3876
if (!payload) return null;
3977
if (payload.previous && payload.timestamp && payload.transactions) return payload;
@@ -82,6 +120,28 @@ var HistorySource = (function() {
82120
}
83121
}
84122

123+
function _getBlockEventsFromMirrors(blockNum, index, callback) {
124+
var mirrors = _archiveMirrors();
125+
if (!mirrors.length || index >= mirrors.length) {
126+
callback(_makeError('Block events unavailable from archive mirrors'));
127+
return;
128+
}
129+
var mirror = _normalizeMirror(mirrors[index]);
130+
var eventsUrl = _eventsUrl(mirror, blockNum);
131+
if (!eventsUrl) {
132+
_getBlockEventsFromMirrors(blockNum, index + 1, callback);
133+
return;
134+
}
135+
_requestJson(eventsUrl, mirror.timeoutMs || 6000, function(err, payload) {
136+
var block = err ? null : _eventsPayloadToThinBlock(payload);
137+
if (block) {
138+
callback(null, block);
139+
return;
140+
}
141+
_getBlockEventsFromMirrors(blockNum, index + 1, callback);
142+
});
143+
}
144+
85145
function _getBlockFromMirrors(blockNum, index, callback) {
86146
var mirrors = _archiveMirrors();
87147
if (!mirrors.length || index >= mirrors.length) {
@@ -111,15 +171,27 @@ var HistorySource = (function() {
111171
return;
112172
}
113173
if (typeof viz === 'undefined' || !viz.api || !viz.api.getBlock) {
114-
_getBlockFromMirrors(blockNum, 0, callback);
174+
_getBlockEventsFromMirrors(blockNum, 0, function(eventsErr, eventsBlock) {
175+
if (!eventsErr && eventsBlock) {
176+
callback(null, eventsBlock);
177+
return;
178+
}
179+
_getBlockFromMirrors(blockNum, 0, callback);
180+
});
115181
return;
116182
}
117183
viz.api.getBlock(blockNum, function(err, block) {
118184
if (!err && block) {
119185
callback(null, block);
120186
return;
121187
}
122-
_getBlockFromMirrors(blockNum, 0, callback);
188+
_getBlockEventsFromMirrors(blockNum, 0, function(eventsErr, eventsBlock) {
189+
if (!eventsErr && eventsBlock) {
190+
callback(null, eventsBlock);
191+
return;
192+
}
193+
_getBlockFromMirrors(blockNum, 0, callback);
194+
});
123195
});
124196
}
125197

app/js/config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ var VizMagicConfig = (function() {
2020
var HISTORY_ARCHIVE_MIRRORS = [
2121
{
2222
url: 'https://vizmagic.web3blind.xyz/archive-mirror/v1/block/{block}.json',
23+
eventsUrl: 'https://vizmagic.web3blind.xyz/archive-mirror/v1/events/block/{block}.json',
24+
apiBase: 'https://vizmagic.web3blind.xyz/archive-mirror',
2325
timeoutMs: 8000
2426
}
2527
];

app/sw.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Viz Magic — Service Worker
2-
var CACHE_NAME = 'viz-magic-v9';
2+
var CACHE_NAME = 'viz-magic-v10';
33
var ASSETS = [
44
'/',
55
'/index.html',
@@ -14,6 +14,7 @@ var ASSETS = [
1414
'/js/utils/crypto.js',
1515
'/js/utils/a11y.js',
1616
'/js/blockchain/connection.js',
17+
'/js/blockchain/history-source.js',
1718
'/js/blockchain/account.js',
1819
'/js/blockchain/broadcast.js',
1920
'/js/blockchain/invite.js',

tests/history-source-regressions.test.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ test('history source wraps VIZ block/account access', function () {
4545
assert.ok(/var HistorySource/.test(historySourceJs), 'HistorySource module missing');
4646
assert.ok(/function getBlock\(blockNum, callback\)/.test(historySourceJs), 'getBlock API missing');
4747
assert.ok(/viz\.api\.getBlock/.test(historySourceJs), 'getBlock should use live VIZ RPC as first implementation');
48-
assert.ok(/_getBlockFromMirrors\(blockNum, 0, callback\)/.test(historySourceJs), 'getBlock should fall back to archive mirrors');
48+
assert.ok(/_getBlockEventsFromMirrors\(blockNum, 0/.test(historySourceJs), 'getBlock should try archive event blocks before block payload mirrors');
49+
assert.ok(/_getBlockFromMirrors\(blockNum, 0, callback\)|_getBlockFromMirrors\(blockNum, 0, function/.test(historySourceJs), 'getBlock should fall back to archive block mirrors');
4950
assert.ok(/XMLHttpRequest/.test(historySourceJs), 'mirror fallback should use browser-safe XHR');
5051
assert.ok(/function getAccountProtocol\(account, protocol, callback\)/.test(historySourceJs), 'account protocol API missing');
5152
assert.ok(/getCapabilities/.test(historySourceJs), 'capabilities API missing');
@@ -54,6 +55,7 @@ test('history source wraps VIZ block/account access', function () {
5455
test('archive mirror config is explicit and points at production nginx path', function () {
5556
assert.ok(/HISTORY_ARCHIVE_MIRRORS/.test(configJs), 'archive mirror config missing');
5657
assert.ok(/vizmagic\.web3blind\.xyz\/archive-mirror\/v1\/block\/\{block\}\.json/.test(configJs), 'production archive mirror URL missing');
58+
assert.ok(/vizmagic\.web3blind\.xyz\/archive-mirror\/v1\/events\/block\/\{block\}\.json/.test(configJs), 'production archive event URL missing');
5759
assert.ok(/timeoutMs:\s*8000/.test(configJs), 'mirror timeout should be explicit');
5860
assert.ok(/\{block\}/.test(configJs), 'mirror URL pattern should document block placeholder');
5961
});

tools/archive-node/README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ It indexes public VIZ blocks, extracts only game-relevant operations, and serves
77
## What it stores
88

99
- block number, `block_id`, `previous`, timestamp, source node;
10-
- raw block JSON for indexed blocks;
10+
- thinned block JSON for indexed blocks (only operations that became game events; no full raw block storage);
1111
- normalized public events for:
1212
- `custom` protocol `VM`;
1313
- `custom` protocol `V`;
1414
- `custom` protocol `VE`;
15-
- `award` operations used for blessings/rewards.
15+
- game-marked `award` operations (`memo` starts with `viz://vm/`).
1616

1717
The implementation stores data in SQLite (`data/archive-node/archive.sqlite`) using Node 22's built-in `node:sqlite` module. `sqlite-schema.sql` documents the live schema.
1818

@@ -43,6 +43,7 @@ node daemon.js
4343
GET /health
4444
GET /v1/status
4545
GET /v1/block/:blockNum.json
46+
GET /v1/events/block/:blockNum.json
4647
GET /v1/range?start=123&end=456&protocol=VM,V,VE&limit=500
4748
GET /v1/account/:account/protocol/:protocol/actions?limit=500
4849
GET /v1/account/:account/protocol/:protocol/latest
@@ -54,6 +55,7 @@ When hosted behind Nginx at `/archive-mirror`, the same API is available under t
5455
GET /archive-mirror/health
5556
GET /archive-mirror/v1/status
5657
GET /archive-mirror/v1/block/123.json
58+
GET /archive-mirror/v1/events/block/123.json
5759
```
5860

5961
## Config

tools/archive-node/archive-node.test.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,13 @@ async function run() {
137137
assert.strictEqual(range.status, 200);
138138
assert.strictEqual(range.body.count, 3);
139139

140+
var blockEvents = await getJson(port, '/archive-mirror/v1/events/block/123.json');
141+
assert.strictEqual(blockEvents.status, 200);
142+
assert.strictEqual(blockEvents.body.blockNum, 123);
143+
assert.strictEqual(blockEvents.body.count, 3);
144+
assert.strictEqual(blockEvents.body.events[0].protocol, 'VM');
145+
assert.strictEqual(blockEvents.body.events[2].payload.memo, 'viz://vm/bless/target-mage');
146+
140147
var account = await getJson(port, '/archive-mirror/v1/account/' + ACCOUNT + '/protocol/VM/actions');
141148
assert.strictEqual(account.status, 200);
142149
assert.strictEqual(account.body.count, 1);

tools/archive-node/public-mirror.example.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"name": "Viz Magic Community Archive",
33
"url": "https://vizmagic.web3blind.xyz/archive-mirror/v1/block/{block}.json",
4+
"eventsUrl": "https://vizmagic.web3blind.xyz/archive-mirror/v1/events/block/{block}.json",
45
"apiBase": "https://vizmagic.web3blind.xyz/archive-mirror",
56
"readOnly": true,
67
"protocols": ["VM", "V", "VE", "award"]

tools/archive-node/server.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,37 @@ function createServer(options) {
104104
return;
105105
}
106106

107+
if (parts.length === 4 && parts[0] === 'v1' && parts[1] === 'events' && parts[2] === 'block') {
108+
var eventBlockRaw = parts[3].replace(/\.json$/, '');
109+
var eventBlockNum = safeNum(eventBlockRaw, 0);
110+
if (!eventBlockNum || eventBlockNum <= 0) {
111+
json(res, 400, { error: 'invalid_block' });
112+
return;
113+
}
114+
var eventBlockRecord = archive.getBlockRecord(eventBlockNum);
115+
if (!eventBlockRecord) {
116+
json(res, 404, { error: 'block_not_indexed', blockNum: eventBlockNum }, { 'Cache-Control': 'no-store' });
117+
return;
118+
}
119+
var blockEvents = archive.queryEvents({
120+
start: eventBlockNum,
121+
end: eventBlockNum,
122+
limit: 5000
123+
});
124+
json(res, 200, {
125+
blockNum: eventBlockRecord.blockNum,
126+
block_id: eventBlockRecord.block_id,
127+
previous: eventBlockRecord.previous,
128+
timestamp: eventBlockRecord.timestamp,
129+
eventCount: eventBlockRecord.eventCount || blockEvents.length,
130+
sourceNode: eventBlockRecord.sourceNode || '',
131+
indexedAt: eventBlockRecord.indexedAt,
132+
events: blockEvents,
133+
count: blockEvents.length
134+
});
135+
return;
136+
}
137+
107138
if (parts.length === 2 && parts[0] === 'v1' && parts[1] === 'range') {
108139
var protocols = parsedUrl.query.protocol ? String(parsedUrl.query.protocol).split(',').map(function(x) { return x.trim(); }).filter(Boolean) : null;
109140
var events = archive.queryEvents({

0 commit comments

Comments
 (0)