@leofcoin/chain 1.10.7 → 1.10.8

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.
@@ -10181,6 +10181,46 @@ const validateChainLink = (localTip, incoming) => {
10181
10181
  return "append";
10182
10182
  };
10183
10183
 
10184
+ const BLOCK_REWARD = 150n;
10185
+ const validateCanonicalValidatorSet = (blockIndex, expectedValidators, validators) => {
10186
+ const actualValidators = validators.map(({ address }) => address);
10187
+ const canonicalExpected = [...new Set(expectedValidators)].sort();
10188
+ if (actualValidators.length !== canonicalExpected.length || actualValidators.some((address, index) => address !== canonicalExpected[index])) {
10189
+ throw new Error(
10190
+ `Block ${blockIndex} validator set mismatch: expected ${canonicalExpected.join(",")}, got ${actualValidators.join(",")}`
10191
+ );
10192
+ }
10193
+ };
10194
+ const validateBlockEconomics = (block, calculatedFees) => {
10195
+ if (block.validators.length === 0) {
10196
+ throw new Error(`Block ${block.index} cannot distribute rewards without validators`);
10197
+ }
10198
+ if (block.reward !== BLOCK_REWARD) {
10199
+ throw new Error(`Block ${block.index} has invalid base reward: expected ${BLOCK_REWARD}, got ${block.reward}`);
10200
+ }
10201
+ if (block.fees !== calculatedFees) {
10202
+ throw new Error(`Block ${block.index} has invalid fees: expected ${calculatedFees}, got ${block.fees}`);
10203
+ }
10204
+ const validatorCount = BigInt(block.validators.length);
10205
+ const expectedReward = calculatedFees / validatorCount + BLOCK_REWARD / validatorCount;
10206
+ for (const validator of block.validators) {
10207
+ if (validator.reward !== expectedReward) {
10208
+ throw new Error(
10209
+ `Block ${block.index} has an invalid reward for validator ${validator.address}: expected ${expectedReward}, got ${validator.reward}`
10210
+ );
10211
+ }
10212
+ }
10213
+ };
10214
+
10215
+ const resolveTransactionReference = async (expectedHash, transactionData) => {
10216
+ const transaction = transactionData instanceof TransactionMessage ? transactionData : new TransactionMessage(transactionData);
10217
+ const actualHash = await transaction.hash();
10218
+ if (actualHash !== expectedHash) {
10219
+ throw new Error(`Transaction hash mismatch: expected ${expectedHash}, got ${actualHash}`);
10220
+ }
10221
+ return transaction;
10222
+ };
10223
+
10184
10224
  const consensusSignableData = (validatorsAddress, type, message) => ({
10185
10225
  from: String(message.from),
10186
10226
  to: validatorsAddress,
@@ -10348,8 +10388,23 @@ class Chain extends VersionControl {
10348
10388
  debug(`[consensus] Block hash mismatch in proposal: expected ${blockHash}, got ${actualHash}`);
10349
10389
  return;
10350
10390
  }
10391
+ if (BigInt(blockMessage.decoded.index) !== index) {
10392
+ debug(`[consensus] Proposal height ${index} does not match block height ${blockMessage.decoded.index}`);
10393
+ return;
10394
+ }
10395
+ if (blockMessage.decoded.producer !== from) {
10396
+ debug(`[consensus] Proposal sender ${from} does not match block producer ${blockMessage.decoded.producer}`);
10397
+ return;
10398
+ }
10399
+ validateChainLink(localBlock, {
10400
+ index: Number(blockMessage.decoded.index),
10401
+ hash: actualHash,
10402
+ previousHash: String(blockMessage.decoded.previousHash)
10403
+ });
10404
+ await this.#validateBlockValidators(blockMessage);
10405
+ await this.#resolveBlockTransactions(blockMessage);
10351
10406
  } catch (e) {
10352
- debug(`[consensus] Cannot fetch proposed block ${blockHash}:`, e?.message);
10407
+ debug(`[consensus] Invalid proposed block ${blockHash}:`, e?.message);
10353
10408
  return;
10354
10409
  }
10355
10410
  this.#consensusRound = Number(round);
@@ -10604,15 +10659,21 @@ class Chain extends VersionControl {
10604
10659
  if (new Set(validatorAddresses).size !== validatorAddresses.length) {
10605
10660
  throw new Error(`Block ${blockMessage.decoded.index} validators contain duplicates`);
10606
10661
  }
10607
- const validatorCount = BigInt(validators.length);
10608
- const expectedReward = blockMessage.decoded.fees / validatorCount + blockMessage.decoded.reward / validatorCount;
10609
- for (const validator of validators) {
10610
- if (validator.reward !== expectedReward) {
10611
- throw new Error(
10612
- `Block ${blockMessage.decoded.index} has an invalid reward for validator ${validator.address}: expected ${expectedReward}, got ${validator.reward}`
10613
- );
10614
- }
10615
- }
10662
+ const expectedValidators = await this.staticCall(addresses.validators, "validators");
10663
+ validateCanonicalValidatorSet(blockMessage.decoded.index, expectedValidators, validators);
10664
+ }
10665
+ async #resolveBlockTransactions(blockMessage) {
10666
+ const transactions = await Promise.all(
10667
+ blockMessage.decoded.transactions.map(async (expectedHash) => {
10668
+ const data = await globalThis.peernet.get(expectedHash, "transaction");
10669
+ const transaction = await resolveTransactionReference(expectedHash, data);
10670
+ await this.validateTransactionSignature(transaction);
10671
+ return transaction;
10672
+ })
10673
+ );
10674
+ const calculatedFees = (await Promise.all(transactions.map((transaction) => calculateFee(transaction.decoded)))).reduce((total, fee) => total + BigInt(fee), 0n);
10675
+ validateBlockEconomics(blockMessage.decoded, calculatedFees);
10676
+ return transactions;
10616
10677
  }
10617
10678
  /** Check if the next block will cross an epoch boundary (block-based timing) */
10618
10679
  #isEpochBoundary(blockHeight) {
@@ -11111,13 +11172,7 @@ class Chain extends VersionControl {
11111
11172
  }
11112
11173
  console.log(`[chain] \u2705 Block data integrity verified: ${hash}`);
11113
11174
  await this.#validateBlockValidators(blockMessage);
11114
- const transactions = await Promise.all(
11115
- blockMessage.decoded.transactions.map(async (hash2) => {
11116
- const data = await peernet.get(hash2, "transaction");
11117
- return new TransactionMessage(data);
11118
- })
11119
- );
11120
- await Promise.all(transactions.map((transaction) => this.validateTransactionSignature(transaction)));
11175
+ const transactions = await this.#resolveBlockTransactions(blockMessage);
11121
11176
  await Promise.all(
11122
11177
  blockMessage.decoded.transactions.map(async (transactionHash) => {
11123
11178
  if (await transactionPoolStore.has(transactionHash)) await transactionPoolStore.delete(transactionHash);
@@ -11243,7 +11298,7 @@ class Chain extends VersionControl {
11243
11298
  fees: BigInt(0),
11244
11299
  timestamp,
11245
11300
  previousHash: "",
11246
- reward: BigInt(150),
11301
+ reward: BLOCK_REWARD,
11247
11302
  index: 0,
11248
11303
  producer: "",
11249
11304
  producerProof: "",
package/exports/chain.js CHANGED
@@ -2276,6 +2276,46 @@ const validateChainLink = (localTip, incoming) => {
2276
2276
  return "append";
2277
2277
  };
2278
2278
 
2279
+ const BLOCK_REWARD = 150n;
2280
+ const validateCanonicalValidatorSet = (blockIndex, expectedValidators, validators) => {
2281
+ const actualValidators = validators.map(({ address }) => address);
2282
+ const canonicalExpected = [...new Set(expectedValidators)].sort();
2283
+ if (actualValidators.length !== canonicalExpected.length || actualValidators.some((address, index) => address !== canonicalExpected[index])) {
2284
+ throw new Error(
2285
+ `Block ${blockIndex} validator set mismatch: expected ${canonicalExpected.join(",")}, got ${actualValidators.join(",")}`
2286
+ );
2287
+ }
2288
+ };
2289
+ const validateBlockEconomics = (block, calculatedFees) => {
2290
+ if (block.validators.length === 0) {
2291
+ throw new Error(`Block ${block.index} cannot distribute rewards without validators`);
2292
+ }
2293
+ if (block.reward !== BLOCK_REWARD) {
2294
+ throw new Error(`Block ${block.index} has invalid base reward: expected ${BLOCK_REWARD}, got ${block.reward}`);
2295
+ }
2296
+ if (block.fees !== calculatedFees) {
2297
+ throw new Error(`Block ${block.index} has invalid fees: expected ${calculatedFees}, got ${block.fees}`);
2298
+ }
2299
+ const validatorCount = BigInt(block.validators.length);
2300
+ const expectedReward = calculatedFees / validatorCount + BLOCK_REWARD / validatorCount;
2301
+ for (const validator of block.validators) {
2302
+ if (validator.reward !== expectedReward) {
2303
+ throw new Error(
2304
+ `Block ${block.index} has an invalid reward for validator ${validator.address}: expected ${expectedReward}, got ${validator.reward}`
2305
+ );
2306
+ }
2307
+ }
2308
+ };
2309
+
2310
+ const resolveTransactionReference = async (expectedHash, transactionData) => {
2311
+ const transaction = transactionData instanceof TransactionMessage ? transactionData : new TransactionMessage(transactionData);
2312
+ const actualHash = await transaction.hash();
2313
+ if (actualHash !== expectedHash) {
2314
+ throw new Error(`Transaction hash mismatch: expected ${expectedHash}, got ${actualHash}`);
2315
+ }
2316
+ return transaction;
2317
+ };
2318
+
2279
2319
  const consensusSignableData = (validatorsAddress, type, message) => ({
2280
2320
  from: String(message.from),
2281
2321
  to: validatorsAddress,
@@ -2443,8 +2483,23 @@ class Chain extends VersionControl {
2443
2483
  debug(`[consensus] Block hash mismatch in proposal: expected ${blockHash}, got ${actualHash}`);
2444
2484
  return;
2445
2485
  }
2486
+ if (BigInt(blockMessage.decoded.index) !== index) {
2487
+ debug(`[consensus] Proposal height ${index} does not match block height ${blockMessage.decoded.index}`);
2488
+ return;
2489
+ }
2490
+ if (blockMessage.decoded.producer !== from) {
2491
+ debug(`[consensus] Proposal sender ${from} does not match block producer ${blockMessage.decoded.producer}`);
2492
+ return;
2493
+ }
2494
+ validateChainLink(localBlock, {
2495
+ index: Number(blockMessage.decoded.index),
2496
+ hash: actualHash,
2497
+ previousHash: String(blockMessage.decoded.previousHash)
2498
+ });
2499
+ await this.#validateBlockValidators(blockMessage);
2500
+ await this.#resolveBlockTransactions(blockMessage);
2446
2501
  } catch (e) {
2447
- debug(`[consensus] Cannot fetch proposed block ${blockHash}:`, e?.message);
2502
+ debug(`[consensus] Invalid proposed block ${blockHash}:`, e?.message);
2448
2503
  return;
2449
2504
  }
2450
2505
  this.#consensusRound = Number(round);
@@ -2699,15 +2754,21 @@ class Chain extends VersionControl {
2699
2754
  if (new Set(validatorAddresses).size !== validatorAddresses.length) {
2700
2755
  throw new Error(`Block ${blockMessage.decoded.index} validators contain duplicates`);
2701
2756
  }
2702
- const validatorCount = BigInt(validators.length);
2703
- const expectedReward = blockMessage.decoded.fees / validatorCount + blockMessage.decoded.reward / validatorCount;
2704
- for (const validator of validators) {
2705
- if (validator.reward !== expectedReward) {
2706
- throw new Error(
2707
- `Block ${blockMessage.decoded.index} has an invalid reward for validator ${validator.address}: expected ${expectedReward}, got ${validator.reward}`
2708
- );
2709
- }
2710
- }
2757
+ const expectedValidators = await this.staticCall(addresses.validators, "validators");
2758
+ validateCanonicalValidatorSet(blockMessage.decoded.index, expectedValidators, validators);
2759
+ }
2760
+ async #resolveBlockTransactions(blockMessage) {
2761
+ const transactions = await Promise.all(
2762
+ blockMessage.decoded.transactions.map(async (expectedHash) => {
2763
+ const data = await globalThis.peernet.get(expectedHash, "transaction");
2764
+ const transaction = await resolveTransactionReference(expectedHash, data);
2765
+ await this.validateTransactionSignature(transaction);
2766
+ return transaction;
2767
+ })
2768
+ );
2769
+ const calculatedFees = (await Promise.all(transactions.map((transaction) => calculateFee(transaction.decoded)))).reduce((total, fee) => total + BigInt(fee), 0n);
2770
+ validateBlockEconomics(blockMessage.decoded, calculatedFees);
2771
+ return transactions;
2711
2772
  }
2712
2773
  /** Check if the next block will cross an epoch boundary (block-based timing) */
2713
2774
  #isEpochBoundary(blockHeight) {
@@ -3206,13 +3267,7 @@ class Chain extends VersionControl {
3206
3267
  }
3207
3268
  console.log(`[chain] \u2705 Block data integrity verified: ${hash}`);
3208
3269
  await this.#validateBlockValidators(blockMessage);
3209
- const transactions = await Promise.all(
3210
- blockMessage.decoded.transactions.map(async (hash2) => {
3211
- const data = await peernet.get(hash2, "transaction");
3212
- return new TransactionMessage(data);
3213
- })
3214
- );
3215
- await Promise.all(transactions.map((transaction) => this.validateTransactionSignature(transaction)));
3270
+ const transactions = await this.#resolveBlockTransactions(blockMessage);
3216
3271
  await Promise.all(
3217
3272
  blockMessage.decoded.transactions.map(async (transactionHash) => {
3218
3273
  if (await transactionPoolStore.has(transactionHash)) await transactionPoolStore.delete(transactionHash);
@@ -3338,7 +3393,7 @@ class Chain extends VersionControl {
3338
3393
  fees: BigInt(0),
3339
3394
  timestamp,
3340
3395
  previousHash: "",
3341
- reward: BigInt(150),
3396
+ reward: BLOCK_REWARD,
3342
3397
  index: 0,
3343
3398
  producer: "",
3344
3399
  producerProof: "",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leofcoin/chain",
3
- "version": "1.10.7",
3
+ "version": "1.10.8",
4
4
  "description": "Official javascript implementation",
5
5
  "private": false,
6
6
  "exports": {