@bancor/carbon-sdk
Version:
The SDK is a READ-ONLY tool, intended to facilitate working with Carbon contracts. It's a convenient wrapper around our matching algorithm, allowing programs and users get a ready to use transaction data that will allow them to manage strategies and fulfi
2 lines (1 loc) • 6.94 kB
JavaScript
import{Logger as e}from"../../common/logger/index.js";const t=new e("ChainSync.ts");class a{_fetcher;_chainCache;_syncCalled=!1;_slowPollPairs=!1;_uncachedPairs=[];_lastFetch=Date.now();_numOfPairsToBatch;_msToWaitBetweenSyncs;_chunkSize;_activeTimers=new Set;_isStopped=!1;_reinitializeSelf(){this._syncCalled=!1,this._slowPollPairs=!1,this._uncachedPairs=[],this._lastFetch=Date.now(),this._activeTimers=new Set,this._isStopped=!1}_resetSelf(){const e=this._syncCalled;this.stop(),this._reinitializeSelf(),this._chainCache.clear(),e&&this.startDataSync()}constructor(e,t,a=100,s=1e3,i=1e3){this._reinitializeSelf(),this._fetcher=e,this._chainCache=t,this._numOfPairsToBatch=a,this._msToWaitBetweenSyncs=s,this._chunkSize=i}stop(){t.debug("Stopping all ChainSync timers"),this._isStopped=!0,this._activeTimers.forEach((e=>clearTimeout(e))),this._activeTimers.clear()}_setTimeout(e,a){if(this._isStopped)return t.debug("Ignoring timer creation after stop() was called"),0;const s=Number(setTimeout((()=>{this._activeTimers.delete(s),e()}),a));return this._activeTimers.add(s),s}async startDataSync(){if(t.debug("startDataSync called"),this._syncCalled)throw new Error("ChainSync.startDataSync() can only be called once");this._syncCalled=!0;if(0===this._chainCache.getLatestBlockNumber()){t.debug("startDataSync - cache is new",arguments);const e=await this._fetcher.getBlockNumber();if("number"!=typeof e)throw t.error("Fatal! startDataSync - getBlockNumber returned value is not a number. This indicates a serious bug in the provider. At this point, the only thing we can do is to crash the program, as the cache cannot be set to a valid state."),new Error("Fatal! startDataSync - getBlockNumber returned value is not a number.");this._chainCache.applyEvents([],e)}await this._updateUncachedPairsFromChain(),await Promise.all([this._populateFeesData(this._uncachedPairs),this._populatePairsData(),this._syncEvents()])}async _updateUncachedPairsFromChain(){t.debug("_updateUncachedPairsFromChain fetches pairs");const e=await this._fetcher.pairs();t.debug("_updateUncachedPairsFromChain fetched pairs",e),this._lastFetch=Date.now(),0===e.length&&t.error("_updateUncachedPairsFromChain fetched no pairs - this indicates a problem"),this._uncachedPairs=e.filter((e=>!this._chainCache.hasCachedPair(e[0],e[1])))}async _populateFeesData(e){if(t.debug("populateFeesData called"),0===e.length)return void t.log("populateFeesData called with no pairs - skipping");const a=await this._fetcher.pairsTradingFeePPM(e);t.debug("populateFeesData fetched fee updates",a),a.forEach((e=>{this._chainCache.addPairFees(e[0],e[1],e[2])}))}async _populatePairsData(){t.debug("_populatePairsData called"),this._slowPollPairs=!1;const e=async()=>{try{if(0===this._uncachedPairs.length){if(this._slowPollPairs&&Date.now()-this._lastFetch<6e4)return void this._setTimeout(e,1e3);await this._updateUncachedPairsFromChain()}return this._uncachedPairs.length>0&&(t.debug("_populatePairsData will now sync data for",this._uncachedPairs),await this._syncPairDataBatch()),t.debug("_populatePairsData handled all pairs and goes to slow poll mode"),this._slowPollPairs=!0,void this._setTimeout(e,1e3)}catch(a){t.error("Error while syncing pairs data",a),this._setTimeout(e,6e4)}};await e()}async _syncPairDataBatch(){const e=[];for(let t=0;t<this._uncachedPairs.length;t+=this._numOfPairsToBatch)e.push(this._uncachedPairs.slice(t,t+this._numOfPairsToBatch));t.debug("_syncPairDataBatch batches",e);try{const a=await Promise.all(e.map((e=>this._fetcher.strategiesByPairs(e))));t.debug("_syncPairDataBatch strategiesBatches",a),this._chainCache.bulkAddPairs(a.flat()),this._uncachedPairs=[]}catch(e){throw t.error("Failed to fetch strategies for pairs batch:",e),e}}async syncPairData(e,a){if(!this._syncCalled)throw new Error("ChainSync.startDataSync() must be called before syncPairData()");try{const t=await this._fetcher.strategiesByPair(e,a);if(this._chainCache.hasCachedPair(e,a))return;this._chainCache.addPair(e,a,t)}catch(s){t.error("Failed to fetch strategies for pair:",e,a,s)}}async _syncEvents(){t.debug("_syncEvents called");const e=async()=>{t.debug("_syncEvents processEvents - new cycle started");try{const e=await this._fetcher.getBlockNumber();if("number"!=typeof e)throw t.error("_syncEvents - getBlockNumber returned value is not a number. This indicates a serious bug in the provider. Throwing an error in hope that the next iteration will get a valid number."),new Error("_syncEvents - getBlockNumber returned value is not a number.");const a=this._chainCache.getLatestBlockNumber();if(t.debug("_syncEvents processEvents - latestBlock (start point for new cycle)",a,"currentBlock",e),e>a){if(await this._detectReorg(e))return t.debug("_syncEvents detected reorg - resetting"),void this._resetSelf();t.debug("_syncEvents fetches events",a+1,e);const s=await this._fetcher.getEvents(a+1,e,this._chunkSize);t.debug("_syncEvents fetched events",s);const i=[];for(const e of s)if("StrategyCreated"===e.type){const a=e.data;this._chainCache.hasCachedPair(a.token0,a.token1)||(t.debug("_syncEvents noticed new pair created",a.token0,a.token1),i.push([a.token0,a.token1]))}this._chainCache.applyEvents(s,e),s.some((e=>"TradingFeePPMUpdated"===e.type))&&(t.debug("_syncEvents noticed at least one default fee update - refetching pair fees for all pairs"),await this._populateFeesData([...await this._fetcher.pairs()])),i.length>0&&(t.debug("_syncEvents noticed at least one new pair created - setting slow poll mode to false"),this._slowPollPairs=!1,t.debug("_syncEvents fetching fees for the new pairs"),await this._populateFeesData(i))}}catch(e){t.error("Error syncing events:",e)}this._setTimeout(e,this._msToWaitBetweenSyncs)};this._setTimeout(e,1)}async _detectReorg(e){t.debug("_detectReorg called");const a=this._chainCache.blocksMetadata,s={};for(const i of a){const{number:a,hash:r}=i;if(a>e)return t.log("reorg detected for block number",a,"larger than current block",e,"with hash",r),!0;try{const e=await this._fetcher.getBlock(a);if(!e||!e.hash||!e.number)return t.error("Failed to fetch block data for block number",a,"- treating as potential reorg"),!0;const c=e.hash;if(r!==c)return t.log("reorg detected for block number",a,"old hash",r,"new hash",c),!0;s[a]={number:i.number,hash:i.hash}}catch(e){return t.error("Error fetching block data for block number",a,":",e),!0}}t.debug("_detectReorg no reorg detected, updating blocks metadata");const i=[];for(let a=0;a<3;a++){const r=e-a;if(s[r])i.push(s[r]);else try{const e=await this._fetcher.getBlock(r);if(!e||!e.number||!e.hash){t.error("Failed to fetch new block data for block number",r,"- skipping this block");continue}i.push({number:e.number,hash:e.hash})}catch(e){t.error("Error fetching new block data for block number",r,":",e,"- skipping this block");continue}}return this._chainCache.blocksMetadata=i,t.debug("_detectReorg updated blocks metadata"),!1}}export{a as ChainSync};