@atomiqlabs/chain-solana 10.0.0-dev.3 → 11.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 (114) hide show
  1. package/LICENSE +201 -201
  2. package/dist/index.d.ts +29 -29
  3. package/dist/index.js +45 -45
  4. package/dist/solana/SolanaChainType.d.ts +10 -10
  5. package/dist/solana/SolanaChainType.js +2 -2
  6. package/dist/solana/SolanaChains.d.ts +20 -20
  7. package/dist/solana/SolanaChains.js +25 -25
  8. package/dist/solana/SolanaInitializer.d.ts +18 -18
  9. package/dist/solana/SolanaInitializer.js +63 -63
  10. package/dist/solana/btcrelay/SolanaBtcRelay.d.ts +228 -228
  11. package/dist/solana/btcrelay/SolanaBtcRelay.js +441 -441
  12. package/dist/solana/btcrelay/headers/SolanaBtcHeader.d.ts +29 -29
  13. package/dist/solana/btcrelay/headers/SolanaBtcHeader.js +34 -34
  14. package/dist/solana/btcrelay/headers/SolanaBtcStoredHeader.d.ts +46 -46
  15. package/dist/solana/btcrelay/headers/SolanaBtcStoredHeader.js +78 -78
  16. package/dist/solana/btcrelay/program/programIdl.json +671 -671
  17. package/dist/solana/chain/SolanaAction.d.ts +26 -26
  18. package/dist/solana/chain/SolanaAction.js +86 -86
  19. package/dist/solana/chain/SolanaChainInterface.d.ts +58 -58
  20. package/dist/solana/chain/SolanaChainInterface.js +112 -112
  21. package/dist/solana/chain/SolanaModule.d.ts +14 -14
  22. package/dist/solana/chain/SolanaModule.js +13 -13
  23. package/dist/solana/chain/modules/SolanaAddresses.d.ts +8 -8
  24. package/dist/solana/chain/modules/SolanaAddresses.js +22 -22
  25. package/dist/solana/chain/modules/SolanaBlocks.d.ts +28 -28
  26. package/dist/solana/chain/modules/SolanaBlocks.js +72 -72
  27. package/dist/solana/chain/modules/SolanaEvents.d.ts +25 -25
  28. package/dist/solana/chain/modules/SolanaEvents.js +58 -58
  29. package/dist/solana/chain/modules/SolanaFees.d.ts +121 -121
  30. package/dist/solana/chain/modules/SolanaFees.js +379 -379
  31. package/dist/solana/chain/modules/SolanaSignatures.d.ts +23 -23
  32. package/dist/solana/chain/modules/SolanaSignatures.js +39 -39
  33. package/dist/solana/chain/modules/SolanaSlots.d.ts +31 -31
  34. package/dist/solana/chain/modules/SolanaSlots.js +68 -68
  35. package/dist/solana/chain/modules/SolanaTokens.d.ts +136 -136
  36. package/dist/solana/chain/modules/SolanaTokens.js +248 -248
  37. package/dist/solana/chain/modules/SolanaTransactions.d.ts +124 -124
  38. package/dist/solana/chain/modules/SolanaTransactions.js +332 -332
  39. package/dist/solana/events/SolanaChainEvents.d.ts +88 -88
  40. package/dist/solana/events/SolanaChainEvents.js +256 -256
  41. package/dist/solana/events/SolanaChainEventsBrowser.d.ts +85 -85
  42. package/dist/solana/events/SolanaChainEventsBrowser.js +194 -194
  43. package/dist/solana/program/SolanaProgramBase.d.ts +40 -40
  44. package/dist/solana/program/SolanaProgramBase.js +43 -43
  45. package/dist/solana/program/SolanaProgramModule.d.ts +8 -8
  46. package/dist/solana/program/SolanaProgramModule.js +11 -11
  47. package/dist/solana/program/modules/SolanaProgramEvents.d.ts +59 -59
  48. package/dist/solana/program/modules/SolanaProgramEvents.js +103 -103
  49. package/dist/solana/swaps/SolanaSwapData.d.ts +59 -59
  50. package/dist/solana/swaps/SolanaSwapData.js +267 -267
  51. package/dist/solana/swaps/SolanaSwapModule.d.ts +10 -10
  52. package/dist/solana/swaps/SolanaSwapModule.js +11 -11
  53. package/dist/solana/swaps/SolanaSwapProgram.d.ts +202 -202
  54. package/dist/solana/swaps/SolanaSwapProgram.js +470 -463
  55. package/dist/solana/swaps/SwapTypeEnum.d.ts +11 -11
  56. package/dist/solana/swaps/SwapTypeEnum.js +42 -42
  57. package/dist/solana/swaps/modules/SolanaDataAccount.d.ts +94 -94
  58. package/dist/solana/swaps/modules/SolanaDataAccount.js +231 -231
  59. package/dist/solana/swaps/modules/SolanaLpVault.d.ts +71 -71
  60. package/dist/solana/swaps/modules/SolanaLpVault.js +173 -173
  61. package/dist/solana/swaps/modules/SwapClaim.d.ts +129 -129
  62. package/dist/solana/swaps/modules/SwapClaim.js +291 -291
  63. package/dist/solana/swaps/modules/SwapInit.d.ts +217 -217
  64. package/dist/solana/swaps/modules/SwapInit.js +519 -519
  65. package/dist/solana/swaps/modules/SwapRefund.d.ts +82 -82
  66. package/dist/solana/swaps/modules/SwapRefund.js +252 -252
  67. package/dist/solana/swaps/programIdl.json +945 -945
  68. package/dist/solana/swaps/programTypes.d.ts +943 -943
  69. package/dist/solana/swaps/programTypes.js +945 -945
  70. package/dist/solana/wallet/SolanaKeypairWallet.d.ts +9 -9
  71. package/dist/solana/wallet/SolanaKeypairWallet.js +33 -33
  72. package/dist/solana/wallet/SolanaSigner.d.ts +10 -10
  73. package/dist/solana/wallet/SolanaSigner.js +16 -16
  74. package/dist/utils/Utils.d.ts +53 -53
  75. package/dist/utils/Utils.js +170 -170
  76. package/package.json +41 -41
  77. package/src/index.ts +36 -36
  78. package/src/solana/SolanaChainType.ts +25 -25
  79. package/src/solana/SolanaChains.ts +23 -23
  80. package/src/solana/SolanaInitializer.ts +102 -102
  81. package/src/solana/btcrelay/SolanaBtcRelay.ts +589 -588
  82. package/src/solana/btcrelay/headers/SolanaBtcHeader.ts +57 -57
  83. package/src/solana/btcrelay/headers/SolanaBtcStoredHeader.ts +102 -102
  84. package/src/solana/btcrelay/program/programIdl.json +670 -670
  85. package/src/solana/chain/SolanaAction.ts +108 -108
  86. package/src/solana/chain/SolanaChainInterface.ts +174 -174
  87. package/src/solana/chain/SolanaModule.ts +20 -20
  88. package/src/solana/chain/modules/SolanaAddresses.ts +20 -20
  89. package/src/solana/chain/modules/SolanaBlocks.ts +78 -78
  90. package/src/solana/chain/modules/SolanaEvents.ts +56 -56
  91. package/src/solana/chain/modules/SolanaFees.ts +450 -450
  92. package/src/solana/chain/modules/SolanaSignatures.ts +39 -39
  93. package/src/solana/chain/modules/SolanaSlots.ts +82 -82
  94. package/src/solana/chain/modules/SolanaTokens.ts +307 -307
  95. package/src/solana/chain/modules/SolanaTransactions.ts +370 -370
  96. package/src/solana/events/SolanaChainEvents.ts +299 -299
  97. package/src/solana/events/SolanaChainEventsBrowser.ts +256 -256
  98. package/src/solana/program/SolanaProgramBase.ts +79 -79
  99. package/src/solana/program/SolanaProgramModule.ts +15 -15
  100. package/src/solana/program/modules/SolanaProgramEvents.ts +140 -140
  101. package/src/solana/swaps/SolanaSwapData.ts +379 -379
  102. package/src/solana/swaps/SolanaSwapModule.ts +16 -16
  103. package/src/solana/swaps/SolanaSwapProgram.ts +697 -692
  104. package/src/solana/swaps/SwapTypeEnum.ts +29 -29
  105. package/src/solana/swaps/modules/SolanaDataAccount.ts +307 -307
  106. package/src/solana/swaps/modules/SolanaLpVault.ts +215 -215
  107. package/src/solana/swaps/modules/SwapClaim.ts +389 -389
  108. package/src/solana/swaps/modules/SwapInit.ts +663 -663
  109. package/src/solana/swaps/modules/SwapRefund.ts +312 -312
  110. package/src/solana/swaps/programIdl.json +944 -944
  111. package/src/solana/swaps/programTypes.ts +1885 -1885
  112. package/src/solana/wallet/SolanaKeypairWallet.ts +36 -36
  113. package/src/solana/wallet/SolanaSigner.ts +23 -23
  114. package/src/utils/Utils.ts +180 -180
@@ -1,588 +1,589 @@
1
- import {
2
- Connection,
3
- PublicKey,
4
- Signer,
5
- SystemProgram,
6
- Transaction
7
- } from "@solana/web3.js";
8
- import {SolanaBtcStoredHeader, SolanaBtcStoredHeaderType} from "./headers/SolanaBtcStoredHeader";
9
- import {SolanaBtcHeader} from "./headers/SolanaBtcHeader";
10
- import * as programIdl from "./program/programIdl.json";
11
- import {BitcoinRpc, BtcBlock, BtcRelay, StatePredictorUtils} from "@atomiqlabs/base";
12
- import {MethodsBuilder} from "@coral-xyz/anchor/dist/cjs/program/namespace/methods";
13
- import {SolanaProgramBase} from "../program/SolanaProgramBase";
14
- import {SolanaAction} from "../chain/SolanaAction";
15
- import {Buffer} from "buffer";
16
- import {SolanaTx} from "../chain/modules/SolanaTransactions";
17
- import {SolanaSigner} from "../wallet/SolanaSigner";
18
- import * as BN from "bn.js";
19
- import {SolanaChainInterface} from "../chain/SolanaChainInterface";
20
- import {getLogger} from "../../utils/Utils";
21
-
22
- const MAX_CLOSE_IX_PER_TX = 10;
23
-
24
- function serializeBlockHeader(e: BtcBlock): SolanaBtcHeader {
25
- return new SolanaBtcHeader({
26
- version: e.getVersion(),
27
- reversedPrevBlockhash: [...Buffer.from(e.getPrevBlockhash(), "hex").reverse()],
28
- merkleRoot: [...Buffer.from(e.getMerkleRoot(), "hex").reverse()],
29
- timestamp: e.getTimestamp(),
30
- nbits: e.getNbits(),
31
- nonce: e.getNonce(),
32
- hash: Buffer.from(e.getHash(), "hex").reverse()
33
- });
34
- };
35
-
36
- export class SolanaBtcRelay<B extends BtcBlock> extends SolanaProgramBase<any> implements BtcRelay<SolanaBtcStoredHeader, {tx: Transaction, signers: Signer[]}, B, SolanaSigner> {
37
-
38
- /**
39
- * Creates initialization action for initializing the btc relay
40
- *
41
- * @param signer
42
- * @param header
43
- * @param epochStart
44
- * @param pastBlocksTimestamps
45
- * @constructor
46
- * @private
47
- */
48
- private async Initialize(signer: PublicKey, header: B, epochStart: number, pastBlocksTimestamps: number[]): Promise<SolanaAction> {
49
- const serializedBlock = serializeBlockHeader(header);
50
- return new SolanaAction(signer, this.Chain,
51
- await this.program.methods
52
- .initialize(
53
- serializedBlock,
54
- header.getHeight(),
55
- header.getChainWork(),
56
- epochStart,
57
- pastBlocksTimestamps
58
- )
59
- .accounts({
60
- signer,
61
- mainState: this.BtcRelayMainState,
62
- headerTopic: this.BtcRelayHeader(serializedBlock.hash),
63
- systemProgram: SystemProgram.programId
64
- })
65
- .instruction()
66
- )
67
- }
68
-
69
- /**
70
- * Creates verify action to be used with the swap program, specifies the action to be firstIxBeforeComputeBudget,
71
- * such that the verify instruction will always be the 0th in the transaction, this is required because
72
- * swap program expects the verify instruction to be at the 0th position
73
- *
74
- * @param signer
75
- * @param reversedTxId
76
- * @param confirmations
77
- * @param position
78
- * @param reversedMerkleProof
79
- * @param committedHeader
80
- */
81
- public async Verify(
82
- signer: PublicKey,
83
- reversedTxId: Buffer,
84
- confirmations: number,
85
- position: number,
86
- reversedMerkleProof: Buffer[],
87
- committedHeader: SolanaBtcStoredHeader
88
- ): Promise<SolanaAction> {
89
- return new SolanaAction(signer, this.Chain,
90
- await this.program.methods
91
- .verifyTransaction(
92
- reversedTxId,
93
- confirmations,
94
- position,
95
- reversedMerkleProof,
96
- committedHeader
97
- )
98
- .accounts({
99
- signer,
100
- mainState: this.BtcRelayMainState
101
- })
102
- .instruction(),
103
- null,
104
- null,
105
- null,
106
- true
107
- );
108
- }
109
-
110
- public async CloseForkAccount(signer: PublicKey, forkId: number): Promise<SolanaAction> {
111
- return new SolanaAction(signer, this.Chain,
112
- await this.program.methods
113
- .closeForkAccount(
114
- new BN(forkId)
115
- )
116
- .accounts({
117
- signer,
118
- forkState: this.BtcRelayFork(forkId, signer),
119
- systemProgram: SystemProgram.programId,
120
- })
121
- .instruction(),
122
- 20000
123
- )
124
- }
125
-
126
- BtcRelayMainState = this.pda("state");
127
- BtcRelayHeader = this.pda("header", (hash: Buffer) => [hash]);
128
- BtcRelayFork = this.pda("fork",
129
- (forkId: number, pubkey: PublicKey) => [new BN(forkId).toArrayLike(Buffer, "le", 8), pubkey.toBuffer()]
130
- );
131
-
132
- bitcoinRpc: BitcoinRpc<B>;
133
-
134
- readonly maxHeadersPerTx: number = 5;
135
- readonly maxForkHeadersPerTx: number = 4;
136
- readonly maxShortForkHeadersPerTx: number = 4;
137
-
138
- constructor(
139
- chainInterface: SolanaChainInterface,
140
- bitcoinRpc: BitcoinRpc<B>,
141
- programAddress?: string
142
- ) {
143
- super(chainInterface, programIdl, programAddress);
144
- this.bitcoinRpc = bitcoinRpc;
145
- }
146
-
147
- /**
148
- * Gets set of block commitments representing current main chain from the mainState
149
- *
150
- * @param mainState
151
- * @private
152
- */
153
- private getBlockCommitmentsSet(mainState: any): Set<string> {
154
- const storedCommitments = new Set<string>();
155
- mainState.blockCommitments.forEach(e => {
156
- storedCommitments.add(Buffer.from(e).toString("hex"));
157
- });
158
- return storedCommitments;
159
- }
160
-
161
- /**
162
- * Computes subsequent commited headers as they will appear on the blockchain when transactions
163
- * are submitted & confirmed
164
- *
165
- * @param initialStoredHeader
166
- * @param syncedHeaders
167
- * @private
168
- */
169
- private computeCommitedHeaders(initialStoredHeader: SolanaBtcStoredHeader, syncedHeaders: SolanaBtcHeader[]) {
170
- const computedCommitedHeaders = [initialStoredHeader];
171
- for(let blockHeader of syncedHeaders) {
172
- computedCommitedHeaders.push(computedCommitedHeaders[computedCommitedHeaders.length-1].computeNext(blockHeader));
173
- }
174
- return computedCommitedHeaders;
175
- }
176
-
177
- /**
178
- * A common logic for submitting blockheaders in a transaction
179
- *
180
- * @param signer
181
- * @param headers headers to sync to the btc relay
182
- * @param storedHeader current latest stored block header for a given fork
183
- * @param tipWork work of the current tip in a given fork
184
- * @param forkId forkId to submit to, forkId=0 means main chain
185
- * @param feeRate feeRate for the transaction
186
- * @param createTx transaction generator function
187
- * @private
188
- */
189
- private async _saveHeaders(
190
- signer: PublicKey,
191
- headers: BtcBlock[],
192
- storedHeader: SolanaBtcStoredHeader,
193
- tipWork: Buffer,
194
- forkId: number,
195
- feeRate: string,
196
- createTx: (blockHeaders: SolanaBtcHeader[]) => MethodsBuilder<any, any>
197
- ) {
198
- const blockHeaderObj = headers.map(serializeBlockHeader);
199
-
200
- const tx = await createTx(blockHeaderObj)
201
- .remainingAccounts(blockHeaderObj.map(e => {
202
- return {
203
- pubkey: this.BtcRelayHeader(e.hash),
204
- isSigner: false,
205
- isWritable: false
206
- }
207
- }))
208
- .transaction();
209
- tx.feePayer = signer;
210
-
211
- this.Chain.Fees.applyFeeRateBegin(tx, null, feeRate);
212
- this.Chain.Fees.applyFeeRateEnd(tx, null, feeRate);
213
-
214
- const computedCommitedHeaders = this.computeCommitedHeaders(storedHeader, blockHeaderObj);
215
- const lastStoredHeader = computedCommitedHeaders[computedCommitedHeaders.length-1];
216
- if(forkId!==0 && StatePredictorUtils.gtBuffer(Buffer.from(lastStoredHeader.chainWork), tipWork)) {
217
- //Fork's work is higher than main chain's work, this fork will become a main chain
218
- forkId = 0;
219
- }
220
-
221
- return {
222
- forkId: forkId,
223
- lastStoredHeader,
224
- tx: {
225
- tx,
226
- signers: []
227
- },
228
- computedCommitedHeaders
229
- }
230
- }
231
-
232
- /**
233
- * Returns data about current main chain tip stored in the btc relay
234
- */
235
- public async getTipData(): Promise<{ commitHash: string; blockhash: string, chainWork: Buffer, blockheight: number }> {
236
- const data: any = await this.program.account.mainState.fetchNullable(this.BtcRelayMainState);
237
- if(data==null) return null;
238
-
239
- return {
240
- blockheight: data.blockHeight,
241
- commitHash: Buffer.from(data.tipCommitHash).toString("hex"),
242
- blockhash: Buffer.from(data.tipBlockHash).reverse().toString("hex"),
243
- chainWork: Buffer.from(data.chainWork)
244
- }
245
- }
246
-
247
- /**
248
- * Retrieves blockheader with a specific blockhash, returns null if requiredBlockheight is provided and
249
- * btc relay contract is not synced up to the desired blockheight
250
- *
251
- * @param blockData
252
- * @param requiredBlockheight
253
- */
254
- public async retrieveLogAndBlockheight(blockData: {blockhash: string}, requiredBlockheight?: number): Promise<{
255
- header: SolanaBtcStoredHeader,
256
- height: number
257
- } | null> {
258
- const mainState: any = await this.program.account.mainState.fetch(this.BtcRelayMainState);
259
-
260
- if(requiredBlockheight!=null && mainState.blockHeight < requiredBlockheight) {
261
- return null;
262
- }
263
-
264
- const storedCommitments = this.getBlockCommitmentsSet(mainState);
265
- const blockHashBuffer = Buffer.from(blockData.blockhash, 'hex').reverse();
266
- const topicKey = this.BtcRelayHeader(blockHashBuffer);
267
-
268
- const data = await this.Events.findInEvents(topicKey, async (event) => {
269
- if(event.name==="StoreFork" || event.name==="StoreHeader") {
270
- const eventData: any = event.data;
271
- const commitHash = Buffer.from(eventData.commitHash).toString("hex");
272
- if(blockHashBuffer.equals(Buffer.from(eventData.blockHash)) && storedCommitments.has(commitHash))
273
- return {
274
- header: new SolanaBtcStoredHeader(eventData.header as SolanaBtcStoredHeaderType),
275
- height: mainState.blockHeight as number,
276
- commitHash
277
- };
278
- }
279
- });
280
- if(data!=null) this.logger.debug("retrieveLogAndBlockheight(): block found," +
281
- " commit hash: "+data.commitHash+" blockhash: "+blockData.blockhash+" height: "+data.height);
282
-
283
- return data;
284
- }
285
-
286
- /**
287
- * Retrieves blockheader data by blockheader's commit hash,
288
- *
289
- * @param commitmentHashStr
290
- * @param blockData
291
- */
292
- public async retrieveLogByCommitHash(commitmentHashStr: string, blockData: {blockhash: string}): Promise<SolanaBtcStoredHeader> {
293
- const blockHashBuffer = Buffer.from(blockData.blockhash, "hex").reverse();
294
- const topicKey = this.BtcRelayHeader(blockHashBuffer);
295
-
296
- const data = await this.Events.findInEvents(topicKey, async (event) => {
297
- if(event.name==="StoreFork" || event.name==="StoreHeader") {
298
- const eventData: any = event.data;
299
- const commitHash = Buffer.from(eventData.commitHash).toString("hex");
300
- if(commitmentHashStr===commitHash)
301
- return new SolanaBtcStoredHeader(eventData.header as SolanaBtcStoredHeaderType);
302
- }
303
- });
304
- if(data!=null) this.logger.debug("retrieveLogByCommitHash(): block found," +
305
- " commit hash: "+commitmentHashStr+" blockhash: "+blockData.blockhash+" height: "+data.blockheight);
306
-
307
- return data;
308
- }
309
-
310
- /**
311
- * Retrieves latest known stored blockheader & blockheader from bitcoin RPC that is in the main chain
312
- */
313
- public async retrieveLatestKnownBlockLog(): Promise<{
314
- resultStoredHeader: SolanaBtcStoredHeader,
315
- resultBitcoinHeader: B
316
- }> {
317
- const mainState: any = await this.program.account.mainState.fetch(this.BtcRelayMainState);
318
- const storedCommitments = this.getBlockCommitmentsSet(mainState);
319
-
320
- const data = await this.Events.findInEvents(this.program.programId, async (event) => {
321
- if(event.name==="StoreFork" || event.name==="StoreHeader") {
322
- const eventData: any = event.data;
323
- const blockHashHex = Buffer.from(eventData.blockHash).reverse().toString("hex");
324
- const isInMainChain = await this.bitcoinRpc.isInMainChain(blockHashHex).catch(() => false);
325
- const commitHash = Buffer.from(eventData.commitHash).toString("hex");
326
- //Check if this fork is part of main chain
327
- if(isInMainChain && storedCommitments.has(commitHash))
328
- return {
329
- resultStoredHeader: new SolanaBtcStoredHeader(eventData.header),
330
- resultBitcoinHeader: await this.bitcoinRpc.getBlockHeader(blockHashHex),
331
- commitHash: commitHash
332
- };
333
- }
334
- }, null, 10);
335
- if(data!=null) this.logger.debug("retrieveLatestKnownBlockLog(): block found," +
336
- " commit hash: "+data.commitHash+" blockhash: "+data.resultBitcoinHeader.getHash()+
337
- " height: "+data.resultStoredHeader.blockheight);
338
-
339
- return data;
340
- }
341
-
342
- /**
343
- * Saves initial block header when the btc relay is in uninitialized state
344
- *
345
- * @param signer
346
- * @param header a bitcoin blockheader to submit
347
- * @param epochStart timestamp of the start of the epoch (block timestamp at blockheight-(blockheight%2016))
348
- * @param pastBlocksTimestamps timestamp of the past 10 blocks
349
- * @param feeRate fee rate to use for the transaction
350
- */
351
- async saveInitialHeader(
352
- signer: string,
353
- header: B,
354
- epochStart: number,
355
- pastBlocksTimestamps: number[],
356
- feeRate?: string
357
- ): Promise<{ tx: Transaction; signers: Signer[]; }> {
358
- if(pastBlocksTimestamps.length!==10) throw new Error("Invalid prevBlocksTimestamps");
359
-
360
- const action = await this.Initialize(new PublicKey(signer), header, epochStart, pastBlocksTimestamps);
361
-
362
- this.logger.debug("saveInitialHeader(): saving initial header, blockhash: "+header.getHash()+
363
- " blockheight: "+header.getHeight()+" epochStart: "+epochStart+" past block timestamps: "+pastBlocksTimestamps.join());
364
-
365
- return await action.tx(feeRate);
366
- }
367
-
368
- /**
369
- * Saves blockheaders as a bitcoin main chain to the btc relay
370
- *
371
- * @param signer
372
- * @param mainHeaders
373
- * @param storedHeader
374
- * @param feeRate
375
- */
376
- public saveMainHeaders(signer: string, mainHeaders: BtcBlock[], storedHeader: SolanaBtcStoredHeader, feeRate?: string) {
377
- this.logger.debug("saveMainHeaders(): submitting main blockheaders, count: "+mainHeaders.length);
378
- const _signer = new PublicKey(signer);
379
- return this._saveHeaders(_signer, mainHeaders, storedHeader, null, 0, feeRate,
380
- (blockHeaders) => this.program.methods
381
- .submitBlockHeaders(
382
- blockHeaders,
383
- storedHeader
384
- )
385
- .accounts({
386
- signer: _signer,
387
- mainState: this.BtcRelayMainState,
388
- })
389
- );
390
- }
391
-
392
- /**
393
- * Creates a new long fork and submits the headers to it
394
- *
395
- * @param signer
396
- * @param forkHeaders
397
- * @param storedHeader
398
- * @param tipWork
399
- * @param feeRate
400
- */
401
- public async saveNewForkHeaders(signer: string, forkHeaders: BtcBlock[], storedHeader: SolanaBtcStoredHeader, tipWork: Buffer, feeRate?: string) {
402
- const mainState: any = await this.program.account.mainState.fetch(this.BtcRelayMainState);
403
- let forkId: BN = mainState.forkCounter;
404
-
405
- const _signer = new PublicKey(signer);
406
-
407
- this.logger.debug("saveNewForkHeaders(): submitting new fork & blockheaders," +
408
- " count: "+forkHeaders.length+" forkId: "+forkId.toString(10));
409
- return await this._saveHeaders(_signer, forkHeaders, storedHeader, tipWork, forkId.toNumber(), feeRate,
410
- (blockHeaders) => this.program.methods
411
- .submitForkHeaders(
412
- blockHeaders,
413
- storedHeader,
414
- forkId,
415
- true
416
- )
417
- .accounts({
418
- signer: _signer,
419
- mainState: this.BtcRelayMainState,
420
- forkState: this.BtcRelayFork(forkId.toNumber(), _signer),
421
- systemProgram: SystemProgram.programId,
422
- })
423
- );
424
- }
425
-
426
- /**
427
- * Continues submitting blockheaders to a given fork
428
- *
429
- * @param signer
430
- * @param forkHeaders
431
- * @param storedHeader
432
- * @param forkId
433
- * @param tipWork
434
- * @param feeRate
435
- */
436
- public saveForkHeaders(signer: string, forkHeaders: BtcBlock[], storedHeader: SolanaBtcStoredHeader, forkId: number, tipWork: Buffer, feeRate?: string) {
437
- this.logger.debug("saveForkHeaders(): submitting blockheaders to existing fork," +
438
- " count: "+forkHeaders.length+" forkId: "+forkId.toString(10));
439
-
440
- const _signer = new PublicKey(signer);
441
-
442
- return this._saveHeaders(_signer, forkHeaders, storedHeader, tipWork, forkId, feeRate,
443
- (blockHeaders) => this.program.methods
444
- .submitForkHeaders(
445
- blockHeaders,
446
- storedHeader,
447
- new BN(forkId),
448
- false
449
- )
450
- .accounts({
451
- signer: _signer,
452
- mainState: this.BtcRelayMainState,
453
- forkState: this.BtcRelayFork(forkId, _signer),
454
- systemProgram: SystemProgram.programId,
455
- })
456
- )
457
- }
458
-
459
- /**
460
- * Submits short fork with given blockheaders
461
- *
462
- * @param signer
463
- * @param forkHeaders
464
- * @param storedHeader
465
- * @param tipWork
466
- * @param feeRate
467
- */
468
- public saveShortForkHeaders(signer: string, forkHeaders: BtcBlock[], storedHeader: SolanaBtcStoredHeader, tipWork: Buffer, feeRate?: string) {
469
- this.logger.debug("saveShortForkHeaders(): submitting short fork blockheaders," +
470
- " count: "+forkHeaders.length);
471
-
472
- const _signer = new PublicKey(signer);
473
-
474
- return this._saveHeaders(_signer, forkHeaders, storedHeader, tipWork, -1, feeRate,
475
- (blockHeaders) => this.program.methods
476
- .submitShortForkHeaders(
477
- blockHeaders,
478
- storedHeader
479
- )
480
- .accounts({
481
- signer: _signer,
482
- mainState: this.BtcRelayMainState
483
- })
484
- );
485
- }
486
-
487
- /**
488
- * Sweeps fork data PDAs back to self
489
- *
490
- * @param signer
491
- * @param lastSweepId lastCheckedId returned from the previous sweepForkData() call
492
- * @returns {number} lastCheckedId that should be passed to the next call of sweepForkData()
493
- */
494
- public async sweepForkData(signer: SolanaSigner, lastSweepId?: number): Promise<number | null> {
495
- const mainState: any = await this.program.account.mainState.fetch(this.BtcRelayMainState);
496
- let forkId: number = mainState.forkCounter.toNumber();
497
-
498
- const txs: SolanaTx[] = [];
499
- let action = new SolanaAction(signer.getPublicKey(), this.Chain);
500
-
501
- let lastCheckedId = lastSweepId;
502
- for(
503
- let i = lastSweepId==null ? 0 : lastSweepId+1;
504
- i<=forkId; i++
505
- ) {
506
- lastCheckedId = i;
507
-
508
- const accountAddr = this.BtcRelayFork(i, signer.getPublicKey());
509
- let forkState: any = await this.program.account.forkState.fetchNullable(accountAddr);
510
- if(forkState==null) continue;
511
-
512
- this.logger.info("sweepForkData(): sweeping forkId: "+i);
513
- action.add(await this.CloseForkAccount(signer.getPublicKey(), i));
514
-
515
- if(action.ixsLength()>=MAX_CLOSE_IX_PER_TX) {
516
- await action.addToTxs(txs);
517
- action = new SolanaAction(signer.getPublicKey(), this.Chain);
518
- }
519
- }
520
-
521
- if(action.ixsLength()>=MAX_CLOSE_IX_PER_TX) {
522
- await action.addToTxs(txs);
523
- }
524
-
525
- if(txs.length>0) {
526
- const signatures = await this.Chain.sendAndConfirm(signer, txs, true);
527
- this.logger.info("sweepForkData(): forks swept, signatures: "+signatures.join());
528
- }
529
-
530
- return lastCheckedId;
531
- }
532
-
533
- /**
534
- * Estimate required synchronization fee (worst case) to synchronize btc relay to the required blockheight
535
- *
536
- * @param requiredBlockheight
537
- * @param feeRate
538
- */
539
- public async estimateSynchronizeFee(requiredBlockheight: number, feeRate?: string): Promise<bigint> {
540
- const tipData = await this.getTipData();
541
- const currBlockheight = tipData.blockheight;
542
-
543
- const blockheightDelta = requiredBlockheight-currBlockheight;
544
-
545
- if(blockheightDelta<=0) return 0n;
546
-
547
- const synchronizationFee = BigInt(blockheightDelta) * await this.getFeePerBlock(feeRate);
548
- this.logger.debug("estimateSynchronizeFee(): required blockheight: "+requiredBlockheight+
549
- " blockheight delta: "+blockheightDelta+" fee: "+synchronizationFee.toString(10));
550
-
551
- return synchronizationFee;
552
- }
553
-
554
- /**
555
- * Returns fee required (in SOL) to synchronize a single block to btc relay
556
- *
557
- * @param feeRate
558
- */
559
- public async getFeePerBlock(feeRate?: string): Promise<bigint> {
560
- // feeRate = feeRate || await this.getMainFeeRate(null);
561
- // return BASE_FEE_SOL_PER_BLOCKHEADER.add(this.Fees.getPriorityFee(200000, feeRate, false));
562
- return 50000n;
563
- }
564
-
565
- /**
566
- * Gets fee rate required for submitting blockheaders to the main chain
567
- */
568
- public getMainFeeRate(signer: string | null): Promise<string> {
569
- const _signer = signer==null ? null : new PublicKey(signer);
570
- return this.Chain.Fees.getFeeRate(_signer==null ? [this.BtcRelayMainState] : [
571
- _signer,
572
- this.BtcRelayMainState
573
- ]);
574
- }
575
-
576
- /**
577
- * Gets fee rate required for submitting blockheaders to the specific fork
578
- */
579
- public getForkFeeRate(signer: string, forkId: number): Promise<string> {
580
- const _signer = new PublicKey(signer);
581
- return this.Chain.Fees.getFeeRate([
582
- _signer,
583
- this.BtcRelayMainState,
584
- this.BtcRelayFork(forkId, _signer)
585
- ]);
586
- }
587
-
588
- }
1
+ import {
2
+ Connection,
3
+ PublicKey,
4
+ Signer,
5
+ SystemProgram,
6
+ Transaction
7
+ } from "@solana/web3.js";
8
+ import {SolanaBtcStoredHeader, SolanaBtcStoredHeaderType} from "./headers/SolanaBtcStoredHeader";
9
+ import {SolanaBtcHeader} from "./headers/SolanaBtcHeader";
10
+ import * as programIdl from "./program/programIdl.json";
11
+ import {BitcoinRpc, BtcBlock, BtcRelay, StatePredictorUtils} from "@atomiqlabs/base";
12
+ import {MethodsBuilder} from "@coral-xyz/anchor/dist/cjs/program/namespace/methods";
13
+ import {SolanaProgramBase} from "../program/SolanaProgramBase";
14
+ import {SolanaAction} from "../chain/SolanaAction";
15
+ import {Buffer} from "buffer";
16
+ import {SolanaTx} from "../chain/modules/SolanaTransactions";
17
+ import {SolanaSigner} from "../wallet/SolanaSigner";
18
+ import * as BN from "bn.js";
19
+ import {SolanaChainInterface} from "../chain/SolanaChainInterface";
20
+ import {getLogger} from "../../utils/Utils";
21
+
22
+ const MAX_CLOSE_IX_PER_TX = 10;
23
+
24
+ function serializeBlockHeader(e: BtcBlock): SolanaBtcHeader {
25
+ return new SolanaBtcHeader({
26
+ version: e.getVersion(),
27
+ reversedPrevBlockhash: [...Buffer.from(e.getPrevBlockhash(), "hex").reverse()],
28
+ merkleRoot: [...Buffer.from(e.getMerkleRoot(), "hex").reverse()],
29
+ timestamp: e.getTimestamp(),
30
+ nbits: e.getNbits(),
31
+ nonce: e.getNonce(),
32
+ hash: Buffer.from(e.getHash(), "hex").reverse()
33
+ });
34
+ };
35
+
36
+ export class SolanaBtcRelay<B extends BtcBlock> extends SolanaProgramBase<any> implements BtcRelay<SolanaBtcStoredHeader, {tx: Transaction, signers: Signer[]}, B, SolanaSigner> {
37
+
38
+ /**
39
+ * Creates initialization action for initializing the btc relay
40
+ *
41
+ * @param signer
42
+ * @param header
43
+ * @param epochStart
44
+ * @param pastBlocksTimestamps
45
+ * @constructor
46
+ * @private
47
+ */
48
+ private async Initialize(signer: PublicKey, header: B, epochStart: number, pastBlocksTimestamps: number[]): Promise<SolanaAction> {
49
+ const serializedBlock = serializeBlockHeader(header);
50
+ return new SolanaAction(signer, this.Chain,
51
+ await this.program.methods
52
+ .initialize(
53
+ serializedBlock,
54
+ header.getHeight(),
55
+ header.getChainWork(),
56
+ epochStart,
57
+ pastBlocksTimestamps
58
+ )
59
+ .accounts({
60
+ signer,
61
+ mainState: this.BtcRelayMainState,
62
+ headerTopic: this.BtcRelayHeader(serializedBlock.hash),
63
+ systemProgram: SystemProgram.programId
64
+ })
65
+ .instruction(),
66
+ 100_000
67
+ )
68
+ }
69
+
70
+ /**
71
+ * Creates verify action to be used with the swap program, specifies the action to be firstIxBeforeComputeBudget,
72
+ * such that the verify instruction will always be the 0th in the transaction, this is required because
73
+ * swap program expects the verify instruction to be at the 0th position
74
+ *
75
+ * @param signer
76
+ * @param reversedTxId
77
+ * @param confirmations
78
+ * @param position
79
+ * @param reversedMerkleProof
80
+ * @param committedHeader
81
+ */
82
+ public async Verify(
83
+ signer: PublicKey,
84
+ reversedTxId: Buffer,
85
+ confirmations: number,
86
+ position: number,
87
+ reversedMerkleProof: Buffer[],
88
+ committedHeader: SolanaBtcStoredHeader
89
+ ): Promise<SolanaAction> {
90
+ return new SolanaAction(signer, this.Chain,
91
+ await this.program.methods
92
+ .verifyTransaction(
93
+ reversedTxId,
94
+ confirmations,
95
+ position,
96
+ reversedMerkleProof,
97
+ committedHeader
98
+ )
99
+ .accounts({
100
+ signer,
101
+ mainState: this.BtcRelayMainState
102
+ })
103
+ .instruction(),
104
+ null,
105
+ null,
106
+ null,
107
+ true
108
+ );
109
+ }
110
+
111
+ public async CloseForkAccount(signer: PublicKey, forkId: number): Promise<SolanaAction> {
112
+ return new SolanaAction(signer, this.Chain,
113
+ await this.program.methods
114
+ .closeForkAccount(
115
+ new BN(forkId)
116
+ )
117
+ .accounts({
118
+ signer,
119
+ forkState: this.BtcRelayFork(forkId, signer),
120
+ systemProgram: SystemProgram.programId,
121
+ })
122
+ .instruction(),
123
+ 20000
124
+ )
125
+ }
126
+
127
+ BtcRelayMainState = this.pda("state");
128
+ BtcRelayHeader = this.pda("header", (hash: Buffer) => [hash]);
129
+ BtcRelayFork = this.pda("fork",
130
+ (forkId: number, pubkey: PublicKey) => [new BN(forkId).toArrayLike(Buffer, "le", 8), pubkey.toBuffer()]
131
+ );
132
+
133
+ bitcoinRpc: BitcoinRpc<B>;
134
+
135
+ readonly maxHeadersPerTx: number = 5;
136
+ readonly maxForkHeadersPerTx: number = 4;
137
+ readonly maxShortForkHeadersPerTx: number = 4;
138
+
139
+ constructor(
140
+ chainInterface: SolanaChainInterface,
141
+ bitcoinRpc: BitcoinRpc<B>,
142
+ programAddress?: string
143
+ ) {
144
+ super(chainInterface, programIdl, programAddress);
145
+ this.bitcoinRpc = bitcoinRpc;
146
+ }
147
+
148
+ /**
149
+ * Gets set of block commitments representing current main chain from the mainState
150
+ *
151
+ * @param mainState
152
+ * @private
153
+ */
154
+ private getBlockCommitmentsSet(mainState: any): Set<string> {
155
+ const storedCommitments = new Set<string>();
156
+ mainState.blockCommitments.forEach(e => {
157
+ storedCommitments.add(Buffer.from(e).toString("hex"));
158
+ });
159
+ return storedCommitments;
160
+ }
161
+
162
+ /**
163
+ * Computes subsequent commited headers as they will appear on the blockchain when transactions
164
+ * are submitted & confirmed
165
+ *
166
+ * @param initialStoredHeader
167
+ * @param syncedHeaders
168
+ * @private
169
+ */
170
+ private computeCommitedHeaders(initialStoredHeader: SolanaBtcStoredHeader, syncedHeaders: SolanaBtcHeader[]) {
171
+ const computedCommitedHeaders = [initialStoredHeader];
172
+ for(let blockHeader of syncedHeaders) {
173
+ computedCommitedHeaders.push(computedCommitedHeaders[computedCommitedHeaders.length-1].computeNext(blockHeader));
174
+ }
175
+ return computedCommitedHeaders;
176
+ }
177
+
178
+ /**
179
+ * A common logic for submitting blockheaders in a transaction
180
+ *
181
+ * @param signer
182
+ * @param headers headers to sync to the btc relay
183
+ * @param storedHeader current latest stored block header for a given fork
184
+ * @param tipWork work of the current tip in a given fork
185
+ * @param forkId forkId to submit to, forkId=0 means main chain
186
+ * @param feeRate feeRate for the transaction
187
+ * @param createTx transaction generator function
188
+ * @private
189
+ */
190
+ private async _saveHeaders(
191
+ signer: PublicKey,
192
+ headers: BtcBlock[],
193
+ storedHeader: SolanaBtcStoredHeader,
194
+ tipWork: Buffer,
195
+ forkId: number,
196
+ feeRate: string,
197
+ createTx: (blockHeaders: SolanaBtcHeader[]) => MethodsBuilder<any, any>
198
+ ) {
199
+ const blockHeaderObj = headers.map(serializeBlockHeader);
200
+
201
+ const tx = await createTx(blockHeaderObj)
202
+ .remainingAccounts(blockHeaderObj.map(e => {
203
+ return {
204
+ pubkey: this.BtcRelayHeader(e.hash),
205
+ isSigner: false,
206
+ isWritable: false
207
+ }
208
+ }))
209
+ .transaction();
210
+ tx.feePayer = signer;
211
+
212
+ this.Chain.Fees.applyFeeRateBegin(tx, null, feeRate);
213
+ this.Chain.Fees.applyFeeRateEnd(tx, null, feeRate);
214
+
215
+ const computedCommitedHeaders = this.computeCommitedHeaders(storedHeader, blockHeaderObj);
216
+ const lastStoredHeader = computedCommitedHeaders[computedCommitedHeaders.length-1];
217
+ if(forkId!==0 && StatePredictorUtils.gtBuffer(Buffer.from(lastStoredHeader.chainWork), tipWork)) {
218
+ //Fork's work is higher than main chain's work, this fork will become a main chain
219
+ forkId = 0;
220
+ }
221
+
222
+ return {
223
+ forkId: forkId,
224
+ lastStoredHeader,
225
+ tx: {
226
+ tx,
227
+ signers: []
228
+ },
229
+ computedCommitedHeaders
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Returns data about current main chain tip stored in the btc relay
235
+ */
236
+ public async getTipData(): Promise<{ commitHash: string; blockhash: string, chainWork: Buffer, blockheight: number }> {
237
+ const data: any = await this.program.account.mainState.fetchNullable(this.BtcRelayMainState);
238
+ if(data==null) return null;
239
+
240
+ return {
241
+ blockheight: data.blockHeight,
242
+ commitHash: Buffer.from(data.tipCommitHash).toString("hex"),
243
+ blockhash: Buffer.from(data.tipBlockHash).reverse().toString("hex"),
244
+ chainWork: Buffer.from(data.chainWork)
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Retrieves blockheader with a specific blockhash, returns null if requiredBlockheight is provided and
250
+ * btc relay contract is not synced up to the desired blockheight
251
+ *
252
+ * @param blockData
253
+ * @param requiredBlockheight
254
+ */
255
+ public async retrieveLogAndBlockheight(blockData: {blockhash: string}, requiredBlockheight?: number): Promise<{
256
+ header: SolanaBtcStoredHeader,
257
+ height: number
258
+ } | null> {
259
+ const mainState: any = await this.program.account.mainState.fetch(this.BtcRelayMainState);
260
+
261
+ if(requiredBlockheight!=null && mainState.blockHeight < requiredBlockheight) {
262
+ return null;
263
+ }
264
+
265
+ const storedCommitments = this.getBlockCommitmentsSet(mainState);
266
+ const blockHashBuffer = Buffer.from(blockData.blockhash, 'hex').reverse();
267
+ const topicKey = this.BtcRelayHeader(blockHashBuffer);
268
+
269
+ const data = await this.Events.findInEvents(topicKey, async (event) => {
270
+ if(event.name==="StoreFork" || event.name==="StoreHeader") {
271
+ const eventData: any = event.data;
272
+ const commitHash = Buffer.from(eventData.commitHash).toString("hex");
273
+ if(blockHashBuffer.equals(Buffer.from(eventData.blockHash)) && storedCommitments.has(commitHash))
274
+ return {
275
+ header: new SolanaBtcStoredHeader(eventData.header as SolanaBtcStoredHeaderType),
276
+ height: mainState.blockHeight as number,
277
+ commitHash
278
+ };
279
+ }
280
+ });
281
+ if(data!=null) this.logger.debug("retrieveLogAndBlockheight(): block found," +
282
+ " commit hash: "+data.commitHash+" blockhash: "+blockData.blockhash+" height: "+data.height);
283
+
284
+ return data;
285
+ }
286
+
287
+ /**
288
+ * Retrieves blockheader data by blockheader's commit hash,
289
+ *
290
+ * @param commitmentHashStr
291
+ * @param blockData
292
+ */
293
+ public async retrieveLogByCommitHash(commitmentHashStr: string, blockData: {blockhash: string}): Promise<SolanaBtcStoredHeader> {
294
+ const blockHashBuffer = Buffer.from(blockData.blockhash, "hex").reverse();
295
+ const topicKey = this.BtcRelayHeader(blockHashBuffer);
296
+
297
+ const data = await this.Events.findInEvents(topicKey, async (event) => {
298
+ if(event.name==="StoreFork" || event.name==="StoreHeader") {
299
+ const eventData: any = event.data;
300
+ const commitHash = Buffer.from(eventData.commitHash).toString("hex");
301
+ if(commitmentHashStr===commitHash)
302
+ return new SolanaBtcStoredHeader(eventData.header as SolanaBtcStoredHeaderType);
303
+ }
304
+ });
305
+ if(data!=null) this.logger.debug("retrieveLogByCommitHash(): block found," +
306
+ " commit hash: "+commitmentHashStr+" blockhash: "+blockData.blockhash+" height: "+data.blockheight);
307
+
308
+ return data;
309
+ }
310
+
311
+ /**
312
+ * Retrieves latest known stored blockheader & blockheader from bitcoin RPC that is in the main chain
313
+ */
314
+ public async retrieveLatestKnownBlockLog(): Promise<{
315
+ resultStoredHeader: SolanaBtcStoredHeader,
316
+ resultBitcoinHeader: B
317
+ }> {
318
+ const mainState: any = await this.program.account.mainState.fetch(this.BtcRelayMainState);
319
+ const storedCommitments = this.getBlockCommitmentsSet(mainState);
320
+
321
+ const data = await this.Events.findInEvents(this.program.programId, async (event) => {
322
+ if(event.name==="StoreFork" || event.name==="StoreHeader") {
323
+ const eventData: any = event.data;
324
+ const blockHashHex = Buffer.from(eventData.blockHash).reverse().toString("hex");
325
+ const isInMainChain = await this.bitcoinRpc.isInMainChain(blockHashHex).catch(() => false);
326
+ const commitHash = Buffer.from(eventData.commitHash).toString("hex");
327
+ //Check if this fork is part of main chain
328
+ if(isInMainChain && storedCommitments.has(commitHash))
329
+ return {
330
+ resultStoredHeader: new SolanaBtcStoredHeader(eventData.header),
331
+ resultBitcoinHeader: await this.bitcoinRpc.getBlockHeader(blockHashHex),
332
+ commitHash: commitHash
333
+ };
334
+ }
335
+ }, null, 10);
336
+ if(data!=null) this.logger.debug("retrieveLatestKnownBlockLog(): block found," +
337
+ " commit hash: "+data.commitHash+" blockhash: "+data.resultBitcoinHeader.getHash()+
338
+ " height: "+data.resultStoredHeader.blockheight);
339
+
340
+ return data;
341
+ }
342
+
343
+ /**
344
+ * Saves initial block header when the btc relay is in uninitialized state
345
+ *
346
+ * @param signer
347
+ * @param header a bitcoin blockheader to submit
348
+ * @param epochStart timestamp of the start of the epoch (block timestamp at blockheight-(blockheight%2016))
349
+ * @param pastBlocksTimestamps timestamp of the past 10 blocks
350
+ * @param feeRate fee rate to use for the transaction
351
+ */
352
+ async saveInitialHeader(
353
+ signer: string,
354
+ header: B,
355
+ epochStart: number,
356
+ pastBlocksTimestamps: number[],
357
+ feeRate?: string
358
+ ): Promise<{ tx: Transaction; signers: Signer[]; }> {
359
+ if(pastBlocksTimestamps.length!==10) throw new Error("Invalid prevBlocksTimestamps");
360
+
361
+ const action = await this.Initialize(new PublicKey(signer), header, epochStart, pastBlocksTimestamps);
362
+
363
+ this.logger.debug("saveInitialHeader(): saving initial header, blockhash: "+header.getHash()+
364
+ " blockheight: "+header.getHeight()+" epochStart: "+epochStart+" past block timestamps: "+pastBlocksTimestamps.join());
365
+
366
+ return await action.tx(feeRate);
367
+ }
368
+
369
+ /**
370
+ * Saves blockheaders as a bitcoin main chain to the btc relay
371
+ *
372
+ * @param signer
373
+ * @param mainHeaders
374
+ * @param storedHeader
375
+ * @param feeRate
376
+ */
377
+ public saveMainHeaders(signer: string, mainHeaders: BtcBlock[], storedHeader: SolanaBtcStoredHeader, feeRate?: string) {
378
+ this.logger.debug("saveMainHeaders(): submitting main blockheaders, count: "+mainHeaders.length);
379
+ const _signer = new PublicKey(signer);
380
+ return this._saveHeaders(_signer, mainHeaders, storedHeader, null, 0, feeRate,
381
+ (blockHeaders) => this.program.methods
382
+ .submitBlockHeaders(
383
+ blockHeaders,
384
+ storedHeader
385
+ )
386
+ .accounts({
387
+ signer: _signer,
388
+ mainState: this.BtcRelayMainState,
389
+ })
390
+ );
391
+ }
392
+
393
+ /**
394
+ * Creates a new long fork and submits the headers to it
395
+ *
396
+ * @param signer
397
+ * @param forkHeaders
398
+ * @param storedHeader
399
+ * @param tipWork
400
+ * @param feeRate
401
+ */
402
+ public async saveNewForkHeaders(signer: string, forkHeaders: BtcBlock[], storedHeader: SolanaBtcStoredHeader, tipWork: Buffer, feeRate?: string) {
403
+ const mainState: any = await this.program.account.mainState.fetch(this.BtcRelayMainState);
404
+ let forkId: BN = mainState.forkCounter;
405
+
406
+ const _signer = new PublicKey(signer);
407
+
408
+ this.logger.debug("saveNewForkHeaders(): submitting new fork & blockheaders," +
409
+ " count: "+forkHeaders.length+" forkId: "+forkId.toString(10));
410
+ return await this._saveHeaders(_signer, forkHeaders, storedHeader, tipWork, forkId.toNumber(), feeRate,
411
+ (blockHeaders) => this.program.methods
412
+ .submitForkHeaders(
413
+ blockHeaders,
414
+ storedHeader,
415
+ forkId,
416
+ true
417
+ )
418
+ .accounts({
419
+ signer: _signer,
420
+ mainState: this.BtcRelayMainState,
421
+ forkState: this.BtcRelayFork(forkId.toNumber(), _signer),
422
+ systemProgram: SystemProgram.programId,
423
+ })
424
+ );
425
+ }
426
+
427
+ /**
428
+ * Continues submitting blockheaders to a given fork
429
+ *
430
+ * @param signer
431
+ * @param forkHeaders
432
+ * @param storedHeader
433
+ * @param forkId
434
+ * @param tipWork
435
+ * @param feeRate
436
+ */
437
+ public saveForkHeaders(signer: string, forkHeaders: BtcBlock[], storedHeader: SolanaBtcStoredHeader, forkId: number, tipWork: Buffer, feeRate?: string) {
438
+ this.logger.debug("saveForkHeaders(): submitting blockheaders to existing fork," +
439
+ " count: "+forkHeaders.length+" forkId: "+forkId.toString(10));
440
+
441
+ const _signer = new PublicKey(signer);
442
+
443
+ return this._saveHeaders(_signer, forkHeaders, storedHeader, tipWork, forkId, feeRate,
444
+ (blockHeaders) => this.program.methods
445
+ .submitForkHeaders(
446
+ blockHeaders,
447
+ storedHeader,
448
+ new BN(forkId),
449
+ false
450
+ )
451
+ .accounts({
452
+ signer: _signer,
453
+ mainState: this.BtcRelayMainState,
454
+ forkState: this.BtcRelayFork(forkId, _signer),
455
+ systemProgram: SystemProgram.programId,
456
+ })
457
+ )
458
+ }
459
+
460
+ /**
461
+ * Submits short fork with given blockheaders
462
+ *
463
+ * @param signer
464
+ * @param forkHeaders
465
+ * @param storedHeader
466
+ * @param tipWork
467
+ * @param feeRate
468
+ */
469
+ public saveShortForkHeaders(signer: string, forkHeaders: BtcBlock[], storedHeader: SolanaBtcStoredHeader, tipWork: Buffer, feeRate?: string) {
470
+ this.logger.debug("saveShortForkHeaders(): submitting short fork blockheaders," +
471
+ " count: "+forkHeaders.length);
472
+
473
+ const _signer = new PublicKey(signer);
474
+
475
+ return this._saveHeaders(_signer, forkHeaders, storedHeader, tipWork, -1, feeRate,
476
+ (blockHeaders) => this.program.methods
477
+ .submitShortForkHeaders(
478
+ blockHeaders,
479
+ storedHeader
480
+ )
481
+ .accounts({
482
+ signer: _signer,
483
+ mainState: this.BtcRelayMainState
484
+ })
485
+ );
486
+ }
487
+
488
+ /**
489
+ * Sweeps fork data PDAs back to self
490
+ *
491
+ * @param signer
492
+ * @param lastSweepId lastCheckedId returned from the previous sweepForkData() call
493
+ * @returns {number} lastCheckedId that should be passed to the next call of sweepForkData()
494
+ */
495
+ public async sweepForkData(signer: SolanaSigner, lastSweepId?: number): Promise<number | null> {
496
+ const mainState: any = await this.program.account.mainState.fetch(this.BtcRelayMainState);
497
+ let forkId: number = mainState.forkCounter.toNumber();
498
+
499
+ const txs: SolanaTx[] = [];
500
+ let action = new SolanaAction(signer.getPublicKey(), this.Chain);
501
+
502
+ let lastCheckedId = lastSweepId;
503
+ for(
504
+ let i = lastSweepId==null ? 0 : lastSweepId+1;
505
+ i<=forkId; i++
506
+ ) {
507
+ lastCheckedId = i;
508
+
509
+ const accountAddr = this.BtcRelayFork(i, signer.getPublicKey());
510
+ let forkState: any = await this.program.account.forkState.fetchNullable(accountAddr);
511
+ if(forkState==null) continue;
512
+
513
+ this.logger.info("sweepForkData(): sweeping forkId: "+i);
514
+ action.add(await this.CloseForkAccount(signer.getPublicKey(), i));
515
+
516
+ if(action.ixsLength()>=MAX_CLOSE_IX_PER_TX) {
517
+ await action.addToTxs(txs);
518
+ action = new SolanaAction(signer.getPublicKey(), this.Chain);
519
+ }
520
+ }
521
+
522
+ if(action.ixsLength()>=MAX_CLOSE_IX_PER_TX) {
523
+ await action.addToTxs(txs);
524
+ }
525
+
526
+ if(txs.length>0) {
527
+ const signatures = await this.Chain.sendAndConfirm(signer, txs, true);
528
+ this.logger.info("sweepForkData(): forks swept, signatures: "+signatures.join());
529
+ }
530
+
531
+ return lastCheckedId;
532
+ }
533
+
534
+ /**
535
+ * Estimate required synchronization fee (worst case) to synchronize btc relay to the required blockheight
536
+ *
537
+ * @param requiredBlockheight
538
+ * @param feeRate
539
+ */
540
+ public async estimateSynchronizeFee(requiredBlockheight: number, feeRate?: string): Promise<bigint> {
541
+ const tipData = await this.getTipData();
542
+ const currBlockheight = tipData.blockheight;
543
+
544
+ const blockheightDelta = requiredBlockheight-currBlockheight;
545
+
546
+ if(blockheightDelta<=0) return 0n;
547
+
548
+ const synchronizationFee = BigInt(blockheightDelta) * await this.getFeePerBlock(feeRate);
549
+ this.logger.debug("estimateSynchronizeFee(): required blockheight: "+requiredBlockheight+
550
+ " blockheight delta: "+blockheightDelta+" fee: "+synchronizationFee.toString(10));
551
+
552
+ return synchronizationFee;
553
+ }
554
+
555
+ /**
556
+ * Returns fee required (in SOL) to synchronize a single block to btc relay
557
+ *
558
+ * @param feeRate
559
+ */
560
+ public async getFeePerBlock(feeRate?: string): Promise<bigint> {
561
+ // feeRate = feeRate || await this.getMainFeeRate(null);
562
+ // return BASE_FEE_SOL_PER_BLOCKHEADER.add(this.Fees.getPriorityFee(200000, feeRate, false));
563
+ return 50000n;
564
+ }
565
+
566
+ /**
567
+ * Gets fee rate required for submitting blockheaders to the main chain
568
+ */
569
+ public getMainFeeRate(signer: string | null): Promise<string> {
570
+ const _signer = signer==null ? null : new PublicKey(signer);
571
+ return this.Chain.Fees.getFeeRate(_signer==null ? [this.BtcRelayMainState] : [
572
+ _signer,
573
+ this.BtcRelayMainState
574
+ ]);
575
+ }
576
+
577
+ /**
578
+ * Gets fee rate required for submitting blockheaders to the specific fork
579
+ */
580
+ public getForkFeeRate(signer: string, forkId: number): Promise<string> {
581
+ const _signer = new PublicKey(signer);
582
+ return this.Chain.Fees.getFeeRate([
583
+ _signer,
584
+ this.BtcRelayMainState,
585
+ this.BtcRelayFork(forkId, _signer)
586
+ ]);
587
+ }
588
+
589
+ }