@atomiqlabs/chain-starknet 2.0.0-beta.8 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/LICENSE +201 -201
  2. package/dist/index.d.ts +38 -38
  3. package/dist/index.js +54 -54
  4. package/dist/starknet/StarknetChainType.d.ts +13 -13
  5. package/dist/starknet/StarknetChainType.js +2 -2
  6. package/dist/starknet/StarknetInitializer.d.ts +19 -19
  7. package/dist/starknet/StarknetInitializer.js +64 -64
  8. package/dist/starknet/btcrelay/BtcRelayAbi.d.ts +250 -250
  9. package/dist/starknet/btcrelay/BtcRelayAbi.js +341 -341
  10. package/dist/starknet/btcrelay/StarknetBtcRelay.d.ts +186 -186
  11. package/dist/starknet/btcrelay/StarknetBtcRelay.js +379 -379
  12. package/dist/starknet/btcrelay/headers/StarknetBtcHeader.d.ts +31 -31
  13. package/dist/starknet/btcrelay/headers/StarknetBtcHeader.js +74 -74
  14. package/dist/starknet/btcrelay/headers/StarknetBtcStoredHeader.d.ts +51 -51
  15. package/dist/starknet/btcrelay/headers/StarknetBtcStoredHeader.js +113 -113
  16. package/dist/starknet/chain/StarknetAction.d.ts +27 -27
  17. package/dist/starknet/chain/StarknetAction.js +73 -73
  18. package/dist/starknet/chain/StarknetChainInterface.d.ts +52 -52
  19. package/dist/starknet/chain/StarknetChainInterface.js +91 -91
  20. package/dist/starknet/chain/StarknetModule.d.ts +14 -14
  21. package/dist/starknet/chain/StarknetModule.js +13 -13
  22. package/dist/starknet/chain/modules/ERC20Abi.d.ts +755 -755
  23. package/dist/starknet/chain/modules/ERC20Abi.js +1032 -1032
  24. package/dist/starknet/chain/modules/StarknetAccounts.d.ts +6 -6
  25. package/dist/starknet/chain/modules/StarknetAccounts.js +24 -24
  26. package/dist/starknet/chain/modules/StarknetAddresses.d.ts +9 -9
  27. package/dist/starknet/chain/modules/StarknetAddresses.js +26 -26
  28. package/dist/starknet/chain/modules/StarknetBlocks.d.ts +19 -19
  29. package/dist/starknet/chain/modules/StarknetBlocks.js +49 -49
  30. package/dist/starknet/chain/modules/StarknetEvents.d.ts +44 -44
  31. package/dist/starknet/chain/modules/StarknetEvents.js +88 -88
  32. package/dist/starknet/chain/modules/StarknetFees.d.ts +55 -55
  33. package/dist/starknet/chain/modules/StarknetFees.js +100 -102
  34. package/dist/starknet/chain/modules/StarknetSignatures.d.ts +29 -29
  35. package/dist/starknet/chain/modules/StarknetSignatures.js +72 -72
  36. package/dist/starknet/chain/modules/StarknetTokens.d.ts +67 -67
  37. package/dist/starknet/chain/modules/StarknetTokens.js +98 -98
  38. package/dist/starknet/chain/modules/StarknetTransactions.d.ts +93 -93
  39. package/dist/starknet/chain/modules/StarknetTransactions.js +255 -255
  40. package/dist/starknet/contract/StarknetContractBase.d.ts +13 -13
  41. package/dist/starknet/contract/StarknetContractBase.js +16 -16
  42. package/dist/starknet/contract/StarknetContractModule.d.ts +8 -8
  43. package/dist/starknet/contract/StarknetContractModule.js +11 -11
  44. package/dist/starknet/contract/modules/StarknetContractEvents.d.ts +51 -51
  45. package/dist/starknet/contract/modules/StarknetContractEvents.js +97 -97
  46. package/dist/starknet/events/StarknetChainEvents.d.ts +21 -21
  47. package/dist/starknet/events/StarknetChainEvents.js +52 -52
  48. package/dist/starknet/events/StarknetChainEventsBrowser.d.ts +91 -91
  49. package/dist/starknet/events/StarknetChainEventsBrowser.js +294 -294
  50. package/dist/starknet/provider/RpcProviderWithRetries.d.ts +21 -21
  51. package/dist/starknet/provider/RpcProviderWithRetries.js +32 -32
  52. package/dist/starknet/spv_swap/SpvVaultContractAbi.d.ts +488 -488
  53. package/dist/starknet/spv_swap/SpvVaultContractAbi.js +656 -656
  54. package/dist/starknet/spv_swap/StarknetSpvVaultContract.d.ts +65 -65
  55. package/dist/starknet/spv_swap/StarknetSpvVaultContract.js +376 -376
  56. package/dist/starknet/spv_swap/StarknetSpvVaultData.d.ts +49 -49
  57. package/dist/starknet/spv_swap/StarknetSpvVaultData.js +144 -144
  58. package/dist/starknet/spv_swap/StarknetSpvWithdrawalData.d.ts +24 -24
  59. package/dist/starknet/spv_swap/StarknetSpvWithdrawalData.js +61 -61
  60. package/dist/starknet/swaps/EscrowManagerAbi.d.ts +405 -405
  61. package/dist/starknet/swaps/EscrowManagerAbi.js +547 -547
  62. package/dist/starknet/swaps/StarknetSwapContract.d.ts +196 -196
  63. package/dist/starknet/swaps/StarknetSwapContract.js +395 -395
  64. package/dist/starknet/swaps/StarknetSwapData.d.ts +67 -67
  65. package/dist/starknet/swaps/StarknetSwapData.js +281 -281
  66. package/dist/starknet/swaps/StarknetSwapModule.d.ts +10 -10
  67. package/dist/starknet/swaps/StarknetSwapModule.js +11 -11
  68. package/dist/starknet/swaps/handlers/IHandler.d.ts +13 -13
  69. package/dist/starknet/swaps/handlers/IHandler.js +2 -2
  70. package/dist/starknet/swaps/handlers/claim/ClaimHandlers.d.ts +13 -13
  71. package/dist/starknet/swaps/handlers/claim/ClaimHandlers.js +13 -13
  72. package/dist/starknet/swaps/handlers/claim/HashlockClaimHandler.d.ts +21 -21
  73. package/dist/starknet/swaps/handlers/claim/HashlockClaimHandler.js +44 -44
  74. package/dist/starknet/swaps/handlers/claim/btc/BitcoinNoncedOutputClaimHandler.d.ts +24 -24
  75. package/dist/starknet/swaps/handlers/claim/btc/BitcoinNoncedOutputClaimHandler.js +48 -48
  76. package/dist/starknet/swaps/handlers/claim/btc/BitcoinOutputClaimHandler.d.ts +25 -25
  77. package/dist/starknet/swaps/handlers/claim/btc/BitcoinOutputClaimHandler.js +40 -40
  78. package/dist/starknet/swaps/handlers/claim/btc/BitcoinTxIdClaimHandler.d.ts +20 -20
  79. package/dist/starknet/swaps/handlers/claim/btc/BitcoinTxIdClaimHandler.js +30 -30
  80. package/dist/starknet/swaps/handlers/claim/btc/IBitcoinClaimHandler.d.ts +45 -45
  81. package/dist/starknet/swaps/handlers/claim/btc/IBitcoinClaimHandler.js +52 -52
  82. package/dist/starknet/swaps/handlers/refund/TimelockRefundHandler.d.ts +17 -17
  83. package/dist/starknet/swaps/handlers/refund/TimelockRefundHandler.js +27 -27
  84. package/dist/starknet/swaps/modules/StarknetLpVault.d.ts +69 -69
  85. package/dist/starknet/swaps/modules/StarknetLpVault.js +122 -122
  86. package/dist/starknet/swaps/modules/StarknetSwapClaim.d.ts +53 -53
  87. package/dist/starknet/swaps/modules/StarknetSwapClaim.js +100 -100
  88. package/dist/starknet/swaps/modules/StarknetSwapInit.d.ts +84 -84
  89. package/dist/starknet/swaps/modules/StarknetSwapInit.js +164 -164
  90. package/dist/starknet/swaps/modules/StarknetSwapRefund.d.ts +62 -62
  91. package/dist/starknet/swaps/modules/StarknetSwapRefund.js +128 -128
  92. package/dist/starknet/wallet/StarknetKeypairWallet.d.ts +7 -7
  93. package/dist/starknet/wallet/StarknetKeypairWallet.js +30 -30
  94. package/dist/starknet/wallet/StarknetSigner.d.ts +12 -12
  95. package/dist/starknet/wallet/StarknetSigner.js +46 -46
  96. package/dist/utils/Utils.d.ts +37 -37
  97. package/dist/utils/Utils.js +257 -257
  98. package/package.json +37 -37
  99. package/src/index.ts +47 -47
  100. package/src/starknet/StarknetChainType.ts +28 -28
  101. package/src/starknet/StarknetInitializer.ts +95 -95
  102. package/src/starknet/btcrelay/BtcRelayAbi.ts +338 -338
  103. package/src/starknet/btcrelay/StarknetBtcRelay.ts +491 -491
  104. package/src/starknet/btcrelay/headers/StarknetBtcHeader.ts +100 -100
  105. package/src/starknet/btcrelay/headers/StarknetBtcStoredHeader.ts +141 -141
  106. package/src/starknet/chain/StarknetAction.ts +90 -90
  107. package/src/starknet/chain/StarknetChainInterface.ts +149 -149
  108. package/src/starknet/chain/StarknetModule.ts +19 -19
  109. package/src/starknet/chain/modules/ERC20Abi.ts +1029 -1029
  110. package/src/starknet/chain/modules/StarknetAccounts.ts +25 -25
  111. package/src/starknet/chain/modules/StarknetAddresses.ts +22 -22
  112. package/src/starknet/chain/modules/StarknetBlocks.ts +58 -58
  113. package/src/starknet/chain/modules/StarknetEvents.ts +104 -104
  114. package/src/starknet/chain/modules/StarknetFees.ts +132 -135
  115. package/src/starknet/chain/modules/StarknetSignatures.ts +90 -90
  116. package/src/starknet/chain/modules/StarknetTokens.ts +116 -116
  117. package/src/starknet/chain/modules/StarknetTransactions.ts +278 -278
  118. package/src/starknet/contract/StarknetContractBase.ts +26 -26
  119. package/src/starknet/contract/StarknetContractModule.ts +16 -16
  120. package/src/starknet/contract/modules/StarknetContractEvents.ts +134 -134
  121. package/src/starknet/events/StarknetChainEvents.ts +67 -67
  122. package/src/starknet/events/StarknetChainEventsBrowser.ts +412 -412
  123. package/src/starknet/provider/RpcProviderWithRetries.ts +43 -43
  124. package/src/starknet/spv_swap/SpvVaultContractAbi.ts +656 -656
  125. package/src/starknet/spv_swap/StarknetSpvVaultContract.ts +477 -477
  126. package/src/starknet/spv_swap/StarknetSpvVaultData.ts +194 -194
  127. package/src/starknet/spv_swap/StarknetSpvWithdrawalData.ts +68 -68
  128. package/src/starknet/swaps/EscrowManagerAbi.ts +546 -546
  129. package/src/starknet/swaps/StarknetSwapContract.ts +605 -605
  130. package/src/starknet/swaps/StarknetSwapData.ts +394 -394
  131. package/src/starknet/swaps/StarknetSwapModule.ts +17 -17
  132. package/src/starknet/swaps/handlers/IHandler.ts +20 -20
  133. package/src/starknet/swaps/handlers/claim/ClaimHandlers.ts +23 -23
  134. package/src/starknet/swaps/handlers/claim/HashlockClaimHandler.ts +53 -53
  135. package/src/starknet/swaps/handlers/claim/btc/BitcoinNoncedOutputClaimHandler.ts +73 -73
  136. package/src/starknet/swaps/handlers/claim/btc/BitcoinOutputClaimHandler.ts +67 -67
  137. package/src/starknet/swaps/handlers/claim/btc/BitcoinTxIdClaimHandler.ts +50 -50
  138. package/src/starknet/swaps/handlers/claim/btc/IBitcoinClaimHandler.ts +102 -102
  139. package/src/starknet/swaps/handlers/refund/TimelockRefundHandler.ts +38 -38
  140. package/src/starknet/swaps/modules/StarknetLpVault.ts +147 -147
  141. package/src/starknet/swaps/modules/StarknetSwapClaim.ts +141 -141
  142. package/src/starknet/swaps/modules/StarknetSwapInit.ts +225 -225
  143. package/src/starknet/swaps/modules/StarknetSwapRefund.ts +197 -197
  144. package/src/starknet/wallet/StarknetKeypairWallet.ts +39 -39
  145. package/src/starknet/wallet/StarknetSigner.ts +55 -55
  146. package/src/utils/Utils.ts +248 -248
@@ -1,491 +1,491 @@
1
- import {Buffer} from "buffer";
2
- import {StarknetBtcHeader} from "./headers/StarknetBtcHeader";
3
- import {BitcoinNetwork, BitcoinRpc, BtcBlock, BtcRelay, RelaySynchronizer, StatePredictorUtils} from "@atomiqlabs/base";
4
- import {
5
- bigNumberishToBuffer,
6
- bufferToU32Array, getLogger,
7
- toHex, tryWithRetries,
8
- u32ReverseEndianness
9
- } from "../../utils/Utils";
10
- import {StarknetContractBase} from "../contract/StarknetContractBase";
11
- import {StarknetBtcStoredHeader} from "./headers/StarknetBtcStoredHeader";
12
- import {StarknetTx} from "../chain/modules/StarknetTransactions";
13
- import {StarknetSigner} from "../wallet/StarknetSigner";
14
- import {BtcRelayAbi} from "./BtcRelayAbi";
15
- import {BigNumberish, hash} from "starknet";
16
- import {StarknetFees} from "../chain/modules/StarknetFees";
17
- import {StarknetChainInterface} from "../chain/StarknetChainInterface";
18
- import {StarknetAction} from "../chain/StarknetAction";
19
-
20
- function serializeBlockHeader(e: BtcBlock): StarknetBtcHeader {
21
- return new StarknetBtcHeader({
22
- reversed_version: u32ReverseEndianness(e.getVersion()),
23
- previous_blockhash: bufferToU32Array(Buffer.from(e.getPrevBlockhash(), "hex").reverse()),
24
- merkle_root: bufferToU32Array(Buffer.from(e.getMerkleRoot(), "hex").reverse()),
25
- reversed_timestamp: u32ReverseEndianness(e.getTimestamp()),
26
- nbits: u32ReverseEndianness(e.getNbits()),
27
- nonce: u32ReverseEndianness(e.getNonce()),
28
- hash: Buffer.from(e.getHash(), "hex").reverse()
29
- });
30
- }
31
-
32
- const GAS_PER_BLOCKHEADER = 850;
33
- const GAS_PER_BLOCKHEADER_FORK = 1000;
34
-
35
- const btcRelayAddreses = {
36
- [BitcoinNetwork.TESTNET4]: "0x0099b63f39f0cabb767361de3d8d3e97212351a51540e2687c2571f4da490dbe",
37
- [BitcoinNetwork.TESTNET]: "0x068601c79da2231d21e015ccfd59c243861156fa523a12c9f987ec28eb8dbc8c",
38
- [BitcoinNetwork.MAINNET]: "0x057b14a4231b82f1e525ff35a722d893ca3dd2bde0baa6cee97937c5be861dbc"
39
- };
40
-
41
- function serializeCalldata(headers: StarknetBtcHeader[], storedHeader: StarknetBtcStoredHeader, span: BigNumberish[]) {
42
- span.push(toHex(headers.length));
43
- headers.forEach(header => {
44
- span.push(...header.serialize());
45
- });
46
- span.push(...storedHeader.serialize());
47
- return span;
48
- }
49
-
50
- const logger = getLogger("StarknetBtcRelay: ");
51
-
52
- export class StarknetBtcRelay<B extends BtcBlock>
53
- extends StarknetContractBase<typeof BtcRelayAbi>
54
- implements BtcRelay<StarknetBtcStoredHeader, StarknetTx, B, StarknetSigner> {
55
-
56
-
57
- public SaveMainHeaders(signer: string, mainHeaders: StarknetBtcHeader[], storedHeader: StarknetBtcStoredHeader): StarknetAction {
58
-
59
- return new StarknetAction(signer, this.Chain,
60
- {
61
- contractAddress: this.contract.address,
62
- entrypoint: "submit_main_blockheaders",
63
- calldata: serializeCalldata(mainHeaders, storedHeader, [])
64
- },
65
- {l1: GAS_PER_BLOCKHEADER * mainHeaders.length, l2: 0}
66
- )
67
- }
68
-
69
- public SaveShortForkHeaders(signer: string, forkHeaders: StarknetBtcHeader[], storedHeader: StarknetBtcStoredHeader): StarknetAction {
70
- return new StarknetAction(signer, this.Chain,
71
- {
72
- contractAddress: this.contract.address,
73
- entrypoint: "submit_short_fork_blockheaders",
74
- calldata: serializeCalldata(forkHeaders, storedHeader, [])
75
- },
76
- {l1: GAS_PER_BLOCKHEADER * forkHeaders.length, l2: 0}
77
- )
78
- }
79
-
80
- public SaveLongForkHeaders(signer: string, forkId: number, forkHeaders: StarknetBtcHeader[], storedHeader: StarknetBtcStoredHeader, totalForkHeaders: number = 100): StarknetAction {
81
- return new StarknetAction(signer, this.Chain,
82
- {
83
- contractAddress: this.contract.address,
84
- entrypoint: "submit_fork_blockheaders",
85
- calldata: serializeCalldata(forkHeaders, storedHeader, [toHex(forkId)])
86
- },
87
- {l1: (GAS_PER_BLOCKHEADER * forkHeaders.length) + (GAS_PER_BLOCKHEADER_FORK * totalForkHeaders), l2: 0}
88
- )
89
- }
90
-
91
- bitcoinRpc: BitcoinRpc<B>;
92
-
93
- readonly maxHeadersPerTx: number = 100;
94
- readonly maxForkHeadersPerTx: number = 100;
95
- readonly maxShortForkHeadersPerTx: number = 100;
96
-
97
- constructor(
98
- chainInterface: StarknetChainInterface,
99
- bitcoinRpc: BitcoinRpc<B>,
100
- bitcoinNetwork: BitcoinNetwork,
101
- contractAddress: string = btcRelayAddreses[bitcoinNetwork],
102
- ) {
103
- super(chainInterface, contractAddress, BtcRelayAbi);
104
- this.bitcoinRpc = bitcoinRpc;
105
- }
106
-
107
- /**
108
- * Computes subsequent commited headers as they will appear on the blockchain when transactions
109
- * are submitted & confirmed
110
- *
111
- * @param initialStoredHeader
112
- * @param syncedHeaders
113
- * @private
114
- */
115
- private computeCommitedHeaders(initialStoredHeader: StarknetBtcStoredHeader, syncedHeaders: StarknetBtcHeader[]) {
116
- const computedCommitedHeaders = [initialStoredHeader];
117
- for(let blockHeader of syncedHeaders) {
118
- computedCommitedHeaders.push(computedCommitedHeaders[computedCommitedHeaders.length-1].computeNext(blockHeader));
119
- }
120
- return computedCommitedHeaders;
121
- }
122
-
123
- /**
124
- * A common logic for submitting blockheaders in a transaction
125
- *
126
- * @param signer
127
- * @param headers headers to sync to the btc relay
128
- * @param storedHeader current latest stored block header for a given fork
129
- * @param tipWork work of the current tip in a given fork
130
- * @param forkId forkId to submit to, forkId=0 means main chain, forkId=-1 means short fork
131
- * @param feeRate feeRate for the transaction
132
- * @private
133
- */
134
- private async _saveHeaders(
135
- signer: string,
136
- headers: BtcBlock[],
137
- storedHeader: StarknetBtcStoredHeader,
138
- tipWork: Buffer,
139
- forkId: number,
140
- feeRate: string
141
- ) {
142
- const blockHeaderObj = headers.map(serializeBlockHeader);
143
- let starknetAction: StarknetAction;
144
- switch(forkId) {
145
- case -1:
146
- starknetAction = this.SaveShortForkHeaders(signer, blockHeaderObj, storedHeader);
147
- break;
148
- case 0:
149
- starknetAction = this.SaveMainHeaders(signer, blockHeaderObj, storedHeader);
150
- break;
151
- default:
152
- starknetAction = this.SaveLongForkHeaders(signer, forkId, blockHeaderObj, storedHeader);
153
- break;
154
- }
155
-
156
- const tx = await starknetAction.tx(feeRate);
157
-
158
- const computedCommitedHeaders = this.computeCommitedHeaders(storedHeader, blockHeaderObj);
159
- const lastStoredHeader = computedCommitedHeaders[computedCommitedHeaders.length-1];
160
- if(forkId!==0 && StatePredictorUtils.gtBuffer(lastStoredHeader.getBlockHash(), tipWork)) {
161
- //Fork's work is higher than main chain's work, this fork will become a main chain
162
- forkId = 0;
163
- }
164
-
165
- return {
166
- forkId: forkId,
167
- lastStoredHeader,
168
- tx,
169
- computedCommitedHeaders
170
- }
171
- }
172
-
173
- private getBlock(commitHash?: BigNumberish, blockHash?: Buffer): Promise<[StarknetBtcStoredHeader, bigint] | null> {
174
- const keys = [commitHash == null ? null : toHex(commitHash)];
175
- if (blockHash != null) {
176
- const starknetBlockHash = hash.computePoseidonHashOnElements(bufferToU32Array(Buffer.from([...blockHash]).reverse()));
177
- keys.push(starknetBlockHash);
178
- }
179
- return this.Events.findInContractEvents(
180
- ["btc_relay::events::StoreHeader", "btc_relay::events::StoreForkHeader"],
181
- keys,
182
- (event) => {
183
- return Promise.resolve([StarknetBtcStoredHeader.fromSerializedFeltArray(event.data), BigInt(event.params.commit_hash)]);
184
- }
185
- );
186
- }
187
-
188
- private async getBlockHeight(): Promise<number> {
189
- return Number(await this.contract.get_blockheight());
190
- }
191
-
192
- /**
193
- * Returns data about current main chain tip stored in the btc relay
194
- */
195
- public async getTipData(): Promise<{ commitHash: string; blockhash: string, chainWork: Buffer, blockheight: number }> {
196
- const commitHash = await this.contract.get_tip_commit_hash();
197
- if(commitHash==null || BigInt(commitHash)===BigInt(0)) return null;
198
-
199
- const result = await this.getBlock(commitHash);
200
- if(result==null) return null;
201
-
202
- const [storedBlockHeader] = result;
203
-
204
- return {
205
- blockheight: storedBlockHeader.getBlockheight(),
206
- commitHash: bigNumberishToBuffer(commitHash, 32).toString("hex"),
207
- blockhash: storedBlockHeader.getBlockHash().toString("hex"),
208
- chainWork: storedBlockHeader.getChainWork()
209
- };
210
- }
211
-
212
- /**
213
- * Retrieves blockheader with a specific blockhash, returns null if requiredBlockheight is provided and
214
- * btc relay contract is not synced up to the desired blockheight
215
- *
216
- * @param blockData
217
- * @param requiredBlockheight
218
- */
219
- public async retrieveLogAndBlockheight(blockData: {blockhash: string}, requiredBlockheight?: number): Promise<{
220
- header: StarknetBtcStoredHeader,
221
- height: number
222
- } | null> {
223
- //TODO: we can fetch the blockheight and events in parallel
224
- const blockHeight = await this.getBlockHeight();
225
- if(requiredBlockheight!=null && blockHeight < requiredBlockheight) {
226
- return null;
227
- }
228
- const result = await this.getBlock(null, Buffer.from(blockData.blockhash, "hex"));
229
- if(result==null) return null;
230
-
231
- const [storedBlockHeader, commitHash] = result;
232
-
233
- //Check if block is part of the main chain
234
- const chainCommitment = await this.contract.get_commit_hash(storedBlockHeader.block_height);
235
- if(BigInt(chainCommitment)!==BigInt(commitHash)) return null;
236
-
237
- logger.debug("retrieveLogAndBlockheight(): block found," +
238
- " commit hash: "+toHex(commitHash)+" blockhash: "+blockData.blockhash+" current btc relay height: "+blockHeight);
239
-
240
- return {header: storedBlockHeader, height: blockHeight};
241
- }
242
-
243
- /**
244
- * Retrieves blockheader data by blockheader's commit hash,
245
- *
246
- * @param commitmentHashStr
247
- * @param blockData
248
- */
249
- public async retrieveLogByCommitHash(commitmentHashStr: string, blockData: {blockhash: string}): Promise<StarknetBtcStoredHeader> {
250
- const result = await this.getBlock(commitmentHashStr, Buffer.from(blockData.blockhash, "hex"));
251
- if(result==null) return null;
252
-
253
- const [storedBlockHeader, commitHash] = result;
254
-
255
- //Check if block is part of the main chain
256
- const chainCommitment = await this.contract.get_commit_hash(storedBlockHeader.block_height);
257
- if(BigInt(chainCommitment)!==BigInt(commitHash)) return null;
258
-
259
- logger.debug("retrieveLogByCommitHash(): block found," +
260
- " commit hash: "+commitmentHashStr+" blockhash: "+blockData.blockhash+" height: "+storedBlockHeader.block_height);
261
-
262
- return storedBlockHeader;
263
- }
264
-
265
- /**
266
- * Retrieves latest known stored blockheader & blockheader from bitcoin RPC that is in the main chain
267
- */
268
- public async retrieveLatestKnownBlockLog(): Promise<{
269
- resultStoredHeader: StarknetBtcStoredHeader,
270
- resultBitcoinHeader: B
271
- }> {
272
- const data = await this.Events.findInContractEvents(
273
- ["btc_relay::events::StoreHeader", "btc_relay::events::StoreForkHeader"],
274
- null,
275
- async (event) => {
276
- const storedHeader = StarknetBtcStoredHeader.fromSerializedFeltArray(event.data);
277
-
278
- const blockHashHex = storedHeader.getBlockHash().toString("hex");
279
- const commitHash = event.params.commit_hash;
280
-
281
- const [isInBtcMainChain, btcRelayCommitHash] = await Promise.all([
282
- this.bitcoinRpc.isInMainChain(blockHashHex).catch(() => false),
283
- this.contract.get_commit_hash(storedHeader.block_height)
284
- ]);
285
-
286
- if(!isInBtcMainChain) return null;
287
- if(BigInt(commitHash)!==BigInt(btcRelayCommitHash)) return null;
288
-
289
- return {
290
- resultStoredHeader: storedHeader,
291
- resultBitcoinHeader: await this.bitcoinRpc.getBlockHeader(blockHashHex),
292
- commitHash: commitHash
293
- }
294
- }
295
- )
296
-
297
- if(data!=null) logger.debug("retrieveLatestKnownBlockLog(): block found," +
298
- " commit hash: "+toHex(data.commitHash)+" blockhash: "+data.resultBitcoinHeader.getHash()+
299
- " height: "+data.resultStoredHeader.getBlockheight());
300
-
301
- return data;
302
- }
303
-
304
- /**
305
- * Saves blockheaders as a bitcoin main chain to the btc relay
306
- *
307
- * @param signer
308
- * @param mainHeaders
309
- * @param storedHeader
310
- * @param feeRate
311
- */
312
- public saveMainHeaders(signer: string, mainHeaders: BtcBlock[], storedHeader: StarknetBtcStoredHeader, feeRate?: string) {
313
- logger.debug("saveMainHeaders(): submitting main blockheaders, count: "+mainHeaders.length);
314
- return this._saveHeaders(signer, mainHeaders, storedHeader, null, 0, feeRate);
315
- }
316
-
317
- /**
318
- * Creates a new long fork and submits the headers to it
319
- *
320
- * @param signer
321
- * @param forkHeaders
322
- * @param storedHeader
323
- * @param tipWork
324
- * @param feeRate
325
- */
326
- public async saveNewForkHeaders(signer: string, forkHeaders: BtcBlock[], storedHeader: StarknetBtcStoredHeader, tipWork: Buffer, feeRate?: string) {
327
- let forkId: number = Math.floor(Math.random() * 0xFFFFFFFFFFFF);
328
-
329
- logger.debug("saveNewForkHeaders(): submitting new fork & blockheaders," +
330
- " count: "+forkHeaders.length+" forkId: 0x"+forkId.toString(16));
331
-
332
- return await this._saveHeaders(signer, forkHeaders, storedHeader, tipWork, forkId, feeRate);
333
- }
334
-
335
- /**
336
- * Continues submitting blockheaders to a given fork
337
- *
338
- * @param signer
339
- * @param forkHeaders
340
- * @param storedHeader
341
- * @param forkId
342
- * @param tipWork
343
- * @param feeRate
344
- */
345
- public saveForkHeaders(signer: string, forkHeaders: BtcBlock[], storedHeader: StarknetBtcStoredHeader, forkId: number, tipWork: Buffer, feeRate?: string) {
346
- logger.debug("saveForkHeaders(): submitting blockheaders to existing fork," +
347
- " count: "+forkHeaders.length+" forkId: 0x"+forkId.toString(16));
348
-
349
- return this._saveHeaders(signer, forkHeaders, storedHeader, tipWork, forkId, feeRate);
350
- }
351
-
352
- /**
353
- * Submits short fork with given blockheaders
354
- *
355
- * @param signer
356
- * @param forkHeaders
357
- * @param storedHeader
358
- * @param tipWork
359
- * @param feeRate
360
- */
361
- public saveShortForkHeaders(signer: string, forkHeaders: BtcBlock[], storedHeader: StarknetBtcStoredHeader, tipWork: Buffer, feeRate?: string) {
362
- logger.debug("saveShortForkHeaders(): submitting short fork blockheaders," +
363
- " count: "+forkHeaders.length);
364
-
365
- return this._saveHeaders(signer, forkHeaders, storedHeader, tipWork, -1, feeRate);
366
- }
367
-
368
- /**
369
- * Estimate required synchronization fee (worst case) to synchronize btc relay to the required blockheight
370
- *
371
- * @param requiredBlockheight
372
- * @param feeRate
373
- */
374
- public async estimateSynchronizeFee(requiredBlockheight: number, feeRate?: string): Promise<bigint> {
375
- const tipData = await this.getTipData();
376
- const currBlockheight = tipData.blockheight;
377
-
378
- const blockheightDelta = requiredBlockheight-currBlockheight;
379
-
380
- if(blockheightDelta<=0) return 0n;
381
-
382
- const synchronizationFee = BigInt(blockheightDelta) * await this.getFeePerBlock(feeRate);
383
- logger.debug("estimateSynchronizeFee(): required blockheight: "+requiredBlockheight+
384
- " blockheight delta: "+blockheightDelta+" fee: "+synchronizationFee.toString(10));
385
-
386
- return synchronizationFee;
387
- }
388
-
389
- /**
390
- * Returns fee required (in SOL) to synchronize a single block to btc relay
391
- *
392
- * @param feeRate
393
- */
394
- public async getFeePerBlock(feeRate?: string): Promise<bigint> {
395
- feeRate ??= await this.Chain.Fees.getFeeRate();
396
- return StarknetFees.getGasFee(GAS_PER_BLOCKHEADER, feeRate);
397
- }
398
-
399
- /**
400
- * Gets fee rate required for submitting blockheaders to the main chain
401
- */
402
- public getMainFeeRate(signer: string | null): Promise<string> {
403
- return this.Chain.Fees.getFeeRate();
404
- }
405
-
406
- /**
407
- * Gets fee rate required for submitting blockheaders to the specific fork
408
- */
409
- public getForkFeeRate(signer: string, forkId: number): Promise<string> {
410
- return this.Chain.Fees.getFeeRate();
411
- }
412
-
413
- saveInitialHeader(signer: string, header: B, epochStart: number, pastBlocksTimestamps: number[], feeRate?: string): Promise<StarknetTx> {
414
- throw new Error("Not supported, starknet contract is initialized with constructor!");
415
- }
416
-
417
- /**
418
- * Gets committed header, identified by blockhash & blockheight, determines required BTC relay blockheight based on
419
- * requiredConfirmations
420
- * If synchronizer is passed & blockhash is not found, it produces transactions to sync up the btc relay to the
421
- * current chain tip & adds them to the txs array
422
- *
423
- * @param signer
424
- * @param btcRelay
425
- * @param btcTxs
426
- * @param txs solana transaction array, in case we need to synchronize the btc relay ourselves the synchronization
427
- * txns are added here
428
- * @param synchronizer optional synchronizer to use to synchronize the btc relay in case it is not yet synchronized
429
- * to the required blockheight
430
- * @param feeRate Fee rate to use for synchronization transactions
431
- * @private
432
- */
433
- static async getCommitedHeadersAndSynchronize(
434
- signer: string,
435
- btcRelay: StarknetBtcRelay<any>,
436
- btcTxs: {blockheight: number, requiredConfirmations: number, blockhash: string}[],
437
- txs: StarknetTx[],
438
- synchronizer?: RelaySynchronizer<StarknetBtcStoredHeader, StarknetTx, any>,
439
- feeRate?: string
440
- ): Promise<{
441
- [blockhash: string]: StarknetBtcStoredHeader
442
- }> {
443
- const leavesTxs: {blockheight: number, requiredConfirmations: number, blockhash: string}[] = [];
444
-
445
- const blockheaders: {
446
- [blockhash: string]: StarknetBtcStoredHeader
447
- } = {};
448
-
449
- for(let btcTx of btcTxs) {
450
- const requiredBlockheight = btcTx.blockheight+btcTx.requiredConfirmations-1;
451
-
452
- const result = await tryWithRetries(
453
- () => btcRelay.retrieveLogAndBlockheight({
454
- blockhash: btcTx.blockhash
455
- }, requiredBlockheight)
456
- );
457
-
458
- if(result!=null) {
459
- blockheaders[result.header.getBlockHash().toString("hex")] = result.header;
460
- } else {
461
- leavesTxs.push(btcTx);
462
- }
463
- }
464
-
465
- if(leavesTxs.length===0) return blockheaders;
466
-
467
- //Need to synchronize
468
- if(synchronizer==null) return null;
469
-
470
- //TODO: We don't have to synchronize to tip, only to our required blockheight
471
- const resp = await synchronizer.syncToLatestTxs(signer.toString(), feeRate);
472
- logger.debug("getCommitedHeaderAndSynchronize(): BTC Relay not synchronized to required blockheight, "+
473
- "synchronizing ourselves in "+resp.txs.length+" txs");
474
- logger.debug("getCommitedHeaderAndSynchronize(): BTC Relay computed header map: ",resp.computedHeaderMap);
475
- txs.push(...resp.txs);
476
-
477
- for(let key in resp.computedHeaderMap) {
478
- const header = resp.computedHeaderMap[key];
479
- blockheaders[header.getBlockHash().toString("hex")] = header;
480
- }
481
-
482
- //Check that blockhashes of all the rest txs are included
483
- for(let btcTx of leavesTxs) {
484
- if(blockheaders[btcTx.blockhash]==null) return null;
485
- }
486
-
487
- //Retrieve computed headers
488
- return blockheaders;
489
- }
490
-
491
- }
1
+ import {Buffer} from "buffer";
2
+ import {StarknetBtcHeader} from "./headers/StarknetBtcHeader";
3
+ import {BitcoinNetwork, BitcoinRpc, BtcBlock, BtcRelay, RelaySynchronizer, StatePredictorUtils} from "@atomiqlabs/base";
4
+ import {
5
+ bigNumberishToBuffer,
6
+ bufferToU32Array, getLogger,
7
+ toHex, tryWithRetries,
8
+ u32ReverseEndianness
9
+ } from "../../utils/Utils";
10
+ import {StarknetContractBase} from "../contract/StarknetContractBase";
11
+ import {StarknetBtcStoredHeader} from "./headers/StarknetBtcStoredHeader";
12
+ import {StarknetTx} from "../chain/modules/StarknetTransactions";
13
+ import {StarknetSigner} from "../wallet/StarknetSigner";
14
+ import {BtcRelayAbi} from "./BtcRelayAbi";
15
+ import {BigNumberish, hash} from "starknet";
16
+ import {StarknetFees} from "../chain/modules/StarknetFees";
17
+ import {StarknetChainInterface} from "../chain/StarknetChainInterface";
18
+ import {StarknetAction} from "../chain/StarknetAction";
19
+
20
+ function serializeBlockHeader(e: BtcBlock): StarknetBtcHeader {
21
+ return new StarknetBtcHeader({
22
+ reversed_version: u32ReverseEndianness(e.getVersion()),
23
+ previous_blockhash: bufferToU32Array(Buffer.from(e.getPrevBlockhash(), "hex").reverse()),
24
+ merkle_root: bufferToU32Array(Buffer.from(e.getMerkleRoot(), "hex").reverse()),
25
+ reversed_timestamp: u32ReverseEndianness(e.getTimestamp()),
26
+ nbits: u32ReverseEndianness(e.getNbits()),
27
+ nonce: u32ReverseEndianness(e.getNonce()),
28
+ hash: Buffer.from(e.getHash(), "hex").reverse()
29
+ });
30
+ }
31
+
32
+ const GAS_PER_BLOCKHEADER = 850;
33
+ const GAS_PER_BLOCKHEADER_FORK = 1000;
34
+
35
+ const btcRelayAddreses = {
36
+ [BitcoinNetwork.TESTNET4]: "0x0099b63f39f0cabb767361de3d8d3e97212351a51540e2687c2571f4da490dbe",
37
+ [BitcoinNetwork.TESTNET]: "0x068601c79da2231d21e015ccfd59c243861156fa523a12c9f987ec28eb8dbc8c",
38
+ [BitcoinNetwork.MAINNET]: "0x057b14a4231b82f1e525ff35a722d893ca3dd2bde0baa6cee97937c5be861dbc"
39
+ };
40
+
41
+ function serializeCalldata(headers: StarknetBtcHeader[], storedHeader: StarknetBtcStoredHeader, span: BigNumberish[]) {
42
+ span.push(toHex(headers.length));
43
+ headers.forEach(header => {
44
+ span.push(...header.serialize());
45
+ });
46
+ span.push(...storedHeader.serialize());
47
+ return span;
48
+ }
49
+
50
+ const logger = getLogger("StarknetBtcRelay: ");
51
+
52
+ export class StarknetBtcRelay<B extends BtcBlock>
53
+ extends StarknetContractBase<typeof BtcRelayAbi>
54
+ implements BtcRelay<StarknetBtcStoredHeader, StarknetTx, B, StarknetSigner> {
55
+
56
+
57
+ public SaveMainHeaders(signer: string, mainHeaders: StarknetBtcHeader[], storedHeader: StarknetBtcStoredHeader): StarknetAction {
58
+
59
+ return new StarknetAction(signer, this.Chain,
60
+ {
61
+ contractAddress: this.contract.address,
62
+ entrypoint: "submit_main_blockheaders",
63
+ calldata: serializeCalldata(mainHeaders, storedHeader, [])
64
+ },
65
+ {l1: GAS_PER_BLOCKHEADER * mainHeaders.length, l2: 0}
66
+ )
67
+ }
68
+
69
+ public SaveShortForkHeaders(signer: string, forkHeaders: StarknetBtcHeader[], storedHeader: StarknetBtcStoredHeader): StarknetAction {
70
+ return new StarknetAction(signer, this.Chain,
71
+ {
72
+ contractAddress: this.contract.address,
73
+ entrypoint: "submit_short_fork_blockheaders",
74
+ calldata: serializeCalldata(forkHeaders, storedHeader, [])
75
+ },
76
+ {l1: GAS_PER_BLOCKHEADER * forkHeaders.length, l2: 0}
77
+ )
78
+ }
79
+
80
+ public SaveLongForkHeaders(signer: string, forkId: number, forkHeaders: StarknetBtcHeader[], storedHeader: StarknetBtcStoredHeader, totalForkHeaders: number = 100): StarknetAction {
81
+ return new StarknetAction(signer, this.Chain,
82
+ {
83
+ contractAddress: this.contract.address,
84
+ entrypoint: "submit_fork_blockheaders",
85
+ calldata: serializeCalldata(forkHeaders, storedHeader, [toHex(forkId)])
86
+ },
87
+ {l1: (GAS_PER_BLOCKHEADER * forkHeaders.length) + (GAS_PER_BLOCKHEADER_FORK * totalForkHeaders), l2: 0}
88
+ )
89
+ }
90
+
91
+ bitcoinRpc: BitcoinRpc<B>;
92
+
93
+ readonly maxHeadersPerTx: number = 100;
94
+ readonly maxForkHeadersPerTx: number = 100;
95
+ readonly maxShortForkHeadersPerTx: number = 100;
96
+
97
+ constructor(
98
+ chainInterface: StarknetChainInterface,
99
+ bitcoinRpc: BitcoinRpc<B>,
100
+ bitcoinNetwork: BitcoinNetwork,
101
+ contractAddress: string = btcRelayAddreses[bitcoinNetwork],
102
+ ) {
103
+ super(chainInterface, contractAddress, BtcRelayAbi);
104
+ this.bitcoinRpc = bitcoinRpc;
105
+ }
106
+
107
+ /**
108
+ * Computes subsequent commited headers as they will appear on the blockchain when transactions
109
+ * are submitted & confirmed
110
+ *
111
+ * @param initialStoredHeader
112
+ * @param syncedHeaders
113
+ * @private
114
+ */
115
+ private computeCommitedHeaders(initialStoredHeader: StarknetBtcStoredHeader, syncedHeaders: StarknetBtcHeader[]) {
116
+ const computedCommitedHeaders = [initialStoredHeader];
117
+ for(let blockHeader of syncedHeaders) {
118
+ computedCommitedHeaders.push(computedCommitedHeaders[computedCommitedHeaders.length-1].computeNext(blockHeader));
119
+ }
120
+ return computedCommitedHeaders;
121
+ }
122
+
123
+ /**
124
+ * A common logic for submitting blockheaders in a transaction
125
+ *
126
+ * @param signer
127
+ * @param headers headers to sync to the btc relay
128
+ * @param storedHeader current latest stored block header for a given fork
129
+ * @param tipWork work of the current tip in a given fork
130
+ * @param forkId forkId to submit to, forkId=0 means main chain, forkId=-1 means short fork
131
+ * @param feeRate feeRate for the transaction
132
+ * @private
133
+ */
134
+ private async _saveHeaders(
135
+ signer: string,
136
+ headers: BtcBlock[],
137
+ storedHeader: StarknetBtcStoredHeader,
138
+ tipWork: Buffer,
139
+ forkId: number,
140
+ feeRate: string
141
+ ) {
142
+ const blockHeaderObj = headers.map(serializeBlockHeader);
143
+ let starknetAction: StarknetAction;
144
+ switch(forkId) {
145
+ case -1:
146
+ starknetAction = this.SaveShortForkHeaders(signer, blockHeaderObj, storedHeader);
147
+ break;
148
+ case 0:
149
+ starknetAction = this.SaveMainHeaders(signer, blockHeaderObj, storedHeader);
150
+ break;
151
+ default:
152
+ starknetAction = this.SaveLongForkHeaders(signer, forkId, blockHeaderObj, storedHeader);
153
+ break;
154
+ }
155
+
156
+ const tx = await starknetAction.tx(feeRate);
157
+
158
+ const computedCommitedHeaders = this.computeCommitedHeaders(storedHeader, blockHeaderObj);
159
+ const lastStoredHeader = computedCommitedHeaders[computedCommitedHeaders.length-1];
160
+ if(forkId!==0 && StatePredictorUtils.gtBuffer(lastStoredHeader.getBlockHash(), tipWork)) {
161
+ //Fork's work is higher than main chain's work, this fork will become a main chain
162
+ forkId = 0;
163
+ }
164
+
165
+ return {
166
+ forkId: forkId,
167
+ lastStoredHeader,
168
+ tx,
169
+ computedCommitedHeaders
170
+ }
171
+ }
172
+
173
+ private getBlock(commitHash?: BigNumberish, blockHash?: Buffer): Promise<[StarknetBtcStoredHeader, bigint] | null> {
174
+ const keys = [commitHash == null ? null : toHex(commitHash)];
175
+ if (blockHash != null) {
176
+ const starknetBlockHash = hash.computePoseidonHashOnElements(bufferToU32Array(Buffer.from([...blockHash]).reverse()));
177
+ keys.push(starknetBlockHash);
178
+ }
179
+ return this.Events.findInContractEvents(
180
+ ["btc_relay::events::StoreHeader", "btc_relay::events::StoreForkHeader"],
181
+ keys,
182
+ (event) => {
183
+ return Promise.resolve([StarknetBtcStoredHeader.fromSerializedFeltArray(event.data), BigInt(event.params.commit_hash)]);
184
+ }
185
+ );
186
+ }
187
+
188
+ private async getBlockHeight(): Promise<number> {
189
+ return Number(await this.contract.get_blockheight());
190
+ }
191
+
192
+ /**
193
+ * Returns data about current main chain tip stored in the btc relay
194
+ */
195
+ public async getTipData(): Promise<{ commitHash: string; blockhash: string, chainWork: Buffer, blockheight: number }> {
196
+ const commitHash = await this.contract.get_tip_commit_hash();
197
+ if(commitHash==null || BigInt(commitHash)===BigInt(0)) return null;
198
+
199
+ const result = await this.getBlock(commitHash);
200
+ if(result==null) return null;
201
+
202
+ const [storedBlockHeader] = result;
203
+
204
+ return {
205
+ blockheight: storedBlockHeader.getBlockheight(),
206
+ commitHash: bigNumberishToBuffer(commitHash, 32).toString("hex"),
207
+ blockhash: storedBlockHeader.getBlockHash().toString("hex"),
208
+ chainWork: storedBlockHeader.getChainWork()
209
+ };
210
+ }
211
+
212
+ /**
213
+ * Retrieves blockheader with a specific blockhash, returns null if requiredBlockheight is provided and
214
+ * btc relay contract is not synced up to the desired blockheight
215
+ *
216
+ * @param blockData
217
+ * @param requiredBlockheight
218
+ */
219
+ public async retrieveLogAndBlockheight(blockData: {blockhash: string}, requiredBlockheight?: number): Promise<{
220
+ header: StarknetBtcStoredHeader,
221
+ height: number
222
+ } | null> {
223
+ //TODO: we can fetch the blockheight and events in parallel
224
+ const blockHeight = await this.getBlockHeight();
225
+ if(requiredBlockheight!=null && blockHeight < requiredBlockheight) {
226
+ return null;
227
+ }
228
+ const result = await this.getBlock(null, Buffer.from(blockData.blockhash, "hex"));
229
+ if(result==null) return null;
230
+
231
+ const [storedBlockHeader, commitHash] = result;
232
+
233
+ //Check if block is part of the main chain
234
+ const chainCommitment = await this.contract.get_commit_hash(storedBlockHeader.block_height);
235
+ if(BigInt(chainCommitment)!==BigInt(commitHash)) return null;
236
+
237
+ logger.debug("retrieveLogAndBlockheight(): block found," +
238
+ " commit hash: "+toHex(commitHash)+" blockhash: "+blockData.blockhash+" current btc relay height: "+blockHeight);
239
+
240
+ return {header: storedBlockHeader, height: blockHeight};
241
+ }
242
+
243
+ /**
244
+ * Retrieves blockheader data by blockheader's commit hash,
245
+ *
246
+ * @param commitmentHashStr
247
+ * @param blockData
248
+ */
249
+ public async retrieveLogByCommitHash(commitmentHashStr: string, blockData: {blockhash: string}): Promise<StarknetBtcStoredHeader> {
250
+ const result = await this.getBlock(commitmentHashStr, Buffer.from(blockData.blockhash, "hex"));
251
+ if(result==null) return null;
252
+
253
+ const [storedBlockHeader, commitHash] = result;
254
+
255
+ //Check if block is part of the main chain
256
+ const chainCommitment = await this.contract.get_commit_hash(storedBlockHeader.block_height);
257
+ if(BigInt(chainCommitment)!==BigInt(commitHash)) return null;
258
+
259
+ logger.debug("retrieveLogByCommitHash(): block found," +
260
+ " commit hash: "+commitmentHashStr+" blockhash: "+blockData.blockhash+" height: "+storedBlockHeader.block_height);
261
+
262
+ return storedBlockHeader;
263
+ }
264
+
265
+ /**
266
+ * Retrieves latest known stored blockheader & blockheader from bitcoin RPC that is in the main chain
267
+ */
268
+ public async retrieveLatestKnownBlockLog(): Promise<{
269
+ resultStoredHeader: StarknetBtcStoredHeader,
270
+ resultBitcoinHeader: B
271
+ }> {
272
+ const data = await this.Events.findInContractEvents(
273
+ ["btc_relay::events::StoreHeader", "btc_relay::events::StoreForkHeader"],
274
+ null,
275
+ async (event) => {
276
+ const storedHeader = StarknetBtcStoredHeader.fromSerializedFeltArray(event.data);
277
+
278
+ const blockHashHex = storedHeader.getBlockHash().toString("hex");
279
+ const commitHash = event.params.commit_hash;
280
+
281
+ const [isInBtcMainChain, btcRelayCommitHash] = await Promise.all([
282
+ this.bitcoinRpc.isInMainChain(blockHashHex).catch(() => false),
283
+ this.contract.get_commit_hash(storedHeader.block_height)
284
+ ]);
285
+
286
+ if(!isInBtcMainChain) return null;
287
+ if(BigInt(commitHash)!==BigInt(btcRelayCommitHash)) return null;
288
+
289
+ return {
290
+ resultStoredHeader: storedHeader,
291
+ resultBitcoinHeader: await this.bitcoinRpc.getBlockHeader(blockHashHex),
292
+ commitHash: commitHash
293
+ }
294
+ }
295
+ )
296
+
297
+ if(data!=null) logger.debug("retrieveLatestKnownBlockLog(): block found," +
298
+ " commit hash: "+toHex(data.commitHash)+" blockhash: "+data.resultBitcoinHeader.getHash()+
299
+ " height: "+data.resultStoredHeader.getBlockheight());
300
+
301
+ return data;
302
+ }
303
+
304
+ /**
305
+ * Saves blockheaders as a bitcoin main chain to the btc relay
306
+ *
307
+ * @param signer
308
+ * @param mainHeaders
309
+ * @param storedHeader
310
+ * @param feeRate
311
+ */
312
+ public saveMainHeaders(signer: string, mainHeaders: BtcBlock[], storedHeader: StarknetBtcStoredHeader, feeRate?: string) {
313
+ logger.debug("saveMainHeaders(): submitting main blockheaders, count: "+mainHeaders.length);
314
+ return this._saveHeaders(signer, mainHeaders, storedHeader, null, 0, feeRate);
315
+ }
316
+
317
+ /**
318
+ * Creates a new long fork and submits the headers to it
319
+ *
320
+ * @param signer
321
+ * @param forkHeaders
322
+ * @param storedHeader
323
+ * @param tipWork
324
+ * @param feeRate
325
+ */
326
+ public async saveNewForkHeaders(signer: string, forkHeaders: BtcBlock[], storedHeader: StarknetBtcStoredHeader, tipWork: Buffer, feeRate?: string) {
327
+ let forkId: number = Math.floor(Math.random() * 0xFFFFFFFFFFFF);
328
+
329
+ logger.debug("saveNewForkHeaders(): submitting new fork & blockheaders," +
330
+ " count: "+forkHeaders.length+" forkId: 0x"+forkId.toString(16));
331
+
332
+ return await this._saveHeaders(signer, forkHeaders, storedHeader, tipWork, forkId, feeRate);
333
+ }
334
+
335
+ /**
336
+ * Continues submitting blockheaders to a given fork
337
+ *
338
+ * @param signer
339
+ * @param forkHeaders
340
+ * @param storedHeader
341
+ * @param forkId
342
+ * @param tipWork
343
+ * @param feeRate
344
+ */
345
+ public saveForkHeaders(signer: string, forkHeaders: BtcBlock[], storedHeader: StarknetBtcStoredHeader, forkId: number, tipWork: Buffer, feeRate?: string) {
346
+ logger.debug("saveForkHeaders(): submitting blockheaders to existing fork," +
347
+ " count: "+forkHeaders.length+" forkId: 0x"+forkId.toString(16));
348
+
349
+ return this._saveHeaders(signer, forkHeaders, storedHeader, tipWork, forkId, feeRate);
350
+ }
351
+
352
+ /**
353
+ * Submits short fork with given blockheaders
354
+ *
355
+ * @param signer
356
+ * @param forkHeaders
357
+ * @param storedHeader
358
+ * @param tipWork
359
+ * @param feeRate
360
+ */
361
+ public saveShortForkHeaders(signer: string, forkHeaders: BtcBlock[], storedHeader: StarknetBtcStoredHeader, tipWork: Buffer, feeRate?: string) {
362
+ logger.debug("saveShortForkHeaders(): submitting short fork blockheaders," +
363
+ " count: "+forkHeaders.length);
364
+
365
+ return this._saveHeaders(signer, forkHeaders, storedHeader, tipWork, -1, feeRate);
366
+ }
367
+
368
+ /**
369
+ * Estimate required synchronization fee (worst case) to synchronize btc relay to the required blockheight
370
+ *
371
+ * @param requiredBlockheight
372
+ * @param feeRate
373
+ */
374
+ public async estimateSynchronizeFee(requiredBlockheight: number, feeRate?: string): Promise<bigint> {
375
+ const tipData = await this.getTipData();
376
+ const currBlockheight = tipData.blockheight;
377
+
378
+ const blockheightDelta = requiredBlockheight-currBlockheight;
379
+
380
+ if(blockheightDelta<=0) return 0n;
381
+
382
+ const synchronizationFee = BigInt(blockheightDelta) * await this.getFeePerBlock(feeRate);
383
+ logger.debug("estimateSynchronizeFee(): required blockheight: "+requiredBlockheight+
384
+ " blockheight delta: "+blockheightDelta+" fee: "+synchronizationFee.toString(10));
385
+
386
+ return synchronizationFee;
387
+ }
388
+
389
+ /**
390
+ * Returns fee required (in SOL) to synchronize a single block to btc relay
391
+ *
392
+ * @param feeRate
393
+ */
394
+ public async getFeePerBlock(feeRate?: string): Promise<bigint> {
395
+ feeRate ??= await this.Chain.Fees.getFeeRate();
396
+ return StarknetFees.getGasFee(GAS_PER_BLOCKHEADER, feeRate);
397
+ }
398
+
399
+ /**
400
+ * Gets fee rate required for submitting blockheaders to the main chain
401
+ */
402
+ public getMainFeeRate(signer: string | null): Promise<string> {
403
+ return this.Chain.Fees.getFeeRate();
404
+ }
405
+
406
+ /**
407
+ * Gets fee rate required for submitting blockheaders to the specific fork
408
+ */
409
+ public getForkFeeRate(signer: string, forkId: number): Promise<string> {
410
+ return this.Chain.Fees.getFeeRate();
411
+ }
412
+
413
+ saveInitialHeader(signer: string, header: B, epochStart: number, pastBlocksTimestamps: number[], feeRate?: string): Promise<StarknetTx> {
414
+ throw new Error("Not supported, starknet contract is initialized with constructor!");
415
+ }
416
+
417
+ /**
418
+ * Gets committed header, identified by blockhash & blockheight, determines required BTC relay blockheight based on
419
+ * requiredConfirmations
420
+ * If synchronizer is passed & blockhash is not found, it produces transactions to sync up the btc relay to the
421
+ * current chain tip & adds them to the txs array
422
+ *
423
+ * @param signer
424
+ * @param btcRelay
425
+ * @param btcTxs
426
+ * @param txs solana transaction array, in case we need to synchronize the btc relay ourselves the synchronization
427
+ * txns are added here
428
+ * @param synchronizer optional synchronizer to use to synchronize the btc relay in case it is not yet synchronized
429
+ * to the required blockheight
430
+ * @param feeRate Fee rate to use for synchronization transactions
431
+ * @private
432
+ */
433
+ static async getCommitedHeadersAndSynchronize(
434
+ signer: string,
435
+ btcRelay: StarknetBtcRelay<any>,
436
+ btcTxs: {blockheight: number, requiredConfirmations: number, blockhash: string}[],
437
+ txs: StarknetTx[],
438
+ synchronizer?: RelaySynchronizer<StarknetBtcStoredHeader, StarknetTx, any>,
439
+ feeRate?: string
440
+ ): Promise<{
441
+ [blockhash: string]: StarknetBtcStoredHeader
442
+ }> {
443
+ const leavesTxs: {blockheight: number, requiredConfirmations: number, blockhash: string}[] = [];
444
+
445
+ const blockheaders: {
446
+ [blockhash: string]: StarknetBtcStoredHeader
447
+ } = {};
448
+
449
+ for(let btcTx of btcTxs) {
450
+ const requiredBlockheight = btcTx.blockheight+btcTx.requiredConfirmations-1;
451
+
452
+ const result = await tryWithRetries(
453
+ () => btcRelay.retrieveLogAndBlockheight({
454
+ blockhash: btcTx.blockhash
455
+ }, requiredBlockheight)
456
+ );
457
+
458
+ if(result!=null) {
459
+ blockheaders[result.header.getBlockHash().toString("hex")] = result.header;
460
+ } else {
461
+ leavesTxs.push(btcTx);
462
+ }
463
+ }
464
+
465
+ if(leavesTxs.length===0) return blockheaders;
466
+
467
+ //Need to synchronize
468
+ if(synchronizer==null) return null;
469
+
470
+ //TODO: We don't have to synchronize to tip, only to our required blockheight
471
+ const resp = await synchronizer.syncToLatestTxs(signer.toString(), feeRate);
472
+ logger.debug("getCommitedHeaderAndSynchronize(): BTC Relay not synchronized to required blockheight, "+
473
+ "synchronizing ourselves in "+resp.txs.length+" txs");
474
+ logger.debug("getCommitedHeaderAndSynchronize(): BTC Relay computed header map: ",resp.computedHeaderMap);
475
+ txs.push(...resp.txs);
476
+
477
+ for(let key in resp.computedHeaderMap) {
478
+ const header = resp.computedHeaderMap[key];
479
+ blockheaders[header.getBlockHash().toString("hex")] = header;
480
+ }
481
+
482
+ //Check that blockhashes of all the rest txs are included
483
+ for(let btcTx of leavesTxs) {
484
+ if(blockheaders[btcTx.blockhash]==null) return null;
485
+ }
486
+
487
+ //Retrieve computed headers
488
+ return blockheaders;
489
+ }
490
+
491
+ }