@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.
@@ -3912,11 +3912,25 @@ class Transaction extends Protocol {
3912
3912
  return Number(nonce);
3913
3913
  }
3914
3914
  async validateNonce(address, nonce) {
3915
- const previousNonce = await this.getNonce(address);
3916
- if (previousNonce > nonce)
3917
- throw new Error(`a transaction with a higher nonce already exists`);
3918
- if (previousNonce === nonce)
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
- // CRITICAL FIX: Use semver comparison (major.minor) not exact match
5618
- const isVersionCompatible = () => {
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
- // CRITICAL FIX: Check version compatibility using semver
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 === this.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 === this.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
- const previousNonce = await this.getNonce(address);
158
- if (previousNonce > nonce)
159
- throw new Error(`a transaction with a higher nonce already exists`);
160
- if (previousNonce === nonce)
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
- // CRITICAL FIX: Use semver comparison (major.minor) not exact match
1720
- const isVersionCompatible = () => {
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
- // CRITICAL FIX: Check version compatibility using semver
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 === this.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 === this.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.2",
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.55",
65
- "@leofcoin/contracts": "^0.1.16",
64
+ "@leofcoin/addresses": "^1.0.56",
65
+ "@leofcoin/contracts": "^0.1.17",
66
66
  "@leofcoin/crypto": "^0.2.37",
67
- "@leofcoin/errors": "^1.0.25",
68
- "@leofcoin/lib": "^1.2.74",
69
- "@leofcoin/messages": "^1.4.40",
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.25",
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.40",
75
- "@leofcoin/workers": "^1.5.27",
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"