@atomiqlabs/chain-evm 1.0.0-dev.38 → 1.0.0-dev.40

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