@leofcoin/chain 1.10.21 → 1.10.22

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/README.md CHANGED
@@ -6,6 +6,27 @@
6
6
 
7
7
  - reachedOneZeroZero
8
8
 
9
+ ## Cold-sync reliability
10
+
11
+ Both server and browser nodes guard provider discovery against fast negative
12
+ responses. A walk succeeds only after a connected peer confirms it provides
13
+ the requested hash; individual discovery requests retain the protocol request
14
+ timeout.
15
+
16
+ Chain content requests probe the exact requested store before requesting bytes
17
+ from a peer. A pending-only copy cannot act as a provider for the canonical
18
+ transaction store. Responses must use the expected codec and advertised hash;
19
+ the first valid response wins, and unavailable content fails explicitly.
20
+
21
+ Historical ancestry is resolved iteratively, finishing transaction dependencies
22
+ before requesting the next parent. Duplicate callers await the same resolution,
23
+ completed blocks survive retries, and missing or cyclic ancestry fails explicitly.
24
+ Only dependencies of the selected target tip are resolved; unrelated hashes in
25
+ a peer's `knownBlocks` inventory do not gate canonical catch-up.
26
+ Transaction bodies are hash-checked before entering the canonical transaction
27
+ store. Resolving data never applies it: canonical loading still uses the
28
+ serialized state writer and chain-link validation.
29
+
9
30
  ## Pending transaction propagation
10
31
 
11
32
  Transactions enter the local pending pool before canonical confirmation. Peers
@@ -1,4 +1,4 @@
1
- import { s as signTransaction, c as MultiWallet, d as createTransactionHash } from './index-DCbLvMKL.js';
1
+ import { s as signTransaction, c as MultiWallet, d as createTransactionHash } from './index-dxD17Fom.js';
2
2
  import { f as fromBase58 } from './index-CvKt4UDE.js';
3
3
  import './browser-crypto-CH0ua22L.js';
4
4
  import './sha2-BKcEJzgd.js';
@@ -1,8 +1,8 @@
1
- import { g as getDefaultExportFromCjs } from './node-browser-Ha4Lvntj.js';
1
+ import { g as getDefaultExportFromCjs } from './node-browser-BMfelxXV.js';
2
2
  import './browser-crypto-CH0ua22L.js';
3
3
  import './index-CvKt4UDE.js';
4
4
  import './sha2-BKcEJzgd.js';
5
- import './connection-monitor-D-krNiRH.js';
5
+ import './connection-monitor-DMGAhcC0.js';
6
6
 
7
7
  function _mergeNamespaces$1(n, m) {
8
8
  m.forEach(function (e) {
@@ -1,6 +1,6 @@
1
1
  import { F as FormatInterface, p as proto, a as proto$1, b as proto$2, T as TransactionMessage, j as jsonStringifyBigInt, c as jsonParseBigInt, C as ContractMessage, d as Codec } from './browser-crypto-CH0ua22L.js';
2
- import { B as BlockMessage, L as LastBlockMessage, P as PROTOCOL_VERSION, R as REACHED_ONE_ZERO_ZERO, C as ConnectionMonitor, a as BWMessage, S as StateMessage } from './connection-monitor-D-krNiRH.js';
3
- import { M as MAX_TRANSACTION_BYTES, a as MAX_BLOCK_TRANSACTION_BYTES, b as MAX_BLOCK_TRANSACTIONS, c as MultiWallet, d as createTransactionHash, e as calculateFee, f as createContractMessage, s as signTransaction, r as randombytes, g as distributeTransactionFee, h as supportsTransactionFees, i as supportsMonetaryPolicy, v as validateBlockResourceLimits, j as calculateMonetaryPolicy, k as distributeAmount, l as aggregateValidatorFees, m as MONETARY_POLICY_AUTHORITY, n as contractFactoryMessage, o as nativeTokenMessage, p as validatorsMessage, q as nameServiceMessage, t as parseUnits, u as formatUnits } from './index-DCbLvMKL.js';
2
+ import { P as Protocol, B as BlockMessage, L as LastBlockMessage, a as PROTOCOL_VERSION, R as REACHED_ONE_ZERO_ZERO, C as ConnectionMonitor, b as BWMessage, S as StateMessage } from './connection-monitor-DMGAhcC0.js';
3
+ import { M as MAX_TRANSACTION_BYTES, a as MAX_BLOCK_TRANSACTION_BYTES, b as MAX_BLOCK_TRANSACTIONS, c as MultiWallet, d as createTransactionHash, e as calculateFee, f as createContractMessage, s as signTransaction, r as randombytes, g as distributeTransactionFee, h as supportsTransactionFees, i as supportsMonetaryPolicy, v as validateBlockResourceLimits, j as calculateMonetaryPolicy, k as distributeAmount, l as aggregateValidatorFees, m as MONETARY_POLICY_AUTHORITY, n as contractFactoryMessage, o as nativeTokenMessage, p as validatorsMessage, q as nameServiceMessage, t as parseUnits, u as formatUnits } from './index-dxD17Fom.js';
4
4
  import { f as fromBase58 } from './index-CvKt4UDE.js';
5
5
  import { g as getDefaultExportFromCjs } from './_commonjsHelpers-B85MJLTf.js';
6
6
  import { q as quorumThreshold } from './quorum-S_qdgiAY.js';
@@ -2915,29 +2915,6 @@ function requireSemver () {
2915
2915
  var semverExports = requireSemver();
2916
2916
  var semver = /*@__PURE__*/getDefaultExportFromCjs(semverExports);
2917
2917
 
2918
- const limit = 1800;
2919
- const transactionLimit = 2500;
2920
- const requestTimeout = 3e4;
2921
- const syncTimeout = 3e4;
2922
- class Protocol {
2923
- constructor(config) {
2924
- this.resolveTimeout = 1e4;
2925
- if (config?.resolveTimeout) this.resolveTimeout = config.resolveTimeout;
2926
- }
2927
- get limit() {
2928
- return limit;
2929
- }
2930
- get transactionLimit() {
2931
- return transactionLimit;
2932
- }
2933
- get requestTimeout() {
2934
- return requestTimeout;
2935
- }
2936
- get syncTimeout() {
2937
- return syncTimeout;
2938
- }
2939
- }
2940
-
2941
2918
  const waitForCanonicalTransaction = (pubsub, transactionPool, hash) => new Promise((resolve, reject) => {
2942
2919
  const completionTopic = `transaction.completed.${hash}`;
2943
2920
  const cleanup = () => {
@@ -4304,6 +4281,15 @@ const validateChainLink = (localTip, incoming) => {
4304
4281
  return "append";
4305
4282
  };
4306
4283
 
4284
+ const resolveTransactionReference = async (expectedHash, transactionData) => {
4285
+ const transaction = transactionData instanceof TransactionMessage ? transactionData : new TransactionMessage(transactionData);
4286
+ const actualHash = await transaction.hash();
4287
+ if (actualHash !== expectedHash) {
4288
+ throw new Error(`Transaction hash mismatch: expected ${expectedHash}, got ${actualHash}`);
4289
+ }
4290
+ return transaction;
4291
+ };
4292
+
4307
4293
  const debug$1 = createDebugger("leofcoin/state");
4308
4294
  class State extends Contract {
4309
4295
  constructor(config) {
@@ -4320,7 +4306,7 @@ class State extends Contract {
4320
4306
  this.#totalSize = 0;
4321
4307
  this.#canonicalStateQueue = new CanonicalStateQueue();
4322
4308
  this.#loaded = false;
4323
- this.#resolvingHashes = /* @__PURE__ */ new Set();
4309
+ this.#resolvingHashes = /* @__PURE__ */ new Map();
4324
4310
  this._wantList = [];
4325
4311
  this.#chainStateHandler = () => {
4326
4312
  return new globalThis.peernet.protos["peernet-response"]({
@@ -4576,7 +4562,8 @@ class State extends Contract {
4576
4562
  if (!exists) {
4577
4563
  const data = await peernet.get(hash, "transaction");
4578
4564
  if (!data) throw new Error(`missing transaction data for ${hash}`);
4579
- await transactionStore.put(hash, data);
4565
+ const transaction = await resolveTransactionReference(hash, data);
4566
+ await transactionStore.put(hash, transaction.encoded);
4580
4567
  }
4581
4568
  const inPool = await transactionPoolStore.has(hash);
4582
4569
  if (inPool) await transactionPoolStore.delete(hash);
@@ -4584,84 +4571,61 @@ class State extends Contract {
4584
4571
  );
4585
4572
  }
4586
4573
  async #resolveBlock(hash) {
4587
- let index = this.#blockHashMap.get(hash);
4588
- let localHash = "0x0";
4589
- try {
4590
- const rawLocalHash = await globalThis.stateStore.get("lastBlock");
4591
- if (rawLocalHash) {
4592
- const decoded = JSON.parse(new TextDecoder().decode(rawLocalHash));
4593
- localHash = typeof decoded === "string" ? decoded : decoded?.hash ?? "0x0";
4594
- }
4595
- } catch (error) {
4596
- debug$1("no local state found");
4597
- }
4598
- if (this.#blocks[index]) {
4599
- const previousHash = this.#blocks[index].previousHash;
4600
- if (previousHash === localHash) return;
4601
- if (previousHash !== "0x0") {
4602
- return this.resolveBlock(previousHash);
4603
- } else {
4604
- return;
4574
+ const localHash = (await this.canonicalTip()).hash;
4575
+ const visited = /* @__PURE__ */ new Set();
4576
+ while (hash !== "0x0" && hash !== localHash) {
4577
+ if (visited.has(hash)) throw new ResolveError(`cyclic block ancestry at ${hash}`);
4578
+ visited.add(hash);
4579
+ const cachedIndex = this.#blockHashMap.get(hash);
4580
+ const cached = this.#blocks[cachedIndex];
4581
+ if (cached) {
4582
+ hash = cached.previousHash;
4583
+ continue;
4605
4584
  }
4606
- }
4607
- try {
4608
- const block = await this.getAndPutBlock(hash);
4609
- const promises = [];
4610
- if (block.decoded.previousHash !== "0x0" && block.decoded.previousHash !== localHash) {
4611
- promises.push(this.resolveBlock(block.decoded.previousHash));
4585
+ try {
4586
+ const block = await this.getAndPutBlock(hash);
4587
+ await this.#resolveTransactions(block.decoded.transactions);
4588
+ const index = block.decoded.index;
4589
+ const size = block.encoded.byteLength;
4590
+ this.#totalSize += size;
4591
+ this.#blocks[index] = { hash, ...block.decoded };
4592
+ this.#blockHashMap.set(hash, index);
4593
+ debug$1(`resolved block: ${hash} @${index} ${formatBytes(size)}`);
4594
+ globalThis.pubsub.publish("block-resolved", { hash, index });
4595
+ this.#lastResolved = this.#blocks[index];
4596
+ this.#lastResolvedTime = Date.now();
4597
+ hash = block.decoded.previousHash;
4598
+ } catch (error) {
4599
+ throw new ResolveError(`block: ${hash}`, { cause: error });
4612
4600
  }
4613
- promises.push(this.#resolveTransactions(block.decoded.transactions));
4614
- await Promise.all(promises);
4615
- index = block.decoded.index;
4616
- const size = block.encoded.length > 0 ? block.encoded.length : block.encoded.byteLength;
4617
- this.#totalSize += size;
4618
- this.#blocks[index] = { hash, ...block.decoded };
4619
- this.#blockHashMap.set(hash, index);
4620
- debug$1(`resolved block: ${hash} @${index} ${formatBytes(size)}`);
4621
- globalThis.pubsub.publish("block-resolved", { hash, index });
4622
- this.#lastResolved = this.#blocks[index];
4623
- this.#lastResolvedTime = Date.now();
4624
- } catch (error) {
4625
- throw new ResolveError(`block: ${hash}@${index}`, { cause: error });
4626
4601
  }
4627
- return;
4628
4602
  }
4629
4603
  async resolveBlock(hash) {
4630
4604
  if (!hash) throw new Error(`expected hash, got: ${hash}`);
4631
4605
  if (hash === "0x0") return;
4632
- if (this.#resolvingHashes.has(hash)) return;
4633
- this.#resolvingHashes.add(hash);
4634
- const isEntering = this.#resolvingHashes.size === 1;
4606
+ const existing = this.#resolvingHashes.get(hash);
4607
+ if (existing) return existing;
4635
4608
  this.#resolving = true;
4636
- try {
4637
- if (isEntering) {
4638
- if (this.jobber.busy && this.jobber.destroy) await this.jobber.destroy();
4639
- await this.jobber.add(() => this.#resolveBlock(hash));
4640
- } else {
4641
- await this.#resolveBlock(hash);
4642
- }
4643
- try {
4644
- const lastBlockHash = await globalThis.stateStore.get("lastBlock");
4645
- if (lastBlockHash === hash) {
4609
+ const resolution = Promise.resolve().then(async () => {
4610
+ for (let attempt = 0; attempt < 3; attempt++) {
4611
+ try {
4612
+ await this.#resolveBlock(hash);
4646
4613
  this.#resolveErrored = false;
4647
4614
  return;
4615
+ } catch (error) {
4616
+ console.error(`block resolution attempt ${attempt + 1} failed for ${hash}`, error);
4617
+ if (attempt === 2) {
4618
+ if (!this.wantList.includes(hash)) this.wantList.push(hash);
4619
+ throw new ResolveError(`block: ${hash}`, { cause: error });
4620
+ }
4648
4621
  }
4649
- } catch (error) {
4650
4622
  }
4651
- } catch (error) {
4652
- console.log({ error });
4653
- this.#resolveErrorCount += 1;
4654
- if (this.#resolveErrorCount < 3) {
4655
- this.#resolvingHashes.delete(hash);
4656
- return this.resolveBlock(hash);
4657
- }
4658
- this.#resolveErrorCount = 0;
4659
- this.wantList.push(hash);
4660
- throw new ResolveError(`block: ${hash}`, { cause: error });
4661
- } finally {
4623
+ }).finally(() => {
4662
4624
  this.#resolvingHashes.delete(hash);
4663
4625
  if (this.#resolvingHashes.size === 0) this.#resolving = false;
4664
- }
4626
+ });
4627
+ this.#resolvingHashes.set(hash, resolution);
4628
+ return resolution;
4665
4629
  }
4666
4630
  async resolveBlocks() {
4667
4631
  if (this.#chainSyncing || this.#resolving) {
@@ -4798,24 +4762,6 @@ class State extends Contract {
4798
4762
  return;
4799
4763
  }
4800
4764
  if (localStateHash !== remoteBlockHash) {
4801
- if (this.wantList.length > 0) {
4802
- debug$1(`Fetching ${this.wantList.length} blocks before resolving`);
4803
- const getBatch = async (batch) => {
4804
- const blocks2 = await Promise.all(
4805
- batch.map(
4806
- (hash) => this.getAndPutBlock(hash).catch((e) => {
4807
- console.warn(`failed to fetch block ${hash}`, e);
4808
- })
4809
- )
4810
- );
4811
- const transactions = blocks2.filter((block) => Boolean(block)).flatMap((block) => block.decoded.transactions);
4812
- return this.#resolveTransactions(transactions);
4813
- };
4814
- for (let i = 0; i < this.wantList.length; i += 50) {
4815
- const batch = this.wantList.slice(i, i + 50);
4816
- await getBatch(batch);
4817
- }
4818
- }
4819
4765
  debug$1(`Resolving remote block: ${remoteBlockHash} @${remoteIndex} (differs from local state)`);
4820
4766
  await this.resolveBlock(remoteBlockHash);
4821
4767
  const blocksSynced = remoteIndex - localIndex;
@@ -5171,15 +5117,6 @@ const validateBlockEconomics = (block, calculatedFees, validatorFees = /* @__PUR
5171
5117
  }
5172
5118
  };
5173
5119
 
5174
- const resolveTransactionReference = async (expectedHash, transactionData) => {
5175
- const transaction = transactionData instanceof TransactionMessage ? transactionData : new TransactionMessage(transactionData);
5176
- const actualHash = await transaction.hash();
5177
- if (actualHash !== expectedHash) {
5178
- throw new Error(`Transaction hash mismatch: expected ${expectedHash}, got ${actualHash}`);
5179
- }
5180
- return transaction;
5181
- };
5182
-
5183
5120
  const consensusSignableData = (validatorsAddress, type, message) => ({
5184
5121
  from: String(message.from),
5185
5122
  to: validatorsAddress,
@@ -6207,13 +6144,6 @@ class Chain extends VersionControl {
6207
6144
  }
6208
6145
  }
6209
6146
  }
6210
- if (this.wantList.length > 0) {
6211
- const promises = await Promise.allSettled(this.wantList.map((hash) => peernet.get(hash, "block")));
6212
- for (let i = promises.length - 1; i >= 0; i -= 1) {
6213
- if (promises[i].status === "fulfilled") this.wantList.splice(i, 1);
6214
- }
6215
- if (this.wantList.length === 0) await this.triggerSync();
6216
- }
6217
6147
  try {
6218
6148
  let stateInfo = await this.#makeRequest(peer, "stateInfo");
6219
6149
  if (stateInfo instanceof Uint8Array) {