@leofcoin/chain 1.9.2 → 1.9.3
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 +61 -24
- package/exports/chain.js +61 -24
- package/package.json +9 -9
package/exports/browser/chain.js
CHANGED
|
@@ -3912,11 +3912,25 @@ class Transaction extends Protocol {
|
|
|
3912
3912
|
return Number(nonce);
|
|
3913
3913
|
}
|
|
3914
3914
|
async validateNonce(address, nonce) {
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3915
|
+
// Compare only against the COMMITTED nonce (accountsStore), not the pool max.
|
|
3916
|
+
// The pool may hold many future nonces from batch sends — rejecting lower nonces
|
|
3917
|
+
// because a higher one is already queued would break concurrent batch submission.
|
|
3918
|
+
let committedNonce;
|
|
3919
|
+
try {
|
|
3920
|
+
if (await globalThis.accountsStore.has(address)) {
|
|
3921
|
+
const raw = await globalThis.accountsStore.get(address);
|
|
3922
|
+
committedNonce = Number(new TextDecoder().decode(raw));
|
|
3923
|
+
}
|
|
3924
|
+
else {
|
|
3925
|
+
committedNonce = await this.#getNonceFallback(address);
|
|
3926
|
+
}
|
|
3927
|
+
}
|
|
3928
|
+
catch {
|
|
3929
|
+
committedNonce = 0;
|
|
3930
|
+
}
|
|
3931
|
+
if (committedNonce >= nonce)
|
|
3919
3932
|
throw new Error(`a transaction with the same nonce already exists`);
|
|
3933
|
+
// Only reject exact duplicates already in the pool (not "higher nonce" rejections)
|
|
3920
3934
|
let transactions = await globalThis.transactionPoolStore.values();
|
|
3921
3935
|
transactions = await this.promiseTransactions(transactions);
|
|
3922
3936
|
transactions = transactions.filter((tx) => tx.decoded.from === address);
|
|
@@ -5607,6 +5621,8 @@ class State extends Contract {
|
|
|
5607
5621
|
}
|
|
5608
5622
|
async #getLatestBlock() {
|
|
5609
5623
|
let promises = [];
|
|
5624
|
+
const connectedPeers = Object.values(globalThis.peernet.connections || {}).filter((peer) => peer.connected);
|
|
5625
|
+
let compatiblePeerCount = 0;
|
|
5610
5626
|
let data = await new globalThis.peernet.protos['peernet-request']({
|
|
5611
5627
|
request: 'lastBlock'
|
|
5612
5628
|
});
|
|
@@ -5614,15 +5630,8 @@ class State extends Contract {
|
|
|
5614
5630
|
for (const id in globalThis.peernet.connections) {
|
|
5615
5631
|
// @ts-ignore
|
|
5616
5632
|
const peer = globalThis.peernet.connections[id];
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
if (!peer.version || !this.version)
|
|
5620
|
-
return false;
|
|
5621
|
-
const [peerMajor, peerMinor] = peer.version.split('.');
|
|
5622
|
-
const [localMajor, localMinor] = this.version.split('.');
|
|
5623
|
-
return peerMajor === localMajor && peerMinor === localMinor;
|
|
5624
|
-
};
|
|
5625
|
-
if (peer.connected && isVersionCompatible()) {
|
|
5633
|
+
if (peer.connected && this.isVersionCompatible(peer.version)) {
|
|
5634
|
+
compatiblePeerCount += 1;
|
|
5626
5635
|
const task = async () => {
|
|
5627
5636
|
try {
|
|
5628
5637
|
const result = await peer.request(node.encoded);
|
|
@@ -5639,9 +5648,15 @@ class State extends Contract {
|
|
|
5639
5648
|
promises.push(task());
|
|
5640
5649
|
}
|
|
5641
5650
|
}
|
|
5651
|
+
if (connectedPeers.length > 0 && compatiblePeerCount === 0) {
|
|
5652
|
+
throw new ResolveError(`latestBlock: no compatible peers found for local version ${this.version} among ${connectedPeers.length} connected peers`);
|
|
5653
|
+
}
|
|
5642
5654
|
// @ts-ignore
|
|
5643
5655
|
console.log({ promises });
|
|
5644
5656
|
promises = (await this.promiseRequests(promises));
|
|
5657
|
+
if (compatiblePeerCount > 0 && promises.length === 0) {
|
|
5658
|
+
throw new ResolveError('latestBlock: no responses from compatible peers');
|
|
5659
|
+
}
|
|
5645
5660
|
console.log({ promises });
|
|
5646
5661
|
let latest = { index: 0, hash: '0x0', previousHash: '0x0' };
|
|
5647
5662
|
promises = promises.sort((a, b) => b.index - a.index);
|
|
@@ -5656,15 +5671,7 @@ class State extends Contract {
|
|
|
5656
5671
|
throw new Error('invalid block @getLatestBlock');
|
|
5657
5672
|
latest = { ...message.decoded, hash };
|
|
5658
5673
|
const peer = promises[0].peer;
|
|
5659
|
-
|
|
5660
|
-
const isVersionCompatible = () => {
|
|
5661
|
-
if (!peer.version || !this.version)
|
|
5662
|
-
return false;
|
|
5663
|
-
const [peerMajor, peerMinor] = peer.version.split('.');
|
|
5664
|
-
const [localMajor, localMinor] = this.version.split('.');
|
|
5665
|
-
return peerMajor === localMajor && peerMinor === localMinor;
|
|
5666
|
-
};
|
|
5667
|
-
if (peer.connected && isVersionCompatible()) {
|
|
5674
|
+
if (peer.connected && this.isVersionCompatible(peer.version)) {
|
|
5668
5675
|
let data = await new globalThis.peernet.protos['peernet-request']({
|
|
5669
5676
|
request: 'knownBlocks'
|
|
5670
5677
|
});
|
|
@@ -5812,7 +5819,7 @@ class State extends Contract {
|
|
|
5812
5819
|
if (this.#chainSyncing)
|
|
5813
5820
|
return false;
|
|
5814
5821
|
// Check if we have any connected peers with the same version
|
|
5815
|
-
const compatiblePeers = Object.values(globalThis.peernet.connections || {}).filter((peer) => peer.connected && peer.version
|
|
5822
|
+
const compatiblePeers = Object.values(globalThis.peernet.connections || {}).filter((peer) => peer.connected && this.isVersionCompatible(peer.version));
|
|
5816
5823
|
if (compatiblePeers.length === 0) {
|
|
5817
5824
|
debug$1('No compatible peers available for sync');
|
|
5818
5825
|
return false;
|
|
@@ -5828,7 +5835,7 @@ class State extends Contract {
|
|
|
5828
5835
|
async #waitForPeers(timeoutMs = 30000) {
|
|
5829
5836
|
return new Promise((resolve) => {
|
|
5830
5837
|
const checkPeers = () => {
|
|
5831
|
-
const peers = Object.values(globalThis.peernet.connections || {}).filter((peer) => peer.connected && peer.version
|
|
5838
|
+
const peers = Object.values(globalThis.peernet.connections || {}).filter((peer) => peer.connected && this.isVersionCompatible(peer.version));
|
|
5832
5839
|
if (peers.length > 0) {
|
|
5833
5840
|
resolve(true);
|
|
5834
5841
|
}
|
|
@@ -6862,6 +6869,36 @@ class Chain extends VersionControl {
|
|
|
6862
6869
|
async #peerConnected(peerId) {
|
|
6863
6870
|
debug(`peer connected: ${peerId}`);
|
|
6864
6871
|
const peer = peernet.getConnection(peerId);
|
|
6872
|
+
if (!peer) {
|
|
6873
|
+
debug(`peer not found: ${peerId}`);
|
|
6874
|
+
return;
|
|
6875
|
+
}
|
|
6876
|
+
if (!peer.version) {
|
|
6877
|
+
try {
|
|
6878
|
+
let versionResponse = await this.#makeRequest(peer, 'version');
|
|
6879
|
+
if (versionResponse instanceof Uint8Array) {
|
|
6880
|
+
const decoded = new TextDecoder().decode(versionResponse);
|
|
6881
|
+
try {
|
|
6882
|
+
versionResponse = JSON.parse(decoded);
|
|
6883
|
+
}
|
|
6884
|
+
catch {
|
|
6885
|
+
versionResponse = decoded;
|
|
6886
|
+
}
|
|
6887
|
+
}
|
|
6888
|
+
if (typeof versionResponse === 'string') {
|
|
6889
|
+
peer.version = versionResponse;
|
|
6890
|
+
}
|
|
6891
|
+
else if (versionResponse &&
|
|
6892
|
+
typeof versionResponse === 'object' &&
|
|
6893
|
+
typeof versionResponse.version === 'string') {
|
|
6894
|
+
peer.version = versionResponse.version;
|
|
6895
|
+
}
|
|
6896
|
+
}
|
|
6897
|
+
catch (error) {
|
|
6898
|
+
debug(`failed to request version from peer ${peerId}:`, error?.message ?? error);
|
|
6899
|
+
return;
|
|
6900
|
+
}
|
|
6901
|
+
}
|
|
6865
6902
|
debug(`peer connected with version ${peer.version}`);
|
|
6866
6903
|
if (!this.isVersionCompatible(peer.version)) {
|
|
6867
6904
|
debug(`versions don't match`);
|
package/exports/chain.js
CHANGED
|
@@ -154,11 +154,25 @@ class Transaction extends Protocol {
|
|
|
154
154
|
return Number(nonce);
|
|
155
155
|
}
|
|
156
156
|
async validateNonce(address, nonce) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
157
|
+
// Compare only against the COMMITTED nonce (accountsStore), not the pool max.
|
|
158
|
+
// The pool may hold many future nonces from batch sends — rejecting lower nonces
|
|
159
|
+
// because a higher one is already queued would break concurrent batch submission.
|
|
160
|
+
let committedNonce;
|
|
161
|
+
try {
|
|
162
|
+
if (await globalThis.accountsStore.has(address)) {
|
|
163
|
+
const raw = await globalThis.accountsStore.get(address);
|
|
164
|
+
committedNonce = Number(new TextDecoder().decode(raw));
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
committedNonce = await this.#getNonceFallback(address);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
committedNonce = 0;
|
|
172
|
+
}
|
|
173
|
+
if (committedNonce >= nonce)
|
|
161
174
|
throw new Error(`a transaction with the same nonce already exists`);
|
|
175
|
+
// Only reject exact duplicates already in the pool (not "higher nonce" rejections)
|
|
162
176
|
let transactions = await globalThis.transactionPoolStore.values();
|
|
163
177
|
transactions = await this.promiseTransactions(transactions);
|
|
164
178
|
transactions = transactions.filter((tx) => tx.decoded.from === address);
|
|
@@ -1709,6 +1723,8 @@ class State extends Contract {
|
|
|
1709
1723
|
}
|
|
1710
1724
|
async #getLatestBlock() {
|
|
1711
1725
|
let promises = [];
|
|
1726
|
+
const connectedPeers = Object.values(globalThis.peernet.connections || {}).filter((peer) => peer.connected);
|
|
1727
|
+
let compatiblePeerCount = 0;
|
|
1712
1728
|
let data = await new globalThis.peernet.protos['peernet-request']({
|
|
1713
1729
|
request: 'lastBlock'
|
|
1714
1730
|
});
|
|
@@ -1716,15 +1732,8 @@ class State extends Contract {
|
|
|
1716
1732
|
for (const id in globalThis.peernet.connections) {
|
|
1717
1733
|
// @ts-ignore
|
|
1718
1734
|
const peer = globalThis.peernet.connections[id];
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
if (!peer.version || !this.version)
|
|
1722
|
-
return false;
|
|
1723
|
-
const [peerMajor, peerMinor] = peer.version.split('.');
|
|
1724
|
-
const [localMajor, localMinor] = this.version.split('.');
|
|
1725
|
-
return peerMajor === localMajor && peerMinor === localMinor;
|
|
1726
|
-
};
|
|
1727
|
-
if (peer.connected && isVersionCompatible()) {
|
|
1735
|
+
if (peer.connected && this.isVersionCompatible(peer.version)) {
|
|
1736
|
+
compatiblePeerCount += 1;
|
|
1728
1737
|
const task = async () => {
|
|
1729
1738
|
try {
|
|
1730
1739
|
const result = await peer.request(node.encoded);
|
|
@@ -1741,9 +1750,15 @@ class State extends Contract {
|
|
|
1741
1750
|
promises.push(task());
|
|
1742
1751
|
}
|
|
1743
1752
|
}
|
|
1753
|
+
if (connectedPeers.length > 0 && compatiblePeerCount === 0) {
|
|
1754
|
+
throw new ResolveError(`latestBlock: no compatible peers found for local version ${this.version} among ${connectedPeers.length} connected peers`);
|
|
1755
|
+
}
|
|
1744
1756
|
// @ts-ignore
|
|
1745
1757
|
console.log({ promises });
|
|
1746
1758
|
promises = (await this.promiseRequests(promises));
|
|
1759
|
+
if (compatiblePeerCount > 0 && promises.length === 0) {
|
|
1760
|
+
throw new ResolveError('latestBlock: no responses from compatible peers');
|
|
1761
|
+
}
|
|
1747
1762
|
console.log({ promises });
|
|
1748
1763
|
let latest = { index: 0, hash: '0x0', previousHash: '0x0' };
|
|
1749
1764
|
promises = promises.sort((a, b) => b.index - a.index);
|
|
@@ -1758,15 +1773,7 @@ class State extends Contract {
|
|
|
1758
1773
|
throw new Error('invalid block @getLatestBlock');
|
|
1759
1774
|
latest = { ...message.decoded, hash };
|
|
1760
1775
|
const peer = promises[0].peer;
|
|
1761
|
-
|
|
1762
|
-
const isVersionCompatible = () => {
|
|
1763
|
-
if (!peer.version || !this.version)
|
|
1764
|
-
return false;
|
|
1765
|
-
const [peerMajor, peerMinor] = peer.version.split('.');
|
|
1766
|
-
const [localMajor, localMinor] = this.version.split('.');
|
|
1767
|
-
return peerMajor === localMajor && peerMinor === localMinor;
|
|
1768
|
-
};
|
|
1769
|
-
if (peer.connected && isVersionCompatible()) {
|
|
1776
|
+
if (peer.connected && this.isVersionCompatible(peer.version)) {
|
|
1770
1777
|
let data = await new globalThis.peernet.protos['peernet-request']({
|
|
1771
1778
|
request: 'knownBlocks'
|
|
1772
1779
|
});
|
|
@@ -1914,7 +1921,7 @@ class State extends Contract {
|
|
|
1914
1921
|
if (this.#chainSyncing)
|
|
1915
1922
|
return false;
|
|
1916
1923
|
// Check if we have any connected peers with the same version
|
|
1917
|
-
const compatiblePeers = Object.values(globalThis.peernet.connections || {}).filter((peer) => peer.connected && peer.version
|
|
1924
|
+
const compatiblePeers = Object.values(globalThis.peernet.connections || {}).filter((peer) => peer.connected && this.isVersionCompatible(peer.version));
|
|
1918
1925
|
if (compatiblePeers.length === 0) {
|
|
1919
1926
|
debug$1('No compatible peers available for sync');
|
|
1920
1927
|
return false;
|
|
@@ -1930,7 +1937,7 @@ class State extends Contract {
|
|
|
1930
1937
|
async #waitForPeers(timeoutMs = 30000) {
|
|
1931
1938
|
return new Promise((resolve) => {
|
|
1932
1939
|
const checkPeers = () => {
|
|
1933
|
-
const peers = Object.values(globalThis.peernet.connections || {}).filter((peer) => peer.connected && peer.version
|
|
1940
|
+
const peers = Object.values(globalThis.peernet.connections || {}).filter((peer) => peer.connected && this.isVersionCompatible(peer.version));
|
|
1934
1941
|
if (peers.length > 0) {
|
|
1935
1942
|
resolve(true);
|
|
1936
1943
|
}
|
|
@@ -2964,6 +2971,36 @@ class Chain extends VersionControl {
|
|
|
2964
2971
|
async #peerConnected(peerId) {
|
|
2965
2972
|
debug(`peer connected: ${peerId}`);
|
|
2966
2973
|
const peer = peernet.getConnection(peerId);
|
|
2974
|
+
if (!peer) {
|
|
2975
|
+
debug(`peer not found: ${peerId}`);
|
|
2976
|
+
return;
|
|
2977
|
+
}
|
|
2978
|
+
if (!peer.version) {
|
|
2979
|
+
try {
|
|
2980
|
+
let versionResponse = await this.#makeRequest(peer, 'version');
|
|
2981
|
+
if (versionResponse instanceof Uint8Array) {
|
|
2982
|
+
const decoded = new TextDecoder().decode(versionResponse);
|
|
2983
|
+
try {
|
|
2984
|
+
versionResponse = JSON.parse(decoded);
|
|
2985
|
+
}
|
|
2986
|
+
catch {
|
|
2987
|
+
versionResponse = decoded;
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
if (typeof versionResponse === 'string') {
|
|
2991
|
+
peer.version = versionResponse;
|
|
2992
|
+
}
|
|
2993
|
+
else if (versionResponse &&
|
|
2994
|
+
typeof versionResponse === 'object' &&
|
|
2995
|
+
typeof versionResponse.version === 'string') {
|
|
2996
|
+
peer.version = versionResponse.version;
|
|
2997
|
+
}
|
|
2998
|
+
}
|
|
2999
|
+
catch (error) {
|
|
3000
|
+
debug(`failed to request version from peer ${peerId}:`, error?.message ?? error);
|
|
3001
|
+
return;
|
|
3002
|
+
}
|
|
3003
|
+
}
|
|
2967
3004
|
debug(`peer connected with version ${peer.version}`);
|
|
2968
3005
|
if (!this.isVersionCompatible(peer.version)) {
|
|
2969
3006
|
debug(`versions don't match`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leofcoin/chain",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.3",
|
|
4
4
|
"description": "Official javascript implementation",
|
|
5
5
|
"private": false,
|
|
6
6
|
"exports": {
|
|
@@ -61,18 +61,18 @@
|
|
|
61
61
|
"tslib": "^2.8.1"
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
|
-
"@leofcoin/addresses": "^1.0.
|
|
65
|
-
"@leofcoin/contracts": "^0.1.
|
|
64
|
+
"@leofcoin/addresses": "^1.0.56",
|
|
65
|
+
"@leofcoin/contracts": "^0.1.17",
|
|
66
66
|
"@leofcoin/crypto": "^0.2.37",
|
|
67
|
-
"@leofcoin/errors": "^1.0.
|
|
68
|
-
"@leofcoin/lib": "^1.2.
|
|
69
|
-
"@leofcoin/messages": "^1.4.
|
|
67
|
+
"@leofcoin/errors": "^1.0.26",
|
|
68
|
+
"@leofcoin/lib": "^1.2.75",
|
|
69
|
+
"@leofcoin/messages": "^1.4.41",
|
|
70
70
|
"@leofcoin/multi-wallet": "^3.1.8",
|
|
71
|
-
"@leofcoin/networks": "^1.1.
|
|
71
|
+
"@leofcoin/networks": "^1.1.26",
|
|
72
72
|
"@leofcoin/peernet": "^1.2.17",
|
|
73
73
|
"@leofcoin/storage": "^3.5.38",
|
|
74
|
-
"@leofcoin/utils": "^1.1.
|
|
75
|
-
"@leofcoin/workers": "^1.5.
|
|
74
|
+
"@leofcoin/utils": "^1.1.41",
|
|
75
|
+
"@leofcoin/workers": "^1.5.28",
|
|
76
76
|
"@vandeurenglenn/base58": "^1.1.9",
|
|
77
77
|
"@vandeurenglenn/easy-worker": "^1.0.3",
|
|
78
78
|
"semver": "^7.7.4"
|