@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 +17 -0
- package/exports/browser/chain.js +43 -40
- package/exports/chain.js +43 -40
- package/package.json +6 -6
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>.
|
package/exports/browser/chain.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
6091
|
-
transactionsInPool = JSON.parse(transactionsInPool);
|
|
6092
|
-
} catch {
|
|
6093
|
-
return [];
|
|
6094
|
-
}
|
|
6084
|
+
transactionsInPool = JSON.parse(transactionsInPool);
|
|
6095
6085
|
}
|
|
6096
|
-
if (!Array.isArray(transactionsInPool))
|
|
6097
|
-
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
|
|
6101
|
-
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
|
|
6107
|
-
|
|
6108
|
-
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
2974
|
-
transactionsInPool = JSON.parse(transactionsInPool);
|
|
2975
|
-
} catch {
|
|
2976
|
-
return [];
|
|
2977
|
-
}
|
|
2967
|
+
transactionsInPool = JSON.parse(transactionsInPool);
|
|
2978
2968
|
}
|
|
2979
|
-
if (!Array.isArray(transactionsInPool))
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
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
|
|
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 (
|
|
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.
|
|
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.
|
|
84
|
+
"@rollup/plugin-typescript": "^12.3.1",
|
|
85
85
|
"@types/semver": "^7.8.0",
|
|
86
86
|
"@vandeurenglenn/debug": "^1.4.0",
|
|
87
|
-
"rollup": "^4.
|
|
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.
|
|
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.
|
|
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.
|
|
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",
|