@atomiqlabs/chain-evm 1.0.0-dev.75 → 1.0.0-dev.77

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