@leofcoin/chain 1.7.149 → 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,74 +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
4611
+ #resolvingBlocks;
4612
+ #maxConcurrentResolves;
4613
+ #totalSize;
4649
4614
  /**
4650
4615
  * contains transactions we need before we can successfully load
4651
4616
  */
4652
4617
  get wantList() {
4653
4618
  return this.#machine?.wantList ?? this._wantList;
4654
4619
  }
4655
- get state() {
4656
- return {
4657
- sync: this.#syncState,
4658
- chain: this.#chainState
4659
- };
4660
- }
4661
- get blockHashMap() {
4662
- return this.#blockHashMap.entries();
4663
- }
4664
4620
  get loaded() {
4665
4621
  return this.#loaded;
4666
4622
  }
4667
- get resolving() {
4668
- return this.#resolving;
4623
+ get isSyncing() {
4624
+ return this.#syncing;
4669
4625
  }
4626
+ // Delegate to machine
4670
4627
  get contracts() {
4671
4628
  return this.#machine.contracts;
4672
4629
  }
@@ -4709,35 +4666,38 @@ class State extends Contract {
4709
4666
  get lastBlockHeight() {
4710
4667
  return this.#machine ? this.#machine.lastBlockHeight : 0;
4711
4668
  }
4712
- getBlock(index) {
4713
- return this.#machine.getBlock(index);
4714
- }
4715
- getBlocks(from, to) {
4716
- return this.#machine.getBlocks(from, to);
4717
- }
4718
4669
  get totalSize() {
4719
4670
  return this.#totalSize;
4720
4671
  }
4721
4672
  get machine() {
4722
4673
  return this.#machine;
4723
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
+ }
4724
4684
  constructor(config) {
4725
4685
  super(config);
4726
- this.#lastResolvedTime = 0;
4727
- this.#resolving = false;
4728
- this.#resolveErrorCount = 0;
4729
- this.#chainState = 'loading';
4730
- this.#syncErrorCount = 0;
4731
4686
  this.#blockHashMap = new Map();
4732
- this.#chainSyncing = false;
4733
4687
  this.#blocks = [];
4688
+ this.#loaded = false;
4689
+ // Sync state
4690
+ this.#syncing = false;
4691
+ this.#syncErrorCount = 0;
4692
+ // Block resolution state
4693
+ this.#resolvingBlocks = new Set();
4694
+ this.#maxConcurrentResolves = 10;
4734
4695
  this.knownBlocks = [];
4735
4696
  this.#totalSize = 0;
4736
- this.#loaded = false;
4737
4697
  this._wantList = [];
4738
4698
  this.#chainStateHandler = () => {
4739
4699
  return new globalThis.peernet.protos['peernet-response']({
4740
- response: this.#chainState
4700
+ response: { syncing: this.#syncing, loaded: this.#loaded }
4741
4701
  });
4742
4702
  };
4743
4703
  this.#lastBlockHandler = async () => {
@@ -4779,67 +4739,43 @@ class State extends Contract {
4779
4739
  #lastBlockHandler;
4780
4740
  #knownBlocksHandler;
4781
4741
  async init() {
4782
- this.jobber = new Jobber(this.resolveTimeout);
4783
- await globalThis.peernet.addRequestHandler('lastBlock', this.#lastBlockHandler);
4784
- await globalThis.peernet.addRequestHandler('knownBlocks', this.#knownBlocksHandler);
4785
- await globalThis.peernet.addRequestHandler('chainState', this.#chainStateHandler);
4786
- let localBlockHash;
4787
- let blockMessage;
4788
- 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));
4789
4746
  try {
4790
- const rawBlock = await globalThis.chainStore.has('lastBlock');
4791
- if (rawBlock) {
4792
- localBlockHash = new TextDecoder().decode(await globalThis.chainStore.get('lastBlock'));
4793
- if (localBlockHash !== '0x0') {
4794
- blockMessage = await globalThis.peernet.get(localBlockHash, 'block');
4795
- 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'));
4796
4753
  localBlock = { ...blockMessage.decoded, hash: localBlockHash };
4797
4754
  }
4798
4755
  }
4799
- else {
4800
- localBlock = { index: 0, hash: '0x0', previousHash: '0x0' };
4756
+ catch (error) {
4757
+ debug$1('No local block found');
4801
4758
  }
4802
- }
4803
- catch {
4804
- localBlock = { index: 0, hash: '0x0', previousHash: '0x0' };
4805
- }
4806
- try {
4807
- this.knownBlocks = await blockStore.keys();
4808
- }
4809
- catch (error) {
4810
- debug$1('no local known blocks found');
4811
- }
4812
- try {
4813
- if (localBlock.hash && localBlock.hash !== '0x0') {
4814
- try {
4815
- const states = {
4816
- lastBlock: JSON.parse(new TextDecoder().decode(await globalThis.stateStore.get('lastBlock')))
4817
- };
4818
- if (blockMessage.decoded.index > states.lastBlock.index)
4819
- await this.resolveBlocks();
4820
- }
4821
- catch (error) {
4822
- // no states found, try resolving blocks
4823
- await this.resolveBlocks();
4824
- }
4759
+ // Load known blocks
4760
+ try {
4761
+ this.knownBlocks = await globalThis.blockStore.keys();
4825
4762
  }
4826
- else {
4827
- await this.resolveBlocks();
4763
+ catch (error) {
4764
+ debug$1('No known blocks found');
4828
4765
  }
4766
+ // Initialize machine and resolve blocks if needed
4829
4767
  this.#machine = await new Machine(this.#blocks);
4768
+ if (localBlock.hash !== '0x0') {
4769
+ await this.resolveBlock(localBlock.hash);
4770
+ }
4830
4771
  const lastBlock = await this.#machine.lastBlock;
4831
4772
  if (lastBlock.hash !== '0x0') {
4832
- this.updateState(new BlockMessage(lastBlock));
4773
+ await this.updateState(new BlockMessage(lastBlock));
4833
4774
  }
4834
4775
  this.#loaded = true;
4835
- // await this.#loadBlocks(this.#blocks)
4836
4776
  }
4837
4777
  catch (error) {
4838
- console.log('e');
4839
- if (isResolveError(error)) {
4840
- console.error(error);
4841
- }
4842
- console.log(error);
4778
+ console.error('Failed to initialize state:', error);
4843
4779
  }
4844
4780
  }
4845
4781
  async updateState(message) {
@@ -4873,45 +4809,40 @@ class State extends Contract {
4873
4809
  return block;
4874
4810
  }
4875
4811
  async #resolveBlock(hash) {
4876
- let index = this.#blockHashMap.get(hash);
4877
- let localHash = '0x0';
4878
- try {
4879
- if (await globalThis.stateStore.has('lastBlock'))
4880
- localHash = await globalThis.stateStore.get('lastBlock');
4881
- }
4882
- catch (error) {
4883
- globalThis.stateStore.put('lastBlock', new TextEncoder().encode('0x0'));
4884
- debug$1('no local state found');
4812
+ if (this.#resolvingBlocks.has(hash)) {
4813
+ return; // Already resolving this block
4885
4814
  }
4886
- debug$1(`resolving block: ${hash} @${index !== undefined ? index : 'unknown'}`);
4887
- debug$1(`local state hash: ${localHash}`);
4888
- if (this.#blocks[index]) {
4889
- // Block already exists, check if we need to resolve previous blocks
4890
- const previousHash = this.#blocks[index].previousHash;
4891
- if (previousHash === localHash)
4892
- return;
4893
- if (previousHash !== '0x0') {
4894
- // Previous block not in memory, recursively resolve it
4895
- return this.resolveBlock(previousHash);
4896
- }
4897
- else {
4898
- // Previous block already exists or is genesis, stop resolving
4815
+ this.#resolvingBlocks.add(hash);
4816
+ try {
4817
+ let index = this.#blockHashMap.get(hash);
4818
+ debug$1(`resolving block: ${hash} @${index !== undefined ? index : 'unknown'}`);
4819
+ if (this.#blocks[index]) {
4820
+ // Block already exists
4899
4821
  return;
4900
4822
  }
4901
- }
4902
- try {
4903
4823
  const block = await this.getAndPutBlock(hash);
4904
- await Promise.all(block.decoded.transactions.map(async (hash) => {
4905
- // should be in a transaction store already
4906
- if (!(await transactionStore.has(hash))) {
4907
- const data = await peernet.get(hash, 'transaction');
4908
- await transactionStore.put(hash, data);
4909
- }
4910
- ;
4911
- (await transactionPoolStore.has(hash)) && (await transactionPoolStore.delete(hash));
4912
- }));
4913
4824
  index = block.decoded.index;
4914
4825
  const size = block.encoded.length > 0 ? block.encoded.length : block.encoded.byteLength;
4826
+ // Batch transaction operations
4827
+ const transactionsToFetch = [];
4828
+ const transactionHashes = block.decoded.transactions || [];
4829
+ for (const txHash of transactionHashes) {
4830
+ if (!(await globalThis.transactionStore.has(txHash))) {
4831
+ transactionsToFetch.push(txHash);
4832
+ }
4833
+ }
4834
+ // Fetch all missing transactions in parallel
4835
+ if (transactionsToFetch.length > 0) {
4836
+ const fetchedTransactions = await Promise.all(transactionsToFetch.map((txHash) => globalThis.peernet.get(txHash, 'transaction')));
4837
+ // Batch store all transactions
4838
+ await Promise.all(transactionsToFetch.map((txHash, i) => globalThis.transactionStore.put(txHash, fetchedTransactions[i])));
4839
+ }
4840
+ // Remove from pool
4841
+ await Promise.all(transactionHashes.map(async (txHash) => {
4842
+ if (await globalThis.transactionPoolStore.has(txHash)) {
4843
+ await globalThis.transactionPoolStore.delete(txHash);
4844
+ }
4845
+ }));
4915
4846
  this.#totalSize += size;
4916
4847
  this.#blocks[index] = { hash, ...block.decoded };
4917
4848
  this.#blockHashMap.set(hash, index);
@@ -4921,192 +4852,133 @@ class State extends Contract {
4921
4852
  this.#lastResolvedTime = Date.now();
4922
4853
  }
4923
4854
  catch (error) {
4924
- throw new ResolveError(`block: ${hash}@${index}`);
4855
+ throw new ResolveError(`block: ${hash}`);
4925
4856
  }
4926
- return;
4927
- }
4928
- async resolveBlock(hash) {
4929
- if (!hash)
4930
- throw new Error(`expected hash, got: ${hash}`);
4931
- if (hash === '0x0')
4932
- return;
4933
- if (this.#resolving)
4934
- return 'already resolving';
4935
- this.#resolving = true;
4936
- if (this.jobber.busy && this.jobber.destroy)
4937
- await this.jobber.destroy();
4938
- try {
4939
- await this.jobber.add(() => this.#resolveBlock(hash));
4940
- this.#resolving = false;
4941
- try {
4942
- const lastBlockHash = await globalThis.stateStore.get('lastBlock');
4943
- if (lastBlockHash === hash) {
4944
- this.#resolveErrored = false;
4945
- return;
4857
+ finally {
4858
+ this.#resolvingBlocks.delete(hash);
4859
+ }
4860
+ }
4861
+ async #buildBlockChain(latestHash, maxBlocks = 1000) {
4862
+ const chain = [];
4863
+ let currentHash = latestHash;
4864
+ let attempts = 0;
4865
+ while (currentHash !== '0x0' && chain.length < maxBlocks && attempts < maxBlocks + 5) {
4866
+ attempts++;
4867
+ // Check if we already have this block
4868
+ if (this.#blockHashMap.has(currentHash)) {
4869
+ const block = this.#blocks[this.#blockHashMap.get(currentHash)];
4870
+ if (block) {
4871
+ chain.push(currentHash);
4872
+ currentHash = block.previousHash;
4873
+ continue;
4946
4874
  }
4947
4875
  }
4876
+ chain.push(currentHash);
4877
+ // Try to get the block to find previous hash
4878
+ try {
4879
+ const block = await this.getAndPutBlock(currentHash);
4880
+ currentHash = block.decoded.previousHash;
4881
+ }
4948
4882
  catch (error) {
4949
- debug$1('no local state found');
4883
+ debug$1(`Could not fetch block ${currentHash} to determine chain: ${error}`);
4884
+ break;
4950
4885
  }
4951
- if (!this.#blockHashMap.has(this.#lastResolved.previousHash) && this.#lastResolved.previousHash !== '0x0')
4952
- return this.resolveBlock(this.#lastResolved.previousHash);
4953
4886
  }
4954
- catch (error) {
4955
- console.log({ error });
4956
- this.#resolveErrorCount += 1;
4957
- this.#resolving = false;
4958
- if (this.#resolveErrorCount < 3)
4959
- return this.resolveBlock(hash);
4960
- this.#resolveErrorCount = 0;
4961
- this.wantList.push(hash);
4962
- throw new ResolveError(`block: ${hash}`, { cause: error });
4887
+ return chain;
4888
+ }
4889
+ async #resolveBlocksInParallel(hashes) {
4890
+ // Resolve blocks in parallel with concurrency limit
4891
+ const resolving = [];
4892
+ let index = 0;
4893
+ const resolveNext = async () => {
4894
+ while (index < hashes.length) {
4895
+ const hash = hashes[index++];
4896
+ try {
4897
+ await this.#resolveBlock(hash);
4898
+ }
4899
+ catch (error) {
4900
+ debug$1(`Failed to resolve block ${hash}: ${error}`);
4901
+ this.#blockResolveQueue.push({ hash, retries: 0 });
4902
+ }
4903
+ }
4904
+ };
4905
+ // Start concurrent resolution tasks
4906
+ for (let i = 0; i < Math.min(this.#maxConcurrentResolves, hashes.length); i++) {
4907
+ resolving.push(resolveNext());
4963
4908
  }
4909
+ await Promise.all(resolving);
4964
4910
  }
4965
- async resolveBlocks() {
4966
- // Don't re-resolve if already syncing or resolving
4967
- if (this.#chainSyncing || this.#resolving) {
4968
- debug$1('Already syncing or resolving, skipping resolveBlocks()');
4911
+ async resolveBlock(hash) {
4912
+ if (!hash || hash === '0x0')
4969
4913
  return;
4970
- }
4914
+ if (this.#syncing)
4915
+ return;
4916
+ this.#syncing = true;
4917
+ this.#syncErrorCount = 0;
4971
4918
  try {
4972
- if (this.jobber.busy && this.jobber.destroy) {
4973
- await this.jobber.destroy();
4919
+ debug$1(`Building block chain from ${hash}`);
4920
+ const blockChain = await this.#buildBlockChain(hash);
4921
+ debug$1(`Built chain of ${blockChain.length} blocks`);
4922
+ if (blockChain.length > 0) {
4923
+ await this.#resolveBlocksInParallel(blockChain);
4974
4924
  }
4975
4925
  }
4976
4926
  catch (error) {
4977
- console.error(error);
4978
- }
4979
- try {
4980
- const localBlock = await globalThis.chainStore.get('lastBlock');
4981
- const hash = new TextDecoder().decode(localBlock);
4982
- if (hash && hash !== '0x0') {
4983
- debug$1(`Resolving blocks from hash: ${hash}`);
4984
- await this.resolveBlock(hash);
4985
- }
4927
+ console.error('Block resolution failed:', error);
4928
+ this.wantList.push(hash);
4986
4929
  }
4987
- catch (error) {
4988
- console.log(error);
4989
- this.#chainSyncing = false;
4990
- this.#syncState = 'errored';
4991
- this.#resolveErrored = true;
4992
- return this.restoreChain();
4993
- // console.log(e);
4930
+ finally {
4931
+ this.#syncing = false;
4994
4932
  }
4995
4933
  }
4996
- async restoreChain() {
4934
+ async resolveBlocks() {
4935
+ if (this.#syncing)
4936
+ return;
4997
4937
  try {
4998
- const { hash } = await this.#getLatestBlock();
4999
- await globalThis.chainStore.put('lastBlock', hash);
4938
+ const localBlock = await globalThis.chainStore.get('lastBlock');
4939
+ const hash = new TextDecoder().decode(localBlock);
5000
4940
  if (hash && hash !== '0x0') {
5001
4941
  await this.resolveBlock(hash);
5002
4942
  }
5003
4943
  }
5004
4944
  catch (error) {
5005
- console.log(error);
5006
- this.#resolveErrored = true;
5007
- this.#resolveErrorCount += 1;
5008
- this.#resolving = false;
5009
- return this.restoreChain();
5010
- // console.log(e);
4945
+ debug$1('Failed to resolve blocks:', error);
5011
4946
  }
5012
4947
  }
5013
4948
  async syncChain(lastBlock) {
5014
- console.log('check if can sync');
5015
- if (!this.shouldSync)
5016
- return;
5017
- console.log('starting sync');
5018
- this.#syncState = 'syncing';
5019
- this.#chainSyncing = true;
5020
- try {
5021
- if (this.jobber.busy && this.jobber.destroy) {
5022
- await this.jobber.destroy();
5023
- }
5024
- }
5025
- catch (error) {
5026
- console.error(error);
5027
- }
4949
+ if (this.#syncing)
4950
+ return 'syncing';
5028
4951
  if (!lastBlock)
5029
4952
  lastBlock = await this.#getLatestBlock();
5030
- if (globalThis.peernet.peers.length === 0)
5031
- return 'connectionless';
5032
- try {
5033
- await this.#syncChain(lastBlock);
5034
- }
5035
- catch (error) {
5036
- this.#syncErrorCount += 1;
5037
- if (this.#syncErrorCount < 3)
5038
- return this.syncChain(lastBlock);
5039
- this.#syncErrorCount = 0;
5040
- this.#chainSyncing = false;
5041
- this.#syncState = 'errored';
5042
- return this.#syncState;
5043
- }
5044
- if (lastBlock.index === this.#lastBlockInQue?.index)
5045
- this.#lastBlockInQue = undefined;
5046
- this.#syncErrorCount = 0;
5047
- this.#chainSyncing = false;
5048
- if (this.#lastBlockInQue)
5049
- return this.syncChain(this.#lastBlockInQue);
5050
- this.#syncState = 'synced';
5051
- return this.#syncState;
5052
- }
5053
- async #syncChain(lastBlock) {
4953
+ this.#syncing = true;
5054
4954
  try {
5055
- // if (this.knownBlocks?.length === Number(lastBlock.index) + 1) {
5056
- // let promises = []
5057
- // promises = await Promise.allSettled(
5058
- // this.knownBlocks.map(async (address) => {
5059
- // const has = await globalThis.peernet.has(address)
5060
- // return { has, address }
5061
- // })
5062
- // )
5063
- // promises = promises.filter(({ status, value }) => status === 'fulfilled' && !value.has)
5064
- // await Promise.allSettled(promises.map(({ value }) => this.getAndPutBlock(value.address)))
5065
- // }
5066
- const localBlock = await this.lastBlock;
5067
- const localIndex = localBlock ? Number(localBlock.index) : -1;
5068
- const remoteIndex = Number(lastBlock.index);
5069
- const remoteBlockHash = lastBlock.hash;
5070
- // Get the local state hash from chainStore
5071
- let localStateHash = '0x0';
5072
- try {
5073
- localStateHash = new TextDecoder().decode(await globalThis.chainStore.get('lastBlock'));
5074
- }
5075
- catch (error) {
5076
- debug$1(`No local state hash found: ${error}`);
5077
- }
5078
- debug$1(`Local block height: ${localIndex}, remote block height: ${remoteIndex}`);
5079
- debug$1(`Local state hash: ${localStateHash}, remote block hash: ${remoteBlockHash}`);
5080
- // Skip syncing if remote block hash is 0x0 (invalid state)
5081
- if (remoteBlockHash === '0x0') {
5082
- debug$1(`Remote block hash is 0x0, skipping sync`);
5083
- return;
4955
+ if (globalThis.peernet.peers.length === 0) {
4956
+ this.#syncing = false;
4957
+ return 'connectionless';
5084
4958
  }
5085
- // Use state hash comparison: only resolve if remote hash differs from local state hash
5086
- if (localStateHash !== remoteBlockHash) {
5087
- // Remote block hash differs from our local state, need to resolve
5088
- debug$1(`Resolving remote block: ${remoteBlockHash} @${remoteIndex} (differs from local state)`);
5089
- await this.resolveBlock(remoteBlockHash);
5090
- const blocksSynced = remoteIndex - localIndex;
5091
- debug$1(`Resolved ${blocksSynced} new block(s)`);
5092
- const blocks = this.#blocks;
5093
- debug$1(`Loading blocks from index ${localIndex + 1} to ${remoteIndex}`);
5094
- const start = localIndex + 1;
5095
- if (this.#machine && blocks.length > start) {
5096
- await this.#loadBlocks(blocks.slice(start));
5097
- }
5098
- // Update state with the latest block
5099
- if (blocks.length > 0) {
5100
- await this.updateState(new BlockMessage(blocks[blocks.length - 1]));
5101
- }
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));
5102
4965
  }
5103
- else {
5104
- 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]));
5105
4968
  }
4969
+ this.#syncErrorCount = 0;
4970
+ this.#syncing = false;
4971
+ return 'synced';
5106
4972
  }
5107
4973
  catch (error) {
5108
- console.log(error);
5109
- 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';
5110
4982
  }
5111
4983
  }
5112
4984
  async #getLatestBlock() {
@@ -5157,7 +5029,7 @@ class State extends Contract {
5157
5029
  let node = await globalThis.peernet.prepareMessage(data);
5158
5030
  let message = await peer.request(node.encode());
5159
5031
  message = await new globalThis.peernet.protos['peernet-response'](message);
5160
- this.knownBlocks = message.decoded.response;
5032
+ this.wantList.push(...message.decoded.response);
5161
5033
  }
5162
5034
  }
5163
5035
  return latest;
@@ -5277,26 +5149,13 @@ class State extends Contract {
5277
5149
  });
5278
5150
  }
5279
5151
  get canSync() {
5280
- if (this.#chainSyncing)
5281
- return false;
5282
- return true;
5152
+ return !this.#syncing;
5283
5153
  }
5284
5154
  get shouldSync() {
5285
- if (this.#chainSyncing)
5155
+ if (this.#syncing)
5286
5156
  return false;
5287
- // Check if we have any connected peers with the same version
5288
5157
  const compatiblePeers = Object.values(globalThis.peernet.connections || {}).filter((peer) => peer.connected && peer.version === this.version);
5289
- if (compatiblePeers.length === 0) {
5290
- debug$1('No compatible peers available for sync');
5291
- return false;
5292
- }
5293
- if (!this.#chainSyncing ||
5294
- this.#resolveErrored ||
5295
- this.#syncState === 'errored' ||
5296
- this.#syncState === 'connectionless' ||
5297
- this.#lastResolvedTime + this.resolveTimeout > Date.now())
5298
- return true;
5299
- return false;
5158
+ return compatiblePeers.length > 0;
5300
5159
  }
5301
5160
  async #waitForPeers(timeoutMs = 30000) {
5302
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,74 +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
752
+ #resolvingBlocks;
753
+ #maxConcurrentResolves;
754
+ #totalSize;
789
755
  /**
790
756
  * contains transactions we need before we can successfully load
791
757
  */
792
758
  get wantList() {
793
759
  return this.#machine?.wantList ?? this._wantList;
794
760
  }
795
- get state() {
796
- return {
797
- sync: this.#syncState,
798
- chain: this.#chainState
799
- };
800
- }
801
- get blockHashMap() {
802
- return this.#blockHashMap.entries();
803
- }
804
761
  get loaded() {
805
762
  return this.#loaded;
806
763
  }
807
- get resolving() {
808
- return this.#resolving;
764
+ get isSyncing() {
765
+ return this.#syncing;
809
766
  }
767
+ // Delegate to machine
810
768
  get contracts() {
811
769
  return this.#machine.contracts;
812
770
  }
@@ -849,35 +807,38 @@ class State extends Contract {
849
807
  get lastBlockHeight() {
850
808
  return this.#machine ? this.#machine.lastBlockHeight : 0;
851
809
  }
852
- getBlock(index) {
853
- return this.#machine.getBlock(index);
854
- }
855
- getBlocks(from, to) {
856
- return this.#machine.getBlocks(from, to);
857
- }
858
810
  get totalSize() {
859
811
  return this.#totalSize;
860
812
  }
861
813
  get machine() {
862
814
  return this.#machine;
863
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
+ }
864
825
  constructor(config) {
865
826
  super(config);
866
- this.#lastResolvedTime = 0;
867
- this.#resolving = false;
868
- this.#resolveErrorCount = 0;
869
- this.#chainState = 'loading';
870
- this.#syncErrorCount = 0;
871
827
  this.#blockHashMap = new Map();
872
- this.#chainSyncing = false;
873
828
  this.#blocks = [];
829
+ this.#loaded = false;
830
+ // Sync state
831
+ this.#syncing = false;
832
+ this.#syncErrorCount = 0;
833
+ // Block resolution state
834
+ this.#resolvingBlocks = new Set();
835
+ this.#maxConcurrentResolves = 10;
874
836
  this.knownBlocks = [];
875
837
  this.#totalSize = 0;
876
- this.#loaded = false;
877
838
  this._wantList = [];
878
839
  this.#chainStateHandler = () => {
879
840
  return new globalThis.peernet.protos['peernet-response']({
880
- response: this.#chainState
841
+ response: { syncing: this.#syncing, loaded: this.#loaded }
881
842
  });
882
843
  };
883
844
  this.#lastBlockHandler = async () => {
@@ -919,67 +880,43 @@ class State extends Contract {
919
880
  #lastBlockHandler;
920
881
  #knownBlocksHandler;
921
882
  async init() {
922
- this.jobber = new Jobber(this.resolveTimeout);
923
- await globalThis.peernet.addRequestHandler('lastBlock', this.#lastBlockHandler);
924
- await globalThis.peernet.addRequestHandler('knownBlocks', this.#knownBlocksHandler);
925
- await globalThis.peernet.addRequestHandler('chainState', this.#chainStateHandler);
926
- let localBlockHash;
927
- let blockMessage;
928
- 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));
929
887
  try {
930
- const rawBlock = await globalThis.chainStore.has('lastBlock');
931
- if (rawBlock) {
932
- localBlockHash = new TextDecoder().decode(await globalThis.chainStore.get('lastBlock'));
933
- if (localBlockHash !== '0x0') {
934
- blockMessage = await globalThis.peernet.get(localBlockHash, 'block');
935
- 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'));
936
894
  localBlock = { ...blockMessage.decoded, hash: localBlockHash };
937
895
  }
938
896
  }
939
- else {
940
- localBlock = { index: 0, hash: '0x0', previousHash: '0x0' };
897
+ catch (error) {
898
+ debug$1('No local block found');
941
899
  }
942
- }
943
- catch {
944
- localBlock = { index: 0, hash: '0x0', previousHash: '0x0' };
945
- }
946
- try {
947
- this.knownBlocks = await blockStore.keys();
948
- }
949
- catch (error) {
950
- debug$1('no local known blocks found');
951
- }
952
- try {
953
- if (localBlock.hash && localBlock.hash !== '0x0') {
954
- try {
955
- const states = {
956
- lastBlock: JSON.parse(new TextDecoder().decode(await globalThis.stateStore.get('lastBlock')))
957
- };
958
- if (blockMessage.decoded.index > states.lastBlock.index)
959
- await this.resolveBlocks();
960
- }
961
- catch (error) {
962
- // no states found, try resolving blocks
963
- await this.resolveBlocks();
964
- }
900
+ // Load known blocks
901
+ try {
902
+ this.knownBlocks = await globalThis.blockStore.keys();
965
903
  }
966
- else {
967
- await this.resolveBlocks();
904
+ catch (error) {
905
+ debug$1('No known blocks found');
968
906
  }
907
+ // Initialize machine and resolve blocks if needed
969
908
  this.#machine = await new Machine(this.#blocks);
909
+ if (localBlock.hash !== '0x0') {
910
+ await this.resolveBlock(localBlock.hash);
911
+ }
970
912
  const lastBlock = await this.#machine.lastBlock;
971
913
  if (lastBlock.hash !== '0x0') {
972
- this.updateState(new BlockMessage(lastBlock));
914
+ await this.updateState(new BlockMessage(lastBlock));
973
915
  }
974
916
  this.#loaded = true;
975
- // await this.#loadBlocks(this.#blocks)
976
917
  }
977
918
  catch (error) {
978
- console.log('e');
979
- if (isResolveError(error)) {
980
- console.error(error);
981
- }
982
- console.log(error);
919
+ console.error('Failed to initialize state:', error);
983
920
  }
984
921
  }
985
922
  async updateState(message) {
@@ -1013,45 +950,40 @@ class State extends Contract {
1013
950
  return block;
1014
951
  }
1015
952
  async #resolveBlock(hash) {
1016
- let index = this.#blockHashMap.get(hash);
1017
- let localHash = '0x0';
1018
- try {
1019
- if (await globalThis.stateStore.has('lastBlock'))
1020
- localHash = await globalThis.stateStore.get('lastBlock');
953
+ if (this.#resolvingBlocks.has(hash)) {
954
+ return; // Already resolving this block
1021
955
  }
1022
- catch (error) {
1023
- globalThis.stateStore.put('lastBlock', new TextEncoder().encode('0x0'));
1024
- debug$1('no local state found');
1025
- }
1026
- debug$1(`resolving block: ${hash} @${index !== undefined ? index : 'unknown'}`);
1027
- debug$1(`local state hash: ${localHash}`);
1028
- if (this.#blocks[index]) {
1029
- // Block already exists, check if we need to resolve previous blocks
1030
- const previousHash = this.#blocks[index].previousHash;
1031
- if (previousHash === localHash)
1032
- return;
1033
- if (previousHash !== '0x0') {
1034
- // Previous block not in memory, recursively resolve it
1035
- return this.resolveBlock(previousHash);
1036
- }
1037
- else {
1038
- // Previous block already exists or is genesis, stop resolving
956
+ this.#resolvingBlocks.add(hash);
957
+ try {
958
+ let index = this.#blockHashMap.get(hash);
959
+ debug$1(`resolving block: ${hash} @${index !== undefined ? index : 'unknown'}`);
960
+ if (this.#blocks[index]) {
961
+ // Block already exists
1039
962
  return;
1040
963
  }
1041
- }
1042
- try {
1043
964
  const block = await this.getAndPutBlock(hash);
1044
- await Promise.all(block.decoded.transactions.map(async (hash) => {
1045
- // should be in a transaction store already
1046
- if (!(await transactionStore.has(hash))) {
1047
- const data = await peernet.get(hash, 'transaction');
1048
- await transactionStore.put(hash, data);
1049
- }
1050
- ;
1051
- (await transactionPoolStore.has(hash)) && (await transactionPoolStore.delete(hash));
1052
- }));
1053
965
  index = block.decoded.index;
1054
966
  const size = block.encoded.length > 0 ? block.encoded.length : block.encoded.byteLength;
967
+ // Batch transaction operations
968
+ const transactionsToFetch = [];
969
+ const transactionHashes = block.decoded.transactions || [];
970
+ for (const txHash of transactionHashes) {
971
+ if (!(await globalThis.transactionStore.has(txHash))) {
972
+ transactionsToFetch.push(txHash);
973
+ }
974
+ }
975
+ // Fetch all missing transactions in parallel
976
+ if (transactionsToFetch.length > 0) {
977
+ const fetchedTransactions = await Promise.all(transactionsToFetch.map((txHash) => globalThis.peernet.get(txHash, 'transaction')));
978
+ // Batch store all transactions
979
+ await Promise.all(transactionsToFetch.map((txHash, i) => globalThis.transactionStore.put(txHash, fetchedTransactions[i])));
980
+ }
981
+ // Remove from pool
982
+ await Promise.all(transactionHashes.map(async (txHash) => {
983
+ if (await globalThis.transactionPoolStore.has(txHash)) {
984
+ await globalThis.transactionPoolStore.delete(txHash);
985
+ }
986
+ }));
1055
987
  this.#totalSize += size;
1056
988
  this.#blocks[index] = { hash, ...block.decoded };
1057
989
  this.#blockHashMap.set(hash, index);
@@ -1061,192 +993,133 @@ class State extends Contract {
1061
993
  this.#lastResolvedTime = Date.now();
1062
994
  }
1063
995
  catch (error) {
1064
- throw new ResolveError(`block: ${hash}@${index}`);
996
+ throw new ResolveError(`block: ${hash}`);
1065
997
  }
1066
- return;
1067
- }
1068
- async resolveBlock(hash) {
1069
- if (!hash)
1070
- throw new Error(`expected hash, got: ${hash}`);
1071
- if (hash === '0x0')
1072
- return;
1073
- if (this.#resolving)
1074
- return 'already resolving';
1075
- this.#resolving = true;
1076
- if (this.jobber.busy && this.jobber.destroy)
1077
- await this.jobber.destroy();
1078
- try {
1079
- await this.jobber.add(() => this.#resolveBlock(hash));
1080
- this.#resolving = false;
1081
- try {
1082
- const lastBlockHash = await globalThis.stateStore.get('lastBlock');
1083
- if (lastBlockHash === hash) {
1084
- this.#resolveErrored = false;
1085
- return;
998
+ finally {
999
+ this.#resolvingBlocks.delete(hash);
1000
+ }
1001
+ }
1002
+ async #buildBlockChain(latestHash, maxBlocks = 1000) {
1003
+ const chain = [];
1004
+ let currentHash = latestHash;
1005
+ let attempts = 0;
1006
+ while (currentHash !== '0x0' && chain.length < maxBlocks && attempts < maxBlocks + 5) {
1007
+ attempts++;
1008
+ // Check if we already have this block
1009
+ if (this.#blockHashMap.has(currentHash)) {
1010
+ const block = this.#blocks[this.#blockHashMap.get(currentHash)];
1011
+ if (block) {
1012
+ chain.push(currentHash);
1013
+ currentHash = block.previousHash;
1014
+ continue;
1086
1015
  }
1087
1016
  }
1017
+ chain.push(currentHash);
1018
+ // Try to get the block to find previous hash
1019
+ try {
1020
+ const block = await this.getAndPutBlock(currentHash);
1021
+ currentHash = block.decoded.previousHash;
1022
+ }
1088
1023
  catch (error) {
1089
- debug$1('no local state found');
1024
+ debug$1(`Could not fetch block ${currentHash} to determine chain: ${error}`);
1025
+ break;
1090
1026
  }
1091
- if (!this.#blockHashMap.has(this.#lastResolved.previousHash) && this.#lastResolved.previousHash !== '0x0')
1092
- return this.resolveBlock(this.#lastResolved.previousHash);
1093
1027
  }
1094
- catch (error) {
1095
- console.log({ error });
1096
- this.#resolveErrorCount += 1;
1097
- this.#resolving = false;
1098
- if (this.#resolveErrorCount < 3)
1099
- return this.resolveBlock(hash);
1100
- this.#resolveErrorCount = 0;
1101
- this.wantList.push(hash);
1102
- throw new ResolveError(`block: ${hash}`, { cause: error });
1028
+ return chain;
1029
+ }
1030
+ async #resolveBlocksInParallel(hashes) {
1031
+ // Resolve blocks in parallel with concurrency limit
1032
+ const resolving = [];
1033
+ let index = 0;
1034
+ const resolveNext = async () => {
1035
+ while (index < hashes.length) {
1036
+ const hash = hashes[index++];
1037
+ try {
1038
+ await this.#resolveBlock(hash);
1039
+ }
1040
+ catch (error) {
1041
+ debug$1(`Failed to resolve block ${hash}: ${error}`);
1042
+ this.#blockResolveQueue.push({ hash, retries: 0 });
1043
+ }
1044
+ }
1045
+ };
1046
+ // Start concurrent resolution tasks
1047
+ for (let i = 0; i < Math.min(this.#maxConcurrentResolves, hashes.length); i++) {
1048
+ resolving.push(resolveNext());
1103
1049
  }
1050
+ await Promise.all(resolving);
1104
1051
  }
1105
- async resolveBlocks() {
1106
- // Don't re-resolve if already syncing or resolving
1107
- if (this.#chainSyncing || this.#resolving) {
1108
- debug$1('Already syncing or resolving, skipping resolveBlocks()');
1052
+ async resolveBlock(hash) {
1053
+ if (!hash || hash === '0x0')
1109
1054
  return;
1110
- }
1055
+ if (this.#syncing)
1056
+ return;
1057
+ this.#syncing = true;
1058
+ this.#syncErrorCount = 0;
1111
1059
  try {
1112
- if (this.jobber.busy && this.jobber.destroy) {
1113
- await this.jobber.destroy();
1060
+ debug$1(`Building block chain from ${hash}`);
1061
+ const blockChain = await this.#buildBlockChain(hash);
1062
+ debug$1(`Built chain of ${blockChain.length} blocks`);
1063
+ if (blockChain.length > 0) {
1064
+ await this.#resolveBlocksInParallel(blockChain);
1114
1065
  }
1115
1066
  }
1116
1067
  catch (error) {
1117
- console.error(error);
1118
- }
1119
- try {
1120
- const localBlock = await globalThis.chainStore.get('lastBlock');
1121
- const hash = new TextDecoder().decode(localBlock);
1122
- if (hash && hash !== '0x0') {
1123
- debug$1(`Resolving blocks from hash: ${hash}`);
1124
- await this.resolveBlock(hash);
1125
- }
1068
+ console.error('Block resolution failed:', error);
1069
+ this.wantList.push(hash);
1126
1070
  }
1127
- catch (error) {
1128
- console.log(error);
1129
- this.#chainSyncing = false;
1130
- this.#syncState = 'errored';
1131
- this.#resolveErrored = true;
1132
- return this.restoreChain();
1133
- // console.log(e);
1071
+ finally {
1072
+ this.#syncing = false;
1134
1073
  }
1135
1074
  }
1136
- async restoreChain() {
1075
+ async resolveBlocks() {
1076
+ if (this.#syncing)
1077
+ return;
1137
1078
  try {
1138
- const { hash } = await this.#getLatestBlock();
1139
- await globalThis.chainStore.put('lastBlock', hash);
1079
+ const localBlock = await globalThis.chainStore.get('lastBlock');
1080
+ const hash = new TextDecoder().decode(localBlock);
1140
1081
  if (hash && hash !== '0x0') {
1141
1082
  await this.resolveBlock(hash);
1142
1083
  }
1143
1084
  }
1144
1085
  catch (error) {
1145
- console.log(error);
1146
- this.#resolveErrored = true;
1147
- this.#resolveErrorCount += 1;
1148
- this.#resolving = false;
1149
- return this.restoreChain();
1150
- // console.log(e);
1086
+ debug$1('Failed to resolve blocks:', error);
1151
1087
  }
1152
1088
  }
1153
1089
  async syncChain(lastBlock) {
1154
- console.log('check if can sync');
1155
- if (!this.shouldSync)
1156
- return;
1157
- console.log('starting sync');
1158
- this.#syncState = 'syncing';
1159
- this.#chainSyncing = true;
1160
- try {
1161
- if (this.jobber.busy && this.jobber.destroy) {
1162
- await this.jobber.destroy();
1163
- }
1164
- }
1165
- catch (error) {
1166
- console.error(error);
1167
- }
1090
+ if (this.#syncing)
1091
+ return 'syncing';
1168
1092
  if (!lastBlock)
1169
1093
  lastBlock = await this.#getLatestBlock();
1170
- if (globalThis.peernet.peers.length === 0)
1171
- return 'connectionless';
1094
+ this.#syncing = true;
1172
1095
  try {
1173
- await this.#syncChain(lastBlock);
1174
- }
1175
- catch (error) {
1176
- this.#syncErrorCount += 1;
1177
- if (this.#syncErrorCount < 3)
1178
- return this.syncChain(lastBlock);
1179
- this.#syncErrorCount = 0;
1180
- this.#chainSyncing = false;
1181
- this.#syncState = 'errored';
1182
- return this.#syncState;
1183
- }
1184
- if (lastBlock.index === this.#lastBlockInQue?.index)
1185
- this.#lastBlockInQue = undefined;
1186
- this.#syncErrorCount = 0;
1187
- this.#chainSyncing = false;
1188
- if (this.#lastBlockInQue)
1189
- return this.syncChain(this.#lastBlockInQue);
1190
- this.#syncState = 'synced';
1191
- return this.#syncState;
1192
- }
1193
- async #syncChain(lastBlock) {
1194
- try {
1195
- // if (this.knownBlocks?.length === Number(lastBlock.index) + 1) {
1196
- // let promises = []
1197
- // promises = await Promise.allSettled(
1198
- // this.knownBlocks.map(async (address) => {
1199
- // const has = await globalThis.peernet.has(address)
1200
- // return { has, address }
1201
- // })
1202
- // )
1203
- // promises = promises.filter(({ status, value }) => status === 'fulfilled' && !value.has)
1204
- // await Promise.allSettled(promises.map(({ value }) => this.getAndPutBlock(value.address)))
1205
- // }
1206
- const localBlock = await this.lastBlock;
1207
- const localIndex = localBlock ? Number(localBlock.index) : -1;
1208
- const remoteIndex = Number(lastBlock.index);
1209
- const remoteBlockHash = lastBlock.hash;
1210
- // Get the local state hash from chainStore
1211
- let localStateHash = '0x0';
1212
- try {
1213
- localStateHash = new TextDecoder().decode(await globalThis.chainStore.get('lastBlock'));
1096
+ if (globalThis.peernet.peers.length === 0) {
1097
+ this.#syncing = false;
1098
+ return 'connectionless';
1214
1099
  }
1215
- catch (error) {
1216
- debug$1(`No local state hash found: ${error}`);
1217
- }
1218
- debug$1(`Local block height: ${localIndex}, remote block height: ${remoteIndex}`);
1219
- debug$1(`Local state hash: ${localStateHash}, remote block hash: ${remoteBlockHash}`);
1220
- // Skip syncing if remote block hash is 0x0 (invalid state)
1221
- if (remoteBlockHash === '0x0') {
1222
- debug$1(`Remote block hash is 0x0, skipping sync`);
1223
- return;
1224
- }
1225
- // Use state hash comparison: only resolve if remote hash differs from local state hash
1226
- if (localStateHash !== remoteBlockHash) {
1227
- // Remote block hash differs from our local state, need to resolve
1228
- debug$1(`Resolving remote block: ${remoteBlockHash} @${remoteIndex} (differs from local state)`);
1229
- await this.resolveBlock(remoteBlockHash);
1230
- const blocksSynced = remoteIndex - localIndex;
1231
- debug$1(`Resolved ${blocksSynced} new block(s)`);
1232
- const blocks = this.#blocks;
1233
- debug$1(`Loading blocks from index ${localIndex + 1} to ${remoteIndex}`);
1234
- const start = localIndex + 1;
1235
- if (this.#machine && blocks.length > start) {
1236
- await this.#loadBlocks(blocks.slice(start));
1237
- }
1238
- // Update state with the latest block
1239
- if (blocks.length > 0) {
1240
- await this.updateState(new BlockMessage(blocks[blocks.length - 1]));
1241
- }
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));
1242
1106
  }
1243
- else {
1244
- 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]));
1245
1109
  }
1110
+ this.#syncErrorCount = 0;
1111
+ this.#syncing = false;
1112
+ return 'synced';
1246
1113
  }
1247
1114
  catch (error) {
1248
- console.log(error);
1249
- 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';
1250
1123
  }
1251
1124
  }
1252
1125
  async #getLatestBlock() {
@@ -1297,7 +1170,7 @@ class State extends Contract {
1297
1170
  let node = await globalThis.peernet.prepareMessage(data);
1298
1171
  let message = await peer.request(node.encode());
1299
1172
  message = await new globalThis.peernet.protos['peernet-response'](message);
1300
- this.knownBlocks = message.decoded.response;
1173
+ this.wantList.push(...message.decoded.response);
1301
1174
  }
1302
1175
  }
1303
1176
  return latest;
@@ -1417,26 +1290,13 @@ class State extends Contract {
1417
1290
  });
1418
1291
  }
1419
1292
  get canSync() {
1420
- if (this.#chainSyncing)
1421
- return false;
1422
- return true;
1293
+ return !this.#syncing;
1423
1294
  }
1424
1295
  get shouldSync() {
1425
- if (this.#chainSyncing)
1296
+ if (this.#syncing)
1426
1297
  return false;
1427
- // Check if we have any connected peers with the same version
1428
1298
  const compatiblePeers = Object.values(globalThis.peernet.connections || {}).filter((peer) => peer.connected && peer.version === this.version);
1429
- if (compatiblePeers.length === 0) {
1430
- debug$1('No compatible peers available for sync');
1431
- return false;
1432
- }
1433
- if (!this.#chainSyncing ||
1434
- this.#resolveErrored ||
1435
- this.#syncState === 'errored' ||
1436
- this.#syncState === 'connectionless' ||
1437
- this.#lastResolvedTime + this.resolveTimeout > Date.now())
1438
- return true;
1439
- return false;
1299
+ return compatiblePeers.length > 0;
1440
1300
  }
1441
1301
  async #waitForPeers(timeoutMs = 30000) {
1442
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
  /**
@@ -53,14 +45,12 @@ export default class State extends Contract {
53
45
  updateState(message: BlockMessage): Promise<void>;
54
46
  getLatestBlock(): Promise<BlockMessage['decoded']>;
55
47
  getAndPutBlock(hash: string): Promise<BlockMessage>;
56
- resolveBlock(hash: any): any;
57
- resolveBlocks(): Promise<any>;
58
- restoreChain(): any;
59
- syncChain(lastBlock?: any): Promise<SyncState>;
48
+ resolveBlock(hash: string): Promise<void>;
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.149",
3
+ "version": "1.7.151",
4
4
  "description": "Official javascript implementation",
5
5
  "private": false,
6
6
  "exports": {