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