@leofcoin/chain 1.9.3 → 1.9.5
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/exports/browser/chain.js +71 -72
- package/exports/browser/{constants-BTdMMS4w.js → constants-M3qbPwZp.js} +316 -214
- package/exports/browser/node-browser.js +1 -18
- package/exports/browser/workers/block-worker.js +1 -1
- package/exports/browser/workers/machine-worker.js +1 -1
- package/exports/browser/workers/{worker-BrtyXRJ7-BrtyXRJ7.js → worker-Bsi6vKgF-Bsi6vKgF.js} +238 -198
- package/exports/chain.d.ts +0 -1
- package/exports/chain.js +71 -72
- package/exports/workers/block-worker.js +1 -1
- package/exports/workers/machine-worker.js +1 -1
- package/exports/workers/{worker-BrtyXRJ7-BrtyXRJ7.js → worker-Bsi6vKgF-Bsi6vKgF.js} +238 -198
- package/package.json +3 -2
package/exports/chain.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { createDebugger } from '@vandeurenglenn/debug';
|
|
2
2
|
import { formatBytes, jsonStringifyBigInt, jsonParseBigInt, parseUnits, formatUnits } from '@leofcoin/utils';
|
|
3
|
-
import { TransactionMessage, BlockMessage, ContractMessage, LastBlockMessage, BWMessage, BWRequestMessage } from '@leofcoin/messages';
|
|
3
|
+
import { TransactionMessage, BlockMessage, ContractMessage, LastBlockMessage, PrevoteMessage, PrecommitMessage, ProposalMessage, BWMessage, StateMessage, BWRequestMessage } from '@leofcoin/messages';
|
|
4
4
|
import addresses, { contractFactory } from '@leofcoin/addresses';
|
|
5
5
|
import { calculateFee, createContractMessage, signTransaction, contractFactoryMessage, nativeTokenMessage, validatorsMessage, nameServiceMessage } from '@leofcoin/lib';
|
|
6
|
+
import codecs from '@leofcoin/codecs/utils';
|
|
6
7
|
import semver from 'semver';
|
|
7
8
|
import { randombytes } from '@leofcoin/crypto';
|
|
8
9
|
import EasyWorker from '@vandeurenglenn/easy-worker';
|
|
9
10
|
import { ContractDeploymentError, ExecutionError, isResolveError, ResolveError, isExecutionError } from '@leofcoin/errors';
|
|
10
11
|
import { log } from 'console';
|
|
11
|
-
import codecs from '@leofcoin/codecs/utils';
|
|
12
12
|
import { P as PROTOCOL_VERSION, R as REACHED_ONE_ZERO_ZERO } from './constants-eo0U5-D_.js';
|
|
13
13
|
import { log as log$1 } from 'node:console';
|
|
14
14
|
import '@leofcoin/networks';
|
|
@@ -1157,11 +1157,6 @@ class Jobber {
|
|
|
1157
1157
|
}
|
|
1158
1158
|
}
|
|
1159
1159
|
|
|
1160
|
-
codecs.addCodec({
|
|
1161
|
-
name: 'last-block-message',
|
|
1162
|
-
codec: 0x6c626d,
|
|
1163
|
-
hashAlg: 'keccak-256'
|
|
1164
|
-
});
|
|
1165
1160
|
const debug$1 = createDebugger('leofcoin/state');
|
|
1166
1161
|
class State extends Contract {
|
|
1167
1162
|
#resolveErrored;
|
|
@@ -2348,16 +2343,20 @@ class ConnectionMonitor {
|
|
|
2348
2343
|
}
|
|
2349
2344
|
}
|
|
2350
2345
|
|
|
2351
|
-
|
|
2352
|
-
name
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
}
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2346
|
+
const ensureCodec = (name, codec, hashAlg = 'keccak-256') => {
|
|
2347
|
+
if (!codecs.codecs[name]) {
|
|
2348
|
+
codecs.addCodec({ name, codec, hashAlg });
|
|
2349
|
+
}
|
|
2350
|
+
};
|
|
2351
|
+
// Backward compatibility for mixed deployments where some nodes still resolve
|
|
2352
|
+
// older @leofcoin/codecs versions that do not include these protocol codecs.
|
|
2353
|
+
ensureCodec('last-block-message', 0x6c626d);
|
|
2354
|
+
ensureCodec('last-block-request-message', 0x6c62726d);
|
|
2355
|
+
ensureCodec('state-message', 0x73746d);
|
|
2356
|
+
ensureCodec('publish-message', 0x70626d);
|
|
2357
|
+
ensureCodec('proposal-message', 0x70726d);
|
|
2358
|
+
ensureCodec('prevote-message', 0x70766d);
|
|
2359
|
+
ensureCodec('precommit-message', 0x7063636d);
|
|
2361
2360
|
const debug = createDebugger('leofcoin/chain');
|
|
2362
2361
|
// check if browser or local
|
|
2363
2362
|
class Chain extends VersionControl {
|
|
@@ -2451,7 +2450,10 @@ class Chain extends VersionControl {
|
|
|
2451
2450
|
return;
|
|
2452
2451
|
this.#castedVotes.add(voteKey);
|
|
2453
2452
|
const from = peernet.selectedAccount;
|
|
2454
|
-
const
|
|
2453
|
+
const voteData = { blockHash, index: BigInt(index), round: BigInt(round), from };
|
|
2454
|
+
const Message = type === 'prevote' ? PrevoteMessage : PrecommitMessage;
|
|
2455
|
+
const message = new Message(voteData);
|
|
2456
|
+
const payload = message.encoded;
|
|
2455
2457
|
try {
|
|
2456
2458
|
globalThis.peernet.publish(`consensus:${type}`, payload);
|
|
2457
2459
|
}
|
|
@@ -2466,16 +2468,17 @@ class Chain extends VersionControl {
|
|
|
2466
2468
|
*/
|
|
2467
2469
|
this.#handleProposal = async (payload) => {
|
|
2468
2470
|
try {
|
|
2469
|
-
const
|
|
2471
|
+
const message = new ProposalMessage(payload);
|
|
2472
|
+
const msg = message.decoded;
|
|
2470
2473
|
const { blockHash, index, round, from } = msg;
|
|
2471
|
-
const validators = await this.#getConsensusValidators(index);
|
|
2472
|
-
const expectedProposerIdx = (index + round) % validators.length;
|
|
2474
|
+
const validators = await this.#getConsensusValidators(Number(index));
|
|
2475
|
+
const expectedProposerIdx = Number((index + round) % BigInt(validators.length));
|
|
2473
2476
|
if (!validators[expectedProposerIdx] || validators[expectedProposerIdx] !== from) {
|
|
2474
2477
|
debug(`[consensus] Proposal from wrong proposer at height ${index} round ${round}`);
|
|
2475
2478
|
return;
|
|
2476
2479
|
}
|
|
2477
2480
|
const localBlock = await this.lastBlock;
|
|
2478
|
-
const localIndex = localBlock?.index !== undefined ?
|
|
2481
|
+
const localIndex = localBlock?.index !== undefined ? localBlock.index : -1n;
|
|
2479
2482
|
if (index <= localIndex) {
|
|
2480
2483
|
debug(`[consensus] Ignoring stale proposal at height ${index} (local: ${localIndex})`);
|
|
2481
2484
|
return;
|
|
@@ -2494,13 +2497,13 @@ class Chain extends VersionControl {
|
|
|
2494
2497
|
debug(`[consensus] Cannot fetch proposed block ${blockHash}:`, e?.message);
|
|
2495
2498
|
return;
|
|
2496
2499
|
}
|
|
2497
|
-
this.#consensusRound = round;
|
|
2500
|
+
this.#consensusRound = Number(round);
|
|
2498
2501
|
if (this.#roundTimer) {
|
|
2499
2502
|
clearTimeout(this.#roundTimer);
|
|
2500
2503
|
this.#roundTimer = null;
|
|
2501
2504
|
}
|
|
2502
2505
|
if (validators.includes(peernet.selectedAccount) && !this.#isJailed(peernet.selectedAccount)) {
|
|
2503
|
-
await this.#castVote('prevote', blockHash, index, round);
|
|
2506
|
+
await this.#castVote('prevote', blockHash, Number(index), Number(round));
|
|
2504
2507
|
}
|
|
2505
2508
|
}
|
|
2506
2509
|
catch (e) {
|
|
@@ -2513,13 +2516,14 @@ class Chain extends VersionControl {
|
|
|
2513
2516
|
*/
|
|
2514
2517
|
this.#handlePrevote = async (payload) => {
|
|
2515
2518
|
try {
|
|
2516
|
-
const
|
|
2519
|
+
const message = new PrevoteMessage(payload);
|
|
2520
|
+
const msg = message.decoded;
|
|
2517
2521
|
const { blockHash, index, round, from } = msg;
|
|
2518
|
-
const validators = await this.#getConsensusValidators(index);
|
|
2522
|
+
const validators = await this.#getConsensusValidators(Number(index));
|
|
2519
2523
|
if (!validators.includes(from))
|
|
2520
2524
|
return;
|
|
2521
2525
|
const localBlock = await this.lastBlock;
|
|
2522
|
-
const localIndex = localBlock?.index !== undefined ?
|
|
2526
|
+
const localIndex = localBlock?.index !== undefined ? localBlock.index : -1n;
|
|
2523
2527
|
if (index <= localIndex)
|
|
2524
2528
|
return;
|
|
2525
2529
|
const voteKey = `${index}:${round}:${blockHash}`;
|
|
@@ -2532,7 +2536,7 @@ class Chain extends VersionControl {
|
|
|
2532
2536
|
if (voteCount >= threshold &&
|
|
2533
2537
|
validators.includes(peernet.selectedAccount) &&
|
|
2534
2538
|
!this.#isJailed(peernet.selectedAccount)) {
|
|
2535
|
-
await this.#castVote('precommit', blockHash, index, round);
|
|
2539
|
+
await this.#castVote('precommit', blockHash, Number(index), Number(round));
|
|
2536
2540
|
}
|
|
2537
2541
|
}
|
|
2538
2542
|
catch (e) {
|
|
@@ -2546,12 +2550,13 @@ class Chain extends VersionControl {
|
|
|
2546
2550
|
*/
|
|
2547
2551
|
this.#handlePrecommit = async (payload) => {
|
|
2548
2552
|
try {
|
|
2549
|
-
const
|
|
2553
|
+
const message = new PrecommitMessage(payload);
|
|
2554
|
+
const msg = message.decoded;
|
|
2550
2555
|
const { blockHash, index, round, from } = msg;
|
|
2551
|
-
const validators = await this.#getConsensusValidators(index);
|
|
2556
|
+
const validators = await this.#getConsensusValidators(Number(index));
|
|
2552
2557
|
if (!validators.includes(from))
|
|
2553
2558
|
return;
|
|
2554
|
-
if (index <= this.#committedHeight)
|
|
2559
|
+
if (index <= BigInt(this.#committedHeight))
|
|
2555
2560
|
return;
|
|
2556
2561
|
const voteKey = `${index}:${round}:${blockHash}`;
|
|
2557
2562
|
if (!this.#precommits.has(voteKey))
|
|
@@ -2560,27 +2565,27 @@ class Chain extends VersionControl {
|
|
|
2560
2565
|
const threshold = Math.ceil((2 * validators.length) / 3);
|
|
2561
2566
|
const voteCount = this.#precommits.get(voteKey).size;
|
|
2562
2567
|
debug(`[consensus] Precommits ${voteKey}: ${voteCount}/${validators.length} (need ${threshold})`);
|
|
2563
|
-
if (voteCount >= threshold && index > this.#committedHeight) {
|
|
2564
|
-
this.#committedHeight = index;
|
|
2568
|
+
if (voteCount >= threshold && index > BigInt(this.#committedHeight)) {
|
|
2569
|
+
this.#committedHeight = Number(index);
|
|
2565
2570
|
this.#consensusRound = 0;
|
|
2566
2571
|
// Prune vote state for committed and older heights
|
|
2567
2572
|
for (const key of [...this.#prevotes.keys()]) {
|
|
2568
|
-
if (
|
|
2573
|
+
if (BigInt(key.split(':')[0]) <= index)
|
|
2569
2574
|
this.#prevotes.delete(key);
|
|
2570
2575
|
}
|
|
2571
2576
|
for (const key of [...this.#precommits.keys()]) {
|
|
2572
|
-
if (
|
|
2577
|
+
if (BigInt(key.split(':')[0]) <= index)
|
|
2573
2578
|
this.#precommits.delete(key);
|
|
2574
2579
|
}
|
|
2575
2580
|
for (const key of [...this.#castedVotes]) {
|
|
2576
|
-
if (
|
|
2581
|
+
if (BigInt(key.split(':')[1]) <= index)
|
|
2577
2582
|
this.#castedVotes.delete(key);
|
|
2578
2583
|
}
|
|
2579
2584
|
// Non-proposers add the block to local state now.
|
|
2580
2585
|
// Proposers already committed state in #createBlock() and their
|
|
2581
2586
|
// lastBlock.index === index, so the guard below skips them.
|
|
2582
2587
|
const currentBlock = await this.lastBlock;
|
|
2583
|
-
const currentIndex = currentBlock?.index !== undefined ?
|
|
2588
|
+
const currentIndex = currentBlock?.index !== undefined ? currentBlock.index : -1n;
|
|
2584
2589
|
if (index > currentIndex) {
|
|
2585
2590
|
debug(`[consensus] ✅ Committing block ${blockHash} at height ${index}`);
|
|
2586
2591
|
try {
|
|
@@ -2853,7 +2858,9 @@ class Chain extends VersionControl {
|
|
|
2853
2858
|
await globalThis.peernet.addRequestHandler('transactionPool', this.#transactionPoolHandler.bind(this));
|
|
2854
2859
|
await globalThis.peernet.addRequestHandler('version', this.#versionHandler.bind(this));
|
|
2855
2860
|
await globalThis.peernet.addRequestHandler('stateInfo', () => {
|
|
2856
|
-
return new globalThis.peernet.protos['peernet-response']({
|
|
2861
|
+
return new globalThis.peernet.protos['peernet-response']({
|
|
2862
|
+
response: new StateMessage(this.machine.states.info).encoded
|
|
2863
|
+
});
|
|
2857
2864
|
});
|
|
2858
2865
|
globalThis.peernet.subscribe('add-block', this.#addBlock.bind(this));
|
|
2859
2866
|
globalThis.peernet.subscribe('invalid-transaction', this.#invalidTransaction.bind(this));
|
|
@@ -2941,13 +2948,8 @@ class Chain extends VersionControl {
|
|
|
2941
2948
|
async getPeerTransactionPool(peer) {
|
|
2942
2949
|
let transactionsInPool = await this.#makeRequest(peer, 'transactionPool');
|
|
2943
2950
|
if (transactionsInPool instanceof Uint8Array) {
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
transactionsInPool = JSON.parse(text);
|
|
2947
|
-
}
|
|
2948
|
-
catch (e) {
|
|
2949
|
-
return [];
|
|
2950
|
-
}
|
|
2951
|
+
debug('transactionPool response must be decoded array payload');
|
|
2952
|
+
return [];
|
|
2951
2953
|
}
|
|
2952
2954
|
if (!Array.isArray(transactionsInPool))
|
|
2953
2955
|
return [];
|
|
@@ -2979,13 +2981,7 @@ class Chain extends VersionControl {
|
|
|
2979
2981
|
try {
|
|
2980
2982
|
let versionResponse = await this.#makeRequest(peer, 'version');
|
|
2981
2983
|
if (versionResponse instanceof Uint8Array) {
|
|
2982
|
-
|
|
2983
|
-
try {
|
|
2984
|
-
versionResponse = JSON.parse(decoded);
|
|
2985
|
-
}
|
|
2986
|
-
catch {
|
|
2987
|
-
versionResponse = decoded;
|
|
2988
|
-
}
|
|
2984
|
+
versionResponse = new TextDecoder().decode(versionResponse);
|
|
2989
2985
|
}
|
|
2990
2986
|
if (typeof versionResponse === 'string') {
|
|
2991
2987
|
peer.version = versionResponse;
|
|
@@ -2995,6 +2991,12 @@ class Chain extends VersionControl {
|
|
|
2995
2991
|
typeof versionResponse.version === 'string') {
|
|
2996
2992
|
peer.version = versionResponse.version;
|
|
2997
2993
|
}
|
|
2994
|
+
if (!peer.version || typeof peer.version !== 'string') {
|
|
2995
|
+
const reason = `invalid version response from peer ${peerId}`;
|
|
2996
|
+
debug(reason);
|
|
2997
|
+
await this.#recordPeerFailure(peerId, reason);
|
|
2998
|
+
return;
|
|
2999
|
+
}
|
|
2998
3000
|
}
|
|
2999
3001
|
catch (error) {
|
|
3000
3002
|
debug(`failed to request version from peer ${peerId}:`, error?.message ?? error);
|
|
@@ -3003,7 +3005,9 @@ class Chain extends VersionControl {
|
|
|
3003
3005
|
}
|
|
3004
3006
|
debug(`peer connected with version ${peer.version}`);
|
|
3005
3007
|
if (!this.isVersionCompatible(peer.version)) {
|
|
3006
|
-
|
|
3008
|
+
const mismatchReason = `incompatible peer version ${peer.version} (local: ${this.version})`;
|
|
3009
|
+
console.error(`[chain] ${mismatchReason}`);
|
|
3010
|
+
await this.#recordPeerFailure(peerId, mismatchReason);
|
|
3007
3011
|
return;
|
|
3008
3012
|
}
|
|
3009
3013
|
let lastBlock;
|
|
@@ -3023,7 +3027,7 @@ class Chain extends VersionControl {
|
|
|
3023
3027
|
// This prevents Byzantine nodes from claiming a fake chain length to steer our sync
|
|
3024
3028
|
const localBlock = await this.lastBlock;
|
|
3025
3029
|
const MAX_SYNC_AHEAD = 100_000;
|
|
3026
|
-
if (lastBlock?.index > (localBlock?.index ?? 0) + MAX_SYNC_AHEAD) {
|
|
3030
|
+
if (lastBlock?.index > BigInt(localBlock?.index ?? 0) + BigInt(MAX_SYNC_AHEAD)) {
|
|
3027
3031
|
const peerName = peer?.peerId || peer?.id || peer?.address || peerId || 'unknown';
|
|
3028
3032
|
debug(`Peer ${peerName} claims unreasonable block height ${lastBlock.index} (local: ${localBlock?.index ?? 0})`);
|
|
3029
3033
|
await this.#recordPeerFailure(peerId, `unreasonable lastBlock index: ${lastBlock.index}`);
|
|
@@ -3039,15 +3043,13 @@ class Chain extends VersionControl {
|
|
|
3039
3043
|
try {
|
|
3040
3044
|
let knownBlocksResponse = await this.#makeRequest(peer, 'knownBlocks');
|
|
3041
3045
|
if (knownBlocksResponse instanceof Uint8Array) {
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
console.log(e);
|
|
3047
|
-
}
|
|
3046
|
+
const reason = `knownBlocks must be object response, got raw bytes from ${peerId}`;
|
|
3047
|
+
debug(reason);
|
|
3048
|
+
await this.#recordPeerFailure(peerId, reason);
|
|
3049
|
+
return;
|
|
3048
3050
|
}
|
|
3049
3051
|
const MAX_WANTLIST_SIZE = 1000;
|
|
3050
|
-
if (knownBlocksResponse.blocks) {
|
|
3052
|
+
if (knownBlocksResponse && Array.isArray(knownBlocksResponse.blocks)) {
|
|
3051
3053
|
const remaining = MAX_WANTLIST_SIZE - this.wantList.length;
|
|
3052
3054
|
if (remaining > 0) {
|
|
3053
3055
|
for (const hash of knownBlocksResponse.blocks.slice(0, remaining)) {
|
|
@@ -3089,12 +3091,7 @@ class Chain extends VersionControl {
|
|
|
3089
3091
|
try {
|
|
3090
3092
|
let stateInfo = await this.#makeRequest(peer, 'stateInfo');
|
|
3091
3093
|
if (stateInfo instanceof Uint8Array) {
|
|
3092
|
-
|
|
3093
|
-
stateInfo = JSON.parse(new TextDecoder().decode(stateInfo));
|
|
3094
|
-
}
|
|
3095
|
-
catch (e) {
|
|
3096
|
-
console.log(e);
|
|
3097
|
-
}
|
|
3094
|
+
stateInfo = new StateMessage(stateInfo).decoded;
|
|
3098
3095
|
}
|
|
3099
3096
|
await this.syncChain(lastBlock);
|
|
3100
3097
|
this.machine.states.info = stateInfo;
|
|
@@ -3112,7 +3109,7 @@ class Chain extends VersionControl {
|
|
|
3112
3109
|
return new globalThis.peernet.protos['peernet-response']({ response: pool });
|
|
3113
3110
|
}
|
|
3114
3111
|
async #versionHandler() {
|
|
3115
|
-
return new globalThis.peernet.protos['peernet-response']({ response:
|
|
3112
|
+
return new globalThis.peernet.protos['peernet-response']({ response: this.version });
|
|
3116
3113
|
}
|
|
3117
3114
|
async #executeTransaction({ hash, from, to, method, params, nonce }) {
|
|
3118
3115
|
try {
|
|
@@ -3464,12 +3461,14 @@ class Chain extends VersionControl {
|
|
|
3464
3461
|
debug(`created block: ${hash} @${block.index}`);
|
|
3465
3462
|
// Phase 2: announce proposal for consensus voting instead of direct add-block
|
|
3466
3463
|
console.log(`[consensus] 📤 Proposing block #${block.index} | hash: ${hash} | round: ${this.#consensusRound}`);
|
|
3467
|
-
const
|
|
3464
|
+
const proposalData = {
|
|
3468
3465
|
blockHash: hash,
|
|
3469
|
-
index: block.index,
|
|
3470
|
-
round: this.#consensusRound,
|
|
3466
|
+
index: BigInt(block.index),
|
|
3467
|
+
round: BigInt(this.#consensusRound),
|
|
3471
3468
|
from: peernet.selectedAccount
|
|
3472
|
-
}
|
|
3469
|
+
};
|
|
3470
|
+
const proposalMessage = new ProposalMessage(proposalData);
|
|
3471
|
+
const proposalPayload = proposalMessage.encoded;
|
|
3473
3472
|
try {
|
|
3474
3473
|
globalThis.peernet.publish('consensus:propose', proposalPayload);
|
|
3475
3474
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { E as EasyWorker, C as ContractMessage, T as TransactionMessage } from './worker-
|
|
1
|
+
import { E as EasyWorker, C as ContractMessage, T as TransactionMessage } from './worker-Bsi6vKgF-Bsi6vKgF.js';
|
|
2
2
|
|
|
3
3
|
/* Do NOT modify this file; see /src.ts/_admin/update-version.ts */
|
|
4
4
|
/**
|