@atomiqlabs/chain-evm 1.0.0-dev.22

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.
Files changed (146) hide show
  1. package/LICENSE +201 -0
  2. package/dist/chains/citrea/CitreaChainType.d.ts +13 -0
  3. package/dist/chains/citrea/CitreaChainType.js +2 -0
  4. package/dist/chains/citrea/CitreaInitializer.d.ts +30 -0
  5. package/dist/chains/citrea/CitreaInitializer.js +120 -0
  6. package/dist/evm/btcrelay/BtcRelayAbi.d.ts +198 -0
  7. package/dist/evm/btcrelay/BtcRelayAbi.js +261 -0
  8. package/dist/evm/btcrelay/BtcRelayTypechain.d.ts +172 -0
  9. package/dist/evm/btcrelay/BtcRelayTypechain.js +2 -0
  10. package/dist/evm/btcrelay/EVMBtcRelay.d.ts +188 -0
  11. package/dist/evm/btcrelay/EVMBtcRelay.js +419 -0
  12. package/dist/evm/btcrelay/headers/EVMBtcHeader.d.ts +33 -0
  13. package/dist/evm/btcrelay/headers/EVMBtcHeader.js +84 -0
  14. package/dist/evm/btcrelay/headers/EVMBtcStoredHeader.d.ts +56 -0
  15. package/dist/evm/btcrelay/headers/EVMBtcStoredHeader.js +123 -0
  16. package/dist/evm/chain/EVMChainInterface.d.ts +51 -0
  17. package/dist/evm/chain/EVMChainInterface.js +90 -0
  18. package/dist/evm/chain/EVMModule.d.ts +9 -0
  19. package/dist/evm/chain/EVMModule.js +13 -0
  20. package/dist/evm/chain/modules/ERC20Abi.d.ts +168 -0
  21. package/dist/evm/chain/modules/ERC20Abi.js +225 -0
  22. package/dist/evm/chain/modules/EVMAddresses.d.ts +9 -0
  23. package/dist/evm/chain/modules/EVMAddresses.js +26 -0
  24. package/dist/evm/chain/modules/EVMBlocks.d.ts +20 -0
  25. package/dist/evm/chain/modules/EVMBlocks.js +64 -0
  26. package/dist/evm/chain/modules/EVMEvents.d.ts +36 -0
  27. package/dist/evm/chain/modules/EVMEvents.js +122 -0
  28. package/dist/evm/chain/modules/EVMFees.d.ts +35 -0
  29. package/dist/evm/chain/modules/EVMFees.js +73 -0
  30. package/dist/evm/chain/modules/EVMSignatures.d.ts +29 -0
  31. package/dist/evm/chain/modules/EVMSignatures.js +68 -0
  32. package/dist/evm/chain/modules/EVMTokens.d.ts +49 -0
  33. package/dist/evm/chain/modules/EVMTokens.js +105 -0
  34. package/dist/evm/chain/modules/EVMTransactions.d.ts +89 -0
  35. package/dist/evm/chain/modules/EVMTransactions.js +216 -0
  36. package/dist/evm/contract/EVMContractBase.d.ts +22 -0
  37. package/dist/evm/contract/EVMContractBase.js +34 -0
  38. package/dist/evm/contract/EVMContractModule.d.ts +8 -0
  39. package/dist/evm/contract/EVMContractModule.js +11 -0
  40. package/dist/evm/contract/modules/EVMContractEvents.d.ts +42 -0
  41. package/dist/evm/contract/modules/EVMContractEvents.js +75 -0
  42. package/dist/evm/events/EVMChainEvents.d.ts +22 -0
  43. package/dist/evm/events/EVMChainEvents.js +67 -0
  44. package/dist/evm/events/EVMChainEventsBrowser.d.ts +86 -0
  45. package/dist/evm/events/EVMChainEventsBrowser.js +294 -0
  46. package/dist/evm/spv_swap/EVMSpvVaultContract.d.ts +64 -0
  47. package/dist/evm/spv_swap/EVMSpvVaultContract.js +410 -0
  48. package/dist/evm/spv_swap/EVMSpvVaultData.d.ts +38 -0
  49. package/dist/evm/spv_swap/EVMSpvVaultData.js +159 -0
  50. package/dist/evm/spv_swap/EVMSpvWithdrawalData.d.ts +19 -0
  51. package/dist/evm/spv_swap/EVMSpvWithdrawalData.js +55 -0
  52. package/dist/evm/spv_swap/SpvVaultContractAbi.d.ts +91 -0
  53. package/dist/evm/spv_swap/SpvVaultContractAbi.js +849 -0
  54. package/dist/evm/spv_swap/SpvVaultContractTypechain.d.ts +450 -0
  55. package/dist/evm/spv_swap/SpvVaultContractTypechain.js +2 -0
  56. package/dist/evm/swaps/EVMSwapContract.d.ts +192 -0
  57. package/dist/evm/swaps/EVMSwapContract.js +373 -0
  58. package/dist/evm/swaps/EVMSwapData.d.ts +64 -0
  59. package/dist/evm/swaps/EVMSwapData.js +254 -0
  60. package/dist/evm/swaps/EVMSwapModule.d.ts +9 -0
  61. package/dist/evm/swaps/EVMSwapModule.js +11 -0
  62. package/dist/evm/swaps/EscrowManagerAbi.d.ts +120 -0
  63. package/dist/evm/swaps/EscrowManagerAbi.js +985 -0
  64. package/dist/evm/swaps/EscrowManagerTypechain.d.ts +475 -0
  65. package/dist/evm/swaps/EscrowManagerTypechain.js +2 -0
  66. package/dist/evm/swaps/handlers/IHandler.d.ts +13 -0
  67. package/dist/evm/swaps/handlers/IHandler.js +2 -0
  68. package/dist/evm/swaps/handlers/claim/ClaimHandlers.d.ts +10 -0
  69. package/dist/evm/swaps/handlers/claim/ClaimHandlers.js +13 -0
  70. package/dist/evm/swaps/handlers/claim/HashlockClaimHandler.d.ts +20 -0
  71. package/dist/evm/swaps/handlers/claim/HashlockClaimHandler.js +39 -0
  72. package/dist/evm/swaps/handlers/claim/btc/BitcoinNoncedOutputClaimHandler.d.ts +24 -0
  73. package/dist/evm/swaps/handlers/claim/btc/BitcoinNoncedOutputClaimHandler.js +59 -0
  74. package/dist/evm/swaps/handlers/claim/btc/BitcoinOutputClaimHandler.d.ts +25 -0
  75. package/dist/evm/swaps/handlers/claim/btc/BitcoinOutputClaimHandler.js +51 -0
  76. package/dist/evm/swaps/handlers/claim/btc/BitcoinTxIdClaimHandler.d.ts +21 -0
  77. package/dist/evm/swaps/handlers/claim/btc/BitcoinTxIdClaimHandler.js +28 -0
  78. package/dist/evm/swaps/handlers/claim/btc/IBitcoinClaimHandler.d.ts +48 -0
  79. package/dist/evm/swaps/handlers/claim/btc/IBitcoinClaimHandler.js +63 -0
  80. package/dist/evm/swaps/handlers/refund/TimelockRefundHandler.d.ts +17 -0
  81. package/dist/evm/swaps/handlers/refund/TimelockRefundHandler.js +28 -0
  82. package/dist/evm/swaps/modules/EVMLpVault.d.ts +69 -0
  83. package/dist/evm/swaps/modules/EVMLpVault.js +131 -0
  84. package/dist/evm/swaps/modules/EVMSwapClaim.d.ts +53 -0
  85. package/dist/evm/swaps/modules/EVMSwapClaim.js +101 -0
  86. package/dist/evm/swaps/modules/EVMSwapInit.d.ts +88 -0
  87. package/dist/evm/swaps/modules/EVMSwapInit.js +241 -0
  88. package/dist/evm/swaps/modules/EVMSwapRefund.d.ts +62 -0
  89. package/dist/evm/swaps/modules/EVMSwapRefund.js +132 -0
  90. package/dist/evm/typechain/common.d.ts +50 -0
  91. package/dist/evm/typechain/common.js +2 -0
  92. package/dist/evm/wallet/EVMSigner.d.ts +9 -0
  93. package/dist/evm/wallet/EVMSigner.js +16 -0
  94. package/dist/index.d.ts +37 -0
  95. package/dist/index.js +53 -0
  96. package/dist/utils/Utils.d.ts +15 -0
  97. package/dist/utils/Utils.js +71 -0
  98. package/package.json +37 -0
  99. package/src/chains/citrea/CitreaChainType.ts +28 -0
  100. package/src/chains/citrea/CitreaInitializer.ts +167 -0
  101. package/src/evm/btcrelay/BtcRelayAbi.ts +258 -0
  102. package/src/evm/btcrelay/BtcRelayTypechain.ts +371 -0
  103. package/src/evm/btcrelay/EVMBtcRelay.ts +517 -0
  104. package/src/evm/btcrelay/headers/EVMBtcHeader.ts +110 -0
  105. package/src/evm/btcrelay/headers/EVMBtcStoredHeader.ts +153 -0
  106. package/src/evm/chain/EVMChainInterface.ts +157 -0
  107. package/src/evm/chain/EVMModule.ts +21 -0
  108. package/src/evm/chain/modules/ERC20Abi.ts +222 -0
  109. package/src/evm/chain/modules/EVMAddresses.ts +24 -0
  110. package/src/evm/chain/modules/EVMBlocks.ts +75 -0
  111. package/src/evm/chain/modules/EVMEvents.ts +139 -0
  112. package/src/evm/chain/modules/EVMFees.ts +105 -0
  113. package/src/evm/chain/modules/EVMSignatures.ts +76 -0
  114. package/src/evm/chain/modules/EVMTokens.ts +115 -0
  115. package/src/evm/chain/modules/EVMTransactions.ts +246 -0
  116. package/src/evm/contract/EVMContractBase.ts +63 -0
  117. package/src/evm/contract/EVMContractModule.ts +16 -0
  118. package/src/evm/contract/modules/EVMContractEvents.ts +102 -0
  119. package/src/evm/events/EVMChainEvents.ts +81 -0
  120. package/src/evm/events/EVMChainEventsBrowser.ts +390 -0
  121. package/src/evm/spv_swap/EVMSpvVaultContract.ts +533 -0
  122. package/src/evm/spv_swap/EVMSpvVaultData.ts +201 -0
  123. package/src/evm/spv_swap/EVMSpvWithdrawalData.ts +70 -0
  124. package/src/evm/spv_swap/SpvVaultContractAbi.ts +846 -0
  125. package/src/evm/spv_swap/SpvVaultContractTypechain.ts +685 -0
  126. package/src/evm/swaps/EVMSwapContract.ts +590 -0
  127. package/src/evm/swaps/EVMSwapData.ts +367 -0
  128. package/src/evm/swaps/EVMSwapModule.ts +16 -0
  129. package/src/evm/swaps/EscrowManagerAbi.ts +982 -0
  130. package/src/evm/swaps/EscrowManagerTypechain.ts +723 -0
  131. package/src/evm/swaps/handlers/IHandler.ts +17 -0
  132. package/src/evm/swaps/handlers/claim/ClaimHandlers.ts +20 -0
  133. package/src/evm/swaps/handlers/claim/HashlockClaimHandler.ts +47 -0
  134. package/src/evm/swaps/handlers/claim/btc/BitcoinNoncedOutputClaimHandler.ts +82 -0
  135. package/src/evm/swaps/handlers/claim/btc/BitcoinOutputClaimHandler.ts +76 -0
  136. package/src/evm/swaps/handlers/claim/btc/BitcoinTxIdClaimHandler.ts +46 -0
  137. package/src/evm/swaps/handlers/claim/btc/IBitcoinClaimHandler.ts +115 -0
  138. package/src/evm/swaps/handlers/refund/TimelockRefundHandler.ts +38 -0
  139. package/src/evm/swaps/modules/EVMLpVault.ts +153 -0
  140. package/src/evm/swaps/modules/EVMSwapClaim.ts +141 -0
  141. package/src/evm/swaps/modules/EVMSwapInit.ts +292 -0
  142. package/src/evm/swaps/modules/EVMSwapRefund.ts +198 -0
  143. package/src/evm/typechain/common.ts +131 -0
  144. package/src/evm/wallet/EVMSigner.ts +23 -0
  145. package/src/index.ts +44 -0
  146. package/src/utils/Utils.ts +81 -0
@@ -0,0 +1,517 @@
1
+ import {BitcoinNetwork, BitcoinRpc, BtcBlock, BtcRelay, RelaySynchronizer, StatePredictorUtils} from "@atomiqlabs/base";
2
+ import {EVMBtcHeader} from "./headers/EVMBtcHeader";
3
+ import {getLogger, tryWithRetries} from "../../utils/Utils";
4
+ import {EVMContractBase, TypedFunctionCall} from "../contract/EVMContractBase";
5
+ import {BtcRelay as BtcRelayTypechain} from "./BtcRelayTypechain";
6
+ import {EVMBtcStoredHeader} from "./headers/EVMBtcStoredHeader";
7
+ import {EVMSigner} from "../wallet/EVMSigner";
8
+ import {EVMTx, EVMTxTrace} from "../chain/modules/EVMTransactions";
9
+ import {EVMFees} from "../chain/modules/EVMFees";
10
+ import {EVMChainInterface} from "../chain/EVMChainInterface";
11
+ import {BtcRelayAbi} from "./BtcRelayAbi";
12
+ import {AbiCoder, hexlify} from "ethers";
13
+
14
+ function serializeBlockHeader(e: BtcBlock): EVMBtcHeader {
15
+ return new EVMBtcHeader({
16
+ version: e.getVersion(),
17
+ previousBlockhash: Buffer.from(e.getPrevBlockhash(), "hex").reverse(),
18
+ merkleRoot: Buffer.from(e.getMerkleRoot(), "hex").reverse(),
19
+ timestamp: e.getTimestamp(),
20
+ nbits: e.getNbits(),
21
+ nonce: e.getNonce(),
22
+ hash: Buffer.from(e.getHash(), "hex").reverse()
23
+ });
24
+ }
25
+
26
+ const GAS_PER_BLOCKHEADER = 30_000;
27
+ const GAS_BASE_MAIN = 15_000;
28
+ const GAS_PER_BLOCKHEADER_FORK = 65_000;
29
+ const GAS_PER_BLOCKHEADER_FORKED = 10_000;
30
+ const GAS_BASE_FORK = 25_000;
31
+
32
+ const logger = getLogger("EVMBtcRelay: ");
33
+
34
+ export class EVMBtcRelay<B extends BtcBlock>
35
+ extends EVMContractBase<BtcRelayTypechain>
36
+ implements BtcRelay<EVMBtcStoredHeader, EVMTx, B, EVMSigner> {
37
+
38
+ public async SaveMainHeaders(signer: string, mainHeaders: EVMBtcHeader[], storedHeader: EVMBtcStoredHeader, feeRate: string): Promise<EVMTx> {
39
+ const tx = await this.contract.submitMainBlockheaders.populateTransaction(Buffer.concat([
40
+ storedHeader.serialize(),
41
+ Buffer.concat(mainHeaders.map(header => header.serializeCompact()))
42
+ ]));
43
+ tx.from = signer;
44
+ EVMFees.applyFeeRate(tx, GAS_BASE_MAIN + (GAS_PER_BLOCKHEADER * mainHeaders.length), feeRate);
45
+ return tx;
46
+ }
47
+
48
+ public async SaveShortForkHeaders(signer: string, forkHeaders: EVMBtcHeader[], storedHeader: EVMBtcStoredHeader, feeRate: string): Promise<EVMTx> {
49
+ const tx = await this.contract.submitShortForkBlockheaders.populateTransaction(Buffer.concat([
50
+ storedHeader.serialize(),
51
+ Buffer.concat(forkHeaders.map(header => header.serializeCompact()))
52
+ ]));
53
+ tx.from = signer;
54
+ EVMFees.applyFeeRate(tx, GAS_BASE_MAIN + (GAS_PER_BLOCKHEADER * forkHeaders.length), feeRate);
55
+ return tx;
56
+ }
57
+
58
+ public async SaveLongForkHeaders(signer: string, forkId: number, forkHeaders: EVMBtcHeader[], storedHeader: EVMBtcStoredHeader, feeRate: string, totalForkHeaders: number = 100): Promise<EVMTx> {
59
+ const tx = await this.contract.submitForkBlockheaders.populateTransaction(forkId, Buffer.concat([
60
+ storedHeader.serialize(),
61
+ Buffer.concat(forkHeaders.map(header => header.serializeCompact()))
62
+ ]));
63
+ tx.from = signer;
64
+ EVMFees.applyFeeRate(tx, GAS_BASE_FORK + (GAS_PER_BLOCKHEADER_FORK * forkHeaders.length) + (GAS_PER_BLOCKHEADER_FORKED * totalForkHeaders), feeRate);
65
+ return tx;
66
+ }
67
+
68
+ bitcoinRpc: BitcoinRpc<B>;
69
+
70
+ readonly maxHeadersPerTx: number = 100;
71
+ readonly maxForkHeadersPerTx: number = 50;
72
+ readonly maxShortForkHeadersPerTx: number = 100;
73
+
74
+ constructor(
75
+ chainInterface: EVMChainInterface<any>,
76
+ bitcoinRpc: BitcoinRpc<B>,
77
+ bitcoinNetwork: BitcoinNetwork,
78
+ contractAddress: string,
79
+ contractDeploymentHeight?: number
80
+ ) {
81
+ super(chainInterface, contractAddress, BtcRelayAbi, contractDeploymentHeight);
82
+ this.bitcoinRpc = bitcoinRpc;
83
+ }
84
+
85
+ /**
86
+ * Computes subsequent commited headers as they will appear on the blockchain when transactions
87
+ * are submitted & confirmed
88
+ *
89
+ * @param initialStoredHeader
90
+ * @param syncedHeaders
91
+ * @private
92
+ */
93
+ private computeCommitedHeaders(initialStoredHeader: EVMBtcStoredHeader, syncedHeaders: EVMBtcHeader[]) {
94
+ const computedCommitedHeaders = [initialStoredHeader];
95
+ for(let blockHeader of syncedHeaders) {
96
+ computedCommitedHeaders.push(computedCommitedHeaders[computedCommitedHeaders.length-1].computeNext(blockHeader));
97
+ }
98
+ return computedCommitedHeaders;
99
+ }
100
+
101
+ /**
102
+ * A common logic for submitting blockheaders in a transaction
103
+ *
104
+ * @param signer
105
+ * @param headers headers to sync to the btc relay
106
+ * @param storedHeader current latest stored block header for a given fork
107
+ * @param tipWork work of the current tip in a given fork
108
+ * @param forkId forkId to submit to, forkId=0 means main chain, forkId=-1 means short fork
109
+ * @param feeRate feeRate for the transaction
110
+ * @param totalForkHeaders Total number of headers in a fork
111
+ * @private
112
+ */
113
+ private async _saveHeaders(
114
+ signer: string,
115
+ headers: BtcBlock[],
116
+ storedHeader: EVMBtcStoredHeader,
117
+ tipWork: Buffer,
118
+ forkId: number,
119
+ feeRate: string,
120
+ totalForkHeaders: number
121
+ ) {
122
+ const blockHeaderObj = headers.map(serializeBlockHeader);
123
+ let tx: EVMTx;
124
+ switch(forkId) {
125
+ case -1:
126
+ tx = await this.SaveShortForkHeaders(signer, blockHeaderObj, storedHeader, feeRate);
127
+ break;
128
+ case 0:
129
+ tx = await this.SaveMainHeaders(signer, blockHeaderObj, storedHeader, feeRate);
130
+ break;
131
+ default:
132
+ tx = await this.SaveLongForkHeaders(signer, forkId, blockHeaderObj, storedHeader, feeRate, totalForkHeaders);
133
+ break;
134
+ }
135
+
136
+ const computedCommitedHeaders = this.computeCommitedHeaders(storedHeader, blockHeaderObj);
137
+ const lastStoredHeader = computedCommitedHeaders[computedCommitedHeaders.length-1];
138
+ if(forkId!==0 && StatePredictorUtils.gtBuffer(lastStoredHeader.getBlockHash(), tipWork)) {
139
+ //Fork's work is higher than main chain's work, this fork will become a main chain
140
+ forkId = 0;
141
+ }
142
+
143
+ return {
144
+ forkId: forkId,
145
+ lastStoredHeader,
146
+ tx,
147
+ computedCommitedHeaders
148
+ }
149
+ }
150
+
151
+ private async findStoredBlockheaderInTraces(txTrace: EVMTxTrace, commitHash: string): Promise<EVMBtcStoredHeader> {
152
+ if(txTrace.to.toLowerCase() === (await this.contract.getAddress()).toLowerCase()) {
153
+ let dataBuffer: Buffer;
154
+ if(txTrace.type==="CREATE") {
155
+ dataBuffer = Buffer.from(txTrace.input.substring(txTrace.input.length-384, txTrace.input.length-64), "hex");
156
+ } else {
157
+ const result = this.parseCalldata(txTrace.input);
158
+ if(result!=null) {
159
+ if(result.name==="submitMainBlockheaders" || result.name==="submitShortForkBlockheaders") {
160
+ const functionCall: TypedFunctionCall<
161
+ typeof this.contract.submitMainBlockheaders |
162
+ typeof this.contract.submitShortForkBlockheaders
163
+ > = result;
164
+ dataBuffer = Buffer.from(hexlify(functionCall.args[0]).substring(2), "hex");
165
+ } else if(result.name==="submitForkBlockheaders") {
166
+ const functionCall: TypedFunctionCall<typeof this.contract.submitForkBlockheaders> = result;
167
+ dataBuffer = Buffer.from(hexlify(functionCall.args[1]).substring(2), "hex");
168
+ }
169
+ }
170
+ }
171
+ if(dataBuffer!=null) {
172
+ let storedHeader = EVMBtcStoredHeader.deserialize(dataBuffer.subarray(0, 160));
173
+ if(storedHeader.getCommitHash()===commitHash) return storedHeader;
174
+ for(let i = 160; i < dataBuffer.length; i+=48) {
175
+ const blockHeader = EVMBtcHeader.deserialize(dataBuffer.subarray(i, i + 48));
176
+ storedHeader = storedHeader.computeNext(blockHeader);
177
+ if(storedHeader.getCommitHash()===commitHash) return storedHeader;
178
+ }
179
+ }
180
+ }
181
+
182
+ if(txTrace.calls!=null) {
183
+ for(let call of txTrace.calls) {
184
+ const result = await this.findStoredBlockheaderInTraces(call, commitHash);
185
+ if(result!=null) return result;
186
+ }
187
+ }
188
+
189
+ return null;
190
+ }
191
+
192
+ private getBlock(commitHash?: string, blockHash?: Buffer): Promise<[EVMBtcStoredHeader, string] | null> {
193
+ return this.Events.findInContractEvents(
194
+ ["StoreHeader", "StoreForkHeader"],
195
+ [
196
+ commitHash,
197
+ blockHash==null ? null : "0x"+Buffer.from([...blockHash]).reverse().toString("hex")
198
+ ],
199
+ async (event) => {
200
+ const txTrace = await this.Chain.Transactions.traceTransaction(event.transactionHash);
201
+ const storedBlockheader = await this.findStoredBlockheaderInTraces(txTrace, event.args.commitHash);
202
+ if(storedBlockheader!=null) return [storedBlockheader, event.args.commitHash];
203
+ }
204
+ );
205
+ }
206
+
207
+ private async getBlockHeight(): Promise<number> {
208
+ return Number(await this.contract.getBlockheight());
209
+ }
210
+
211
+ /**
212
+ * Returns data about current main chain tip stored in the btc relay
213
+ */
214
+ public async getTipData(): Promise<{ commitHash: string; blockhash: string, chainWork: Buffer, blockheight: number }> {
215
+ const commitHash = await this.contract.getTipCommitHash();
216
+ if(commitHash==null || BigInt(commitHash)===BigInt(0)) return null;
217
+
218
+ const result = await this.getBlock(commitHash);
219
+ if(result==null) return null;
220
+
221
+ const storedBlockHeader = result[0];
222
+
223
+ return {
224
+ blockheight: storedBlockHeader.getBlockheight(),
225
+ commitHash: commitHash,
226
+ blockhash: storedBlockHeader.getBlockHash().toString("hex"),
227
+ chainWork: storedBlockHeader.getChainWork()
228
+ };
229
+ }
230
+
231
+ /**
232
+ * Retrieves blockheader with a specific blockhash, returns null if requiredBlockheight is provided and
233
+ * btc relay contract is not synced up to the desired blockheight
234
+ *
235
+ * @param blockData
236
+ * @param requiredBlockheight
237
+ */
238
+ public async retrieveLogAndBlockheight(blockData: {blockhash: string}, requiredBlockheight?: number): Promise<{
239
+ header: EVMBtcStoredHeader,
240
+ height: number
241
+ } | null> {
242
+ //TODO: we can fetch the blockheight and events in parallel
243
+ const blockHeight = await this.getBlockHeight();
244
+ if(requiredBlockheight!=null && blockHeight < requiredBlockheight) {
245
+ return null;
246
+ }
247
+ const result = await this.getBlock(null, Buffer.from(blockData.blockhash, "hex"));
248
+ if(result==null) return null;
249
+
250
+ const [storedBlockHeader, commitHash] = result;
251
+
252
+ //Check if block is part of the main chain
253
+ const chainCommitment = await this.contract.getCommitHash(storedBlockHeader.blockHeight);
254
+ if(chainCommitment!==commitHash) return null;
255
+
256
+ logger.debug("retrieveLogAndBlockheight(): block found," +
257
+ " commit hash: "+commitHash+" blockhash: "+blockData.blockhash+" current btc relay height: "+blockHeight);
258
+
259
+ return {header: storedBlockHeader, height: blockHeight};
260
+ }
261
+
262
+ /**
263
+ * Retrieves blockheader data by blockheader's commit hash,
264
+ *
265
+ * @param commitmentHashStr
266
+ * @param blockData
267
+ */
268
+ public async retrieveLogByCommitHash(commitmentHashStr: string, blockData: {blockhash: string}): Promise<EVMBtcStoredHeader> {
269
+ const result = await this.getBlock(commitmentHashStr, Buffer.from(blockData.blockhash, "hex"));
270
+ if(result==null) return null;
271
+
272
+ const [storedBlockHeader, commitHash] = result;
273
+
274
+ //Check if block is part of the main chain
275
+ const chainCommitment = await this.contract.getCommitHash(storedBlockHeader.blockHeight);
276
+ if(chainCommitment!==commitHash) return null;
277
+
278
+ logger.debug("retrieveLogByCommitHash(): block found," +
279
+ " commit hash: "+commitmentHashStr+" blockhash: "+blockData.blockhash+" height: "+storedBlockHeader.blockHeight);
280
+
281
+ return storedBlockHeader;
282
+ }
283
+
284
+ /**
285
+ * Retrieves latest known stored blockheader & blockheader from bitcoin RPC that is in the main chain
286
+ */
287
+ public async retrieveLatestKnownBlockLog(): Promise<{
288
+ resultStoredHeader: EVMBtcStoredHeader,
289
+ resultBitcoinHeader: B
290
+ }> {
291
+ const data = await this.Events.findInContractEvents(
292
+ ["StoreHeader", "StoreForkHeader"],
293
+ null,
294
+ async (event) => {
295
+ const blockHashHex = Buffer.from(event.args.blockHash.substring(2), "hex").reverse().toString("hex");
296
+ const commitHash = event.args.commitHash;
297
+
298
+ const isInBtcMainChain = await this.bitcoinRpc.isInMainChain(blockHashHex).catch(() => false);
299
+ if(!isInBtcMainChain) return null;
300
+
301
+ const blockHeader = await this.bitcoinRpc.getBlockHeader(blockHashHex);
302
+
303
+ if(commitHash !== await this.contract.getCommitHash(blockHeader.getHeight())) return null;
304
+
305
+ const txTrace = await this.Chain.Transactions.traceTransaction(event.transactionHash);
306
+ const storedHeader = await this.findStoredBlockheaderInTraces(txTrace, commitHash);
307
+ if(storedHeader==null) return null;
308
+
309
+ return {
310
+ resultStoredHeader: storedHeader,
311
+ resultBitcoinHeader: blockHeader,
312
+ commitHash: commitHash
313
+ }
314
+ }
315
+ )
316
+
317
+ if(data!=null) logger.debug("retrieveLatestKnownBlockLog(): block found," +
318
+ " commit hash: "+data.commitHash+" blockhash: "+data.resultBitcoinHeader.getHash()+
319
+ " height: "+data.resultStoredHeader.getBlockheight());
320
+
321
+ return data;
322
+ }
323
+
324
+ /**
325
+ * Saves blockheaders as a bitcoin main chain to the btc relay
326
+ *
327
+ * @param signer
328
+ * @param mainHeaders
329
+ * @param storedHeader
330
+ * @param feeRate
331
+ */
332
+ public async saveMainHeaders(signer: string, mainHeaders: BtcBlock[], storedHeader: EVMBtcStoredHeader, feeRate?: string) {
333
+ feeRate ??= await this.Chain.Fees.getFeeRate();
334
+ logger.debug("saveMainHeaders(): submitting main blockheaders, count: "+mainHeaders.length);
335
+ return this._saveHeaders(signer, mainHeaders, storedHeader, null, 0, feeRate, 0);
336
+ }
337
+
338
+ /**
339
+ * Creates a new long fork and submits the headers to it
340
+ *
341
+ * @param signer
342
+ * @param forkHeaders
343
+ * @param storedHeader
344
+ * @param tipWork
345
+ * @param feeRate
346
+ */
347
+ public async saveNewForkHeaders(signer: string, forkHeaders: BtcBlock[], storedHeader: EVMBtcStoredHeader, tipWork: Buffer, feeRate?: string) {
348
+ let forkId: number = Math.floor(Math.random() * 0xFFFFFFFFFFFF);
349
+ feeRate ??= await this.Chain.Fees.getFeeRate();
350
+
351
+ logger.debug("saveNewForkHeaders(): submitting new fork & blockheaders," +
352
+ " count: "+forkHeaders.length+" forkId: 0x"+forkId.toString(16));
353
+
354
+ return await this._saveHeaders(signer, forkHeaders, storedHeader, tipWork, forkId, feeRate, forkHeaders.length);
355
+ }
356
+
357
+ /**
358
+ * Continues submitting blockheaders to a given fork
359
+ *
360
+ * @param signer
361
+ * @param forkHeaders
362
+ * @param storedHeader
363
+ * @param forkId
364
+ * @param tipWork
365
+ * @param feeRate
366
+ */
367
+ public async saveForkHeaders(signer: string, forkHeaders: BtcBlock[], storedHeader: EVMBtcStoredHeader, forkId: number, tipWork: Buffer, feeRate?: string) {
368
+ feeRate ??= await this.Chain.Fees.getFeeRate();
369
+
370
+ logger.debug("saveForkHeaders(): submitting blockheaders to existing fork," +
371
+ " count: "+forkHeaders.length+" forkId: 0x"+forkId.toString(16));
372
+
373
+ return this._saveHeaders(signer, forkHeaders, storedHeader, tipWork, forkId, feeRate, 100);
374
+ }
375
+
376
+ /**
377
+ * Submits short fork with given blockheaders
378
+ *
379
+ * @param signer
380
+ * @param forkHeaders
381
+ * @param storedHeader
382
+ * @param tipWork
383
+ * @param feeRate
384
+ */
385
+ public async saveShortForkHeaders(signer: string, forkHeaders: BtcBlock[], storedHeader: EVMBtcStoredHeader, tipWork: Buffer, feeRate?: string) {
386
+ feeRate ??= await this.Chain.Fees.getFeeRate();
387
+
388
+ logger.debug("saveShortForkHeaders(): submitting short fork blockheaders," +
389
+ " count: "+forkHeaders.length);
390
+
391
+ return this._saveHeaders(signer, forkHeaders, storedHeader, tipWork, -1, feeRate, 0);
392
+ }
393
+
394
+ /**
395
+ * Estimate required synchronization fee (worst case) to synchronize btc relay to the required blockheight
396
+ *
397
+ * @param requiredBlockheight
398
+ * @param feeRate
399
+ */
400
+ public async estimateSynchronizeFee(requiredBlockheight: number, feeRate?: string): Promise<bigint> {
401
+ const tipData = await this.getTipData();
402
+ const currBlockheight = tipData.blockheight;
403
+
404
+ const blockheightDelta = requiredBlockheight-currBlockheight;
405
+
406
+ if(blockheightDelta<=0) return 0n;
407
+
408
+ const synchronizationFee = BigInt(blockheightDelta) * await this.getFeePerBlock(feeRate);
409
+ logger.debug("estimateSynchronizeFee(): required blockheight: "+requiredBlockheight+
410
+ " blockheight delta: "+blockheightDelta+" fee: "+synchronizationFee.toString(10));
411
+
412
+ return synchronizationFee;
413
+ }
414
+
415
+ /**
416
+ * Returns fee required (in SOL) to synchronize a single block to btc relay
417
+ *
418
+ * @param feeRate
419
+ */
420
+ public async getFeePerBlock(feeRate?: string): Promise<bigint> {
421
+ feeRate ??= await this.Chain.Fees.getFeeRate();
422
+ return EVMFees.getGasFee(GAS_PER_BLOCKHEADER, feeRate);
423
+ }
424
+
425
+ /**
426
+ * Gets fee rate required for submitting blockheaders to the main chain
427
+ */
428
+ public getMainFeeRate(signer: string | null): Promise<string> {
429
+ return this.Chain.Fees.getFeeRate();
430
+ }
431
+
432
+ /**
433
+ * Gets fee rate required for submitting blockheaders to the specific fork
434
+ */
435
+ public getForkFeeRate(signer: string, forkId: number): Promise<string> {
436
+ return this.Chain.Fees.getFeeRate();
437
+ }
438
+
439
+ saveInitialHeader(signer: string, header: B, epochStart: number, pastBlocksTimestamps: number[], feeRate?: string): Promise<EVMTx> {
440
+ throw new Error("Not supported, EVM contract is initialized with constructor!");
441
+ }
442
+
443
+ /**
444
+ * Gets committed header, identified by blockhash & blockheight, determines required BTC relay blockheight based on
445
+ * requiredConfirmations
446
+ * If synchronizer is passed & blockhash is not found, it produces transactions to sync up the btc relay to the
447
+ * current chain tip & adds them to the txs array
448
+ *
449
+ * @param signer
450
+ * @param btcRelay
451
+ * @param btcTxs
452
+ * @param txs solana transaction array, in case we need to synchronize the btc relay ourselves the synchronization
453
+ * txns are added here
454
+ * @param synchronizer optional synchronizer to use to synchronize the btc relay in case it is not yet synchronized
455
+ * to the required blockheight
456
+ * @param feeRate Fee rate to use for synchronization transactions
457
+ * @private
458
+ */
459
+ static async getCommitedHeadersAndSynchronize(
460
+ signer: string,
461
+ btcRelay: EVMBtcRelay<any>,
462
+ btcTxs: {blockheight: number, requiredConfirmations: number, blockhash: string}[],
463
+ txs: EVMTx[],
464
+ synchronizer?: RelaySynchronizer<EVMBtcStoredHeader, EVMTx, any>,
465
+ feeRate?: string
466
+ ): Promise<{
467
+ [blockhash: string]: EVMBtcStoredHeader
468
+ }> {
469
+ const leavesTxs: {blockheight: number, requiredConfirmations: number, blockhash: string}[] = [];
470
+
471
+ const blockheaders: {
472
+ [blockhash: string]: EVMBtcStoredHeader
473
+ } = {};
474
+
475
+ for(let btcTx of btcTxs) {
476
+ const requiredBlockheight = btcTx.blockheight+btcTx.requiredConfirmations-1;
477
+
478
+ const result = await tryWithRetries(
479
+ () => btcRelay.retrieveLogAndBlockheight({
480
+ blockhash: btcTx.blockhash
481
+ }, requiredBlockheight)
482
+ );
483
+
484
+ if(result!=null) {
485
+ blockheaders[result.header.getBlockHash().toString("hex")] = result.header;
486
+ } else {
487
+ leavesTxs.push(btcTx);
488
+ }
489
+ }
490
+
491
+ if(leavesTxs.length===0) return blockheaders;
492
+
493
+ //Need to synchronize
494
+ if(synchronizer==null) return null;
495
+
496
+ //TODO: We don't have to synchronize to tip, only to our required blockheight
497
+ const resp = await synchronizer.syncToLatestTxs(signer.toString(), feeRate);
498
+ logger.debug("getCommitedHeaderAndSynchronize(): BTC Relay not synchronized to required blockheight, "+
499
+ "synchronizing ourselves in "+resp.txs.length+" txs");
500
+ logger.debug("getCommitedHeaderAndSynchronize(): BTC Relay computed header map: ",resp.computedHeaderMap);
501
+ txs.push(...resp.txs);
502
+
503
+ for(let key in resp.computedHeaderMap) {
504
+ const header = resp.computedHeaderMap[key];
505
+ blockheaders[header.getBlockHash().toString("hex")] = header;
506
+ }
507
+
508
+ //Check that blockhashes of all the rest txs are included
509
+ for(let btcTx of leavesTxs) {
510
+ if(blockheaders[btcTx.blockhash]==null) return null;
511
+ }
512
+
513
+ //Retrieve computed headers
514
+ return blockheaders;
515
+ }
516
+
517
+ }
@@ -0,0 +1,110 @@
1
+ import {BtcHeader} from "@atomiqlabs/base";
2
+ import {Buffer} from "buffer";
3
+ import {sha256} from "@noble/hashes/sha2";
4
+
5
+ export type EVMBtcHeaderType = {
6
+ version: number;
7
+ previousBlockhash?: Buffer;
8
+ merkleRoot: Buffer;
9
+ timestamp: number;
10
+ nbits: number;
11
+ nonce: number;
12
+ hash?: Buffer;
13
+ };
14
+
15
+ export class EVMBtcHeader implements BtcHeader {
16
+
17
+ version: number;
18
+ previousBlockhash: Buffer;
19
+ merkleRoot: Buffer;
20
+ timestamp: number;
21
+ nbits: number;
22
+ nonce: number;
23
+ hash?: Buffer;
24
+
25
+ constructor(data: EVMBtcHeaderType) {
26
+ this.version = data.version;
27
+ this.previousBlockhash = data.previousBlockhash;
28
+ this.merkleRoot = data.merkleRoot;
29
+ this.timestamp = data.timestamp;
30
+ this.nbits = data.nbits;
31
+ this.nonce = data.nonce;
32
+ this.hash = data.hash;
33
+ }
34
+
35
+ getMerkleRoot(): Buffer {
36
+ return this.merkleRoot;
37
+ }
38
+
39
+ getNbits(): number {
40
+ return this.nbits;
41
+ }
42
+
43
+ getNonce(): number {
44
+ return this.nonce;
45
+ }
46
+
47
+ getReversedPrevBlockhash(): Buffer {
48
+ return this.previousBlockhash;
49
+ }
50
+
51
+ getTimestamp(): number {
52
+ return this.timestamp;
53
+ }
54
+
55
+ getVersion(): number {
56
+ return this.version;
57
+ }
58
+
59
+ getHash(): Buffer {
60
+ return Buffer.from(sha256(sha256(this.serialize())));
61
+ }
62
+
63
+ serializeCompact(): Buffer {
64
+ const buffer = Buffer.alloc(48);
65
+ buffer.writeUInt32LE(this.version, 0);
66
+ this.merkleRoot.copy(buffer, 4);
67
+ buffer.writeUInt32LE(this.timestamp, 36);
68
+ buffer.writeUInt32LE(this.nbits, 40);
69
+ buffer.writeUInt32LE(this.nonce, 44);
70
+ return buffer;
71
+ }
72
+
73
+ serialize(): Buffer {
74
+ const buffer = Buffer.alloc(80);
75
+ buffer.writeUInt32LE(this.version, 0);
76
+ this.previousBlockhash.copy(buffer, 4);
77
+ this.merkleRoot.copy(buffer, 36);
78
+ buffer.writeUInt32LE(this.timestamp, 68);
79
+ buffer.writeUInt32LE(this.nbits, 72);
80
+ buffer.writeUInt32LE(this.nonce, 76);
81
+ return buffer;
82
+ }
83
+
84
+ static deserialize(rawData: Buffer): EVMBtcHeader {
85
+ if(rawData.length===80) {
86
+ //Regular blockheader
87
+ const version = rawData.readUInt32LE(0);
88
+ const previousBlockhash = Buffer.alloc(32);
89
+ rawData.copy(previousBlockhash, 0, 4, 36);
90
+ const merkleRoot = Buffer.alloc(32);
91
+ rawData.copy(merkleRoot, 0, 36, 68);
92
+ const timestamp = rawData.readUInt32LE(68);
93
+ const nbits = rawData.readUInt32LE(72);
94
+ const nonce = rawData.readUInt32LE(76);
95
+ return new EVMBtcHeader({version, previousBlockhash, merkleRoot, timestamp, nbits, nonce});
96
+ } else if(rawData.length===48) {
97
+ //Compact blockheader
98
+ const version = rawData.readUInt32LE(0);
99
+ const merkleRoot = Buffer.alloc(32);
100
+ rawData.copy(merkleRoot, 0, 4, 36);
101
+ const timestamp = rawData.readUInt32LE(36);
102
+ const nbits = rawData.readUInt32LE(40);
103
+ const nonce = rawData.readUInt32LE(44);
104
+ return new EVMBtcHeader({version, merkleRoot, timestamp, nbits, nonce});
105
+ } else {
106
+ throw new Error("Invalid byte length");
107
+ }
108
+ }
109
+
110
+ }