@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.
- package/exports/browser/chain.js +73 -18
- package/exports/chain.js +73 -18
- package/package.json +1 -1
package/exports/browser/chain.js
CHANGED
|
@@ -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]
|
|
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
|
|
10608
|
-
|
|
10609
|
-
|
|
10610
|
-
|
|
10611
|
-
|
|
10612
|
-
|
|
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
|
|
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:
|
|
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]
|
|
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
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
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
|
|
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:
|
|
3396
|
+
reward: BLOCK_REWARD,
|
|
3342
3397
|
index: 0,
|
|
3343
3398
|
producer: "",
|
|
3344
3399
|
producerProof: "",
|