@atomiqlabs/chain-starknet 4.0.0-dev.12 → 4.0.0-dev.13

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