@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.
- package/exports/browser/chain.js +176 -317
- package/exports/chain.js +177 -317
- package/exports/state.d.ts +8 -18
- package/package.json +1 -1
package/exports/browser/chain.js
CHANGED
|
@@ -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
|
|
4668
|
-
return this.#
|
|
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.#
|
|
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
|
-
|
|
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
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
blockMessage = await new 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
|
-
|
|
4800
|
-
|
|
4756
|
+
catch (error) {
|
|
4757
|
+
debug$1('No local block found');
|
|
4801
4758
|
}
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
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
|
-
|
|
4827
|
-
|
|
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.
|
|
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
|
-
|
|
4877
|
-
|
|
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
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
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}
|
|
4855
|
+
throw new ResolveError(`block: ${hash}`);
|
|
4925
4856
|
}
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
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(
|
|
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
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
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
|
|
4966
|
-
|
|
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
|
-
|
|
4973
|
-
|
|
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
|
-
|
|
4988
|
-
|
|
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
|
|
4934
|
+
async resolveBlocks() {
|
|
4935
|
+
if (this.#syncing)
|
|
4936
|
+
return;
|
|
4997
4937
|
try {
|
|
4998
|
-
const
|
|
4999
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5015
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
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
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
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
|
-
|
|
5104
|
-
|
|
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
|
-
|
|
5109
|
-
|
|
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.
|
|
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
|
-
|
|
5281
|
-
return false;
|
|
5282
|
-
return true;
|
|
5152
|
+
return !this.#syncing;
|
|
5283
5153
|
}
|
|
5284
5154
|
get shouldSync() {
|
|
5285
|
-
if (this.#
|
|
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
|
-
|
|
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,
|
|
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
|
|
808
|
-
return this.#
|
|
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.#
|
|
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
|
-
|
|
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
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
blockMessage = await new 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
|
-
|
|
940
|
-
|
|
897
|
+
catch (error) {
|
|
898
|
+
debug$1('No local block found');
|
|
941
899
|
}
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
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
|
-
|
|
967
|
-
|
|
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.
|
|
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
|
-
|
|
1017
|
-
|
|
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
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
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}
|
|
996
|
+
throw new ResolveError(`block: ${hash}`);
|
|
1065
997
|
}
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
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(
|
|
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
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
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
|
|
1106
|
-
|
|
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
|
-
|
|
1113
|
-
|
|
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
|
-
|
|
1128
|
-
|
|
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
|
|
1075
|
+
async resolveBlocks() {
|
|
1076
|
+
if (this.#syncing)
|
|
1077
|
+
return;
|
|
1137
1078
|
try {
|
|
1138
|
-
const
|
|
1139
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1155
|
-
|
|
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
|
-
|
|
1171
|
-
return 'connectionless';
|
|
1094
|
+
this.#syncing = true;
|
|
1172
1095
|
try {
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
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
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
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
|
-
|
|
1244
|
-
|
|
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
|
-
|
|
1249
|
-
|
|
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.
|
|
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
|
-
|
|
1421
|
-
return false;
|
|
1422
|
-
return true;
|
|
1293
|
+
return !this.#syncing;
|
|
1423
1294
|
}
|
|
1424
1295
|
get shouldSync() {
|
|
1425
|
-
if (this.#
|
|
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
|
-
|
|
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) => {
|
package/exports/state.d.ts
CHANGED
|
@@ -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
|
|
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:
|
|
57
|
-
resolveBlocks(): Promise<
|
|
58
|
-
|
|
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<
|
|
54
|
+
triggerSync(): Promise<"syncing" | "synced" | "errored" | "connectionless">;
|
|
64
55
|
triggerLoad(): Promise<void>;
|
|
65
56
|
}
|
|
66
|
-
export {};
|