import {BtcRelay, BtcStoredHeader, RelaySynchronizer, StatePredictorUtils} from "@atomiqlabs/base"; import {MempoolBitcoinBlock} from "../MempoolBitcoinBlock"; import {MempoolBitcoinRpc} from "../MempoolBitcoinRpc"; import {getLogger, timeoutPromise} from "../../../utils/Utils"; const logger = getLogger("MempoolBtcRelaySynchronizer: ") export class MempoolBtcRelaySynchronizer, TX> implements RelaySynchronizer { bitcoinRpc: MempoolBitcoinRpc; btcRelay: BtcRelay; constructor(btcRelay: BtcRelay, bitcoinRpc: MempoolBitcoinRpc) { this.btcRelay = btcRelay; this.bitcoinRpc = bitcoinRpc; } async syncToLatestTxs(signer: string, feeRate?: string): Promise<{ txs: TX[] targetCommitedHeader: B, computedHeaderMap: {[blockheight: number]: B}, blockHeaderMap: {[blockheight: number]: MempoolBitcoinBlock}, btcRelayTipCommitedHeader: B, btcRelayTipBlockHeader: MempoolBitcoinBlock, latestBlockHeader: MempoolBitcoinBlock, startForkId: number }> { const tipData = await this.btcRelay.getTipData(); let cacheData: { forkId: number, lastStoredHeader: B, tx: TX, computedCommitedHeaders: B[] } = { forkId: 0, lastStoredHeader: null, tx: null, computedCommitedHeaders: null }; const {resultStoredHeader, resultBitcoinHeader} = await this.btcRelay.retrieveLatestKnownBlockLog(); cacheData.lastStoredHeader = resultStoredHeader; if(resultStoredHeader.getBlockheight() { if(cacheData.forkId===-1) { if(mainFee==null) mainFee = await this.btcRelay.getMainFeeRate(signer); cacheData = await this.btcRelay.saveNewForkHeaders(signer, headerCache, cacheData.lastStoredHeader, tipData.chainWork, mainFee); } else if(cacheData.forkId===0) { if(mainFee==null) mainFee = await this.btcRelay.getMainFeeRate(signer); cacheData = await this.btcRelay.saveMainHeaders(signer, headerCache, cacheData.lastStoredHeader, mainFee); } else { if(forkFee==null) forkFee = await this.btcRelay.getForkFeeRate(signer, cacheData.forkId); cacheData = await this.btcRelay.saveForkHeaders(signer, headerCache, cacheData.lastStoredHeader, cacheData.forkId, tipData.chainWork, forkFee) } if(cacheData.forkId!==-1 && cacheData.forkId!==0) startForkId = cacheData.forkId; txsList.push(cacheData.tx); for(let storedHeader of cacheData.computedCommitedHeaders) { computedHeaderMap[storedHeader.getBlockheight()] = storedHeader; } }; let retrievedHeaders: MempoolBitcoinBlock[] = null; let headerCache: MempoolBitcoinBlock[] = []; while(retrievedHeaders==null || retrievedHeaders.length>0) { retrievedHeaders = await this.bitcoinRpc.getPast15Blocks(spvTipBlockHeight+15); let startIndex = retrievedHeaders.findIndex(val => val.height === spvTipBlockHeight); if(startIndex === -1) startIndex = retrievedHeaders.length; //Start from the last block for(let i=startIndex-1;i>=0;i--) { const header = retrievedHeaders[i]; blockHeaderMap[header.height] = header; headerCache.push(header); if(cacheData.forkId===0 ? headerCache.length>=this.btcRelay.maxHeadersPerTx : headerCache.length>=this.btcRelay.maxForkHeadersPerTx) { await saveHeaders(headerCache); headerCache = []; } } if(retrievedHeaders.length>0) { if(spvTipBlockHeight === retrievedHeaders[0].height) break; //Already at the tip spvTipBlockHeight = retrievedHeaders[0].height; await timeoutPromise(1000); } } if(headerCache.length>0) await saveHeaders(headerCache); if(cacheData.forkId!==0) { throw new Error("Unable to synchronize on-chain bitcoin light client! Not enough chainwork at connected RPC."); } return { txs: txsList, targetCommitedHeader: cacheData.lastStoredHeader, blockHeaderMap, computedHeaderMap, btcRelayTipCommitedHeader: resultStoredHeader, btcRelayTipBlockHeader: resultBitcoinHeader, latestBlockHeader: spvTipBlockHeader, startForkId }; } }