@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) • 7.27 kB
JavaScript
import{toPairKey as e}from"../utils/index.js";import{Logger as t}from"../../common/logger/index.js";const a=new t("ChainSync.ts");class s{_fetcher;_chainCache;_syncCalled=!1;_slowPollPairs=!1;_uncachedPairs=[];_lastFetch=Date.now();_numOfPairsToBatch;_msToWaitBetweenSyncs;_chunkSize;constructor(e,t,a=100,s=1e3,r=1e3){this._fetcher=e,this._chainCache=t,this._numOfPairsToBatch=a,this._msToWaitBetweenSyncs=s,this._chunkSize=r}async startDataSync(){if(a.debug("startDataSync called"),this._syncCalled)throw new Error("ChainSync.startDataSync() can only be called once");this._syncCalled=!0;if(0===this._chainCache.getLatestBlockNumber()){a.debug("startDataSync - cache is new",arguments);const e=await this._fetcher.getBlockNumber();if("number"!=typeof e)throw a.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.applyBatchedUpdates(e,[],[],[],[],[])}await this._updateUncachedPairsFromChain(),await Promise.all([this._populateFeesData(this._uncachedPairs),this._populatePairsData(),this._syncEvents()])}async _updateUncachedPairsFromChain(){a.debug("_updateUncachedPairsFromChain fetches pairs");const e=await this._fetcher.pairs();a.debug("_updateUncachedPairsFromChain fetched pairs",e),this._lastFetch=Date.now(),0===e.length&&a.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(a.debug("populateFeesData called"),0===e.length)return void a.log("populateFeesData called with no pairs - skipping");const t=await this._fetcher.pairsTradingFeePPM(e);a.debug("populateFeesData fetched fee updates",t),t.forEach((e=>{this._chainCache.addPairFees(e[0],e[1],e[2])}))}async _populatePairsData(){a.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 setTimeout(e,1e3);await this._updateUncachedPairsFromChain()}return this._uncachedPairs.length>0&&(a.debug("_populatePairsData will now sync data for",this._uncachedPairs),await this._syncPairDataBatch()),a.debug("_populatePairsData handled all pairs and goes to slow poll mode"),this._slowPollPairs=!0,void setTimeout(e,1e3)}catch(t){a.error("Error while syncing pairs data",t),setTimeout(e,6e4)}};setTimeout(e,1)}async _syncPairDataBatch(){const e=[];for(let t=0;t<this._uncachedPairs.length;t+=this._numOfPairsToBatch)e.push(this._uncachedPairs.slice(t,t+this._numOfPairsToBatch));try{(await Promise.all(e.map((e=>this._fetcher.strategiesByPairs(e))))).flat().forEach((e=>{this._chainCache.addPair(e.pair[0],e.pair[1],e.strategies,!0)})),this._uncachedPairs=[]}catch(e){throw a.error("Failed to fetch strategies for pairs batch:",e),e}}async syncPairData(e,t){if(!this._syncCalled)throw new Error("ChainSync.startDataSync() must be called before syncPairData()");const a=await this._fetcher.strategiesByPair(e,t);this._chainCache.hasCachedPair(e,t)||this._chainCache.addPair(e,t,a,!1)}_getBlockChunks(e,t,a){const s=[];for(let r=e;r<=t;r+=a){const e=r,i=Math.min(r+a-1,t);s.push([e,i])}return s}async _syncEvents(){a.debug("_syncEvents called");const t=async()=>{try{const s=await this._fetcher.getBlockNumber();if("number"!=typeof s)throw a.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 r=this._chainCache.getLatestBlockNumber();if(s>r){if(await this._detectReorg(s))return a.debug("_syncEvents detected reorg - resetting"),this._chainCache.clear(),this._chainCache.applyBatchedUpdates(s,[],[],[],[],[]),this._resetPairsFetching(),void setTimeout(t,1);const i=new Set(this._chainCache.getCachedPairs(!1).map((t=>e(t[0],t[1]))));a.debug("_syncEvents fetches events",r+1,s);const n=this._getBlockChunks(r+1,s,this._chunkSize);a.debug("_syncEvents block chunks",n);const c=[],h=[],o=[],l=[],d=[],u=[];for(const e of n){a.debug("_syncEvents fetches events for chunk",e);const t=await this._fetcher.getLatestStrategyCreatedStrategies(e[0],e[1]),s=await this._fetcher.getLatestStrategyUpdatedStrategies(e[0],e[1]),r=await this._fetcher.getLatestStrategyDeletedStrategies(e[0],e[1]),i=await this._fetcher.getLatestTokensTradedTrades(e[0],e[1]),_=await this._fetcher.getLatestPairTradingFeeUpdates(e[0],e[1]),g=await this._fetcher.getLatestTradingFeeUpdates(e[0],e[1]);c.push(t),h.push(s),o.push(r),l.push(i),d.push(_),u.push(g),a.debug("_syncEvents fetched the following events for chunks",n,{createdStrategiesChunk:t,updatedStrategiesChunk:s,deletedStrategiesChunk:r,tradesChunk:i,feeUpdatesChunk:_,defaultFeeUpdatesChunk:g})}const _=c.flat(),g=h.flat(),f=o.flat(),p=l.flat(),b=d.flat(),m=u.flat().length>0;a.debug("_syncEvents fetched events",_,g,f,p,b,m);const y=[];for(const e of _)this._chainCache.hasCachedPair(e.token0,e.token1)||y.push([e.token0,e.token1]);this._chainCache.applyBatchedUpdates(s,b,p.filter((t=>i.has(e(t.sourceToken,t.targetToken)))),_.filter((t=>i.has(e(t.token0,t.token1)))),g.filter((t=>i.has(e(t.token0,t.token1)))),f.filter((t=>i.has(e(t.token0,t.token1))))),m&&(a.debug("_syncEvents noticed at least one default fee update - refetching pair fees for all pairs"),await this._populateFeesData([...await this._fetcher.pairs()])),y.length>0&&(a.debug("_syncEvents noticed at least one new pair created - setting slow poll mode to false"),this._slowPollPairs=!1,a.debug("_syncEvents fetching fees for the new pairs"),await this._populateFeesData(y))}}catch(e){a.error("Error syncing events:",e)}setTimeout(t,this._msToWaitBetweenSyncs)};setTimeout(t,1)}_resetPairsFetching(){this._uncachedPairs=[],this._slowPollPairs=!1}async _detectReorg(e){a.debug("_detectReorg called");const t=this._chainCache.blocksMetadata,s={};for(const r of t){const{number:t,hash:i}=r;if(t>e)return a.log("reorg detected for block number",t,"larger than current block",e,"with hash",i),!0;try{const e=await this._fetcher.getBlock(t);if(!e||!e.hash||!e.number)return a.error("Failed to fetch block data for block number",t,"- treating as potential reorg"),!0;const n=e.hash;if(i!==n)return a.log("reorg detected for block number",t,"old hash",i,"new hash",n),!0;s[t]={number:r.number,hash:r.hash}}catch(e){return a.error("Error fetching block data for block number",t,":",e),!0}}a.debug("_detectReorg no reorg detected, updating blocks metadata");const r=[];for(let t=0;t<3;t++){const i=e-t;if(s[i])r.push(s[i]);else try{const e=await this._fetcher.getBlock(i);if(!e||!e.number||!e.hash){a.error("Failed to fetch new block data for block number",i,"- skipping this block");continue}r.push({number:e.number,hash:e.hash})}catch(e){a.error("Error fetching new block data for block number",i,":",e,"- skipping this block");continue}}return this._chainCache.blocksMetadata=r,a.debug("_detectReorg updated blocks metadata"),!1}}export{s as ChainSync};