@leofcoin/chain 1.10.20 → 1.10.21

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,4 +6,21 @@
6
6
 
7
7
  - reachedOneZeroZero
8
8
 
9
+ ## Pending transaction propagation
10
+
11
+ Transactions enter the local pending pool before canonical confirmation. Peers
12
+ relay newly accepted transaction bytes using the existing `add-transaction`
13
+ topic; duplicate and already-confirmed entries are not relayed again.
14
+
15
+ On compatible peer reconnection, pending pools are reconciled even at equal
16
+ block heights. Missing bodies are fetched from `transactionPool`, checked against
17
+ their advertised hash, and admitted through the normal signature-validation and
18
+ pending-nonce path. Unavailable or invalid entries are logged without preventing
19
+ recovery of the remaining entries.
20
+
21
+ Pool admission and pubsub publication are not validator acknowledgements.
22
+ `sendTransaction().wait` resolves only after canonical block inclusion. Deploy
23
+ the updated chain runtime to intermediate nodes and validators as well as the
24
+ browser to enable relay and reconnect recovery throughout the network.
25
+
9
26
  <a rel="license" href="http://creativecommons.org/licenses/by-nc/4.0/"><img alt="Creative Commons Licence" style="border-width:0" src="https://i.creativecommons.org/l/by-nc/4.0/88x31.png" /></a><br />This work is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc/4.0/">Creative Commons Attribution-NonCommercial 4.0 International License</a>.
@@ -3164,7 +3164,7 @@ class Transaction extends Protocol {
3164
3164
  await globalThis.transactionPoolStore.put(hash, message.encoded);
3165
3165
  this.addPendingNonce(message.decoded.from, message.decoded.nonce);
3166
3166
  try {
3167
- peernet.publish("add-transaction", message.encoded);
3167
+ await peernet.publish("add-transaction", message.encoded);
3168
3168
  } catch (publishError) {
3169
3169
  console.warn("peernet publish failed: add-transaction", publishError?.message ?? publishError);
3170
3170
  }
@@ -6077,39 +6077,29 @@ class Chain extends VersionControl {
6077
6077
  }
6078
6078
  async getPeerTransactionPool(peer) {
6079
6079
  let transactionsInPool = await this.#makeRequest(peer, "transactionPool");
6080
- console.log("raw response for request transactionPool:", transactionsInPool);
6081
6080
  if (transactionsInPool instanceof Uint8Array) {
6082
- try {
6083
- transactionsInPool = JSON.parse(new TextDecoder().decode(transactionsInPool));
6084
- } catch {
6085
- debug("transactionPool response must be decoded array payload");
6086
- return [];
6087
- }
6081
+ transactionsInPool = new TextDecoder().decode(transactionsInPool);
6088
6082
  }
6089
6083
  if (typeof transactionsInPool === "string") {
6090
- try {
6091
- transactionsInPool = JSON.parse(transactionsInPool);
6092
- } catch {
6093
- return [];
6094
- }
6084
+ transactionsInPool = JSON.parse(transactionsInPool);
6095
6085
  }
6096
- if (!Array.isArray(transactionsInPool)) return [];
6097
- const localTransactions = new Set(await globalThis.transactionPoolStore.keys());
6098
- const transactionsToGet = [];
6099
- for (const key of transactionsInPool) {
6100
- if (!localTransactions.has(key)) {
6101
- let txData;
6102
- try {
6103
- txData = await globalThis.peernet.get(key, "transaction");
6104
- } catch (error) {
6105
- debug(`Failed to get transaction ${key}:`, error?.message ?? error);
6106
- }
6107
- if (txData !== void 0) {
6108
- transactionsToGet.push(transactionPoolStore.put(key, txData));
6109
- }
6086
+ if (!Array.isArray(transactionsInPool) || transactionsInPool.some((hash) => typeof hash !== "string")) {
6087
+ throw new Error("transactionPool response must be an array of transaction hashes");
6088
+ }
6089
+ const recovered = [];
6090
+ for (const hash of new Set(transactionsInPool)) {
6091
+ if (await globalThis.transactionPoolStore.has(hash)) continue;
6092
+ if (await globalThis.transactionStore.has(hash)) continue;
6093
+ try {
6094
+ const txData = await globalThis.peernet.get(hash, "transactionPool");
6095
+ if (txData === void 0) throw new Error("pending transaction body unavailable");
6096
+ const transaction = await resolveTransactionReference(hash, txData);
6097
+ recovered.push(await this.#sendTransaction(transaction));
6098
+ } catch (error) {
6099
+ debug(`Failed to recover transaction ${hash}:`, error?.message ?? error);
6110
6100
  }
6111
6101
  }
6112
- return Promise.all(transactionsToGet);
6102
+ return recovered;
6113
6103
  }
6114
6104
  async #peerConnected(peerId) {
6115
6105
  if (typeof peerId !== "string" || !peerId.trim() || peerId === "undefined") {
@@ -6177,6 +6167,14 @@ class Chain extends VersionControl {
6177
6167
  await this.#recordPeerFailure(peerId, `unreasonable lastBlock index: ${lastBlock.index}`);
6178
6168
  return;
6179
6169
  }
6170
+ setTimeout(async () => {
6171
+ try {
6172
+ await this.getPeerTransactionPool(peer);
6173
+ } catch (error) {
6174
+ const peerName = peer?.peerId || peer?.id || peer?.address || peerId || "unknown";
6175
+ debug(`transactionPool request failed: ${peerName}:`, error?.message ?? error);
6176
+ }
6177
+ }, 3e3);
6180
6178
  if (!lastBlock || !lastBlock.hash || lastBlock.hash === "0x0") {
6181
6179
  debug(`peer has no lastBlock: ${peerId}`);
6182
6180
  return;
@@ -6216,15 +6214,6 @@ class Chain extends VersionControl {
6216
6214
  }
6217
6215
  if (this.wantList.length === 0) await this.triggerSync();
6218
6216
  }
6219
- setTimeout(async () => {
6220
- try {
6221
- const peerTransactionPool = higherThenCurrentLocal && await this.getPeerTransactionPool(peer) || [];
6222
- if (this.#participating && peerTransactionPool.length > 0) return this.#runEpoch();
6223
- } catch (error) {
6224
- const peerName = peer?.peerId || peer?.id || peer?.address || peerId || "unknown";
6225
- debug(`transactionPool request failed: ${peerName}:`, error?.message ?? error);
6226
- }
6227
- }, 3e3);
6228
6217
  try {
6229
6218
  let stateInfo = await this.#makeRequest(peer, "stateInfo");
6230
6219
  if (stateInfo instanceof Uint8Array) {
@@ -6601,18 +6590,32 @@ class Chain extends VersionControl {
6601
6590
  await this.validateTransactionSignature(transaction);
6602
6591
  let hash;
6603
6592
  try {
6593
+ hash = await transaction.hash();
6594
+ if (await globalThis.transactionStore.has(hash)) return hash;
6595
+ const alreadyPending = await globalThis.transactionPoolStore.has(hash);
6604
6596
  hash = await enqueueTransaction(transaction, {
6605
6597
  putToPool: (txHash, data) => globalThis.transactionPoolStore.put(txHash, data),
6606
6598
  hasInPool: (txHash) => globalThis.transactionPoolStore.has(txHash),
6607
6599
  hasInStore: (txHash) => globalThis.transactionStore.has(txHash),
6608
6600
  addPendingNonce: (address, nonce) => this.addPendingNonce(address, nonce)
6609
6601
  });
6610
- if (this.#participating && !this.#runningEpoch) this.#runEpoch();
6602
+ if (!alreadyPending) {
6603
+ try {
6604
+ await globalThis.peernet.publish("add-transaction", transaction.encoded);
6605
+ } catch (publishError) {
6606
+ debug("peernet publish failed: add-transaction relay", publishError?.message ?? publishError);
6607
+ }
6608
+ }
6609
+ if (this.#participating && !this.#runningEpoch) {
6610
+ void this.#runEpoch().catch((error) => {
6611
+ debug("transaction epoch scheduling failed:", error?.message ?? error);
6612
+ });
6613
+ }
6611
6614
  return hash;
6612
6615
  } catch (e) {
6613
6616
  if (hash) {
6614
6617
  try {
6615
- globalThis.peernet.publish("invalid-transaction", hash);
6618
+ await globalThis.peernet.publish("invalid-transaction", hash);
6616
6619
  } catch (publishError) {
6617
6620
  debug("peernet publish failed: invalid-transaction", publishError?.message ?? publishError);
6618
6621
  }
@@ -6630,7 +6633,7 @@ class Chain extends VersionControl {
6630
6633
  const event = await super.sendTransaction(transactionMessage);
6631
6634
  await this.#sendTransaction(transactionMessage.encoded);
6632
6635
  try {
6633
- globalThis.peernet.publish("send-transaction", transactionMessage.encoded);
6636
+ await globalThis.peernet.publish("send-transaction", transactionMessage.encoded);
6634
6637
  } catch (publishError) {
6635
6638
  debug("peernet publish failed: send-transaction", publishError?.message ?? publishError);
6636
6639
  }
package/exports/chain.js CHANGED
@@ -266,7 +266,7 @@ class Transaction extends Protocol {
266
266
  await globalThis.transactionPoolStore.put(hash, message.encoded);
267
267
  this.addPendingNonce(message.decoded.from, message.decoded.nonce);
268
268
  try {
269
- peernet.publish("add-transaction", message.encoded);
269
+ await peernet.publish("add-transaction", message.encoded);
270
270
  } catch (publishError) {
271
271
  console.warn("peernet publish failed: add-transaction", publishError?.message ?? publishError);
272
272
  }
@@ -2960,39 +2960,29 @@ class Chain extends VersionControl {
2960
2960
  }
2961
2961
  async getPeerTransactionPool(peer) {
2962
2962
  let transactionsInPool = await this.#makeRequest(peer, "transactionPool");
2963
- console.log("raw response for request transactionPool:", transactionsInPool);
2964
2963
  if (transactionsInPool instanceof Uint8Array) {
2965
- try {
2966
- transactionsInPool = JSON.parse(new TextDecoder().decode(transactionsInPool));
2967
- } catch {
2968
- debug("transactionPool response must be decoded array payload");
2969
- return [];
2970
- }
2964
+ transactionsInPool = new TextDecoder().decode(transactionsInPool);
2971
2965
  }
2972
2966
  if (typeof transactionsInPool === "string") {
2973
- try {
2974
- transactionsInPool = JSON.parse(transactionsInPool);
2975
- } catch {
2976
- return [];
2977
- }
2967
+ transactionsInPool = JSON.parse(transactionsInPool);
2978
2968
  }
2979
- if (!Array.isArray(transactionsInPool)) return [];
2980
- const localTransactions = new Set(await globalThis.transactionPoolStore.keys());
2981
- const transactionsToGet = [];
2982
- for (const key of transactionsInPool) {
2983
- if (!localTransactions.has(key)) {
2984
- let txData;
2985
- try {
2986
- txData = await globalThis.peernet.get(key, "transaction");
2987
- } catch (error) {
2988
- debug(`Failed to get transaction ${key}:`, error?.message ?? error);
2989
- }
2990
- if (txData !== void 0) {
2991
- transactionsToGet.push(transactionPoolStore.put(key, txData));
2992
- }
2969
+ if (!Array.isArray(transactionsInPool) || transactionsInPool.some((hash) => typeof hash !== "string")) {
2970
+ throw new Error("transactionPool response must be an array of transaction hashes");
2971
+ }
2972
+ const recovered = [];
2973
+ for (const hash of new Set(transactionsInPool)) {
2974
+ if (await globalThis.transactionPoolStore.has(hash)) continue;
2975
+ if (await globalThis.transactionStore.has(hash)) continue;
2976
+ try {
2977
+ const txData = await globalThis.peernet.get(hash, "transactionPool");
2978
+ if (txData === void 0) throw new Error("pending transaction body unavailable");
2979
+ const transaction = await resolveTransactionReference(hash, txData);
2980
+ recovered.push(await this.#sendTransaction(transaction));
2981
+ } catch (error) {
2982
+ debug(`Failed to recover transaction ${hash}:`, error?.message ?? error);
2993
2983
  }
2994
2984
  }
2995
- return Promise.all(transactionsToGet);
2985
+ return recovered;
2996
2986
  }
2997
2987
  async #peerConnected(peerId) {
2998
2988
  if (typeof peerId !== "string" || !peerId.trim() || peerId === "undefined") {
@@ -3060,6 +3050,14 @@ class Chain extends VersionControl {
3060
3050
  await this.#recordPeerFailure(peerId, `unreasonable lastBlock index: ${lastBlock.index}`);
3061
3051
  return;
3062
3052
  }
3053
+ setTimeout(async () => {
3054
+ try {
3055
+ await this.getPeerTransactionPool(peer);
3056
+ } catch (error) {
3057
+ const peerName = peer?.peerId || peer?.id || peer?.address || peerId || "unknown";
3058
+ debug(`transactionPool request failed: ${peerName}:`, error?.message ?? error);
3059
+ }
3060
+ }, 3e3);
3063
3061
  if (!lastBlock || !lastBlock.hash || lastBlock.hash === "0x0") {
3064
3062
  debug(`peer has no lastBlock: ${peerId}`);
3065
3063
  return;
@@ -3099,15 +3097,6 @@ class Chain extends VersionControl {
3099
3097
  }
3100
3098
  if (this.wantList.length === 0) await this.triggerSync();
3101
3099
  }
3102
- setTimeout(async () => {
3103
- try {
3104
- const peerTransactionPool = higherThenCurrentLocal && await this.getPeerTransactionPool(peer) || [];
3105
- if (this.#participating && peerTransactionPool.length > 0) return this.#runEpoch();
3106
- } catch (error) {
3107
- const peerName = peer?.peerId || peer?.id || peer?.address || peerId || "unknown";
3108
- debug(`transactionPool request failed: ${peerName}:`, error?.message ?? error);
3109
- }
3110
- }, 3e3);
3111
3100
  try {
3112
3101
  let stateInfo = await this.#makeRequest(peer, "stateInfo");
3113
3102
  if (stateInfo instanceof Uint8Array) {
@@ -3484,18 +3473,32 @@ class Chain extends VersionControl {
3484
3473
  await this.validateTransactionSignature(transaction);
3485
3474
  let hash;
3486
3475
  try {
3476
+ hash = await transaction.hash();
3477
+ if (await globalThis.transactionStore.has(hash)) return hash;
3478
+ const alreadyPending = await globalThis.transactionPoolStore.has(hash);
3487
3479
  hash = await enqueueTransaction(transaction, {
3488
3480
  putToPool: (txHash, data) => globalThis.transactionPoolStore.put(txHash, data),
3489
3481
  hasInPool: (txHash) => globalThis.transactionPoolStore.has(txHash),
3490
3482
  hasInStore: (txHash) => globalThis.transactionStore.has(txHash),
3491
3483
  addPendingNonce: (address, nonce) => this.addPendingNonce(address, nonce)
3492
3484
  });
3493
- if (this.#participating && !this.#runningEpoch) this.#runEpoch();
3485
+ if (!alreadyPending) {
3486
+ try {
3487
+ await globalThis.peernet.publish("add-transaction", transaction.encoded);
3488
+ } catch (publishError) {
3489
+ debug("peernet publish failed: add-transaction relay", publishError?.message ?? publishError);
3490
+ }
3491
+ }
3492
+ if (this.#participating && !this.#runningEpoch) {
3493
+ void this.#runEpoch().catch((error) => {
3494
+ debug("transaction epoch scheduling failed:", error?.message ?? error);
3495
+ });
3496
+ }
3494
3497
  return hash;
3495
3498
  } catch (e) {
3496
3499
  if (hash) {
3497
3500
  try {
3498
- globalThis.peernet.publish("invalid-transaction", hash);
3501
+ await globalThis.peernet.publish("invalid-transaction", hash);
3499
3502
  } catch (publishError) {
3500
3503
  debug("peernet publish failed: invalid-transaction", publishError?.message ?? publishError);
3501
3504
  }
@@ -3513,7 +3516,7 @@ class Chain extends VersionControl {
3513
3516
  const event = await super.sendTransaction(transactionMessage);
3514
3517
  await this.#sendTransaction(transactionMessage.encoded);
3515
3518
  try {
3516
- globalThis.peernet.publish("send-transaction", transactionMessage.encoded);
3519
+ await globalThis.peernet.publish("send-transaction", transactionMessage.encoded);
3517
3520
  } catch (publishError) {
3518
3521
  debug("peernet publish failed: send-transaction", publishError?.message ?? publishError);
3519
3522
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leofcoin/chain",
3
- "version": "1.10.20",
3
+ "version": "1.10.21",
4
4
  "description": "Official javascript implementation",
5
5
  "private": false,
6
6
  "exports": {
@@ -81,10 +81,10 @@
81
81
  "@rollup/plugin-commonjs": "^29.0.3",
82
82
  "@rollup/plugin-json": "^6.1.0",
83
83
  "@rollup/plugin-node-resolve": "^16.0.3",
84
- "@rollup/plugin-typescript": "^12.3.0",
84
+ "@rollup/plugin-typescript": "^12.3.1",
85
85
  "@types/semver": "^7.8.0",
86
86
  "@vandeurenglenn/debug": "^1.4.0",
87
- "rollup": "^4.64.12",
87
+ "rollup": "^4.65.0",
88
88
  "rollup-plugin-esbuild": "^6.2.1",
89
89
  "rollup-plugin-modify": "^3.0.0",
90
90
  "tape": "^5.10.3",
@@ -99,14 +99,14 @@
99
99
  "@leofcoin/contracts": "^0.1.27",
100
100
  "@leofcoin/crypto": "^0.2.41",
101
101
  "@leofcoin/errors": "^1.0.30",
102
- "@leofcoin/lib": "^1.3.15",
102
+ "@leofcoin/lib": "^1.3.16",
103
103
  "@leofcoin/messages": "^1.6.5",
104
104
  "@leofcoin/multi-wallet": "^3.1.11",
105
105
  "@leofcoin/networks": "^1.1.31",
106
- "@leofcoin/peernet": "^3.0.0",
106
+ "@leofcoin/peernet": "^3.1.0",
107
107
  "@leofcoin/storage": "^3.5.38",
108
108
  "@leofcoin/utils": "^1.1.45",
109
- "@leofcoin/workers": "^1.5.40",
109
+ "@leofcoin/workers": "^1.5.42",
110
110
  "@vandeurenglenn/base58": "^1.1.9",
111
111
  "@vandeurenglenn/easy-worker": "^1.0.3",
112
112
  "@vandeurenglenn/typed-array-utils": "^1.2.0",