@atomiqlabs/chain-solana 13.5.13 → 13.5.14
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.
- package/LICENSE +201 -201
- package/README.md +73 -73
- package/dist/index.d.ts +81 -81
- package/dist/index.js +102 -102
- package/dist/node/index.d.ts +9 -9
- package/dist/node/index.js +13 -13
- package/dist/solana/SolanaChainType.d.ts +15 -15
- package/dist/solana/SolanaChainType.js +2 -2
- package/dist/solana/SolanaChains.d.ts +12 -12
- package/dist/solana/SolanaChains.js +45 -45
- package/dist/solana/SolanaInitializer.d.ts +94 -94
- package/dist/solana/SolanaInitializer.js +174 -174
- package/dist/solana/btcrelay/SolanaBtcRelay.d.ts +222 -222
- package/dist/solana/btcrelay/SolanaBtcRelay.js +455 -455
- package/dist/solana/btcrelay/headers/SolanaBtcHeader.d.ts +84 -84
- package/dist/solana/btcrelay/headers/SolanaBtcHeader.js +70 -70
- package/dist/solana/btcrelay/headers/SolanaBtcStoredHeader.d.ts +92 -92
- package/dist/solana/btcrelay/headers/SolanaBtcStoredHeader.js +109 -109
- package/dist/solana/btcrelay/program/programIdl.json +671 -671
- package/dist/solana/chain/SolanaAction.d.ts +26 -26
- package/dist/solana/chain/SolanaAction.js +87 -87
- package/dist/solana/chain/SolanaChainInterface.d.ts +224 -224
- package/dist/solana/chain/SolanaChainInterface.js +275 -275
- package/dist/solana/chain/SolanaModule.d.ts +14 -14
- package/dist/solana/chain/SolanaModule.js +13 -13
- package/dist/solana/chain/modules/SolanaAddresses.d.ts +8 -8
- package/dist/solana/chain/modules/SolanaAddresses.js +22 -22
- package/dist/solana/chain/modules/SolanaBlocks.d.ts +32 -32
- package/dist/solana/chain/modules/SolanaBlocks.js +78 -78
- package/dist/solana/chain/modules/SolanaEvents.d.ts +68 -68
- package/dist/solana/chain/modules/SolanaEvents.js +238 -238
- package/dist/solana/chain/modules/SolanaFees.d.ts +189 -189
- package/dist/solana/chain/modules/SolanaFees.js +434 -434
- package/dist/solana/chain/modules/SolanaSignatures.d.ts +24 -24
- package/dist/solana/chain/modules/SolanaSignatures.js +39 -39
- package/dist/solana/chain/modules/SolanaSlots.d.ts +33 -33
- package/dist/solana/chain/modules/SolanaSlots.js +72 -72
- package/dist/solana/chain/modules/SolanaTokens.d.ts +123 -123
- package/dist/solana/chain/modules/SolanaTokens.js +242 -242
- package/dist/solana/chain/modules/SolanaTransactions.d.ts +149 -149
- package/dist/solana/chain/modules/SolanaTransactions.js +445 -445
- package/dist/solana/connection/ConnectionWithRetries.d.ts +35 -35
- package/dist/solana/connection/ConnectionWithRetries.js +86 -71
- package/dist/solana/events/SolanaChainEvents.d.ts +45 -45
- package/dist/solana/events/SolanaChainEvents.js +108 -108
- package/dist/solana/events/SolanaChainEventsBrowser.d.ts +205 -205
- package/dist/solana/events/SolanaChainEventsBrowser.js +404 -404
- package/dist/solana/program/SolanaProgramBase.d.ts +73 -73
- package/dist/solana/program/SolanaProgramBase.js +54 -54
- package/dist/solana/program/SolanaProgramModule.d.ts +8 -8
- package/dist/solana/program/SolanaProgramModule.js +11 -11
- package/dist/solana/program/modules/SolanaProgramEvents.d.ts +53 -53
- package/dist/solana/program/modules/SolanaProgramEvents.js +117 -117
- package/dist/solana/swaps/SolanaSwapData.d.ts +333 -333
- package/dist/solana/swaps/SolanaSwapData.js +535 -535
- package/dist/solana/swaps/SolanaSwapModule.d.ts +11 -11
- package/dist/solana/swaps/SolanaSwapModule.js +12 -12
- package/dist/solana/swaps/SolanaSwapProgram.d.ts +376 -376
- package/dist/solana/swaps/SolanaSwapProgram.js +769 -769
- package/dist/solana/swaps/SwapTypeEnum.d.ts +11 -11
- package/dist/solana/swaps/SwapTypeEnum.js +43 -43
- package/dist/solana/swaps/modules/SolanaDataAccount.d.ts +95 -95
- package/dist/solana/swaps/modules/SolanaDataAccount.js +232 -232
- package/dist/solana/swaps/modules/SolanaLpVault.d.ts +69 -69
- package/dist/solana/swaps/modules/SolanaLpVault.js +171 -171
- package/dist/solana/swaps/modules/SwapClaim.d.ts +126 -126
- package/dist/solana/swaps/modules/SwapClaim.js +294 -294
- package/dist/solana/swaps/modules/SwapInit.d.ts +213 -213
- package/dist/solana/swaps/modules/SwapInit.js +658 -658
- package/dist/solana/swaps/modules/SwapRefund.d.ts +87 -87
- package/dist/solana/swaps/modules/SwapRefund.js +293 -293
- package/dist/solana/swaps/programIdl.json +945 -945
- package/dist/solana/swaps/programTypes.d.ts +943 -943
- package/dist/solana/swaps/programTypes.js +945 -945
- package/dist/solana/swaps/v1/programIdl.json +945 -945
- package/dist/solana/swaps/v1/programTypes.d.ts +943 -943
- package/dist/solana/swaps/v1/programTypes.js +945 -945
- package/dist/solana/swaps/v2/programIdl.json +952 -952
- package/dist/solana/swaps/v2/programTypes.d.ts +950 -950
- package/dist/solana/swaps/v2/programTypes.js +952 -952
- package/dist/solana/wallet/SolanaKeypairWallet.d.ts +29 -29
- package/dist/solana/wallet/SolanaKeypairWallet.js +50 -50
- package/dist/solana/wallet/SolanaSigner.d.ts +30 -30
- package/dist/solana/wallet/SolanaSigner.js +30 -30
- package/dist/utils/Utils.d.ts +58 -58
- package/dist/utils/Utils.js +170 -170
- package/node/index.d.ts +1 -1
- package/node/index.js +3 -3
- package/package.json +46 -46
- package/src/index.ts +87 -87
- package/src/node/index.ts +9 -9
- package/src/solana/SolanaChainType.ts +32 -32
- package/src/solana/SolanaChains.ts +46 -46
- package/src/solana/SolanaInitializer.ts +278 -278
- package/src/solana/btcrelay/SolanaBtcRelay.ts +615 -615
- package/src/solana/btcrelay/headers/SolanaBtcHeader.ts +116 -116
- package/src/solana/btcrelay/headers/SolanaBtcStoredHeader.ts +148 -148
- package/src/solana/btcrelay/program/programIdl.json +670 -670
- package/src/solana/chain/SolanaAction.ts +109 -109
- package/src/solana/chain/SolanaChainInterface.ts +404 -404
- package/src/solana/chain/SolanaModule.ts +20 -20
- package/src/solana/chain/modules/SolanaAddresses.ts +20 -20
- package/src/solana/chain/modules/SolanaBlocks.ts +89 -89
- package/src/solana/chain/modules/SolanaEvents.ts +271 -271
- package/src/solana/chain/modules/SolanaFees.ts +522 -522
- package/src/solana/chain/modules/SolanaSignatures.ts +39 -39
- package/src/solana/chain/modules/SolanaSlots.ts +85 -85
- package/src/solana/chain/modules/SolanaTokens.ts +300 -300
- package/src/solana/chain/modules/SolanaTransactions.ts +503 -503
- package/src/solana/connection/ConnectionWithRetries.ts +113 -96
- package/src/solana/events/SolanaChainEvents.ts +127 -127
- package/src/solana/events/SolanaChainEventsBrowser.ts +495 -495
- package/src/solana/program/SolanaProgramBase.ts +119 -119
- package/src/solana/program/SolanaProgramModule.ts +15 -15
- package/src/solana/program/modules/SolanaProgramEvents.ts +157 -157
- package/src/solana/swaps/SolanaSwapData.ts +735 -735
- package/src/solana/swaps/SolanaSwapModule.ts +19 -19
- package/src/solana/swaps/SolanaSwapProgram.ts +1074 -1074
- package/src/solana/swaps/SwapTypeEnum.ts +30 -30
- package/src/solana/swaps/modules/SolanaDataAccount.ts +302 -302
- package/src/solana/swaps/modules/SolanaLpVault.ts +208 -208
- package/src/solana/swaps/modules/SwapClaim.ts +387 -387
- package/src/solana/swaps/modules/SwapInit.ts +785 -785
- package/src/solana/swaps/modules/SwapRefund.ts +353 -353
- package/src/solana/swaps/v1/programIdl.json +944 -944
- package/src/solana/swaps/v1/programTypes.ts +1885 -1885
- package/src/solana/swaps/v2/programIdl.json +951 -951
- package/src/solana/swaps/v2/programTypes.ts +1899 -1899
- package/src/solana/wallet/SolanaKeypairWallet.ts +56 -56
- package/src/solana/wallet/SolanaSigner.ts +43 -43
- package/src/utils/Utils.ts +194 -194
|
@@ -1,455 +1,455 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SolanaBtcRelay = void 0;
|
|
4
|
-
const web3_js_1 = require("@solana/web3.js");
|
|
5
|
-
const SolanaBtcStoredHeader_1 = require("./headers/SolanaBtcStoredHeader");
|
|
6
|
-
const SolanaBtcHeader_1 = require("./headers/SolanaBtcHeader");
|
|
7
|
-
const programIdl = require("./program/programIdl.json");
|
|
8
|
-
const base_1 = require("@atomiqlabs/base");
|
|
9
|
-
const SolanaProgramBase_1 = require("../program/SolanaProgramBase");
|
|
10
|
-
const SolanaAction_1 = require("../chain/SolanaAction");
|
|
11
|
-
const buffer_1 = require("buffer");
|
|
12
|
-
const BN = require("bn.js");
|
|
13
|
-
const SolanaFees_1 = require("../chain/modules/SolanaFees");
|
|
14
|
-
const SolanaChains_1 = require("../SolanaChains");
|
|
15
|
-
const MAX_CLOSE_IX_PER_TX = 10;
|
|
16
|
-
function serializeBlockHeader(e) {
|
|
17
|
-
return new SolanaBtcHeader_1.SolanaBtcHeader({
|
|
18
|
-
version: e.getVersion(),
|
|
19
|
-
reversedPrevBlockhash: [...buffer_1.Buffer.from(e.getPrevBlockhash(), "hex").reverse()],
|
|
20
|
-
merkleRoot: [...buffer_1.Buffer.from(e.getMerkleRoot(), "hex").reverse()],
|
|
21
|
-
timestamp: e.getTimestamp(),
|
|
22
|
-
nbits: e.getNbits(),
|
|
23
|
-
nonce: e.getNonce(),
|
|
24
|
-
hash: buffer_1.Buffer.from(e.getHash(), "hex").reverse()
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
;
|
|
28
|
-
/**
|
|
29
|
-
* Solana BTC relay (bitcoin light client) program representation.
|
|
30
|
-
*
|
|
31
|
-
* @category BTC Relay
|
|
32
|
-
*/
|
|
33
|
-
class SolanaBtcRelay extends SolanaProgramBase_1.SolanaProgramBase {
|
|
34
|
-
/**
|
|
35
|
-
* Creates initialization action for initializing the btc relay
|
|
36
|
-
*
|
|
37
|
-
* @param signer
|
|
38
|
-
* @param header
|
|
39
|
-
* @param epochStart
|
|
40
|
-
* @param pastBlocksTimestamps
|
|
41
|
-
* @private
|
|
42
|
-
*/
|
|
43
|
-
async Initialize(signer, header, epochStart, pastBlocksTimestamps) {
|
|
44
|
-
const serializedBlock = serializeBlockHeader(header);
|
|
45
|
-
return new SolanaAction_1.SolanaAction(signer, this._Chain, await this.program.methods
|
|
46
|
-
.initialize(serializedBlock, header.getHeight(), header.getChainWork(), epochStart, pastBlocksTimestamps)
|
|
47
|
-
.accounts({
|
|
48
|
-
signer,
|
|
49
|
-
mainState: this.BtcRelayMainState,
|
|
50
|
-
headerTopic: this.BtcRelayHeader(serializedBlock.getHash()),
|
|
51
|
-
systemProgram: web3_js_1.SystemProgram.programId
|
|
52
|
-
})
|
|
53
|
-
.instruction(), 100000);
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Creates verify action to be used with the swap program, specifies the action to be firstIxBeforeComputeBudget,
|
|
57
|
-
* such that the verify instruction will always be the 0th in the transaction, this is required because
|
|
58
|
-
* swap program expects the verify instruction to be at the 0th position
|
|
59
|
-
*
|
|
60
|
-
* @param signer
|
|
61
|
-
* @param reversedTxId
|
|
62
|
-
* @param confirmations
|
|
63
|
-
* @param position
|
|
64
|
-
* @param reversedMerkleProof
|
|
65
|
-
* @param committedHeader
|
|
66
|
-
*/
|
|
67
|
-
async Verify(signer, reversedTxId, confirmations, position, reversedMerkleProof, committedHeader) {
|
|
68
|
-
return new SolanaAction_1.SolanaAction(signer, this._Chain, await this.program.methods
|
|
69
|
-
.verifyTransaction(reversedTxId, confirmations, position, reversedMerkleProof, committedHeader)
|
|
70
|
-
.accounts({
|
|
71
|
-
signer,
|
|
72
|
-
mainState: this.BtcRelayMainState
|
|
73
|
-
})
|
|
74
|
-
.instruction(), undefined, undefined, undefined, true);
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Creates an action that closes a fork account and refunds rent to the signer.
|
|
78
|
-
*
|
|
79
|
-
* @param signer Signer paying and receiving rent refund
|
|
80
|
-
* @param forkId Fork account identifier to close
|
|
81
|
-
*/
|
|
82
|
-
async CloseForkAccount(signer, forkId) {
|
|
83
|
-
return new SolanaAction_1.SolanaAction(signer, this._Chain, await this.program.methods
|
|
84
|
-
.closeForkAccount(new BN(forkId))
|
|
85
|
-
.accounts({
|
|
86
|
-
signer,
|
|
87
|
-
forkState: this.BtcRelayFork(forkId, signer),
|
|
88
|
-
systemProgram: web3_js_1.SystemProgram.programId,
|
|
89
|
-
})
|
|
90
|
-
.instruction(), 20000);
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* @param chainInterface Underlying chain interface to use for the Solana chain operations
|
|
94
|
-
* @param bitcoinRpc Bitcoin RPC instance to use for read access to the bitcoin blockchain
|
|
95
|
-
* @param programAddress Optional Solana on-chain program address, defaults to the cannonical deployment
|
|
96
|
-
* @param bitcoinNetwork
|
|
97
|
-
* @param contractVersion
|
|
98
|
-
*/
|
|
99
|
-
constructor(chainInterface, bitcoinRpc, programAddress, bitcoinNetwork, contractVersion) {
|
|
100
|
-
if (bitcoinNetwork != null && programAddress == null) {
|
|
101
|
-
programAddress = SolanaChains_1.SolanaChains[bitcoinNetwork]?.addresses[contractVersion ?? "v1"]?.btcRelayContract;
|
|
102
|
-
}
|
|
103
|
-
super(chainInterface, programIdl, programAddress);
|
|
104
|
-
/**
|
|
105
|
-
* PDA of the relay main state account.
|
|
106
|
-
*/
|
|
107
|
-
this.BtcRelayMainState = this.pda("state");
|
|
108
|
-
/**
|
|
109
|
-
* PDA helper for per-header topic accounts.
|
|
110
|
-
*/
|
|
111
|
-
this.BtcRelayHeader = this.pda("header", (hash) => [hash]);
|
|
112
|
-
/**
|
|
113
|
-
* PDA helper for fork state accounts.
|
|
114
|
-
*/
|
|
115
|
-
this.BtcRelayFork = this.pda("fork", (forkId, pubkey) => [new BN(forkId).toArrayLike(buffer_1.Buffer, "le", 8), pubkey.toBuffer()]);
|
|
116
|
-
/**
|
|
117
|
-
* @inheritDoc
|
|
118
|
-
*/
|
|
119
|
-
this.maxHeadersPerTx = 5;
|
|
120
|
-
/**
|
|
121
|
-
* @inheritDoc
|
|
122
|
-
*/
|
|
123
|
-
this.maxForkHeadersPerTx = 4;
|
|
124
|
-
/**
|
|
125
|
-
* @inheritDoc
|
|
126
|
-
*/
|
|
127
|
-
this.maxShortForkHeadersPerTx = 4;
|
|
128
|
-
this._bitcoinRpc = bitcoinRpc;
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Gets set of block commitments representing current main chain from the mainState
|
|
132
|
-
*
|
|
133
|
-
* @param mainState
|
|
134
|
-
* @private
|
|
135
|
-
*/
|
|
136
|
-
getBlockCommitmentsSet(mainState) {
|
|
137
|
-
const storedCommitments = new Set();
|
|
138
|
-
mainState.blockCommitments.forEach((e) => {
|
|
139
|
-
storedCommitments.add(buffer_1.Buffer.from(e).toString("hex"));
|
|
140
|
-
});
|
|
141
|
-
return storedCommitments;
|
|
142
|
-
}
|
|
143
|
-
/**
|
|
144
|
-
* Computes subsequent committed headers as they will appear on the blockchain when transactions
|
|
145
|
-
* are submitted & confirmed
|
|
146
|
-
*
|
|
147
|
-
* @param initialStoredHeader
|
|
148
|
-
* @param syncedHeaders
|
|
149
|
-
* @private
|
|
150
|
-
*/
|
|
151
|
-
computeCommitedHeaders(initialStoredHeader, syncedHeaders) {
|
|
152
|
-
const computedCommitedHeaders = [initialStoredHeader];
|
|
153
|
-
for (let blockHeader of syncedHeaders) {
|
|
154
|
-
computedCommitedHeaders.push(computedCommitedHeaders[computedCommitedHeaders.length - 1].computeNext(blockHeader));
|
|
155
|
-
}
|
|
156
|
-
return computedCommitedHeaders;
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* A common logic for submitting blockheaders in a transaction
|
|
160
|
-
*
|
|
161
|
-
* @param signer
|
|
162
|
-
* @param headers headers to sync to the btc relay
|
|
163
|
-
* @param storedHeader current latest stored block header for a given fork
|
|
164
|
-
* @param forkId forkId to submit to, forkId=0 means main chain
|
|
165
|
-
* @param feeRate feeRate for the transaction
|
|
166
|
-
* @param createTx transaction generator function
|
|
167
|
-
* @private
|
|
168
|
-
*/
|
|
169
|
-
async _saveHeaders(signer, headers, storedHeader, forkId, feeRate, createTx) {
|
|
170
|
-
const blockHeaderObj = headers.map(serializeBlockHeader);
|
|
171
|
-
const tx = await createTx(blockHeaderObj)
|
|
172
|
-
.remainingAccounts(blockHeaderObj.map(e => {
|
|
173
|
-
return {
|
|
174
|
-
pubkey: this.BtcRelayHeader(e.getHash()),
|
|
175
|
-
isSigner: false,
|
|
176
|
-
isWritable: false
|
|
177
|
-
};
|
|
178
|
-
}))
|
|
179
|
-
.transaction();
|
|
180
|
-
tx.feePayer = signer;
|
|
181
|
-
SolanaFees_1.SolanaFees.applyFeeRateBegin(tx, null, feeRate);
|
|
182
|
-
SolanaFees_1.SolanaFees.applyFeeRateEnd(tx, null, feeRate);
|
|
183
|
-
const computedCommitedHeaders = this.computeCommitedHeaders(storedHeader, blockHeaderObj);
|
|
184
|
-
const lastStoredHeader = computedCommitedHeaders[computedCommitedHeaders.length - 1];
|
|
185
|
-
return {
|
|
186
|
-
forkId: forkId,
|
|
187
|
-
lastStoredHeader,
|
|
188
|
-
tx: {
|
|
189
|
-
tx,
|
|
190
|
-
signers: []
|
|
191
|
-
},
|
|
192
|
-
computedCommitedHeaders
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
* @inheritDoc
|
|
197
|
-
*/
|
|
198
|
-
async getTipData() {
|
|
199
|
-
const data = await this.program.account.mainState.fetchNullable(this.BtcRelayMainState);
|
|
200
|
-
if (data == null)
|
|
201
|
-
return null;
|
|
202
|
-
return {
|
|
203
|
-
blockheight: data.blockHeight,
|
|
204
|
-
commitHash: buffer_1.Buffer.from(data.tipCommitHash).toString("hex"),
|
|
205
|
-
blockhash: buffer_1.Buffer.from(data.tipBlockHash).reverse().toString("hex"),
|
|
206
|
-
chainWork: buffer_1.Buffer.from(data.chainWork)
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
/**
|
|
210
|
-
* @inheritDoc
|
|
211
|
-
*/
|
|
212
|
-
async retrieveLogAndBlockheight(blockData, requiredBlockheight) {
|
|
213
|
-
const mainState = await this.program.account.mainState.fetch(this.BtcRelayMainState);
|
|
214
|
-
if (requiredBlockheight != null && mainState.blockHeight < requiredBlockheight) {
|
|
215
|
-
return null;
|
|
216
|
-
}
|
|
217
|
-
const storedCommitments = this.getBlockCommitmentsSet(mainState);
|
|
218
|
-
const blockHashBuffer = buffer_1.Buffer.from(blockData.blockhash, 'hex').reverse();
|
|
219
|
-
const topicKey = this.BtcRelayHeader(blockHashBuffer);
|
|
220
|
-
const data = await this._Events.findInEvents(topicKey, async (event) => {
|
|
221
|
-
if (event.name === "StoreFork" || event.name === "StoreHeader") {
|
|
222
|
-
const eventData = event.data;
|
|
223
|
-
const commitHash = buffer_1.Buffer.from(eventData.commitHash).toString("hex");
|
|
224
|
-
if (blockHashBuffer.equals(buffer_1.Buffer.from(eventData.blockHash)) && storedCommitments.has(commitHash))
|
|
225
|
-
return {
|
|
226
|
-
header: new SolanaBtcStoredHeader_1.SolanaBtcStoredHeader(eventData.header),
|
|
227
|
-
height: mainState.blockHeight,
|
|
228
|
-
commitHash
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
});
|
|
232
|
-
if (data != null)
|
|
233
|
-
this.logger.debug("retrieveLogAndBlockheight(): block found," +
|
|
234
|
-
" commit hash: " + data.commitHash + " blockhash: " + blockData.blockhash + " height: " + data.height);
|
|
235
|
-
return data;
|
|
236
|
-
}
|
|
237
|
-
/**
|
|
238
|
-
* @inheritDoc
|
|
239
|
-
*/
|
|
240
|
-
async retrieveLogByCommitHash(commitmentHashStr, blockData) {
|
|
241
|
-
const blockHashBuffer = buffer_1.Buffer.from(blockData.blockhash, "hex").reverse();
|
|
242
|
-
const topicKey = this.BtcRelayHeader(blockHashBuffer);
|
|
243
|
-
const data = await this._Events.findInEvents(topicKey, async (event) => {
|
|
244
|
-
if (event.name === "StoreFork" || event.name === "StoreHeader") {
|
|
245
|
-
const eventData = event.data;
|
|
246
|
-
const commitHash = buffer_1.Buffer.from(eventData.commitHash).toString("hex");
|
|
247
|
-
if (commitmentHashStr === commitHash)
|
|
248
|
-
return new SolanaBtcStoredHeader_1.SolanaBtcStoredHeader(eventData.header);
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
if (data != null)
|
|
252
|
-
this.logger.debug("retrieveLogByCommitHash(): block found," +
|
|
253
|
-
" commit hash: " + commitmentHashStr + " blockhash: " + blockData.blockhash + " height: " + data.getBlockheight());
|
|
254
|
-
return data;
|
|
255
|
-
}
|
|
256
|
-
/**
|
|
257
|
-
* @inheritDoc
|
|
258
|
-
*/
|
|
259
|
-
async retrieveLatestKnownBlockLog() {
|
|
260
|
-
const mainState = await this.program.account.mainState.fetch(this.BtcRelayMainState);
|
|
261
|
-
const storedCommitments = this.getBlockCommitmentsSet(mainState);
|
|
262
|
-
const data = await this._Events.findInEvents(this.program.programId, async (event) => {
|
|
263
|
-
if (event.name === "StoreFork" || event.name === "StoreHeader") {
|
|
264
|
-
const eventData = event.data;
|
|
265
|
-
const blockHashHex = buffer_1.Buffer.from(eventData.blockHash).reverse().toString("hex");
|
|
266
|
-
const isInMainChain = await this._bitcoinRpc.isInMainChain(blockHashHex).catch(() => false);
|
|
267
|
-
const commitHash = buffer_1.Buffer.from(eventData.commitHash).toString("hex");
|
|
268
|
-
//Check if this fork is part of main chain
|
|
269
|
-
if (isInMainChain && storedCommitments.has(commitHash)) {
|
|
270
|
-
const blockHeader = await this._bitcoinRpc.getBlockHeader(blockHashHex);
|
|
271
|
-
if (blockHeader == null)
|
|
272
|
-
return null;
|
|
273
|
-
return {
|
|
274
|
-
resultStoredHeader: new SolanaBtcStoredHeader_1.SolanaBtcStoredHeader(eventData.header),
|
|
275
|
-
resultBitcoinHeader: blockHeader,
|
|
276
|
-
commitHash: commitHash
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
}, undefined, 10);
|
|
281
|
-
if (data != null)
|
|
282
|
-
this.logger.debug("retrieveLatestKnownBlockLog(): block found," +
|
|
283
|
-
" commit hash: " + data.commitHash + " blockhash: " + data.resultBitcoinHeader.getHash() +
|
|
284
|
-
" height: " + data.resultStoredHeader.getBlockheight());
|
|
285
|
-
return data;
|
|
286
|
-
}
|
|
287
|
-
/**
|
|
288
|
-
* @inheritDoc
|
|
289
|
-
*/
|
|
290
|
-
async saveInitialHeader(signer, header, epochStart, pastBlocksTimestamps, feeRate) {
|
|
291
|
-
if (pastBlocksTimestamps.length !== 10)
|
|
292
|
-
throw new Error("Invalid prevBlocksTimestamps");
|
|
293
|
-
const action = await this.Initialize(new web3_js_1.PublicKey(signer), header, epochStart, pastBlocksTimestamps);
|
|
294
|
-
this.logger.debug("saveInitialHeader(): saving initial header, blockhash: " + header.getHash() +
|
|
295
|
-
" blockheight: " + header.getHeight() + " epochStart: " + epochStart + " past block timestamps: " + pastBlocksTimestamps.join());
|
|
296
|
-
return await action.tx(feeRate);
|
|
297
|
-
}
|
|
298
|
-
/**
|
|
299
|
-
* @inheritDoc
|
|
300
|
-
*/
|
|
301
|
-
async saveMainHeaders(signer, mainHeaders, storedHeader, feeRate) {
|
|
302
|
-
feeRate ?? (feeRate = await this.getMainFeeRate(signer));
|
|
303
|
-
this.logger.debug("saveMainHeaders(): submitting main blockheaders, count: " + mainHeaders.length);
|
|
304
|
-
const _signer = new web3_js_1.PublicKey(signer);
|
|
305
|
-
return await this._saveHeaders(_signer, mainHeaders, storedHeader, 0, feeRate, (blockHeaders) => this.program.methods
|
|
306
|
-
.submitBlockHeaders(blockHeaders, storedHeader)
|
|
307
|
-
.accounts({
|
|
308
|
-
signer: _signer,
|
|
309
|
-
mainState: this.BtcRelayMainState,
|
|
310
|
-
}));
|
|
311
|
-
}
|
|
312
|
-
/**
|
|
313
|
-
* @inheritDoc
|
|
314
|
-
*/
|
|
315
|
-
async saveNewForkHeaders(signer, forkHeaders, storedHeader, tipWork, feeRate) {
|
|
316
|
-
const mainState = await this.program.account.mainState.fetch(this.BtcRelayMainState);
|
|
317
|
-
let forkId = mainState.forkCounter;
|
|
318
|
-
feeRate ?? (feeRate = await this.getForkFeeRate(signer, forkId.toNumber()));
|
|
319
|
-
const _signer = new web3_js_1.PublicKey(signer);
|
|
320
|
-
this.logger.debug("saveNewForkHeaders(): submitting new fork & blockheaders," +
|
|
321
|
-
" count: " + forkHeaders.length + " forkId: " + forkId.toString(10));
|
|
322
|
-
const result = await this._saveHeaders(_signer, forkHeaders, storedHeader, forkId.toNumber(), feeRate, (blockHeaders) => this.program.methods
|
|
323
|
-
.submitForkHeaders(blockHeaders, storedHeader, forkId, true)
|
|
324
|
-
.accounts({
|
|
325
|
-
signer: _signer,
|
|
326
|
-
mainState: this.BtcRelayMainState,
|
|
327
|
-
forkState: this.BtcRelayFork(forkId.toNumber(), _signer),
|
|
328
|
-
systemProgram: web3_js_1.SystemProgram.programId,
|
|
329
|
-
}));
|
|
330
|
-
if (result.forkId !== 0 && base_1.StatePredictorUtils.gtBuffer(result.lastStoredHeader.getChainWork(), tipWork)) {
|
|
331
|
-
//Fork's work is higher than main chain's work, this fork will become a main chain
|
|
332
|
-
result.forkId = 0;
|
|
333
|
-
}
|
|
334
|
-
return result;
|
|
335
|
-
}
|
|
336
|
-
/**
|
|
337
|
-
* @inheritDoc
|
|
338
|
-
*/
|
|
339
|
-
async saveForkHeaders(signer, forkHeaders, storedHeader, forkId, tipWork, feeRate) {
|
|
340
|
-
feeRate ?? (feeRate = await this.getForkFeeRate(signer, forkId));
|
|
341
|
-
this.logger.debug("saveForkHeaders(): submitting blockheaders to existing fork," +
|
|
342
|
-
" count: " + forkHeaders.length + " forkId: " + forkId.toString(10));
|
|
343
|
-
const _signer = new web3_js_1.PublicKey(signer);
|
|
344
|
-
const result = await this._saveHeaders(_signer, forkHeaders, storedHeader, forkId, feeRate, (blockHeaders) => this.program.methods
|
|
345
|
-
.submitForkHeaders(blockHeaders, storedHeader, new BN(forkId), false)
|
|
346
|
-
.accounts({
|
|
347
|
-
signer: _signer,
|
|
348
|
-
mainState: this.BtcRelayMainState,
|
|
349
|
-
forkState: this.BtcRelayFork(forkId, _signer),
|
|
350
|
-
systemProgram: web3_js_1.SystemProgram.programId,
|
|
351
|
-
}));
|
|
352
|
-
if (result.forkId !== 0 && base_1.StatePredictorUtils.gtBuffer(result.lastStoredHeader.getChainWork(), tipWork)) {
|
|
353
|
-
//Fork's work is higher than main chain's work, this fork will become a main chain
|
|
354
|
-
result.forkId = 0;
|
|
355
|
-
}
|
|
356
|
-
return result;
|
|
357
|
-
}
|
|
358
|
-
/**
|
|
359
|
-
* @inheritDoc
|
|
360
|
-
*/
|
|
361
|
-
async saveShortForkHeaders(signer, forkHeaders, storedHeader, tipWork, feeRate) {
|
|
362
|
-
feeRate ?? (feeRate = await this.getMainFeeRate(signer));
|
|
363
|
-
this.logger.debug("saveShortForkHeaders(): submitting short fork blockheaders," +
|
|
364
|
-
" count: " + forkHeaders.length);
|
|
365
|
-
const _signer = new web3_js_1.PublicKey(signer);
|
|
366
|
-
const result = await this._saveHeaders(_signer, forkHeaders, storedHeader, -1, feeRate, (blockHeaders) => this.program.methods
|
|
367
|
-
.submitShortForkHeaders(blockHeaders, storedHeader)
|
|
368
|
-
.accounts({
|
|
369
|
-
signer: _signer,
|
|
370
|
-
mainState: this.BtcRelayMainState
|
|
371
|
-
}));
|
|
372
|
-
if (result.forkId !== 0 && base_1.StatePredictorUtils.gtBuffer(result.lastStoredHeader.getChainWork(), tipWork)) {
|
|
373
|
-
//Fork's work is higher than main chain's work, this fork will become a main chain
|
|
374
|
-
result.forkId = 0;
|
|
375
|
-
}
|
|
376
|
-
return result;
|
|
377
|
-
}
|
|
378
|
-
/**
|
|
379
|
-
* @inheritDoc
|
|
380
|
-
*/
|
|
381
|
-
async sweepForkData(signer, lastSweepId) {
|
|
382
|
-
const mainState = await this.program.account.mainState.fetch(this.BtcRelayMainState);
|
|
383
|
-
let forkId = mainState.forkCounter.toNumber();
|
|
384
|
-
const txs = [];
|
|
385
|
-
let action = new SolanaAction_1.SolanaAction(signer.getPublicKey(), this._Chain);
|
|
386
|
-
let lastCheckedId = lastSweepId;
|
|
387
|
-
for (let i = lastSweepId == null ? 0 : lastSweepId + 1; i <= forkId; i++) {
|
|
388
|
-
lastCheckedId = i;
|
|
389
|
-
const accountAddr = this.BtcRelayFork(i, signer.getPublicKey());
|
|
390
|
-
let forkState = await this.program.account.forkState.fetchNullable(accountAddr);
|
|
391
|
-
if (forkState == null)
|
|
392
|
-
continue;
|
|
393
|
-
this.logger.info("sweepForkData(): sweeping forkId: " + i);
|
|
394
|
-
action.add(await this.CloseForkAccount(signer.getPublicKey(), i));
|
|
395
|
-
if (action.ixsLength() >= MAX_CLOSE_IX_PER_TX) {
|
|
396
|
-
await action.addToTxs(txs);
|
|
397
|
-
action = new SolanaAction_1.SolanaAction(signer.getPublicKey(), this._Chain);
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
if (action.ixsLength() >= MAX_CLOSE_IX_PER_TX) {
|
|
401
|
-
await action.addToTxs(txs);
|
|
402
|
-
}
|
|
403
|
-
if (txs.length > 0) {
|
|
404
|
-
const signatures = await this._Chain.sendAndConfirm(signer, txs, true);
|
|
405
|
-
this.logger.info("sweepForkData(): forks swept, signatures: " + signatures.join());
|
|
406
|
-
}
|
|
407
|
-
return lastCheckedId ?? null;
|
|
408
|
-
}
|
|
409
|
-
/**
|
|
410
|
-
* @inheritDoc
|
|
411
|
-
*/
|
|
412
|
-
async estimateSynchronizeFee(requiredBlockheight, feeRate) {
|
|
413
|
-
const tipData = await this.getTipData();
|
|
414
|
-
if (tipData == null)
|
|
415
|
-
throw new Error("Cannot get relay tip data, relay not initialized?");
|
|
416
|
-
const currBlockheight = tipData.blockheight;
|
|
417
|
-
const blockheightDelta = requiredBlockheight - currBlockheight;
|
|
418
|
-
if (blockheightDelta <= 0)
|
|
419
|
-
return 0n;
|
|
420
|
-
const synchronizationFee = BigInt(blockheightDelta) * await this.getFeePerBlock(feeRate);
|
|
421
|
-
this.logger.debug("estimateSynchronizeFee(): required blockheight: " + requiredBlockheight +
|
|
422
|
-
" blockheight delta: " + blockheightDelta + " fee: " + synchronizationFee.toString(10));
|
|
423
|
-
return synchronizationFee;
|
|
424
|
-
}
|
|
425
|
-
/**
|
|
426
|
-
* @inheritDoc
|
|
427
|
-
*/
|
|
428
|
-
async getFeePerBlock(feeRate) {
|
|
429
|
-
// feeRate = feeRate || await this.getMainFeeRate(null);
|
|
430
|
-
// return BASE_FEE_SOL_PER_BLOCKHEADER.add(this.Fees.getPriorityFee(200000, feeRate, false));
|
|
431
|
-
return 50000n;
|
|
432
|
-
}
|
|
433
|
-
/**
|
|
434
|
-
* @inheritDoc
|
|
435
|
-
*/
|
|
436
|
-
getMainFeeRate(signer) {
|
|
437
|
-
const _signer = signer == null ? null : new web3_js_1.PublicKey(signer);
|
|
438
|
-
return this._Chain.Fees.getFeeRate(_signer == null ? [this.BtcRelayMainState] : [
|
|
439
|
-
_signer,
|
|
440
|
-
this.BtcRelayMainState
|
|
441
|
-
]);
|
|
442
|
-
}
|
|
443
|
-
/**
|
|
444
|
-
* @inheritDoc
|
|
445
|
-
*/
|
|
446
|
-
getForkFeeRate(signer, forkId) {
|
|
447
|
-
const _signer = new web3_js_1.PublicKey(signer);
|
|
448
|
-
return this._Chain.Fees.getFeeRate([
|
|
449
|
-
_signer,
|
|
450
|
-
this.BtcRelayMainState,
|
|
451
|
-
this.BtcRelayFork(forkId, _signer)
|
|
452
|
-
]);
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
exports.SolanaBtcRelay = SolanaBtcRelay;
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SolanaBtcRelay = void 0;
|
|
4
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
5
|
+
const SolanaBtcStoredHeader_1 = require("./headers/SolanaBtcStoredHeader");
|
|
6
|
+
const SolanaBtcHeader_1 = require("./headers/SolanaBtcHeader");
|
|
7
|
+
const programIdl = require("./program/programIdl.json");
|
|
8
|
+
const base_1 = require("@atomiqlabs/base");
|
|
9
|
+
const SolanaProgramBase_1 = require("../program/SolanaProgramBase");
|
|
10
|
+
const SolanaAction_1 = require("../chain/SolanaAction");
|
|
11
|
+
const buffer_1 = require("buffer");
|
|
12
|
+
const BN = require("bn.js");
|
|
13
|
+
const SolanaFees_1 = require("../chain/modules/SolanaFees");
|
|
14
|
+
const SolanaChains_1 = require("../SolanaChains");
|
|
15
|
+
const MAX_CLOSE_IX_PER_TX = 10;
|
|
16
|
+
function serializeBlockHeader(e) {
|
|
17
|
+
return new SolanaBtcHeader_1.SolanaBtcHeader({
|
|
18
|
+
version: e.getVersion(),
|
|
19
|
+
reversedPrevBlockhash: [...buffer_1.Buffer.from(e.getPrevBlockhash(), "hex").reverse()],
|
|
20
|
+
merkleRoot: [...buffer_1.Buffer.from(e.getMerkleRoot(), "hex").reverse()],
|
|
21
|
+
timestamp: e.getTimestamp(),
|
|
22
|
+
nbits: e.getNbits(),
|
|
23
|
+
nonce: e.getNonce(),
|
|
24
|
+
hash: buffer_1.Buffer.from(e.getHash(), "hex").reverse()
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
;
|
|
28
|
+
/**
|
|
29
|
+
* Solana BTC relay (bitcoin light client) program representation.
|
|
30
|
+
*
|
|
31
|
+
* @category BTC Relay
|
|
32
|
+
*/
|
|
33
|
+
class SolanaBtcRelay extends SolanaProgramBase_1.SolanaProgramBase {
|
|
34
|
+
/**
|
|
35
|
+
* Creates initialization action for initializing the btc relay
|
|
36
|
+
*
|
|
37
|
+
* @param signer
|
|
38
|
+
* @param header
|
|
39
|
+
* @param epochStart
|
|
40
|
+
* @param pastBlocksTimestamps
|
|
41
|
+
* @private
|
|
42
|
+
*/
|
|
43
|
+
async Initialize(signer, header, epochStart, pastBlocksTimestamps) {
|
|
44
|
+
const serializedBlock = serializeBlockHeader(header);
|
|
45
|
+
return new SolanaAction_1.SolanaAction(signer, this._Chain, await this.program.methods
|
|
46
|
+
.initialize(serializedBlock, header.getHeight(), header.getChainWork(), epochStart, pastBlocksTimestamps)
|
|
47
|
+
.accounts({
|
|
48
|
+
signer,
|
|
49
|
+
mainState: this.BtcRelayMainState,
|
|
50
|
+
headerTopic: this.BtcRelayHeader(serializedBlock.getHash()),
|
|
51
|
+
systemProgram: web3_js_1.SystemProgram.programId
|
|
52
|
+
})
|
|
53
|
+
.instruction(), 100000);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Creates verify action to be used with the swap program, specifies the action to be firstIxBeforeComputeBudget,
|
|
57
|
+
* such that the verify instruction will always be the 0th in the transaction, this is required because
|
|
58
|
+
* swap program expects the verify instruction to be at the 0th position
|
|
59
|
+
*
|
|
60
|
+
* @param signer
|
|
61
|
+
* @param reversedTxId
|
|
62
|
+
* @param confirmations
|
|
63
|
+
* @param position
|
|
64
|
+
* @param reversedMerkleProof
|
|
65
|
+
* @param committedHeader
|
|
66
|
+
*/
|
|
67
|
+
async Verify(signer, reversedTxId, confirmations, position, reversedMerkleProof, committedHeader) {
|
|
68
|
+
return new SolanaAction_1.SolanaAction(signer, this._Chain, await this.program.methods
|
|
69
|
+
.verifyTransaction(reversedTxId, confirmations, position, reversedMerkleProof, committedHeader)
|
|
70
|
+
.accounts({
|
|
71
|
+
signer,
|
|
72
|
+
mainState: this.BtcRelayMainState
|
|
73
|
+
})
|
|
74
|
+
.instruction(), undefined, undefined, undefined, true);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Creates an action that closes a fork account and refunds rent to the signer.
|
|
78
|
+
*
|
|
79
|
+
* @param signer Signer paying and receiving rent refund
|
|
80
|
+
* @param forkId Fork account identifier to close
|
|
81
|
+
*/
|
|
82
|
+
async CloseForkAccount(signer, forkId) {
|
|
83
|
+
return new SolanaAction_1.SolanaAction(signer, this._Chain, await this.program.methods
|
|
84
|
+
.closeForkAccount(new BN(forkId))
|
|
85
|
+
.accounts({
|
|
86
|
+
signer,
|
|
87
|
+
forkState: this.BtcRelayFork(forkId, signer),
|
|
88
|
+
systemProgram: web3_js_1.SystemProgram.programId,
|
|
89
|
+
})
|
|
90
|
+
.instruction(), 20000);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* @param chainInterface Underlying chain interface to use for the Solana chain operations
|
|
94
|
+
* @param bitcoinRpc Bitcoin RPC instance to use for read access to the bitcoin blockchain
|
|
95
|
+
* @param programAddress Optional Solana on-chain program address, defaults to the cannonical deployment
|
|
96
|
+
* @param bitcoinNetwork
|
|
97
|
+
* @param contractVersion
|
|
98
|
+
*/
|
|
99
|
+
constructor(chainInterface, bitcoinRpc, programAddress, bitcoinNetwork, contractVersion) {
|
|
100
|
+
if (bitcoinNetwork != null && programAddress == null) {
|
|
101
|
+
programAddress = SolanaChains_1.SolanaChains[bitcoinNetwork]?.addresses[contractVersion ?? "v1"]?.btcRelayContract;
|
|
102
|
+
}
|
|
103
|
+
super(chainInterface, programIdl, programAddress);
|
|
104
|
+
/**
|
|
105
|
+
* PDA of the relay main state account.
|
|
106
|
+
*/
|
|
107
|
+
this.BtcRelayMainState = this.pda("state");
|
|
108
|
+
/**
|
|
109
|
+
* PDA helper for per-header topic accounts.
|
|
110
|
+
*/
|
|
111
|
+
this.BtcRelayHeader = this.pda("header", (hash) => [hash]);
|
|
112
|
+
/**
|
|
113
|
+
* PDA helper for fork state accounts.
|
|
114
|
+
*/
|
|
115
|
+
this.BtcRelayFork = this.pda("fork", (forkId, pubkey) => [new BN(forkId).toArrayLike(buffer_1.Buffer, "le", 8), pubkey.toBuffer()]);
|
|
116
|
+
/**
|
|
117
|
+
* @inheritDoc
|
|
118
|
+
*/
|
|
119
|
+
this.maxHeadersPerTx = 5;
|
|
120
|
+
/**
|
|
121
|
+
* @inheritDoc
|
|
122
|
+
*/
|
|
123
|
+
this.maxForkHeadersPerTx = 4;
|
|
124
|
+
/**
|
|
125
|
+
* @inheritDoc
|
|
126
|
+
*/
|
|
127
|
+
this.maxShortForkHeadersPerTx = 4;
|
|
128
|
+
this._bitcoinRpc = bitcoinRpc;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Gets set of block commitments representing current main chain from the mainState
|
|
132
|
+
*
|
|
133
|
+
* @param mainState
|
|
134
|
+
* @private
|
|
135
|
+
*/
|
|
136
|
+
getBlockCommitmentsSet(mainState) {
|
|
137
|
+
const storedCommitments = new Set();
|
|
138
|
+
mainState.blockCommitments.forEach((e) => {
|
|
139
|
+
storedCommitments.add(buffer_1.Buffer.from(e).toString("hex"));
|
|
140
|
+
});
|
|
141
|
+
return storedCommitments;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Computes subsequent committed headers as they will appear on the blockchain when transactions
|
|
145
|
+
* are submitted & confirmed
|
|
146
|
+
*
|
|
147
|
+
* @param initialStoredHeader
|
|
148
|
+
* @param syncedHeaders
|
|
149
|
+
* @private
|
|
150
|
+
*/
|
|
151
|
+
computeCommitedHeaders(initialStoredHeader, syncedHeaders) {
|
|
152
|
+
const computedCommitedHeaders = [initialStoredHeader];
|
|
153
|
+
for (let blockHeader of syncedHeaders) {
|
|
154
|
+
computedCommitedHeaders.push(computedCommitedHeaders[computedCommitedHeaders.length - 1].computeNext(blockHeader));
|
|
155
|
+
}
|
|
156
|
+
return computedCommitedHeaders;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* A common logic for submitting blockheaders in a transaction
|
|
160
|
+
*
|
|
161
|
+
* @param signer
|
|
162
|
+
* @param headers headers to sync to the btc relay
|
|
163
|
+
* @param storedHeader current latest stored block header for a given fork
|
|
164
|
+
* @param forkId forkId to submit to, forkId=0 means main chain
|
|
165
|
+
* @param feeRate feeRate for the transaction
|
|
166
|
+
* @param createTx transaction generator function
|
|
167
|
+
* @private
|
|
168
|
+
*/
|
|
169
|
+
async _saveHeaders(signer, headers, storedHeader, forkId, feeRate, createTx) {
|
|
170
|
+
const blockHeaderObj = headers.map(serializeBlockHeader);
|
|
171
|
+
const tx = await createTx(blockHeaderObj)
|
|
172
|
+
.remainingAccounts(blockHeaderObj.map(e => {
|
|
173
|
+
return {
|
|
174
|
+
pubkey: this.BtcRelayHeader(e.getHash()),
|
|
175
|
+
isSigner: false,
|
|
176
|
+
isWritable: false
|
|
177
|
+
};
|
|
178
|
+
}))
|
|
179
|
+
.transaction();
|
|
180
|
+
tx.feePayer = signer;
|
|
181
|
+
SolanaFees_1.SolanaFees.applyFeeRateBegin(tx, null, feeRate);
|
|
182
|
+
SolanaFees_1.SolanaFees.applyFeeRateEnd(tx, null, feeRate);
|
|
183
|
+
const computedCommitedHeaders = this.computeCommitedHeaders(storedHeader, blockHeaderObj);
|
|
184
|
+
const lastStoredHeader = computedCommitedHeaders[computedCommitedHeaders.length - 1];
|
|
185
|
+
return {
|
|
186
|
+
forkId: forkId,
|
|
187
|
+
lastStoredHeader,
|
|
188
|
+
tx: {
|
|
189
|
+
tx,
|
|
190
|
+
signers: []
|
|
191
|
+
},
|
|
192
|
+
computedCommitedHeaders
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* @inheritDoc
|
|
197
|
+
*/
|
|
198
|
+
async getTipData() {
|
|
199
|
+
const data = await this.program.account.mainState.fetchNullable(this.BtcRelayMainState);
|
|
200
|
+
if (data == null)
|
|
201
|
+
return null;
|
|
202
|
+
return {
|
|
203
|
+
blockheight: data.blockHeight,
|
|
204
|
+
commitHash: buffer_1.Buffer.from(data.tipCommitHash).toString("hex"),
|
|
205
|
+
blockhash: buffer_1.Buffer.from(data.tipBlockHash).reverse().toString("hex"),
|
|
206
|
+
chainWork: buffer_1.Buffer.from(data.chainWork)
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* @inheritDoc
|
|
211
|
+
*/
|
|
212
|
+
async retrieveLogAndBlockheight(blockData, requiredBlockheight) {
|
|
213
|
+
const mainState = await this.program.account.mainState.fetch(this.BtcRelayMainState);
|
|
214
|
+
if (requiredBlockheight != null && mainState.blockHeight < requiredBlockheight) {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
const storedCommitments = this.getBlockCommitmentsSet(mainState);
|
|
218
|
+
const blockHashBuffer = buffer_1.Buffer.from(blockData.blockhash, 'hex').reverse();
|
|
219
|
+
const topicKey = this.BtcRelayHeader(blockHashBuffer);
|
|
220
|
+
const data = await this._Events.findInEvents(topicKey, async (event) => {
|
|
221
|
+
if (event.name === "StoreFork" || event.name === "StoreHeader") {
|
|
222
|
+
const eventData = event.data;
|
|
223
|
+
const commitHash = buffer_1.Buffer.from(eventData.commitHash).toString("hex");
|
|
224
|
+
if (blockHashBuffer.equals(buffer_1.Buffer.from(eventData.blockHash)) && storedCommitments.has(commitHash))
|
|
225
|
+
return {
|
|
226
|
+
header: new SolanaBtcStoredHeader_1.SolanaBtcStoredHeader(eventData.header),
|
|
227
|
+
height: mainState.blockHeight,
|
|
228
|
+
commitHash
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
if (data != null)
|
|
233
|
+
this.logger.debug("retrieveLogAndBlockheight(): block found," +
|
|
234
|
+
" commit hash: " + data.commitHash + " blockhash: " + blockData.blockhash + " height: " + data.height);
|
|
235
|
+
return data;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* @inheritDoc
|
|
239
|
+
*/
|
|
240
|
+
async retrieveLogByCommitHash(commitmentHashStr, blockData) {
|
|
241
|
+
const blockHashBuffer = buffer_1.Buffer.from(blockData.blockhash, "hex").reverse();
|
|
242
|
+
const topicKey = this.BtcRelayHeader(blockHashBuffer);
|
|
243
|
+
const data = await this._Events.findInEvents(topicKey, async (event) => {
|
|
244
|
+
if (event.name === "StoreFork" || event.name === "StoreHeader") {
|
|
245
|
+
const eventData = event.data;
|
|
246
|
+
const commitHash = buffer_1.Buffer.from(eventData.commitHash).toString("hex");
|
|
247
|
+
if (commitmentHashStr === commitHash)
|
|
248
|
+
return new SolanaBtcStoredHeader_1.SolanaBtcStoredHeader(eventData.header);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
if (data != null)
|
|
252
|
+
this.logger.debug("retrieveLogByCommitHash(): block found," +
|
|
253
|
+
" commit hash: " + commitmentHashStr + " blockhash: " + blockData.blockhash + " height: " + data.getBlockheight());
|
|
254
|
+
return data;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* @inheritDoc
|
|
258
|
+
*/
|
|
259
|
+
async retrieveLatestKnownBlockLog() {
|
|
260
|
+
const mainState = await this.program.account.mainState.fetch(this.BtcRelayMainState);
|
|
261
|
+
const storedCommitments = this.getBlockCommitmentsSet(mainState);
|
|
262
|
+
const data = await this._Events.findInEvents(this.program.programId, async (event) => {
|
|
263
|
+
if (event.name === "StoreFork" || event.name === "StoreHeader") {
|
|
264
|
+
const eventData = event.data;
|
|
265
|
+
const blockHashHex = buffer_1.Buffer.from(eventData.blockHash).reverse().toString("hex");
|
|
266
|
+
const isInMainChain = await this._bitcoinRpc.isInMainChain(blockHashHex).catch(() => false);
|
|
267
|
+
const commitHash = buffer_1.Buffer.from(eventData.commitHash).toString("hex");
|
|
268
|
+
//Check if this fork is part of main chain
|
|
269
|
+
if (isInMainChain && storedCommitments.has(commitHash)) {
|
|
270
|
+
const blockHeader = await this._bitcoinRpc.getBlockHeader(blockHashHex);
|
|
271
|
+
if (blockHeader == null)
|
|
272
|
+
return null;
|
|
273
|
+
return {
|
|
274
|
+
resultStoredHeader: new SolanaBtcStoredHeader_1.SolanaBtcStoredHeader(eventData.header),
|
|
275
|
+
resultBitcoinHeader: blockHeader,
|
|
276
|
+
commitHash: commitHash
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}, undefined, 10);
|
|
281
|
+
if (data != null)
|
|
282
|
+
this.logger.debug("retrieveLatestKnownBlockLog(): block found," +
|
|
283
|
+
" commit hash: " + data.commitHash + " blockhash: " + data.resultBitcoinHeader.getHash() +
|
|
284
|
+
" height: " + data.resultStoredHeader.getBlockheight());
|
|
285
|
+
return data;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* @inheritDoc
|
|
289
|
+
*/
|
|
290
|
+
async saveInitialHeader(signer, header, epochStart, pastBlocksTimestamps, feeRate) {
|
|
291
|
+
if (pastBlocksTimestamps.length !== 10)
|
|
292
|
+
throw new Error("Invalid prevBlocksTimestamps");
|
|
293
|
+
const action = await this.Initialize(new web3_js_1.PublicKey(signer), header, epochStart, pastBlocksTimestamps);
|
|
294
|
+
this.logger.debug("saveInitialHeader(): saving initial header, blockhash: " + header.getHash() +
|
|
295
|
+
" blockheight: " + header.getHeight() + " epochStart: " + epochStart + " past block timestamps: " + pastBlocksTimestamps.join());
|
|
296
|
+
return await action.tx(feeRate);
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* @inheritDoc
|
|
300
|
+
*/
|
|
301
|
+
async saveMainHeaders(signer, mainHeaders, storedHeader, feeRate) {
|
|
302
|
+
feeRate ?? (feeRate = await this.getMainFeeRate(signer));
|
|
303
|
+
this.logger.debug("saveMainHeaders(): submitting main blockheaders, count: " + mainHeaders.length);
|
|
304
|
+
const _signer = new web3_js_1.PublicKey(signer);
|
|
305
|
+
return await this._saveHeaders(_signer, mainHeaders, storedHeader, 0, feeRate, (blockHeaders) => this.program.methods
|
|
306
|
+
.submitBlockHeaders(blockHeaders, storedHeader)
|
|
307
|
+
.accounts({
|
|
308
|
+
signer: _signer,
|
|
309
|
+
mainState: this.BtcRelayMainState,
|
|
310
|
+
}));
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* @inheritDoc
|
|
314
|
+
*/
|
|
315
|
+
async saveNewForkHeaders(signer, forkHeaders, storedHeader, tipWork, feeRate) {
|
|
316
|
+
const mainState = await this.program.account.mainState.fetch(this.BtcRelayMainState);
|
|
317
|
+
let forkId = mainState.forkCounter;
|
|
318
|
+
feeRate ?? (feeRate = await this.getForkFeeRate(signer, forkId.toNumber()));
|
|
319
|
+
const _signer = new web3_js_1.PublicKey(signer);
|
|
320
|
+
this.logger.debug("saveNewForkHeaders(): submitting new fork & blockheaders," +
|
|
321
|
+
" count: " + forkHeaders.length + " forkId: " + forkId.toString(10));
|
|
322
|
+
const result = await this._saveHeaders(_signer, forkHeaders, storedHeader, forkId.toNumber(), feeRate, (blockHeaders) => this.program.methods
|
|
323
|
+
.submitForkHeaders(blockHeaders, storedHeader, forkId, true)
|
|
324
|
+
.accounts({
|
|
325
|
+
signer: _signer,
|
|
326
|
+
mainState: this.BtcRelayMainState,
|
|
327
|
+
forkState: this.BtcRelayFork(forkId.toNumber(), _signer),
|
|
328
|
+
systemProgram: web3_js_1.SystemProgram.programId,
|
|
329
|
+
}));
|
|
330
|
+
if (result.forkId !== 0 && base_1.StatePredictorUtils.gtBuffer(result.lastStoredHeader.getChainWork(), tipWork)) {
|
|
331
|
+
//Fork's work is higher than main chain's work, this fork will become a main chain
|
|
332
|
+
result.forkId = 0;
|
|
333
|
+
}
|
|
334
|
+
return result;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* @inheritDoc
|
|
338
|
+
*/
|
|
339
|
+
async saveForkHeaders(signer, forkHeaders, storedHeader, forkId, tipWork, feeRate) {
|
|
340
|
+
feeRate ?? (feeRate = await this.getForkFeeRate(signer, forkId));
|
|
341
|
+
this.logger.debug("saveForkHeaders(): submitting blockheaders to existing fork," +
|
|
342
|
+
" count: " + forkHeaders.length + " forkId: " + forkId.toString(10));
|
|
343
|
+
const _signer = new web3_js_1.PublicKey(signer);
|
|
344
|
+
const result = await this._saveHeaders(_signer, forkHeaders, storedHeader, forkId, feeRate, (blockHeaders) => this.program.methods
|
|
345
|
+
.submitForkHeaders(blockHeaders, storedHeader, new BN(forkId), false)
|
|
346
|
+
.accounts({
|
|
347
|
+
signer: _signer,
|
|
348
|
+
mainState: this.BtcRelayMainState,
|
|
349
|
+
forkState: this.BtcRelayFork(forkId, _signer),
|
|
350
|
+
systemProgram: web3_js_1.SystemProgram.programId,
|
|
351
|
+
}));
|
|
352
|
+
if (result.forkId !== 0 && base_1.StatePredictorUtils.gtBuffer(result.lastStoredHeader.getChainWork(), tipWork)) {
|
|
353
|
+
//Fork's work is higher than main chain's work, this fork will become a main chain
|
|
354
|
+
result.forkId = 0;
|
|
355
|
+
}
|
|
356
|
+
return result;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* @inheritDoc
|
|
360
|
+
*/
|
|
361
|
+
async saveShortForkHeaders(signer, forkHeaders, storedHeader, tipWork, feeRate) {
|
|
362
|
+
feeRate ?? (feeRate = await this.getMainFeeRate(signer));
|
|
363
|
+
this.logger.debug("saveShortForkHeaders(): submitting short fork blockheaders," +
|
|
364
|
+
" count: " + forkHeaders.length);
|
|
365
|
+
const _signer = new web3_js_1.PublicKey(signer);
|
|
366
|
+
const result = await this._saveHeaders(_signer, forkHeaders, storedHeader, -1, feeRate, (blockHeaders) => this.program.methods
|
|
367
|
+
.submitShortForkHeaders(blockHeaders, storedHeader)
|
|
368
|
+
.accounts({
|
|
369
|
+
signer: _signer,
|
|
370
|
+
mainState: this.BtcRelayMainState
|
|
371
|
+
}));
|
|
372
|
+
if (result.forkId !== 0 && base_1.StatePredictorUtils.gtBuffer(result.lastStoredHeader.getChainWork(), tipWork)) {
|
|
373
|
+
//Fork's work is higher than main chain's work, this fork will become a main chain
|
|
374
|
+
result.forkId = 0;
|
|
375
|
+
}
|
|
376
|
+
return result;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* @inheritDoc
|
|
380
|
+
*/
|
|
381
|
+
async sweepForkData(signer, lastSweepId) {
|
|
382
|
+
const mainState = await this.program.account.mainState.fetch(this.BtcRelayMainState);
|
|
383
|
+
let forkId = mainState.forkCounter.toNumber();
|
|
384
|
+
const txs = [];
|
|
385
|
+
let action = new SolanaAction_1.SolanaAction(signer.getPublicKey(), this._Chain);
|
|
386
|
+
let lastCheckedId = lastSweepId;
|
|
387
|
+
for (let i = lastSweepId == null ? 0 : lastSweepId + 1; i <= forkId; i++) {
|
|
388
|
+
lastCheckedId = i;
|
|
389
|
+
const accountAddr = this.BtcRelayFork(i, signer.getPublicKey());
|
|
390
|
+
let forkState = await this.program.account.forkState.fetchNullable(accountAddr);
|
|
391
|
+
if (forkState == null)
|
|
392
|
+
continue;
|
|
393
|
+
this.logger.info("sweepForkData(): sweeping forkId: " + i);
|
|
394
|
+
action.add(await this.CloseForkAccount(signer.getPublicKey(), i));
|
|
395
|
+
if (action.ixsLength() >= MAX_CLOSE_IX_PER_TX) {
|
|
396
|
+
await action.addToTxs(txs);
|
|
397
|
+
action = new SolanaAction_1.SolanaAction(signer.getPublicKey(), this._Chain);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
if (action.ixsLength() >= MAX_CLOSE_IX_PER_TX) {
|
|
401
|
+
await action.addToTxs(txs);
|
|
402
|
+
}
|
|
403
|
+
if (txs.length > 0) {
|
|
404
|
+
const signatures = await this._Chain.sendAndConfirm(signer, txs, true);
|
|
405
|
+
this.logger.info("sweepForkData(): forks swept, signatures: " + signatures.join());
|
|
406
|
+
}
|
|
407
|
+
return lastCheckedId ?? null;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* @inheritDoc
|
|
411
|
+
*/
|
|
412
|
+
async estimateSynchronizeFee(requiredBlockheight, feeRate) {
|
|
413
|
+
const tipData = await this.getTipData();
|
|
414
|
+
if (tipData == null)
|
|
415
|
+
throw new Error("Cannot get relay tip data, relay not initialized?");
|
|
416
|
+
const currBlockheight = tipData.blockheight;
|
|
417
|
+
const blockheightDelta = requiredBlockheight - currBlockheight;
|
|
418
|
+
if (blockheightDelta <= 0)
|
|
419
|
+
return 0n;
|
|
420
|
+
const synchronizationFee = BigInt(blockheightDelta) * await this.getFeePerBlock(feeRate);
|
|
421
|
+
this.logger.debug("estimateSynchronizeFee(): required blockheight: " + requiredBlockheight +
|
|
422
|
+
" blockheight delta: " + blockheightDelta + " fee: " + synchronizationFee.toString(10));
|
|
423
|
+
return synchronizationFee;
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* @inheritDoc
|
|
427
|
+
*/
|
|
428
|
+
async getFeePerBlock(feeRate) {
|
|
429
|
+
// feeRate = feeRate || await this.getMainFeeRate(null);
|
|
430
|
+
// return BASE_FEE_SOL_PER_BLOCKHEADER.add(this.Fees.getPriorityFee(200000, feeRate, false));
|
|
431
|
+
return 50000n;
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* @inheritDoc
|
|
435
|
+
*/
|
|
436
|
+
getMainFeeRate(signer) {
|
|
437
|
+
const _signer = signer == null ? null : new web3_js_1.PublicKey(signer);
|
|
438
|
+
return this._Chain.Fees.getFeeRate(_signer == null ? [this.BtcRelayMainState] : [
|
|
439
|
+
_signer,
|
|
440
|
+
this.BtcRelayMainState
|
|
441
|
+
]);
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* @inheritDoc
|
|
445
|
+
*/
|
|
446
|
+
getForkFeeRate(signer, forkId) {
|
|
447
|
+
const _signer = new web3_js_1.PublicKey(signer);
|
|
448
|
+
return this._Chain.Fees.getFeeRate([
|
|
449
|
+
_signer,
|
|
450
|
+
this.BtcRelayMainState,
|
|
451
|
+
this.BtcRelayFork(forkId, _signer)
|
|
452
|
+
]);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
exports.SolanaBtcRelay = SolanaBtcRelay;
|