@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/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
- codecs.addCodec({
2352
- name: 'last-block-message',
2353
- codec: 0x6c626d,
2354
- hashAlg: 'keccak-256'
2355
- });
2356
- codecs.addCodec({
2357
- name: 'last-block-request-message',
2358
- codec: 0x6c62726d,
2359
- hashAlg: 'keccak-256'
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 payload = new TextEncoder().encode(JSON.stringify({ blockHash, index, round, from }));
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 msg = JSON.parse(new TextDecoder().decode(payload));
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 ? Number(localBlock.index) : -1;
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 msg = JSON.parse(new TextDecoder().decode(payload));
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 ? Number(localBlock.index) : -1;
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 msg = JSON.parse(new TextDecoder().decode(payload));
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 (Number(key.split(':')[0]) <= index)
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 (Number(key.split(':')[0]) <= index)
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 (Number(key.split(':')[1]) <= index)
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 ? Number(currentBlock.index) : -1;
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']({ response: this.machine.states.info });
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
- try {
2945
- const text = new TextDecoder().decode(transactionsInPool);
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
- const decoded = new TextDecoder().decode(versionResponse);
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
- debug(`versions don't match`);
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
- try {
3043
- knownBlocksResponse = JSON.parse(new TextDecoder().decode(knownBlocksResponse));
3044
- }
3045
- catch (e) {
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
- try {
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: { version: this.version } });
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 proposalPayload = new TextEncoder().encode(JSON.stringify({
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, B as BlockMessage } from './worker-BrtyXRJ7-BrtyXRJ7.js';
1
+ import { E as EasyWorker, B as BlockMessage } from './worker-Bsi6vKgF-Bsi6vKgF.js';
2
2
 
3
3
  const worker = new EasyWorker();
4
4
  globalThis.BigNumber = BigNumber;
@@ -1,4 +1,4 @@
1
- import { E as EasyWorker, C as ContractMessage, T as TransactionMessage } from './worker-BrtyXRJ7-BrtyXRJ7.js';
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
  /**