@leofcoin/chain 1.10.3 → 1.10.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
@@ -670,6 +670,7 @@ class Machine {
670
670
  break;
671
671
  }
672
672
  case "machine-ready": {
673
+ this.readyResolve(this);
673
674
  pubsub.publish("machine.ready", true);
674
675
  break;
675
676
  }
@@ -865,7 +866,6 @@ class Machine {
865
866
  }
866
867
  };
867
868
  this.worker.postMessage(message);
868
- this.readyResolve(this);
869
869
  });
870
870
  }
871
871
  async #runContract(contractMessage) {
@@ -1392,15 +1392,18 @@ class State extends Contract {
1392
1392
  }
1393
1393
  async getAndPutBlock(hash) {
1394
1394
  let block = await globalThis.peernet.get(hash, "block");
1395
- if (block !== void 0) {
1396
- if (!(block instanceof Uint8Array)) {
1397
- block = new Uint8Array(Object.values(block));
1398
- }
1399
- block = await new BlockMessage(block);
1400
- const { index } = block.decoded;
1401
- if (this.#blocks[index] && this.#blocks[index].hash !== block.hash) throw `invalid block ${hash} @${index}`;
1402
- if (!await globalThis.peernet.has(hash)) await globalThis.peernet.put(hash, block.encoded, "block");
1403
- }
1395
+ if (block === void 0 || block === null) {
1396
+ throw new ResolveError(`block data unavailable for ${hash}`);
1397
+ }
1398
+ if (!(block instanceof Uint8Array)) block = new Uint8Array(Object.values(block));
1399
+ block = await new BlockMessage(block);
1400
+ const resolvedHash = await block.hash();
1401
+ if (resolvedHash !== hash) throw new ResolveError(`block hash mismatch: requested ${hash}, received ${resolvedHash}`);
1402
+ const { index } = block.decoded;
1403
+ if (this.#blocks[index] && this.#blocks[index].hash !== resolvedHash) {
1404
+ throw new ResolveError(`conflicting block ${resolvedHash} @${index}`);
1405
+ }
1406
+ if (!await globalThis.peernet.has(hash)) await globalThis.peernet.put(hash, block.encoded, "block");
1404
1407
  return block;
1405
1408
  }
1406
1409
  async #resolveTransactions(transactions) {
@@ -2273,6 +2276,40 @@ const verifyConsensusMessage = async (validatorsAddress, type, message, network)
2273
2276
  }
2274
2277
  };
2275
2278
 
2279
+ const nextBlockIndex = (lastIndex) => Number(lastIndex ?? -1) + 1;
2280
+ const proposalDelay = ({
2281
+ now,
2282
+ lastBlockTimestamp = 0,
2283
+ lastProposalAt = 0,
2284
+ blockTime
2285
+ }) => {
2286
+ const canonicalBase = Math.min(lastBlockTimestamp, now);
2287
+ const nextFromCanonicalBlock = canonicalBase > 0 ? canonicalBase + blockTime : 0;
2288
+ const nextFromLocalAttempt = lastProposalAt > 0 ? lastProposalAt + blockTime : 0;
2289
+ return Math.max(0, Math.max(nextFromCanonicalBlock, nextFromLocalAttempt) - now);
2290
+ };
2291
+
2292
+ const compareTransactionNonces = (left, right) => {
2293
+ const leftNonce = BigInt(left ?? 0);
2294
+ const rightNonce = BigInt(right ?? 0);
2295
+ if (leftNonce < rightNonce) return -1;
2296
+ if (leftNonce > rightNonce) return 1;
2297
+ return 0;
2298
+ };
2299
+ const pruneCanonicalTransactions = async (transactions, latestTransactionHashes, isStored, removePending) => {
2300
+ const latest = new Set(latestTransactionHashes);
2301
+ const pending = [];
2302
+ for (const transaction of transactions) {
2303
+ const hash = await transaction.hash();
2304
+ if (latest.has(hash) || await isStored(hash)) {
2305
+ await removePending(hash);
2306
+ continue;
2307
+ }
2308
+ pending.push({ transaction, hash });
2309
+ }
2310
+ return pending;
2311
+ };
2312
+
2276
2313
  const debug = createDebugger("leofcoin/chain");
2277
2314
  class Chain extends VersionControl {
2278
2315
  constructor(config) {
@@ -2280,6 +2317,8 @@ class Chain extends VersionControl {
2280
2317
  this.#slotTime = 1e4;
2281
2318
  this.#blockTime = 6e3;
2282
2319
  // 6 second target block time
2320
+ /** Wall-clock time of the last local proposal attempt. */
2321
+ this.#lastProposalAt = 0;
2283
2322
  this.#epochLength = 10;
2284
2323
  this.utils = {};
2285
2324
  /** {Address[]} */
@@ -2495,6 +2534,7 @@ class Chain extends VersionControl {
2495
2534
  #state;
2496
2535
  #slotTime;
2497
2536
  #blockTime;
2537
+ #lastProposalAt;
2498
2538
  #epochLength;
2499
2539
  #validators;
2500
2540
  #runningEpoch;
@@ -2568,10 +2608,10 @@ class Chain extends VersionControl {
2568
2608
  globalThis.peernet?.network || "leofcoin"
2569
2609
  );
2570
2610
  }
2571
- async #getConsensusValidators(nextBlockIndex) {
2611
+ async #getConsensusValidators(nextBlockIndex2) {
2572
2612
  const localBlock = await this.lastBlock;
2573
2613
  const localIndex = localBlock?.index !== void 0 ? Number(localBlock.index) : -1;
2574
- if (Array.isArray(localBlock?.validators) && localBlock.validators.length > 0 && (nextBlockIndex === void 0 || nextBlockIndex === localIndex + 1)) {
2614
+ if (Array.isArray(localBlock?.validators) && localBlock.validators.length > 0 && (nextBlockIndex2 === void 0 || nextBlockIndex2 === localIndex + 1)) {
2575
2615
  return [
2576
2616
  ...new Set(
2577
2617
  localBlock.validators.map((validator) => validator.address).filter((address) => Boolean(address))
@@ -2662,9 +2702,7 @@ class Chain extends VersionControl {
2662
2702
  async #runEpoch() {
2663
2703
  if (this.#runningEpoch) return;
2664
2704
  this.#runningEpoch = true;
2665
- console.log("epoch");
2666
2705
  const validators = await this.#getConsensusValidators();
2667
- console.log({ validators });
2668
2706
  if (this.#isJailed(peernet.selectedAccount)) {
2669
2707
  this.#runningEpoch = false;
2670
2708
  return;
@@ -2673,8 +2711,16 @@ class Chain extends VersionControl {
2673
2711
  this.#runningEpoch = false;
2674
2712
  return;
2675
2713
  }
2676
- const localBlock = await this.lastBlock;
2677
- const nextIndex = (localBlock?.index !== void 0 ? Number(localBlock.index) : -1) + 1;
2714
+ let localBlock = await this.lastBlock;
2715
+ const delay = proposalDelay({
2716
+ now: Date.now(),
2717
+ lastBlockTimestamp: Number(localBlock?.timestamp ?? 0),
2718
+ lastProposalAt: this.#lastProposalAt,
2719
+ blockTime: this.#blockTime
2720
+ });
2721
+ if (delay > 0) await this.#sleep(delay);
2722
+ localBlock = await this.lastBlock;
2723
+ const nextIndex = nextBlockIndex(localBlock?.index);
2678
2724
  const proposerIdx = (nextIndex + this.#consensusRound) % validators.length;
2679
2725
  const isProposer = validators[proposerIdx] === peernet.selectedAccount;
2680
2726
  if (!isProposer) {
@@ -2694,18 +2740,13 @@ class Chain extends VersionControl {
2694
2740
  clearTimeout(this.#roundTimer);
2695
2741
  this.#roundTimer = null;
2696
2742
  }
2697
- const start = Date.now();
2743
+ this.#lastProposalAt = Date.now();
2698
2744
  try {
2699
2745
  await this.#createBlock();
2700
2746
  } catch (error) {
2701
2747
  console.error(error);
2702
2748
  }
2703
- const end = Date.now();
2704
- console.log((end - start) / 1e3 + " s");
2705
- const elapsed = end - start;
2706
- const remaining = this.#blockTime - elapsed;
2707
2749
  const hasMore = await this.hasTransactionToHandle();
2708
- if (!hasMore && remaining > 0) await this.#sleep(remaining);
2709
2750
  this.#runningEpoch = false;
2710
2751
  if (hasMore && !this.#proposalInFlight) return this.#runEpoch();
2711
2752
  }
@@ -2984,6 +3025,10 @@ class Chain extends VersionControl {
2984
3025
  return Promise.all(transactionsToGet);
2985
3026
  }
2986
3027
  async #peerConnected(peerId) {
3028
+ if (typeof peerId !== "string" || !peerId.trim() || peerId === "undefined") {
3029
+ debug("ignored peer connection without a valid peer id");
3030
+ return;
3031
+ }
2987
3032
  debug(`peer connected: ${peerId}`);
2988
3033
  const peer = peernet.getConnection(peerId);
2989
3034
  if (!peer) {
@@ -3195,7 +3240,7 @@ class Chain extends VersionControl {
3195
3240
  if (a.decoded.priority !== b.decoded.priority) {
3196
3241
  return (b.decoded.priority ? 1 : 0) - (a.decoded.priority ? 1 : 0);
3197
3242
  }
3198
- const nonceDiff = (a.decoded?.nonce ?? 0) - (b.decoded?.nonce ?? 0);
3243
+ const nonceDiff = compareTransactionNonces(a.decoded?.nonce, b.decoded?.nonce);
3199
3244
  if (nonceDiff !== 0) return nonceDiff;
3200
3245
  return 0;
3201
3246
  });
@@ -3293,7 +3338,6 @@ class Chain extends VersionControl {
3293
3338
  // todo filter tx that need to wait on prev nonce
3294
3339
  async #createBlock(limit = this.transactionLimit) {
3295
3340
  if (this.#proposalInFlight) return;
3296
- console.log(await globalThis.transactionPoolStore.size());
3297
3341
  if (await globalThis.transactionPoolStore.size() === 0) return;
3298
3342
  let transactions = await globalThis.transactionPoolStore.values(this.transactionLimit);
3299
3343
  if (Object.keys(transactions)?.length === 0) return;
@@ -3312,29 +3356,29 @@ class Chain extends VersionControl {
3312
3356
  };
3313
3357
  const latestTransactions = await this.machine.latestTransactions();
3314
3358
  transactions = await this.promiseTransactions(transactions);
3315
- const allTransactions = transactions.sort((a, b) => {
3316
- if (a.decoded.priority !== b.decoded.priority) {
3317
- return (b.decoded.priority ? 1 : 0) - (a.decoded.priority ? 1 : 0);
3359
+ const pendingTransactions = await pruneCanonicalTransactions(
3360
+ transactions,
3361
+ latestTransactions,
3362
+ (hash) => globalThis.transactionStore.has(hash),
3363
+ (hash) => globalThis.transactionPoolStore.delete(hash)
3364
+ );
3365
+ const allTransactions = pendingTransactions.sort((a, b) => {
3366
+ if (a.transaction.decoded.priority !== b.transaction.decoded.priority) {
3367
+ return (b.transaction.decoded.priority ? 1 : 0) - (a.transaction.decoded.priority ? 1 : 0);
3318
3368
  }
3319
- const nonceDiff = (a.decoded?.nonce ?? 0) - (b.decoded?.nonce ?? 0);
3369
+ const nonceDiff = compareTransactionNonces(a.transaction.decoded?.nonce, b.transaction.decoded?.nonce);
3320
3370
  if (nonceDiff !== 0) return nonceDiff;
3321
3371
  return 0;
3322
3372
  });
3323
- for (const transaction of allTransactions) {
3373
+ for (const { transaction, hash } of allTransactions) {
3324
3374
  await this.validateTransactionSignature(transaction);
3325
- const hash = await transaction.hash();
3326
- if (latestTransactions.includes(hash) || await globalThis.transactionStore.has(hash)) {
3327
- continue;
3328
- }
3329
3375
  block.transactions.push(hash);
3330
3376
  block.fees += BigInt(await calculateFee(transaction.decoded));
3331
3377
  await globalThis.peernet.put(hash, transaction.encoded, "transaction");
3332
3378
  }
3333
3379
  if (block.transactions.length === 0) return;
3334
3380
  const localBlock = await this.lastBlock;
3335
- block.index = localBlock.index;
3336
- if (block.index === void 0) block.index = 0;
3337
- else block.index += 1;
3381
+ block.index = nextBlockIndex(localBlock?.index);
3338
3382
  block.previousHash = localBlock.hash || "0x0";
3339
3383
  const canonicalValidators = await this.staticCall(addresses.validators, "validators");
3340
3384
  const sortedValidators = [...canonicalValidators].sort();
@@ -1,4 +1,4 @@
1
- import { E as EasyWorker, F as FormatInterface, p as proto$3, i as index$3, a as index$2, b as proto$2 } from './worker-jeGMMCte-BwFbyiks.js';
1
+ import { E as EasyWorker, F as FormatInterface, p as proto$3, i as index$3, a as index$2, b as proto$2 } from './worker-CZqErLI7-BxofVJAn.js';
2
2
 
3
3
  class ValidatorMessage extends FormatInterface {
4
4
  get messageName() {