@leofcoin/chain 1.7.150 → 1.7.151

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.
@@ -4128,7 +4128,6 @@ class ExecutionError extends LeofcoinError {
4128
4128
  class ContractDeploymentError extends LeofcoinError {
4129
4129
  name = 'ContractDeploymentError';
4130
4130
  }
4131
- const isResolveError = (error) => error.name === 'ResolveError';
4132
4131
  const isExecutionError = (error) => error.name === 'ExecutionError';
4133
4132
 
4134
4133
  // import State from './state'
@@ -4599,77 +4598,32 @@ class Machine {
4599
4598
  }
4600
4599
  }
4601
4600
 
4602
- class Jobber {
4603
- constructor(timeout) {
4604
- this.busy = false;
4605
- this.timeout = timeout;
4606
- }
4607
- add(fn) {
4608
- this.busy = true;
4609
- return new Promise(async (resolve, reject) => {
4610
- const timeout = setTimeout(() => {
4611
- reject('timeout');
4612
- }, this.timeout);
4613
- this.destroy = () => {
4614
- clearTimeout(timeout);
4615
- this.busy = false;
4616
- resolve('stopped');
4617
- };
4618
- try {
4619
- const result = await fn();
4620
- clearTimeout(timeout);
4621
- this.busy = false;
4622
- resolve(result);
4623
- }
4624
- catch (error) {
4625
- clearTimeout(timeout);
4626
- reject(error);
4627
- }
4628
- });
4629
- }
4630
- }
4631
-
4632
4601
  const debug$1 = createDebugger('leofcoin/state');
4633
4602
  class State extends Contract {
4634
- #resolveErrored;
4635
- #lastResolvedTime;
4636
- #lastResolved;
4637
- #resolving;
4638
- #resolveErrorCount;
4639
- #syncState;
4640
- #chainState;
4641
- #lastBlockInQue;
4642
- #syncErrorCount;
4643
4603
  #blockHashMap;
4644
- #chainSyncing;
4645
4604
  #blocks;
4646
- #totalSize;
4647
4605
  #machine;
4648
4606
  #loaded;
4607
+ // Sync state
4608
+ #syncing;
4609
+ #syncErrorCount;
4610
+ // Block resolution state
4649
4611
  #resolvingBlocks;
4650
4612
  #maxConcurrentResolves;
4651
- #blockResolveQueue;
4613
+ #totalSize;
4652
4614
  /**
4653
4615
  * contains transactions we need before we can successfully load
4654
4616
  */
4655
4617
  get wantList() {
4656
4618
  return this.#machine?.wantList ?? this._wantList;
4657
4619
  }
4658
- get state() {
4659
- return {
4660
- sync: this.#syncState,
4661
- chain: this.#chainState
4662
- };
4663
- }
4664
- get blockHashMap() {
4665
- return this.#blockHashMap.entries();
4666
- }
4667
4620
  get loaded() {
4668
4621
  return this.#loaded;
4669
4622
  }
4670
- get resolving() {
4671
- return this.#resolving;
4623
+ get isSyncing() {
4624
+ return this.#syncing;
4672
4625
  }
4626
+ // Delegate to machine
4673
4627
  get contracts() {
4674
4628
  return this.#machine.contracts;
4675
4629
  }
@@ -4712,38 +4666,38 @@ class State extends Contract {
4712
4666
  get lastBlockHeight() {
4713
4667
  return this.#machine ? this.#machine.lastBlockHeight : 0;
4714
4668
  }
4715
- getBlock(index) {
4716
- return this.#machine.getBlock(index);
4717
- }
4718
- getBlocks(from, to) {
4719
- return this.#machine.getBlocks(from, to);
4720
- }
4721
4669
  get totalSize() {
4722
4670
  return this.#totalSize;
4723
4671
  }
4724
4672
  get machine() {
4725
4673
  return this.#machine;
4726
4674
  }
4675
+ get blockHashMap() {
4676
+ return this.#blockHashMap.entries();
4677
+ }
4678
+ getBlock(index) {
4679
+ return this.#machine.getBlock(index);
4680
+ }
4681
+ getBlocks(from, to) {
4682
+ return this.#machine.getBlocks(from, to);
4683
+ }
4727
4684
  constructor(config) {
4728
4685
  super(config);
4729
- this.#lastResolvedTime = 0;
4730
- this.#resolving = false;
4731
- this.#resolveErrorCount = 0;
4732
- this.#chainState = 'loading';
4733
- this.#syncErrorCount = 0;
4734
4686
  this.#blockHashMap = new Map();
4735
- this.#chainSyncing = false;
4736
4687
  this.#blocks = [];
4737
- this.knownBlocks = [];
4738
- this.#totalSize = 0;
4739
4688
  this.#loaded = false;
4740
- this._wantList = [];
4689
+ // Sync state
4690
+ this.#syncing = false;
4691
+ this.#syncErrorCount = 0;
4692
+ // Block resolution state
4741
4693
  this.#resolvingBlocks = new Set();
4742
4694
  this.#maxConcurrentResolves = 10;
4743
- this.#blockResolveQueue = [];
4695
+ this.knownBlocks = [];
4696
+ this.#totalSize = 0;
4697
+ this._wantList = [];
4744
4698
  this.#chainStateHandler = () => {
4745
4699
  return new globalThis.peernet.protos['peernet-response']({
4746
- response: this.#chainState
4700
+ response: { syncing: this.#syncing, loaded: this.#loaded }
4747
4701
  });
4748
4702
  };
4749
4703
  this.#lastBlockHandler = async () => {
@@ -4785,67 +4739,43 @@ class State extends Contract {
4785
4739
  #lastBlockHandler;
4786
4740
  #knownBlocksHandler;
4787
4741
  async init() {
4788
- this.jobber = new Jobber(this.resolveTimeout);
4789
- await globalThis.peernet.addRequestHandler('lastBlock', this.#lastBlockHandler);
4790
- await globalThis.peernet.addRequestHandler('knownBlocks', this.#knownBlocksHandler);
4791
- await globalThis.peernet.addRequestHandler('chainState', this.#chainStateHandler);
4792
- let localBlockHash;
4793
- let blockMessage;
4794
- let localBlock;
4742
+ // Register request handlers
4743
+ await globalThis.peernet.addRequestHandler('lastBlock', this.#lastBlockHandler.bind(this));
4744
+ await globalThis.peernet.addRequestHandler('knownBlocks', this.#knownBlocksHandler.bind(this));
4745
+ await globalThis.peernet.addRequestHandler('chainState', this.#chainStateHandler.bind(this));
4795
4746
  try {
4796
- const rawBlock = await globalThis.chainStore.has('lastBlock');
4797
- if (rawBlock) {
4798
- localBlockHash = new TextDecoder().decode(await globalThis.chainStore.get('lastBlock'));
4799
- if (localBlockHash !== '0x0') {
4800
- blockMessage = await globalThis.peernet.get(localBlockHash, 'block');
4801
- blockMessage = await new BlockMessage(blockMessage);
4747
+ // Load local block state
4748
+ let localBlock = { index: 0, hash: '0x0', previousHash: '0x0' };
4749
+ try {
4750
+ const localBlockHash = new TextDecoder().decode(await globalThis.chainStore.get('lastBlock'));
4751
+ if (localBlockHash && localBlockHash !== '0x0') {
4752
+ const blockMessage = await new BlockMessage(await globalThis.peernet.get(localBlockHash, 'block'));
4802
4753
  localBlock = { ...blockMessage.decoded, hash: localBlockHash };
4803
4754
  }
4804
4755
  }
4805
- else {
4806
- localBlock = { index: 0, hash: '0x0', previousHash: '0x0' };
4756
+ catch (error) {
4757
+ debug$1('No local block found');
4807
4758
  }
4808
- }
4809
- catch {
4810
- localBlock = { index: 0, hash: '0x0', previousHash: '0x0' };
4811
- }
4812
- try {
4813
- this.knownBlocks = await blockStore.keys();
4814
- }
4815
- catch (error) {
4816
- debug$1('no local known blocks found');
4817
- }
4818
- try {
4819
- if (localBlock.hash && localBlock.hash !== '0x0') {
4820
- try {
4821
- const states = {
4822
- lastBlock: JSON.parse(new TextDecoder().decode(await globalThis.stateStore.get('lastBlock')))
4823
- };
4824
- if (blockMessage.decoded.index > states.lastBlock.index)
4825
- await this.resolveBlocks();
4826
- }
4827
- catch (error) {
4828
- // no states found, try resolving blocks
4829
- await this.resolveBlocks();
4830
- }
4759
+ // Load known blocks
4760
+ try {
4761
+ this.knownBlocks = await globalThis.blockStore.keys();
4831
4762
  }
4832
- else {
4833
- await this.resolveBlocks();
4763
+ catch (error) {
4764
+ debug$1('No known blocks found');
4834
4765
  }
4766
+ // Initialize machine and resolve blocks if needed
4835
4767
  this.#machine = await new Machine(this.#blocks);
4768
+ if (localBlock.hash !== '0x0') {
4769
+ await this.resolveBlock(localBlock.hash);
4770
+ }
4836
4771
  const lastBlock = await this.#machine.lastBlock;
4837
4772
  if (lastBlock.hash !== '0x0') {
4838
- this.updateState(new BlockMessage(lastBlock));
4773
+ await this.updateState(new BlockMessage(lastBlock));
4839
4774
  }
4840
4775
  this.#loaded = true;
4841
- // await this.#loadBlocks(this.#blocks)
4842
4776
  }
4843
4777
  catch (error) {
4844
- console.log('e');
4845
- if (isResolveError(error)) {
4846
- console.error(error);
4847
- }
4848
- console.log(error);
4778
+ console.error('Failed to initialize state:', error);
4849
4779
  }
4850
4780
  }
4851
4781
  async updateState(message) {
@@ -4979,196 +4909,76 @@ class State extends Contract {
4979
4909
  await Promise.all(resolving);
4980
4910
  }
4981
4911
  async resolveBlock(hash) {
4982
- if (!hash)
4983
- throw new Error(`expected hash, got: ${hash}`);
4984
- if (hash === '0x0')
4912
+ if (!hash || hash === '0x0')
4985
4913
  return;
4986
- if (this.#resolving) {
4987
- debug$1('Already resolving, queueing block');
4914
+ if (this.#syncing)
4988
4915
  return;
4989
- }
4990
- this.#resolving = true;
4916
+ this.#syncing = true;
4917
+ this.#syncErrorCount = 0;
4991
4918
  try {
4992
- // Build the entire block chain from latest to genesis
4993
4919
  debug$1(`Building block chain from ${hash}`);
4994
4920
  const blockChain = await this.#buildBlockChain(hash);
4995
4921
  debug$1(`Built chain of ${blockChain.length} blocks`);
4996
- // Resolve all blocks in parallel
4997
4922
  if (blockChain.length > 0) {
4998
4923
  await this.#resolveBlocksInParallel(blockChain);
4999
4924
  }
5000
- this.#resolving = false;
5001
- this.#resolveErrorCount = 0;
5002
- this.#resolveErrored = false;
5003
- try {
5004
- const lastBlockHash = await globalThis.stateStore.get('lastBlock');
5005
- if (lastBlockHash === hash) {
5006
- debug$1('Resolved to latest block state');
5007
- return;
5008
- }
5009
- }
5010
- catch (error) {
5011
- debug$1('no local state found');
5012
- }
5013
4925
  }
5014
4926
  catch (error) {
5015
- console.log({ error });
5016
- this.#resolveErrorCount += 1;
5017
- this.#resolving = false;
5018
- if (this.#resolveErrorCount < 3) {
5019
- debug$1(`Retry ${this.#resolveErrorCount}/3 for block ${hash}`);
5020
- return this.resolveBlock(hash);
5021
- }
5022
- this.#resolveErrorCount = 0;
4927
+ console.error('Block resolution failed:', error);
5023
4928
  this.wantList.push(hash);
5024
- throw new ResolveError(`block: ${hash}`, { cause: error });
4929
+ }
4930
+ finally {
4931
+ this.#syncing = false;
5025
4932
  }
5026
4933
  }
5027
4934
  async resolveBlocks() {
5028
- // Don't re-resolve if already syncing or resolving
5029
- if (this.#chainSyncing || this.#resolving) {
5030
- debug$1('Already syncing or resolving, skipping resolveBlocks()');
4935
+ if (this.#syncing)
5031
4936
  return;
5032
- }
5033
- try {
5034
- if (this.jobber.busy && this.jobber.destroy) {
5035
- await this.jobber.destroy();
5036
- }
5037
- }
5038
- catch (error) {
5039
- console.error(error);
5040
- }
5041
4937
  try {
5042
4938
  const localBlock = await globalThis.chainStore.get('lastBlock');
5043
4939
  const hash = new TextDecoder().decode(localBlock);
5044
- if (hash && hash !== '0x0') {
5045
- debug$1(`Resolving blocks from hash: ${hash}`);
5046
- await this.resolveBlock(hash);
5047
- }
5048
- }
5049
- catch (error) {
5050
- console.log(error);
5051
- this.#chainSyncing = false;
5052
- this.#syncState = 'errored';
5053
- this.#resolveErrored = true;
5054
- return this.restoreChain();
5055
- // console.log(e);
5056
- }
5057
- }
5058
- async restoreChain() {
5059
- try {
5060
- const { hash } = await this.#getLatestBlock();
5061
- await globalThis.chainStore.put('lastBlock', hash);
5062
4940
  if (hash && hash !== '0x0') {
5063
4941
  await this.resolveBlock(hash);
5064
4942
  }
5065
4943
  }
5066
4944
  catch (error) {
5067
- console.log(error);
5068
- this.#resolveErrored = true;
5069
- this.#resolveErrorCount += 1;
5070
- this.#resolving = false;
5071
- return this.restoreChain();
5072
- // console.log(e);
4945
+ debug$1('Failed to resolve blocks:', error);
5073
4946
  }
5074
4947
  }
5075
4948
  async syncChain(lastBlock) {
5076
- console.log('check if can sync');
5077
- if (!this.shouldSync)
5078
- return;
5079
- console.log('starting sync');
5080
- this.#syncState = 'syncing';
5081
- this.#chainSyncing = true;
5082
- try {
5083
- if (this.jobber.busy && this.jobber.destroy) {
5084
- await this.jobber.destroy();
5085
- }
5086
- }
5087
- catch (error) {
5088
- console.error(error);
5089
- }
4949
+ if (this.#syncing)
4950
+ return 'syncing';
5090
4951
  if (!lastBlock)
5091
4952
  lastBlock = await this.#getLatestBlock();
5092
- if (globalThis.peernet.peers.length === 0)
5093
- return 'connectionless';
4953
+ this.#syncing = true;
5094
4954
  try {
5095
- await this.#syncChain(lastBlock);
5096
- }
5097
- catch (error) {
5098
- this.#syncErrorCount += 1;
5099
- if (this.#syncErrorCount < 3)
5100
- return this.syncChain(lastBlock);
5101
- this.#syncErrorCount = 0;
5102
- this.#chainSyncing = false;
5103
- this.#syncState = 'errored';
5104
- return this.#syncState;
5105
- }
5106
- if (lastBlock.index === this.#lastBlockInQue?.index)
5107
- this.#lastBlockInQue = undefined;
5108
- this.#syncErrorCount = 0;
5109
- this.#chainSyncing = false;
5110
- if (this.#lastBlockInQue)
5111
- return this.syncChain(this.#lastBlockInQue);
5112
- this.#syncState = 'synced';
5113
- return this.#syncState;
5114
- }
5115
- async #syncChain(lastBlock) {
5116
- try {
5117
- // if (this.knownBlocks?.length === Number(lastBlock.index) + 1) {
5118
- // let promises = []
5119
- // promises = await Promise.allSettled(
5120
- // this.knownBlocks.map(async (address) => {
5121
- // const has = await globalThis.peernet.has(address)
5122
- // return { has, address }
5123
- // })
5124
- // )
5125
- // promises = promises.filter(({ status, value }) => status === 'fulfilled' && !value.has)
5126
- // await Promise.allSettled(promises.map(({ value }) => this.getAndPutBlock(value.address)))
5127
- // }
5128
- const localBlock = await this.lastBlock;
5129
- const localIndex = localBlock ? Number(localBlock.index) : -1;
5130
- const remoteIndex = Number(lastBlock.index);
5131
- const remoteBlockHash = lastBlock.hash;
5132
- // Get the local state hash from chainStore
5133
- let localStateHash = '0x0';
5134
- try {
5135
- localStateHash = new TextDecoder().decode(await globalThis.chainStore.get('lastBlock'));
4955
+ if (globalThis.peernet.peers.length === 0) {
4956
+ this.#syncing = false;
4957
+ return 'connectionless';
5136
4958
  }
5137
- catch (error) {
5138
- debug$1(`No local state hash found: ${error}`);
4959
+ await this.resolveBlock(lastBlock.hash);
4960
+ const blocks = this.#blocks;
4961
+ const localIndex = (await this.lastBlock).index || -1;
4962
+ const start = Math.max(0, localIndex + 1);
4963
+ if (this.#machine && blocks.length > start) {
4964
+ await this.#loadBlocks(blocks.slice(start));
5139
4965
  }
5140
- debug$1(`Local block height: ${localIndex}, remote block height: ${remoteIndex}`);
5141
- debug$1(`Local state hash: ${localStateHash}, remote block hash: ${remoteBlockHash}`);
5142
- // Skip syncing if remote block hash is 0x0 (invalid state)
5143
- if (remoteBlockHash === '0x0') {
5144
- debug$1(`Remote block hash is 0x0, skipping sync`);
5145
- return;
5146
- }
5147
- // Use state hash comparison: only resolve if remote hash differs from local state hash
5148
- if (localStateHash !== remoteBlockHash) {
5149
- // Remote block hash differs from our local state, need to resolve
5150
- debug$1(`Resolving remote block: ${remoteBlockHash} @${remoteIndex} (differs from local state)`);
5151
- await this.resolveBlock(remoteBlockHash);
5152
- const blocksSynced = remoteIndex - localIndex;
5153
- debug$1(`Resolved ${blocksSynced} new block(s)`);
5154
- const blocks = this.#blocks;
5155
- debug$1(`Loading blocks from index ${localIndex + 1} to ${remoteIndex}`);
5156
- const start = localIndex + 1;
5157
- if (this.#machine && blocks.length > start) {
5158
- await this.#loadBlocks(blocks.slice(start));
5159
- }
5160
- // Update state with the latest block
5161
- if (blocks.length > 0) {
5162
- await this.updateState(new BlockMessage(blocks[blocks.length - 1]));
5163
- }
5164
- }
5165
- else {
5166
- debug$1(`Block already in local state. Remote hash: ${remoteBlockHash} matches local state`);
4966
+ if (blocks.length > 0) {
4967
+ await this.updateState(new BlockMessage(blocks[blocks.length - 1]));
5167
4968
  }
4969
+ this.#syncErrorCount = 0;
4970
+ this.#syncing = false;
4971
+ return 'synced';
5168
4972
  }
5169
4973
  catch (error) {
5170
- console.log(error);
5171
- throw error;
4974
+ this.#syncErrorCount++;
4975
+ if (this.#syncErrorCount < 3) {
4976
+ this.#syncing = false;
4977
+ return this.syncChain(lastBlock);
4978
+ }
4979
+ this.#syncErrorCount = 0;
4980
+ this.#syncing = false;
4981
+ return 'errored';
5172
4982
  }
5173
4983
  }
5174
4984
  async #getLatestBlock() {
@@ -5339,26 +5149,13 @@ class State extends Contract {
5339
5149
  });
5340
5150
  }
5341
5151
  get canSync() {
5342
- if (this.#chainSyncing)
5343
- return false;
5344
- return true;
5152
+ return !this.#syncing;
5345
5153
  }
5346
5154
  get shouldSync() {
5347
- if (this.#chainSyncing)
5155
+ if (this.#syncing)
5348
5156
  return false;
5349
- // Check if we have any connected peers with the same version
5350
5157
  const compatiblePeers = Object.values(globalThis.peernet.connections || {}).filter((peer) => peer.connected && peer.version === this.version);
5351
- if (compatiblePeers.length === 0) {
5352
- debug$1('No compatible peers available for sync');
5353
- return false;
5354
- }
5355
- if (!this.#chainSyncing ||
5356
- this.#resolveErrored ||
5357
- this.#syncState === 'errored' ||
5358
- this.#syncState === 'connectionless' ||
5359
- this.#lastResolvedTime + this.resolveTimeout > Date.now())
5360
- return true;
5361
- return false;
5158
+ return compatiblePeers.length > 0;
5362
5159
  }
5363
5160
  async #waitForPeers(timeoutMs = 30000) {
5364
5161
  return new Promise((resolve) => {
package/exports/chain.js CHANGED
@@ -6,7 +6,7 @@ import { calculateFee, createContractMessage, signTransaction, contractFactoryMe
6
6
  import semver from 'semver';
7
7
  import { randombytes } from '@leofcoin/crypto';
8
8
  import EasyWorker from '@vandeurenglenn/easy-worker';
9
- import { ContractDeploymentError, ExecutionError, isResolveError, ResolveError, isExecutionError } from '@leofcoin/errors';
9
+ import { ContractDeploymentError, ExecutionError, ResolveError, isExecutionError } from '@leofcoin/errors';
10
10
 
11
11
  const limit = 1800;
12
12
  const transactionLimit = 1000;
@@ -739,77 +739,32 @@ class Machine {
739
739
  }
740
740
  }
741
741
 
742
- class Jobber {
743
- constructor(timeout) {
744
- this.busy = false;
745
- this.timeout = timeout;
746
- }
747
- add(fn) {
748
- this.busy = true;
749
- return new Promise(async (resolve, reject) => {
750
- const timeout = setTimeout(() => {
751
- reject('timeout');
752
- }, this.timeout);
753
- this.destroy = () => {
754
- clearTimeout(timeout);
755
- this.busy = false;
756
- resolve('stopped');
757
- };
758
- try {
759
- const result = await fn();
760
- clearTimeout(timeout);
761
- this.busy = false;
762
- resolve(result);
763
- }
764
- catch (error) {
765
- clearTimeout(timeout);
766
- reject(error);
767
- }
768
- });
769
- }
770
- }
771
-
772
742
  const debug$1 = createDebugger('leofcoin/state');
773
743
  class State extends Contract {
774
- #resolveErrored;
775
- #lastResolvedTime;
776
- #lastResolved;
777
- #resolving;
778
- #resolveErrorCount;
779
- #syncState;
780
- #chainState;
781
- #lastBlockInQue;
782
- #syncErrorCount;
783
744
  #blockHashMap;
784
- #chainSyncing;
785
745
  #blocks;
786
- #totalSize;
787
746
  #machine;
788
747
  #loaded;
748
+ // Sync state
749
+ #syncing;
750
+ #syncErrorCount;
751
+ // Block resolution state
789
752
  #resolvingBlocks;
790
753
  #maxConcurrentResolves;
791
- #blockResolveQueue;
754
+ #totalSize;
792
755
  /**
793
756
  * contains transactions we need before we can successfully load
794
757
  */
795
758
  get wantList() {
796
759
  return this.#machine?.wantList ?? this._wantList;
797
760
  }
798
- get state() {
799
- return {
800
- sync: this.#syncState,
801
- chain: this.#chainState
802
- };
803
- }
804
- get blockHashMap() {
805
- return this.#blockHashMap.entries();
806
- }
807
761
  get loaded() {
808
762
  return this.#loaded;
809
763
  }
810
- get resolving() {
811
- return this.#resolving;
764
+ get isSyncing() {
765
+ return this.#syncing;
812
766
  }
767
+ // Delegate to machine
813
768
  get contracts() {
814
769
  return this.#machine.contracts;
815
770
  }
@@ -852,38 +807,38 @@ class State extends Contract {
852
807
  get lastBlockHeight() {
853
808
  return this.#machine ? this.#machine.lastBlockHeight : 0;
854
809
  }
855
- getBlock(index) {
856
- return this.#machine.getBlock(index);
857
- }
858
- getBlocks(from, to) {
859
- return this.#machine.getBlocks(from, to);
860
- }
861
810
  get totalSize() {
862
811
  return this.#totalSize;
863
812
  }
864
813
  get machine() {
865
814
  return this.#machine;
866
815
  }
816
+ get blockHashMap() {
817
+ return this.#blockHashMap.entries();
818
+ }
819
+ getBlock(index) {
820
+ return this.#machine.getBlock(index);
821
+ }
822
+ getBlocks(from, to) {
823
+ return this.#machine.getBlocks(from, to);
824
+ }
867
825
  constructor(config) {
868
826
  super(config);
869
- this.#lastResolvedTime = 0;
870
- this.#resolving = false;
871
- this.#resolveErrorCount = 0;
872
- this.#chainState = 'loading';
873
- this.#syncErrorCount = 0;
874
827
  this.#blockHashMap = new Map();
875
- this.#chainSyncing = false;
876
828
  this.#blocks = [];
877
- this.knownBlocks = [];
878
- this.#totalSize = 0;
879
829
  this.#loaded = false;
880
- this._wantList = [];
830
+ // Sync state
831
+ this.#syncing = false;
832
+ this.#syncErrorCount = 0;
833
+ // Block resolution state
881
834
  this.#resolvingBlocks = new Set();
882
835
  this.#maxConcurrentResolves = 10;
883
- this.#blockResolveQueue = [];
836
+ this.knownBlocks = [];
837
+ this.#totalSize = 0;
838
+ this._wantList = [];
884
839
  this.#chainStateHandler = () => {
885
840
  return new globalThis.peernet.protos['peernet-response']({
886
- response: this.#chainState
841
+ response: { syncing: this.#syncing, loaded: this.#loaded }
887
842
  });
888
843
  };
889
844
  this.#lastBlockHandler = async () => {
@@ -925,67 +880,43 @@ class State extends Contract {
925
880
  #lastBlockHandler;
926
881
  #knownBlocksHandler;
927
882
  async init() {
928
- this.jobber = new Jobber(this.resolveTimeout);
929
- await globalThis.peernet.addRequestHandler('lastBlock', this.#lastBlockHandler);
930
- await globalThis.peernet.addRequestHandler('knownBlocks', this.#knownBlocksHandler);
931
- await globalThis.peernet.addRequestHandler('chainState', this.#chainStateHandler);
932
- let localBlockHash;
933
- let blockMessage;
934
- let localBlock;
883
+ // Register request handlers
884
+ await globalThis.peernet.addRequestHandler('lastBlock', this.#lastBlockHandler.bind(this));
885
+ await globalThis.peernet.addRequestHandler('knownBlocks', this.#knownBlocksHandler.bind(this));
886
+ await globalThis.peernet.addRequestHandler('chainState', this.#chainStateHandler.bind(this));
935
887
  try {
936
- const rawBlock = await globalThis.chainStore.has('lastBlock');
937
- if (rawBlock) {
938
- localBlockHash = new TextDecoder().decode(await globalThis.chainStore.get('lastBlock'));
939
- if (localBlockHash !== '0x0') {
940
- blockMessage = await globalThis.peernet.get(localBlockHash, 'block');
941
- blockMessage = await new BlockMessage(blockMessage);
888
+ // Load local block state
889
+ let localBlock = { index: 0, hash: '0x0', previousHash: '0x0' };
890
+ try {
891
+ const localBlockHash = new TextDecoder().decode(await globalThis.chainStore.get('lastBlock'));
892
+ if (localBlockHash && localBlockHash !== '0x0') {
893
+ const blockMessage = await new BlockMessage(await globalThis.peernet.get(localBlockHash, 'block'));
942
894
  localBlock = { ...blockMessage.decoded, hash: localBlockHash };
943
895
  }
944
896
  }
945
- else {
946
- localBlock = { index: 0, hash: '0x0', previousHash: '0x0' };
897
+ catch (error) {
898
+ debug$1('No local block found');
947
899
  }
948
- }
949
- catch {
950
- localBlock = { index: 0, hash: '0x0', previousHash: '0x0' };
951
- }
952
- try {
953
- this.knownBlocks = await blockStore.keys();
954
- }
955
- catch (error) {
956
- debug$1('no local known blocks found');
957
- }
958
- try {
959
- if (localBlock.hash && localBlock.hash !== '0x0') {
960
- try {
961
- const states = {
962
- lastBlock: JSON.parse(new TextDecoder().decode(await globalThis.stateStore.get('lastBlock')))
963
- };
964
- if (blockMessage.decoded.index > states.lastBlock.index)
965
- await this.resolveBlocks();
966
- }
967
- catch (error) {
968
- // no states found, try resolving blocks
969
- await this.resolveBlocks();
970
- }
900
+ // Load known blocks
901
+ try {
902
+ this.knownBlocks = await globalThis.blockStore.keys();
971
903
  }
972
- else {
973
- await this.resolveBlocks();
904
+ catch (error) {
905
+ debug$1('No known blocks found');
974
906
  }
907
+ // Initialize machine and resolve blocks if needed
975
908
  this.#machine = await new Machine(this.#blocks);
909
+ if (localBlock.hash !== '0x0') {
910
+ await this.resolveBlock(localBlock.hash);
911
+ }
976
912
  const lastBlock = await this.#machine.lastBlock;
977
913
  if (lastBlock.hash !== '0x0') {
978
- this.updateState(new BlockMessage(lastBlock));
914
+ await this.updateState(new BlockMessage(lastBlock));
979
915
  }
980
916
  this.#loaded = true;
981
- // await this.#loadBlocks(this.#blocks)
982
917
  }
983
918
  catch (error) {
984
- console.log('e');
985
- if (isResolveError(error)) {
986
- console.error(error);
987
- }
988
- console.log(error);
919
+ console.error('Failed to initialize state:', error);
989
920
  }
990
921
  }
991
922
  async updateState(message) {
@@ -1119,196 +1050,76 @@ class State extends Contract {
1119
1050
  await Promise.all(resolving);
1120
1051
  }
1121
1052
  async resolveBlock(hash) {
1122
- if (!hash)
1123
- throw new Error(`expected hash, got: ${hash}`);
1124
- if (hash === '0x0')
1053
+ if (!hash || hash === '0x0')
1125
1054
  return;
1126
- if (this.#resolving) {
1127
- debug$1('Already resolving, queueing block');
1055
+ if (this.#syncing)
1128
1056
  return;
1129
- }
1130
- this.#resolving = true;
1057
+ this.#syncing = true;
1058
+ this.#syncErrorCount = 0;
1131
1059
  try {
1132
- // Build the entire block chain from latest to genesis
1133
1060
  debug$1(`Building block chain from ${hash}`);
1134
1061
  const blockChain = await this.#buildBlockChain(hash);
1135
1062
  debug$1(`Built chain of ${blockChain.length} blocks`);
1136
- // Resolve all blocks in parallel
1137
1063
  if (blockChain.length > 0) {
1138
1064
  await this.#resolveBlocksInParallel(blockChain);
1139
1065
  }
1140
- this.#resolving = false;
1141
- this.#resolveErrorCount = 0;
1142
- this.#resolveErrored = false;
1143
- try {
1144
- const lastBlockHash = await globalThis.stateStore.get('lastBlock');
1145
- if (lastBlockHash === hash) {
1146
- debug$1('Resolved to latest block state');
1147
- return;
1148
- }
1149
- }
1150
- catch (error) {
1151
- debug$1('no local state found');
1152
- }
1153
1066
  }
1154
1067
  catch (error) {
1155
- console.log({ error });
1156
- this.#resolveErrorCount += 1;
1157
- this.#resolving = false;
1158
- if (this.#resolveErrorCount < 3) {
1159
- debug$1(`Retry ${this.#resolveErrorCount}/3 for block ${hash}`);
1160
- return this.resolveBlock(hash);
1161
- }
1162
- this.#resolveErrorCount = 0;
1068
+ console.error('Block resolution failed:', error);
1163
1069
  this.wantList.push(hash);
1164
- throw new ResolveError(`block: ${hash}`, { cause: error });
1070
+ }
1071
+ finally {
1072
+ this.#syncing = false;
1165
1073
  }
1166
1074
  }
1167
1075
  async resolveBlocks() {
1168
- // Don't re-resolve if already syncing or resolving
1169
- if (this.#chainSyncing || this.#resolving) {
1170
- debug$1('Already syncing or resolving, skipping resolveBlocks()');
1076
+ if (this.#syncing)
1171
1077
  return;
1172
- }
1173
- try {
1174
- if (this.jobber.busy && this.jobber.destroy) {
1175
- await this.jobber.destroy();
1176
- }
1177
- }
1178
- catch (error) {
1179
- console.error(error);
1180
- }
1181
1078
  try {
1182
1079
  const localBlock = await globalThis.chainStore.get('lastBlock');
1183
1080
  const hash = new TextDecoder().decode(localBlock);
1184
- if (hash && hash !== '0x0') {
1185
- debug$1(`Resolving blocks from hash: ${hash}`);
1186
- await this.resolveBlock(hash);
1187
- }
1188
- }
1189
- catch (error) {
1190
- console.log(error);
1191
- this.#chainSyncing = false;
1192
- this.#syncState = 'errored';
1193
- this.#resolveErrored = true;
1194
- return this.restoreChain();
1195
- // console.log(e);
1196
- }
1197
- }
1198
- async restoreChain() {
1199
- try {
1200
- const { hash } = await this.#getLatestBlock();
1201
- await globalThis.chainStore.put('lastBlock', hash);
1202
1081
  if (hash && hash !== '0x0') {
1203
1082
  await this.resolveBlock(hash);
1204
1083
  }
1205
1084
  }
1206
1085
  catch (error) {
1207
- console.log(error);
1208
- this.#resolveErrored = true;
1209
- this.#resolveErrorCount += 1;
1210
- this.#resolving = false;
1211
- return this.restoreChain();
1212
- // console.log(e);
1086
+ debug$1('Failed to resolve blocks:', error);
1213
1087
  }
1214
1088
  }
1215
1089
  async syncChain(lastBlock) {
1216
- console.log('check if can sync');
1217
- if (!this.shouldSync)
1218
- return;
1219
- console.log('starting sync');
1220
- this.#syncState = 'syncing';
1221
- this.#chainSyncing = true;
1222
- try {
1223
- if (this.jobber.busy && this.jobber.destroy) {
1224
- await this.jobber.destroy();
1225
- }
1226
- }
1227
- catch (error) {
1228
- console.error(error);
1229
- }
1090
+ if (this.#syncing)
1091
+ return 'syncing';
1230
1092
  if (!lastBlock)
1231
1093
  lastBlock = await this.#getLatestBlock();
1232
- if (globalThis.peernet.peers.length === 0)
1233
- return 'connectionless';
1094
+ this.#syncing = true;
1234
1095
  try {
1235
- await this.#syncChain(lastBlock);
1236
- }
1237
- catch (error) {
1238
- this.#syncErrorCount += 1;
1239
- if (this.#syncErrorCount < 3)
1240
- return this.syncChain(lastBlock);
1241
- this.#syncErrorCount = 0;
1242
- this.#chainSyncing = false;
1243
- this.#syncState = 'errored';
1244
- return this.#syncState;
1245
- }
1246
- if (lastBlock.index === this.#lastBlockInQue?.index)
1247
- this.#lastBlockInQue = undefined;
1248
- this.#syncErrorCount = 0;
1249
- this.#chainSyncing = false;
1250
- if (this.#lastBlockInQue)
1251
- return this.syncChain(this.#lastBlockInQue);
1252
- this.#syncState = 'synced';
1253
- return this.#syncState;
1254
- }
1255
- async #syncChain(lastBlock) {
1256
- try {
1257
- // if (this.knownBlocks?.length === Number(lastBlock.index) + 1) {
1258
- // let promises = []
1259
- // promises = await Promise.allSettled(
1260
- // this.knownBlocks.map(async (address) => {
1261
- // const has = await globalThis.peernet.has(address)
1262
- // return { has, address }
1263
- // })
1264
- // )
1265
- // promises = promises.filter(({ status, value }) => status === 'fulfilled' && !value.has)
1266
- // await Promise.allSettled(promises.map(({ value }) => this.getAndPutBlock(value.address)))
1267
- // }
1268
- const localBlock = await this.lastBlock;
1269
- const localIndex = localBlock ? Number(localBlock.index) : -1;
1270
- const remoteIndex = Number(lastBlock.index);
1271
- const remoteBlockHash = lastBlock.hash;
1272
- // Get the local state hash from chainStore
1273
- let localStateHash = '0x0';
1274
- try {
1275
- localStateHash = new TextDecoder().decode(await globalThis.chainStore.get('lastBlock'));
1096
+ if (globalThis.peernet.peers.length === 0) {
1097
+ this.#syncing = false;
1098
+ return 'connectionless';
1276
1099
  }
1277
- catch (error) {
1278
- debug$1(`No local state hash found: ${error}`);
1100
+ await this.resolveBlock(lastBlock.hash);
1101
+ const blocks = this.#blocks;
1102
+ const localIndex = (await this.lastBlock).index || -1;
1103
+ const start = Math.max(0, localIndex + 1);
1104
+ if (this.#machine && blocks.length > start) {
1105
+ await this.#loadBlocks(blocks.slice(start));
1279
1106
  }
1280
- debug$1(`Local block height: ${localIndex}, remote block height: ${remoteIndex}`);
1281
- debug$1(`Local state hash: ${localStateHash}, remote block hash: ${remoteBlockHash}`);
1282
- // Skip syncing if remote block hash is 0x0 (invalid state)
1283
- if (remoteBlockHash === '0x0') {
1284
- debug$1(`Remote block hash is 0x0, skipping sync`);
1285
- return;
1286
- }
1287
- // Use state hash comparison: only resolve if remote hash differs from local state hash
1288
- if (localStateHash !== remoteBlockHash) {
1289
- // Remote block hash differs from our local state, need to resolve
1290
- debug$1(`Resolving remote block: ${remoteBlockHash} @${remoteIndex} (differs from local state)`);
1291
- await this.resolveBlock(remoteBlockHash);
1292
- const blocksSynced = remoteIndex - localIndex;
1293
- debug$1(`Resolved ${blocksSynced} new block(s)`);
1294
- const blocks = this.#blocks;
1295
- debug$1(`Loading blocks from index ${localIndex + 1} to ${remoteIndex}`);
1296
- const start = localIndex + 1;
1297
- if (this.#machine && blocks.length > start) {
1298
- await this.#loadBlocks(blocks.slice(start));
1299
- }
1300
- // Update state with the latest block
1301
- if (blocks.length > 0) {
1302
- await this.updateState(new BlockMessage(blocks[blocks.length - 1]));
1303
- }
1304
- }
1305
- else {
1306
- debug$1(`Block already in local state. Remote hash: ${remoteBlockHash} matches local state`);
1107
+ if (blocks.length > 0) {
1108
+ await this.updateState(new BlockMessage(blocks[blocks.length - 1]));
1307
1109
  }
1110
+ this.#syncErrorCount = 0;
1111
+ this.#syncing = false;
1112
+ return 'synced';
1308
1113
  }
1309
1114
  catch (error) {
1310
- console.log(error);
1311
- throw error;
1115
+ this.#syncErrorCount++;
1116
+ if (this.#syncErrorCount < 3) {
1117
+ this.#syncing = false;
1118
+ return this.syncChain(lastBlock);
1119
+ }
1120
+ this.#syncErrorCount = 0;
1121
+ this.#syncing = false;
1122
+ return 'errored';
1312
1123
  }
1313
1124
  }
1314
1125
  async #getLatestBlock() {
@@ -1479,26 +1290,13 @@ class State extends Contract {
1479
1290
  });
1480
1291
  }
1481
1292
  get canSync() {
1482
- if (this.#chainSyncing)
1483
- return false;
1484
- return true;
1293
+ return !this.#syncing;
1485
1294
  }
1486
1295
  get shouldSync() {
1487
- if (this.#chainSyncing)
1296
+ if (this.#syncing)
1488
1297
  return false;
1489
- // Check if we have any connected peers with the same version
1490
1298
  const compatiblePeers = Object.values(globalThis.peernet.connections || {}).filter((peer) => peer.connected && peer.version === this.version);
1491
- if (compatiblePeers.length === 0) {
1492
- debug$1('No compatible peers available for sync');
1493
- return false;
1494
- }
1495
- if (!this.#chainSyncing ||
1496
- this.#resolveErrored ||
1497
- this.#syncState === 'errored' ||
1498
- this.#syncState === 'connectionless' ||
1499
- this.#lastResolvedTime + this.resolveTimeout > Date.now())
1500
- return true;
1501
- return false;
1299
+ return compatiblePeers.length > 0;
1502
1300
  }
1503
1301
  async #waitForPeers(timeoutMs = 30000) {
1504
1302
  return new Promise((resolve) => {
@@ -1,26 +1,17 @@
1
1
  import { BlockMessage } from '@leofcoin/messages';
2
2
  import Contract from './contract.js';
3
3
  import Machine from './machine.js';
4
- import Jobber from './jobs/jobber.js';
5
4
  import { BlockHash } from './types.js';
6
- declare type SyncState = 'syncing' | 'synced' | 'errored' | 'connectionless';
7
- declare type ChainState = 'loading' | 'loaded';
8
5
  export default class State extends Contract {
9
6
  #private;
10
7
  knownBlocks: BlockHash[];
11
- jobber: Jobber;
12
8
  _wantList: any[];
13
9
  /**
14
10
  * contains transactions we need before we can successfully load
15
11
  */
16
12
  get wantList(): string[];
17
- get state(): {
18
- sync: SyncState;
19
- chain: ChainState;
20
- };
21
- get blockHashMap(): MapIterator<[any, any]>;
22
13
  get loaded(): boolean;
23
- get resolving(): boolean;
14
+ get isSyncing(): boolean;
24
15
  get contracts(): Promise<any>;
25
16
  get totalContracts(): Promise<any>;
26
17
  get nativeCalls(): Promise<any>;
@@ -39,10 +30,11 @@ export default class State extends Contract {
39
30
  previousHash: string;
40
31
  };
41
32
  get lastBlockHeight(): Promise<any> | 0;
42
- getBlock(index: any): Promise<any>;
43
- getBlocks(from?: any, to?: any): Promise<[]>;
44
33
  get totalSize(): number;
45
34
  get machine(): Machine;
35
+ get blockHashMap(): MapIterator<[any, any]>;
36
+ getBlock(index: any): Promise<any>;
37
+ getBlocks(from?: any, to?: any): Promise<[]>;
46
38
  constructor(config: any);
47
39
  clearPool(): Promise<void>;
48
40
  /**
@@ -54,13 +46,11 @@ export default class State extends Contract {
54
46
  getLatestBlock(): Promise<BlockMessage['decoded']>;
55
47
  getAndPutBlock(hash: string): Promise<BlockMessage>;
56
48
  resolveBlock(hash: string): Promise<void>;
57
- resolveBlocks(): Promise<any>;
58
- restoreChain(): any;
59
- syncChain(lastBlock?: any): Promise<SyncState>;
49
+ resolveBlocks(): Promise<void>;
50
+ syncChain(lastBlock?: any): Promise<'syncing' | 'synced' | 'errored' | 'connectionless'>;
60
51
  promiseRequests(promises: any): Promise<unknown>;
61
52
  get canSync(): boolean;
62
53
  get shouldSync(): boolean;
63
- triggerSync(): Promise<SyncState>;
54
+ triggerSync(): Promise<"syncing" | "synced" | "errored" | "connectionless">;
64
55
  triggerLoad(): Promise<void>;
65
56
  }
66
- export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leofcoin/chain",
3
- "version": "1.7.150",
3
+ "version": "1.7.151",
4
4
  "description": "Official javascript implementation",
5
5
  "private": false,
6
6
  "exports": {