@atomiqlabs/chain-evm 1.0.0-dev.75 → 1.0.0-dev.77
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/chains/botanix/BotanixChainType.d.ts +13 -13
- package/dist/chains/botanix/BotanixChainType.js +2 -2
- package/dist/chains/botanix/BotanixInitializer.d.ts +30 -30
- package/dist/chains/botanix/BotanixInitializer.js +122 -122
- package/dist/chains/citrea/CitreaBtcRelay.d.ts +21 -21
- package/dist/chains/citrea/CitreaBtcRelay.js +43 -43
- package/dist/chains/citrea/CitreaChainType.d.ts +13 -13
- package/dist/chains/citrea/CitreaChainType.js +2 -2
- package/dist/chains/citrea/CitreaFees.d.ts +29 -29
- package/dist/chains/citrea/CitreaFees.js +67 -67
- package/dist/chains/citrea/CitreaInitializer.d.ts +30 -30
- package/dist/chains/citrea/CitreaInitializer.js +129 -129
- package/dist/chains/citrea/CitreaSpvVaultContract.d.ts +15 -15
- package/dist/chains/citrea/CitreaSpvVaultContract.js +74 -74
- package/dist/chains/citrea/CitreaSwapContract.d.ts +22 -22
- package/dist/chains/citrea/CitreaSwapContract.js +96 -96
- package/dist/chains/citrea/CitreaTokens.d.ts +9 -9
- package/dist/chains/citrea/CitreaTokens.js +20 -20
- package/dist/evm/btcrelay/BtcRelayAbi.d.ts +198 -198
- package/dist/evm/btcrelay/BtcRelayAbi.js +261 -261
- package/dist/evm/btcrelay/BtcRelayTypechain.d.ts +172 -172
- package/dist/evm/btcrelay/BtcRelayTypechain.js +2 -2
- package/dist/evm/btcrelay/EVMBtcRelay.d.ts +197 -197
- package/dist/evm/btcrelay/EVMBtcRelay.js +435 -435
- package/dist/evm/btcrelay/headers/EVMBtcHeader.d.ts +33 -33
- package/dist/evm/btcrelay/headers/EVMBtcHeader.js +84 -84
- package/dist/evm/btcrelay/headers/EVMBtcStoredHeader.d.ts +56 -56
- package/dist/evm/btcrelay/headers/EVMBtcStoredHeader.js +123 -123
- package/dist/evm/chain/EVMChainInterface.d.ts +51 -51
- package/dist/evm/chain/EVMChainInterface.js +89 -89
- package/dist/evm/chain/EVMModule.d.ts +9 -9
- package/dist/evm/chain/EVMModule.js +13 -13
- package/dist/evm/chain/modules/ERC20Abi.d.ts +168 -168
- package/dist/evm/chain/modules/ERC20Abi.js +225 -225
- package/dist/evm/chain/modules/EVMAddresses.d.ts +10 -10
- package/dist/evm/chain/modules/EVMAddresses.js +30 -30
- package/dist/evm/chain/modules/EVMBlocks.d.ts +20 -20
- package/dist/evm/chain/modules/EVMBlocks.js +64 -64
- package/dist/evm/chain/modules/EVMEvents.d.ts +36 -36
- package/dist/evm/chain/modules/EVMEvents.js +122 -122
- package/dist/evm/chain/modules/EVMFees.d.ts +36 -36
- package/dist/evm/chain/modules/EVMFees.js +74 -74
- package/dist/evm/chain/modules/EVMSignatures.d.ts +29 -29
- package/dist/evm/chain/modules/EVMSignatures.js +68 -68
- package/dist/evm/chain/modules/EVMTokens.d.ts +70 -70
- package/dist/evm/chain/modules/EVMTokens.js +142 -142
- package/dist/evm/chain/modules/EVMTransactions.d.ts +94 -94
- package/dist/evm/chain/modules/EVMTransactions.js +286 -286
- package/dist/evm/contract/EVMContractBase.d.ts +22 -22
- package/dist/evm/contract/EVMContractBase.js +34 -34
- package/dist/evm/contract/EVMContractModule.d.ts +8 -8
- package/dist/evm/contract/EVMContractModule.js +11 -11
- package/dist/evm/contract/modules/EVMContractEvents.d.ts +42 -42
- package/dist/evm/contract/modules/EVMContractEvents.js +75 -75
- package/dist/evm/events/EVMChainEvents.d.ts +22 -22
- package/dist/evm/events/EVMChainEvents.js +69 -69
- package/dist/evm/events/EVMChainEventsBrowser.d.ts +102 -102
- package/dist/evm/events/EVMChainEventsBrowser.js +413 -404
- package/dist/evm/providers/JsonRpcProviderWithRetries.d.ts +15 -15
- package/dist/evm/providers/JsonRpcProviderWithRetries.js +19 -19
- package/dist/evm/providers/ReconnectingWebSocketProvider.d.ts +22 -22
- package/dist/evm/providers/ReconnectingWebSocketProvider.js +87 -87
- package/dist/evm/providers/SocketProvider.d.ts +111 -111
- package/dist/evm/providers/SocketProvider.js +334 -334
- package/dist/evm/providers/WebSocketProviderWithRetries.d.ts +17 -17
- package/dist/evm/providers/WebSocketProviderWithRetries.js +19 -19
- package/dist/evm/spv_swap/EVMSpvVaultContract.d.ts +79 -79
- package/dist/evm/spv_swap/EVMSpvVaultContract.js +482 -482
- package/dist/evm/spv_swap/EVMSpvVaultData.d.ts +39 -39
- package/dist/evm/spv_swap/EVMSpvVaultData.js +0 -180
- package/dist/evm/spv_swap/EVMSpvWithdrawalData.d.ts +19 -19
- package/dist/evm/spv_swap/EVMSpvWithdrawalData.js +55 -55
- package/dist/evm/spv_swap/SpvVaultContractAbi.d.ts +91 -91
- package/dist/evm/spv_swap/SpvVaultContractAbi.js +849 -849
- package/dist/evm/spv_swap/SpvVaultContractTypechain.d.ts +450 -450
- package/dist/evm/spv_swap/SpvVaultContractTypechain.js +2 -2
- package/dist/evm/swaps/EVMSwapContract.d.ts +193 -193
- package/dist/evm/swaps/EVMSwapContract.js +378 -378
- package/dist/evm/swaps/EVMSwapData.d.ts +66 -66
- package/dist/evm/swaps/EVMSwapData.js +260 -260
- package/dist/evm/swaps/EVMSwapModule.d.ts +9 -9
- package/dist/evm/swaps/EVMSwapModule.js +11 -11
- package/dist/evm/swaps/EscrowManagerAbi.d.ts +120 -120
- package/dist/evm/swaps/EscrowManagerAbi.js +985 -985
- package/dist/evm/swaps/EscrowManagerTypechain.d.ts +475 -475
- package/dist/evm/swaps/EscrowManagerTypechain.js +2 -2
- package/dist/evm/swaps/handlers/IHandler.d.ts +13 -13
- package/dist/evm/swaps/handlers/IHandler.js +2 -2
- package/dist/evm/swaps/handlers/claim/ClaimHandlers.d.ts +10 -10
- package/dist/evm/swaps/handlers/claim/ClaimHandlers.js +13 -13
- package/dist/evm/swaps/handlers/claim/HashlockClaimHandler.d.ts +20 -20
- package/dist/evm/swaps/handlers/claim/HashlockClaimHandler.js +39 -39
- package/dist/evm/swaps/handlers/claim/btc/BitcoinNoncedOutputClaimHandler.d.ts +24 -24
- package/dist/evm/swaps/handlers/claim/btc/BitcoinNoncedOutputClaimHandler.js +59 -59
- package/dist/evm/swaps/handlers/claim/btc/BitcoinOutputClaimHandler.d.ts +25 -25
- package/dist/evm/swaps/handlers/claim/btc/BitcoinOutputClaimHandler.js +51 -51
- package/dist/evm/swaps/handlers/claim/btc/BitcoinTxIdClaimHandler.d.ts +21 -21
- package/dist/evm/swaps/handlers/claim/btc/BitcoinTxIdClaimHandler.js +28 -28
- package/dist/evm/swaps/handlers/claim/btc/IBitcoinClaimHandler.d.ts +48 -48
- package/dist/evm/swaps/handlers/claim/btc/IBitcoinClaimHandler.js +63 -63
- package/dist/evm/swaps/handlers/refund/TimelockRefundHandler.d.ts +17 -17
- package/dist/evm/swaps/handlers/refund/TimelockRefundHandler.js +28 -28
- package/dist/evm/swaps/modules/EVMLpVault.d.ts +69 -69
- package/dist/evm/swaps/modules/EVMLpVault.js +134 -134
- package/dist/evm/swaps/modules/EVMSwapClaim.d.ts +54 -54
- package/dist/evm/swaps/modules/EVMSwapClaim.js +137 -137
- package/dist/evm/swaps/modules/EVMSwapInit.d.ts +88 -88
- package/dist/evm/swaps/modules/EVMSwapInit.js +274 -274
- package/dist/evm/swaps/modules/EVMSwapRefund.d.ts +62 -62
- package/dist/evm/swaps/modules/EVMSwapRefund.js +167 -167
- package/dist/evm/typechain/common.d.ts +50 -50
- package/dist/evm/typechain/common.js +2 -2
- package/dist/evm/wallet/EVMBrowserSigner.d.ts +5 -5
- package/dist/evm/wallet/EVMBrowserSigner.js +11 -11
- package/dist/evm/wallet/EVMPersistentSigner.d.ts +29 -29
- package/dist/evm/wallet/EVMPersistentSigner.js +222 -222
- package/dist/evm/wallet/EVMSigner.d.ts +11 -11
- package/dist/evm/wallet/EVMSigner.js +24 -24
- package/dist/index.d.ts +44 -44
- package/dist/index.js +60 -60
- package/dist/utils/Utils.d.ts +17 -17
- package/dist/utils/Utils.js +81 -81
- package/package.json +39 -39
- package/src/chains/botanix/BotanixChainType.ts +28 -28
- package/src/chains/botanix/BotanixInitializer.ts +171 -171
- package/src/chains/citrea/CitreaBtcRelay.ts +57 -57
- package/src/chains/citrea/CitreaChainType.ts +28 -28
- package/src/chains/citrea/CitreaFees.ts +77 -77
- package/src/chains/citrea/CitreaInitializer.ts +178 -178
- package/src/chains/citrea/CitreaSpvVaultContract.ts +75 -75
- package/src/chains/citrea/CitreaSwapContract.ts +102 -102
- package/src/chains/citrea/CitreaTokens.ts +21 -21
- package/src/evm/btcrelay/BtcRelayAbi.ts +258 -258
- package/src/evm/btcrelay/BtcRelayTypechain.ts +371 -371
- package/src/evm/btcrelay/EVMBtcRelay.ts +537 -537
- package/src/evm/btcrelay/headers/EVMBtcHeader.ts +109 -109
- package/src/evm/btcrelay/headers/EVMBtcStoredHeader.ts +152 -152
- package/src/evm/chain/EVMChainInterface.ts +155 -155
- package/src/evm/chain/EVMModule.ts +21 -21
- package/src/evm/chain/modules/ERC20Abi.ts +222 -222
- package/src/evm/chain/modules/EVMAddresses.ts +28 -28
- package/src/evm/chain/modules/EVMBlocks.ts +75 -75
- package/src/evm/chain/modules/EVMEvents.ts +139 -139
- package/src/evm/chain/modules/EVMFees.ts +104 -104
- package/src/evm/chain/modules/EVMSignatures.ts +76 -76
- package/src/evm/chain/modules/EVMTokens.ts +155 -155
- package/src/evm/chain/modules/EVMTransactions.ts +325 -325
- package/src/evm/contract/EVMContractBase.ts +63 -63
- package/src/evm/contract/EVMContractModule.ts +16 -16
- package/src/evm/contract/modules/EVMContractEvents.ts +102 -102
- package/src/evm/events/EVMChainEvents.ts +82 -82
- package/src/evm/events/EVMChainEventsBrowser.ts +534 -525
- package/src/evm/providers/JsonRpcProviderWithRetries.ts +24 -24
- package/src/evm/providers/ReconnectingWebSocketProvider.ts +101 -101
- package/src/evm/providers/SocketProvider.ts +368 -368
- package/src/evm/providers/WebSocketProviderWithRetries.ts +27 -27
- package/src/evm/spv_swap/EVMSpvVaultContract.ts +615 -615
- package/src/evm/spv_swap/EVMSpvVaultData.ts +224 -224
- package/src/evm/spv_swap/EVMSpvWithdrawalData.ts +70 -70
- package/src/evm/spv_swap/SpvVaultContractAbi.ts +846 -846
- package/src/evm/spv_swap/SpvVaultContractTypechain.ts +685 -685
- package/src/evm/swaps/EVMSwapContract.ts +600 -600
- package/src/evm/swaps/EVMSwapData.ts +378 -378
- package/src/evm/swaps/EVMSwapModule.ts +16 -16
- package/src/evm/swaps/EscrowManagerAbi.ts +982 -982
- package/src/evm/swaps/EscrowManagerTypechain.ts +723 -723
- package/src/evm/swaps/handlers/IHandler.ts +17 -17
- package/src/evm/swaps/handlers/claim/ClaimHandlers.ts +20 -20
- package/src/evm/swaps/handlers/claim/HashlockClaimHandler.ts +46 -46
- package/src/evm/swaps/handlers/claim/btc/BitcoinNoncedOutputClaimHandler.ts +82 -82
- package/src/evm/swaps/handlers/claim/btc/BitcoinOutputClaimHandler.ts +76 -76
- package/src/evm/swaps/handlers/claim/btc/BitcoinTxIdClaimHandler.ts +46 -46
- package/src/evm/swaps/handlers/claim/btc/IBitcoinClaimHandler.ts +115 -115
- package/src/evm/swaps/handlers/refund/TimelockRefundHandler.ts +37 -37
- package/src/evm/swaps/modules/EVMLpVault.ts +154 -154
- package/src/evm/swaps/modules/EVMSwapClaim.ts +172 -172
- package/src/evm/swaps/modules/EVMSwapInit.ts +328 -328
- package/src/evm/swaps/modules/EVMSwapRefund.ts +229 -229
- package/src/evm/typechain/common.ts +131 -131
- package/src/evm/wallet/EVMBrowserSigner.ts +11 -11
- package/src/evm/wallet/EVMPersistentSigner.ts +298 -298
- package/src/evm/wallet/EVMSigner.ts +31 -31
- package/src/index.ts +53 -53
- package/src/utils/Utils.ts +92 -92
|
@@ -1,537 +1,537 @@
|
|
|
1
|
-
import {BitcoinNetwork, BitcoinRpc, BtcBlock, BtcRelay, RelaySynchronizer, StatePredictorUtils} from "@atomiqlabs/base";
|
|
2
|
-
import {EVMBtcHeader} from "./headers/EVMBtcHeader";
|
|
3
|
-
import {getLogger, tryWithRetries} from "../../utils/Utils";
|
|
4
|
-
import {EVMContractBase, TypedFunctionCall} from "../contract/EVMContractBase";
|
|
5
|
-
import {BtcRelay as BtcRelayTypechain} from "./BtcRelayTypechain";
|
|
6
|
-
import {EVMBtcStoredHeader} from "./headers/EVMBtcStoredHeader";
|
|
7
|
-
import {EVMSigner} from "../wallet/EVMSigner";
|
|
8
|
-
import {EVMTx, EVMTxTrace} from "../chain/modules/EVMTransactions";
|
|
9
|
-
import {EVMFees} from "../chain/modules/EVMFees";
|
|
10
|
-
import {EVMChainInterface} from "../chain/EVMChainInterface";
|
|
11
|
-
import {BtcRelayAbi} from "./BtcRelayAbi";
|
|
12
|
-
import {AbiCoder, hexlify} from "ethers";
|
|
13
|
-
import {PromiseLruCache} from "promise-cache-ts";
|
|
14
|
-
|
|
15
|
-
function serializeBlockHeader(e: BtcBlock): EVMBtcHeader {
|
|
16
|
-
return new EVMBtcHeader({
|
|
17
|
-
version: e.getVersion(),
|
|
18
|
-
previousBlockhash: Buffer.from(e.getPrevBlockhash(), "hex").reverse(),
|
|
19
|
-
merkleRoot: Buffer.from(e.getMerkleRoot(), "hex").reverse(),
|
|
20
|
-
timestamp: e.getTimestamp(),
|
|
21
|
-
nbits: e.getNbits(),
|
|
22
|
-
nonce: e.getNonce(),
|
|
23
|
-
hash: Buffer.from(e.getHash(), "hex").reverse()
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const logger = getLogger("EVMBtcRelay: ");
|
|
28
|
-
|
|
29
|
-
export class EVMBtcRelay<B extends BtcBlock>
|
|
30
|
-
extends EVMContractBase<BtcRelayTypechain>
|
|
31
|
-
implements BtcRelay<EVMBtcStoredHeader, EVMTx, B, EVMSigner> {
|
|
32
|
-
|
|
33
|
-
public static GasCosts = {
|
|
34
|
-
GAS_PER_BLOCKHEADER: 30_000,
|
|
35
|
-
GAS_BASE_MAIN: 15_000 + 21_000,
|
|
36
|
-
GAS_PER_BLOCKHEADER_FORK: 65_000,
|
|
37
|
-
GAS_PER_BLOCKHEADER_FORKED: 10_000,
|
|
38
|
-
GAS_BASE_FORK: 25_000 + 21_000
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
public async SaveMainHeaders(signer: string, mainHeaders: EVMBtcHeader[], storedHeader: EVMBtcStoredHeader, feeRate: string): Promise<EVMTx> {
|
|
42
|
-
const tx = await this.contract.submitMainBlockheaders.populateTransaction(Buffer.concat([
|
|
43
|
-
storedHeader.serialize(),
|
|
44
|
-
Buffer.concat(mainHeaders.map(header => header.serializeCompact()))
|
|
45
|
-
]));
|
|
46
|
-
tx.from = signer;
|
|
47
|
-
EVMFees.applyFeeRate(tx, EVMBtcRelay.GasCosts.GAS_BASE_MAIN + (EVMBtcRelay.GasCosts.GAS_PER_BLOCKHEADER * mainHeaders.length), feeRate);
|
|
48
|
-
return tx;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
public async SaveShortForkHeaders(signer: string, forkHeaders: EVMBtcHeader[], storedHeader: EVMBtcStoredHeader, feeRate: string): Promise<EVMTx> {
|
|
52
|
-
const tx = await this.contract.submitShortForkBlockheaders.populateTransaction(Buffer.concat([
|
|
53
|
-
storedHeader.serialize(),
|
|
54
|
-
Buffer.concat(forkHeaders.map(header => header.serializeCompact()))
|
|
55
|
-
]));
|
|
56
|
-
tx.from = signer;
|
|
57
|
-
EVMFees.applyFeeRate(tx, EVMBtcRelay.GasCosts.GAS_BASE_MAIN + (EVMBtcRelay.GasCosts.GAS_PER_BLOCKHEADER * forkHeaders.length), feeRate);
|
|
58
|
-
return tx;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
public async SaveLongForkHeaders(signer: string, forkId: number, forkHeaders: EVMBtcHeader[], storedHeader: EVMBtcStoredHeader, feeRate: string, totalForkHeaders: number = 100): Promise<EVMTx> {
|
|
62
|
-
const tx = await this.contract.submitForkBlockheaders.populateTransaction(forkId, Buffer.concat([
|
|
63
|
-
storedHeader.serialize(),
|
|
64
|
-
Buffer.concat(forkHeaders.map(header => header.serializeCompact()))
|
|
65
|
-
]));
|
|
66
|
-
tx.from = signer;
|
|
67
|
-
EVMFees.applyFeeRate(tx, EVMBtcRelay.GasCosts.GAS_BASE_FORK + (EVMBtcRelay.GasCosts.GAS_PER_BLOCKHEADER_FORK * forkHeaders.length) + (EVMBtcRelay.GasCosts.GAS_PER_BLOCKHEADER_FORKED * totalForkHeaders), feeRate);
|
|
68
|
-
return tx;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
bitcoinRpc: BitcoinRpc<B>;
|
|
72
|
-
|
|
73
|
-
readonly maxHeadersPerTx: number = 100;
|
|
74
|
-
readonly maxForkHeadersPerTx: number = 50;
|
|
75
|
-
readonly maxShortForkHeadersPerTx: number = 100;
|
|
76
|
-
|
|
77
|
-
constructor(
|
|
78
|
-
chainInterface: EVMChainInterface<any>,
|
|
79
|
-
bitcoinRpc: BitcoinRpc<B>,
|
|
80
|
-
bitcoinNetwork: BitcoinNetwork,
|
|
81
|
-
contractAddress: string,
|
|
82
|
-
contractDeploymentHeight?: number
|
|
83
|
-
) {
|
|
84
|
-
super(chainInterface, contractAddress, BtcRelayAbi, contractDeploymentHeight);
|
|
85
|
-
this.bitcoinRpc = bitcoinRpc;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Computes subsequent commited headers as they will appear on the blockchain when transactions
|
|
90
|
-
* are submitted & confirmed
|
|
91
|
-
*
|
|
92
|
-
* @param initialStoredHeader
|
|
93
|
-
* @param syncedHeaders
|
|
94
|
-
* @private
|
|
95
|
-
*/
|
|
96
|
-
private computeCommitedHeaders(initialStoredHeader: EVMBtcStoredHeader, syncedHeaders: EVMBtcHeader[]) {
|
|
97
|
-
const computedCommitedHeaders = [initialStoredHeader];
|
|
98
|
-
for(let blockHeader of syncedHeaders) {
|
|
99
|
-
computedCommitedHeaders.push(computedCommitedHeaders[computedCommitedHeaders.length-1].computeNext(blockHeader));
|
|
100
|
-
}
|
|
101
|
-
return computedCommitedHeaders;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* A common logic for submitting blockheaders in a transaction
|
|
106
|
-
*
|
|
107
|
-
* @param signer
|
|
108
|
-
* @param headers headers to sync to the btc relay
|
|
109
|
-
* @param storedHeader current latest stored block header for a given fork
|
|
110
|
-
* @param tipWork work of the current tip in a given fork
|
|
111
|
-
* @param forkId forkId to submit to, forkId=0 means main chain, forkId=-1 means short fork
|
|
112
|
-
* @param feeRate feeRate for the transaction
|
|
113
|
-
* @param totalForkHeaders Total number of headers in a fork
|
|
114
|
-
* @private
|
|
115
|
-
*/
|
|
116
|
-
private async _saveHeaders(
|
|
117
|
-
signer: string,
|
|
118
|
-
headers: BtcBlock[],
|
|
119
|
-
storedHeader: EVMBtcStoredHeader,
|
|
120
|
-
tipWork: Buffer,
|
|
121
|
-
forkId: number,
|
|
122
|
-
feeRate: string,
|
|
123
|
-
totalForkHeaders: number
|
|
124
|
-
) {
|
|
125
|
-
const blockHeaderObj = headers.map(serializeBlockHeader);
|
|
126
|
-
let tx: EVMTx;
|
|
127
|
-
switch(forkId) {
|
|
128
|
-
case -1:
|
|
129
|
-
tx = await this.SaveShortForkHeaders(signer, blockHeaderObj, storedHeader, feeRate);
|
|
130
|
-
break;
|
|
131
|
-
case 0:
|
|
132
|
-
tx = await this.SaveMainHeaders(signer, blockHeaderObj, storedHeader, feeRate);
|
|
133
|
-
break;
|
|
134
|
-
default:
|
|
135
|
-
tx = await this.SaveLongForkHeaders(signer, forkId, blockHeaderObj, storedHeader, feeRate, totalForkHeaders);
|
|
136
|
-
break;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const computedCommitedHeaders = this.computeCommitedHeaders(storedHeader, blockHeaderObj);
|
|
140
|
-
const lastStoredHeader = computedCommitedHeaders[computedCommitedHeaders.length-1];
|
|
141
|
-
if(forkId!==0 && StatePredictorUtils.gtBuffer(lastStoredHeader.getBlockHash(), tipWork)) {
|
|
142
|
-
//Fork's work is higher than main chain's work, this fork will become a main chain
|
|
143
|
-
forkId = 0;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return {
|
|
147
|
-
forkId: forkId,
|
|
148
|
-
lastStoredHeader,
|
|
149
|
-
tx,
|
|
150
|
-
computedCommitedHeaders
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
private async findStoredBlockheaderInTraces(txTrace: EVMTxTrace, commitHash: string): Promise<EVMBtcStoredHeader> {
|
|
155
|
-
if(txTrace.to.toLowerCase() === (await this.contract.getAddress()).toLowerCase()) {
|
|
156
|
-
let dataBuffer: Buffer;
|
|
157
|
-
if(txTrace.type==="CREATE") {
|
|
158
|
-
dataBuffer = Buffer.from(txTrace.input.substring(txTrace.input.length-384, txTrace.input.length-64), "hex");
|
|
159
|
-
} else {
|
|
160
|
-
const result = this.parseCalldata(txTrace.input);
|
|
161
|
-
if(result!=null) {
|
|
162
|
-
if(result.name==="submitMainBlockheaders" || result.name==="submitShortForkBlockheaders") {
|
|
163
|
-
const functionCall: TypedFunctionCall<
|
|
164
|
-
typeof this.contract.submitMainBlockheaders |
|
|
165
|
-
typeof this.contract.submitShortForkBlockheaders
|
|
166
|
-
> = result;
|
|
167
|
-
dataBuffer = Buffer.from(hexlify(functionCall.args[0]).substring(2), "hex");
|
|
168
|
-
} else if(result.name==="submitForkBlockheaders") {
|
|
169
|
-
const functionCall: TypedFunctionCall<typeof this.contract.submitForkBlockheaders> = result;
|
|
170
|
-
dataBuffer = Buffer.from(hexlify(functionCall.args[1]).substring(2), "hex");
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
if(dataBuffer!=null) {
|
|
175
|
-
let storedHeader = EVMBtcStoredHeader.deserialize(dataBuffer.subarray(0, 160));
|
|
176
|
-
if(storedHeader.getCommitHash()===commitHash) return storedHeader;
|
|
177
|
-
for(let i = 160; i < dataBuffer.length; i+=48) {
|
|
178
|
-
const blockHeader = EVMBtcHeader.deserialize(dataBuffer.subarray(i, i + 48));
|
|
179
|
-
storedHeader = storedHeader.computeNext(blockHeader);
|
|
180
|
-
if(storedHeader.getCommitHash()===commitHash) return storedHeader;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if(txTrace.calls!=null) {
|
|
186
|
-
for(let call of txTrace.calls) {
|
|
187
|
-
const result = await this.findStoredBlockheaderInTraces(call, commitHash);
|
|
188
|
-
if(result!=null) return result;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return null;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
private commitHashCache: PromiseLruCache<string, [EVMBtcStoredHeader, string]> = new PromiseLruCache<string, [EVMBtcStoredHeader, string]>(1000);
|
|
196
|
-
private blockHashCache: PromiseLruCache<string, [EVMBtcStoredHeader, string]> = new PromiseLruCache<string, [EVMBtcStoredHeader, string]>(1000);
|
|
197
|
-
|
|
198
|
-
private getBlock(commitHash?: string, blockHash?: Buffer): Promise<[EVMBtcStoredHeader, string] | null> {
|
|
199
|
-
const blockHashString = blockHash==null ? null : "0x"+Buffer.from([...blockHash]).reverse().toString("hex");
|
|
200
|
-
|
|
201
|
-
const generator = () => this.Events.findInContractEvents<[EVMBtcStoredHeader, string], "StoreHeader" | "StoreForkHeader">(
|
|
202
|
-
["StoreHeader", "StoreForkHeader"],
|
|
203
|
-
[
|
|
204
|
-
commitHash,
|
|
205
|
-
blockHashString
|
|
206
|
-
],
|
|
207
|
-
async (event) => {
|
|
208
|
-
const txTrace = await this.Chain.Transactions.traceTransaction(event.transactionHash);
|
|
209
|
-
const storedBlockheader = await this.findStoredBlockheaderInTraces(txTrace, event.args.commitHash);
|
|
210
|
-
if(storedBlockheader==null) return null;
|
|
211
|
-
|
|
212
|
-
this.commitHashCache.set(event.args.commitHash, Promise.resolve([storedBlockheader, event.args.commitHash]));
|
|
213
|
-
this.blockHashCache.set(event.args.blockHash, Promise.resolve([storedBlockheader, event.args.commitHash]));
|
|
214
|
-
return [storedBlockheader, event.args.commitHash];
|
|
215
|
-
}
|
|
216
|
-
);
|
|
217
|
-
|
|
218
|
-
if(commitHash!=null) return this.commitHashCache.getOrComputeAsync(commitHash, generator);
|
|
219
|
-
if(blockHashString!=null) return this.blockHashCache.getOrComputeAsync(blockHashString, generator);
|
|
220
|
-
|
|
221
|
-
return null;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
private async getBlockHeight(): Promise<number> {
|
|
225
|
-
return Number(await this.contract.getBlockheight());
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Returns data about current main chain tip stored in the btc relay
|
|
230
|
-
*/
|
|
231
|
-
public async getTipData(): Promise<{ commitHash: string; blockhash: string, chainWork: Buffer, blockheight: number }> {
|
|
232
|
-
const commitHash = await this.contract.getTipCommitHash();
|
|
233
|
-
if(commitHash==null || BigInt(commitHash)===BigInt(0)) return null;
|
|
234
|
-
|
|
235
|
-
const result = await this.getBlock(commitHash);
|
|
236
|
-
if(result==null) return null;
|
|
237
|
-
|
|
238
|
-
const storedBlockHeader = result[0];
|
|
239
|
-
|
|
240
|
-
return {
|
|
241
|
-
blockheight: storedBlockHeader.getBlockheight(),
|
|
242
|
-
commitHash: commitHash,
|
|
243
|
-
blockhash: storedBlockHeader.getBlockHash().toString("hex"),
|
|
244
|
-
chainWork: storedBlockHeader.getChainWork()
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Retrieves blockheader with a specific blockhash, returns null if requiredBlockheight is provided and
|
|
250
|
-
* btc relay contract is not synced up to the desired blockheight
|
|
251
|
-
*
|
|
252
|
-
* @param blockData
|
|
253
|
-
* @param requiredBlockheight
|
|
254
|
-
*/
|
|
255
|
-
public async retrieveLogAndBlockheight(blockData: {blockhash: string}, requiredBlockheight?: number): Promise<{
|
|
256
|
-
header: EVMBtcStoredHeader,
|
|
257
|
-
height: number
|
|
258
|
-
} | null> {
|
|
259
|
-
//TODO: we can fetch the blockheight and events in parallel
|
|
260
|
-
const blockHeight = await this.getBlockHeight();
|
|
261
|
-
if(requiredBlockheight!=null && blockHeight < requiredBlockheight) {
|
|
262
|
-
return null;
|
|
263
|
-
}
|
|
264
|
-
const result = await this.getBlock(null, Buffer.from(blockData.blockhash, "hex"));
|
|
265
|
-
if(result==null) return null;
|
|
266
|
-
|
|
267
|
-
const [storedBlockHeader, commitHash] = result;
|
|
268
|
-
|
|
269
|
-
//Check if block is part of the main chain
|
|
270
|
-
const chainCommitment = await this.contract.getCommitHash(storedBlockHeader.blockHeight);
|
|
271
|
-
if(chainCommitment!==commitHash) return null;
|
|
272
|
-
|
|
273
|
-
logger.debug("retrieveLogAndBlockheight(): block found," +
|
|
274
|
-
" commit hash: "+commitHash+" blockhash: "+blockData.blockhash+" current btc relay height: "+blockHeight);
|
|
275
|
-
|
|
276
|
-
return {header: storedBlockHeader, height: blockHeight};
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Retrieves blockheader data by blockheader's commit hash,
|
|
281
|
-
*
|
|
282
|
-
* @param commitmentHashStr
|
|
283
|
-
* @param blockData
|
|
284
|
-
*/
|
|
285
|
-
public async retrieveLogByCommitHash(commitmentHashStr: string, blockData: {blockhash: string}): Promise<EVMBtcStoredHeader> {
|
|
286
|
-
const result = await this.getBlock(commitmentHashStr, Buffer.from(blockData.blockhash, "hex"));
|
|
287
|
-
if(result==null) return null;
|
|
288
|
-
|
|
289
|
-
const [storedBlockHeader, commitHash] = result;
|
|
290
|
-
|
|
291
|
-
//Check if block is part of the main chain
|
|
292
|
-
const chainCommitment = await this.contract.getCommitHash(storedBlockHeader.blockHeight);
|
|
293
|
-
if(chainCommitment!==commitHash) return null;
|
|
294
|
-
|
|
295
|
-
logger.debug("retrieveLogByCommitHash(): block found," +
|
|
296
|
-
" commit hash: "+commitmentHashStr+" blockhash: "+blockData.blockhash+" height: "+storedBlockHeader.blockHeight);
|
|
297
|
-
|
|
298
|
-
return storedBlockHeader;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
/**
|
|
302
|
-
* Retrieves latest known stored blockheader & blockheader from bitcoin RPC that is in the main chain
|
|
303
|
-
*/
|
|
304
|
-
public async retrieveLatestKnownBlockLog(): Promise<{
|
|
305
|
-
resultStoredHeader: EVMBtcStoredHeader,
|
|
306
|
-
resultBitcoinHeader: B
|
|
307
|
-
}> {
|
|
308
|
-
const data = await this.Events.findInContractEvents(
|
|
309
|
-
["StoreHeader", "StoreForkHeader"],
|
|
310
|
-
null,
|
|
311
|
-
async (event) => {
|
|
312
|
-
const blockHashHex = Buffer.from(event.args.blockHash.substring(2), "hex").reverse().toString("hex");
|
|
313
|
-
const commitHash = event.args.commitHash;
|
|
314
|
-
|
|
315
|
-
const isInBtcMainChain = await this.bitcoinRpc.isInMainChain(blockHashHex).catch(() => false);
|
|
316
|
-
if(!isInBtcMainChain) return null;
|
|
317
|
-
|
|
318
|
-
const blockHeader = await this.bitcoinRpc.getBlockHeader(blockHashHex);
|
|
319
|
-
|
|
320
|
-
if(commitHash !== await this.contract.getCommitHash(blockHeader.getHeight())) return null;
|
|
321
|
-
|
|
322
|
-
const txTrace = await this.Chain.Transactions.traceTransaction(event.transactionHash);
|
|
323
|
-
const storedHeader = await this.findStoredBlockheaderInTraces(txTrace, commitHash);
|
|
324
|
-
if(storedHeader==null) return null;
|
|
325
|
-
|
|
326
|
-
return {
|
|
327
|
-
resultStoredHeader: storedHeader,
|
|
328
|
-
resultBitcoinHeader: blockHeader,
|
|
329
|
-
commitHash: commitHash
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
)
|
|
333
|
-
|
|
334
|
-
if(data!=null) logger.debug("retrieveLatestKnownBlockLog(): block found," +
|
|
335
|
-
" commit hash: "+data.commitHash+" blockhash: "+data.resultBitcoinHeader.getHash()+
|
|
336
|
-
" height: "+data.resultStoredHeader.getBlockheight());
|
|
337
|
-
|
|
338
|
-
return data;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/**
|
|
342
|
-
* Saves blockheaders as a bitcoin main chain to the btc relay
|
|
343
|
-
*
|
|
344
|
-
* @param signer
|
|
345
|
-
* @param mainHeaders
|
|
346
|
-
* @param storedHeader
|
|
347
|
-
* @param feeRate
|
|
348
|
-
*/
|
|
349
|
-
public async saveMainHeaders(signer: string, mainHeaders: BtcBlock[], storedHeader: EVMBtcStoredHeader, feeRate?: string) {
|
|
350
|
-
feeRate ??= await this.Chain.Fees.getFeeRate();
|
|
351
|
-
logger.debug("saveMainHeaders(): submitting main blockheaders, count: "+mainHeaders.length);
|
|
352
|
-
return this._saveHeaders(signer, mainHeaders, storedHeader, null, 0, feeRate, 0);
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
* Creates a new long fork and submits the headers to it
|
|
357
|
-
*
|
|
358
|
-
* @param signer
|
|
359
|
-
* @param forkHeaders
|
|
360
|
-
* @param storedHeader
|
|
361
|
-
* @param tipWork
|
|
362
|
-
* @param feeRate
|
|
363
|
-
*/
|
|
364
|
-
public async saveNewForkHeaders(signer: string, forkHeaders: BtcBlock[], storedHeader: EVMBtcStoredHeader, tipWork: Buffer, feeRate?: string) {
|
|
365
|
-
let forkId: number = Math.floor(Math.random() * 0xFFFFFFFFFFFF);
|
|
366
|
-
feeRate ??= await this.Chain.Fees.getFeeRate();
|
|
367
|
-
|
|
368
|
-
logger.debug("saveNewForkHeaders(): submitting new fork & blockheaders," +
|
|
369
|
-
" count: "+forkHeaders.length+" forkId: 0x"+forkId.toString(16));
|
|
370
|
-
|
|
371
|
-
return await this._saveHeaders(signer, forkHeaders, storedHeader, tipWork, forkId, feeRate, forkHeaders.length);
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
/**
|
|
375
|
-
* Continues submitting blockheaders to a given fork
|
|
376
|
-
*
|
|
377
|
-
* @param signer
|
|
378
|
-
* @param forkHeaders
|
|
379
|
-
* @param storedHeader
|
|
380
|
-
* @param forkId
|
|
381
|
-
* @param tipWork
|
|
382
|
-
* @param feeRate
|
|
383
|
-
*/
|
|
384
|
-
public async saveForkHeaders(signer: string, forkHeaders: BtcBlock[], storedHeader: EVMBtcStoredHeader, forkId: number, tipWork: Buffer, feeRate?: string) {
|
|
385
|
-
feeRate ??= await this.Chain.Fees.getFeeRate();
|
|
386
|
-
|
|
387
|
-
logger.debug("saveForkHeaders(): submitting blockheaders to existing fork," +
|
|
388
|
-
" count: "+forkHeaders.length+" forkId: 0x"+forkId.toString(16));
|
|
389
|
-
|
|
390
|
-
return this._saveHeaders(signer, forkHeaders, storedHeader, tipWork, forkId, feeRate, 100);
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
/**
|
|
394
|
-
* Submits short fork with given blockheaders
|
|
395
|
-
*
|
|
396
|
-
* @param signer
|
|
397
|
-
* @param forkHeaders
|
|
398
|
-
* @param storedHeader
|
|
399
|
-
* @param tipWork
|
|
400
|
-
* @param feeRate
|
|
401
|
-
*/
|
|
402
|
-
public async saveShortForkHeaders(signer: string, forkHeaders: BtcBlock[], storedHeader: EVMBtcStoredHeader, tipWork: Buffer, feeRate?: string) {
|
|
403
|
-
feeRate ??= await this.Chain.Fees.getFeeRate();
|
|
404
|
-
|
|
405
|
-
logger.debug("saveShortForkHeaders(): submitting short fork blockheaders," +
|
|
406
|
-
" count: "+forkHeaders.length);
|
|
407
|
-
|
|
408
|
-
return this._saveHeaders(signer, forkHeaders, storedHeader, tipWork, -1, feeRate, 0);
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
/**
|
|
412
|
-
* Estimate required synchronization fee (worst case) to synchronize btc relay to the required blockheight
|
|
413
|
-
*
|
|
414
|
-
* @param requiredBlockheight
|
|
415
|
-
* @param feeRate
|
|
416
|
-
*/
|
|
417
|
-
public async estimateSynchronizeFee(requiredBlockheight: number, feeRate?: string): Promise<bigint> {
|
|
418
|
-
feeRate ??= await this.Chain.Fees.getFeeRate();
|
|
419
|
-
|
|
420
|
-
const tipData = await this.getTipData();
|
|
421
|
-
const currBlockheight = tipData.blockheight;
|
|
422
|
-
|
|
423
|
-
const blockheightDelta = requiredBlockheight-currBlockheight;
|
|
424
|
-
|
|
425
|
-
if(blockheightDelta<=0) return 0n;
|
|
426
|
-
|
|
427
|
-
const synchronizationFee = (BigInt(blockheightDelta) * await this.getFeePerBlock(feeRate))
|
|
428
|
-
+ EVMFees.getGasFee(EVMBtcRelay.GasCosts.GAS_BASE_MAIN * Math.ceil(blockheightDelta / this.maxHeadersPerTx), feeRate);
|
|
429
|
-
logger.debug("estimateSynchronizeFee(): required blockheight: "+requiredBlockheight+
|
|
430
|
-
" blockheight delta: "+blockheightDelta+" fee: "+synchronizationFee.toString(10));
|
|
431
|
-
|
|
432
|
-
return synchronizationFee;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
/**
|
|
436
|
-
* Returns fee required (in native token) to synchronize a single block to btc relay
|
|
437
|
-
*
|
|
438
|
-
* @param feeRate
|
|
439
|
-
*/
|
|
440
|
-
public async getFeePerBlock(feeRate?: string): Promise<bigint> {
|
|
441
|
-
feeRate ??= await this.Chain.Fees.getFeeRate();
|
|
442
|
-
return EVMFees.getGasFee(EVMBtcRelay.GasCosts.GAS_PER_BLOCKHEADER, feeRate);
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
/**
|
|
446
|
-
* Gets fee rate required for submitting blockheaders to the main chain
|
|
447
|
-
*/
|
|
448
|
-
public getMainFeeRate(signer: string | null): Promise<string> {
|
|
449
|
-
return this.Chain.Fees.getFeeRate();
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
/**
|
|
453
|
-
* Gets fee rate required for submitting blockheaders to the specific fork
|
|
454
|
-
*/
|
|
455
|
-
public getForkFeeRate(signer: string, forkId: number): Promise<string> {
|
|
456
|
-
return this.Chain.Fees.getFeeRate();
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
saveInitialHeader(signer: string, header: B, epochStart: number, pastBlocksTimestamps: number[], feeRate?: string): Promise<EVMTx> {
|
|
460
|
-
throw new Error("Not supported, EVM contract is initialized with constructor!");
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
/**
|
|
464
|
-
* Gets committed header, identified by blockhash & blockheight, determines required BTC relay blockheight based on
|
|
465
|
-
* requiredConfirmations
|
|
466
|
-
* If synchronizer is passed & blockhash is not found, it produces transactions to sync up the btc relay to the
|
|
467
|
-
* current chain tip & adds them to the txs array
|
|
468
|
-
*
|
|
469
|
-
* @param signer
|
|
470
|
-
* @param btcRelay
|
|
471
|
-
* @param btcTxs
|
|
472
|
-
* @param txs solana transaction array, in case we need to synchronize the btc relay ourselves the synchronization
|
|
473
|
-
* txns are added here
|
|
474
|
-
* @param synchronizer optional synchronizer to use to synchronize the btc relay in case it is not yet synchronized
|
|
475
|
-
* to the required blockheight
|
|
476
|
-
* @param feeRate Fee rate to use for synchronization transactions
|
|
477
|
-
* @private
|
|
478
|
-
*/
|
|
479
|
-
static async getCommitedHeadersAndSynchronize(
|
|
480
|
-
signer: string,
|
|
481
|
-
btcRelay: EVMBtcRelay<any>,
|
|
482
|
-
btcTxs: {blockheight: number, requiredConfirmations: number, blockhash: string}[],
|
|
483
|
-
txs: EVMTx[],
|
|
484
|
-
synchronizer?: RelaySynchronizer<EVMBtcStoredHeader, EVMTx, any>,
|
|
485
|
-
feeRate?: string
|
|
486
|
-
): Promise<{
|
|
487
|
-
[blockhash: string]: EVMBtcStoredHeader
|
|
488
|
-
}> {
|
|
489
|
-
const leavesTxs: {blockheight: number, requiredConfirmations: number, blockhash: string}[] = [];
|
|
490
|
-
|
|
491
|
-
const blockheaders: {
|
|
492
|
-
[blockhash: string]: EVMBtcStoredHeader
|
|
493
|
-
} = {};
|
|
494
|
-
|
|
495
|
-
for(let btcTx of btcTxs) {
|
|
496
|
-
const requiredBlockheight = btcTx.blockheight+btcTx.requiredConfirmations-1;
|
|
497
|
-
|
|
498
|
-
const result = await tryWithRetries(
|
|
499
|
-
() => btcRelay.retrieveLogAndBlockheight({
|
|
500
|
-
blockhash: btcTx.blockhash
|
|
501
|
-
}, requiredBlockheight)
|
|
502
|
-
);
|
|
503
|
-
|
|
504
|
-
if(result!=null) {
|
|
505
|
-
blockheaders[result.header.getBlockHash().toString("hex")] = result.header;
|
|
506
|
-
} else {
|
|
507
|
-
leavesTxs.push(btcTx);
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
if(leavesTxs.length===0) return blockheaders;
|
|
512
|
-
|
|
513
|
-
//Need to synchronize
|
|
514
|
-
if(synchronizer==null) return null;
|
|
515
|
-
|
|
516
|
-
//TODO: We don't have to synchronize to tip, only to our required blockheight
|
|
517
|
-
const resp = await synchronizer.syncToLatestTxs(signer.toString(), feeRate);
|
|
518
|
-
logger.debug("getCommitedHeaderAndSynchronize(): BTC Relay not synchronized to required blockheight, "+
|
|
519
|
-
"synchronizing ourselves in "+resp.txs.length+" txs");
|
|
520
|
-
logger.debug("getCommitedHeaderAndSynchronize(): BTC Relay computed header map: ",resp.computedHeaderMap);
|
|
521
|
-
txs.push(...resp.txs);
|
|
522
|
-
|
|
523
|
-
for(let key in resp.computedHeaderMap) {
|
|
524
|
-
const header = resp.computedHeaderMap[key];
|
|
525
|
-
blockheaders[header.getBlockHash().toString("hex")] = header;
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
//Check that blockhashes of all the rest txs are included
|
|
529
|
-
for(let btcTx of leavesTxs) {
|
|
530
|
-
if(blockheaders[btcTx.blockhash]==null) return null;
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
//Retrieve computed headers
|
|
534
|
-
return blockheaders;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
}
|
|
1
|
+
import {BitcoinNetwork, BitcoinRpc, BtcBlock, BtcRelay, RelaySynchronizer, StatePredictorUtils} from "@atomiqlabs/base";
|
|
2
|
+
import {EVMBtcHeader} from "./headers/EVMBtcHeader";
|
|
3
|
+
import {getLogger, tryWithRetries} from "../../utils/Utils";
|
|
4
|
+
import {EVMContractBase, TypedFunctionCall} from "../contract/EVMContractBase";
|
|
5
|
+
import {BtcRelay as BtcRelayTypechain} from "./BtcRelayTypechain";
|
|
6
|
+
import {EVMBtcStoredHeader} from "./headers/EVMBtcStoredHeader";
|
|
7
|
+
import {EVMSigner} from "../wallet/EVMSigner";
|
|
8
|
+
import {EVMTx, EVMTxTrace} from "../chain/modules/EVMTransactions";
|
|
9
|
+
import {EVMFees} from "../chain/modules/EVMFees";
|
|
10
|
+
import {EVMChainInterface} from "../chain/EVMChainInterface";
|
|
11
|
+
import {BtcRelayAbi} from "./BtcRelayAbi";
|
|
12
|
+
import {AbiCoder, hexlify} from "ethers";
|
|
13
|
+
import {PromiseLruCache} from "promise-cache-ts";
|
|
14
|
+
|
|
15
|
+
function serializeBlockHeader(e: BtcBlock): EVMBtcHeader {
|
|
16
|
+
return new EVMBtcHeader({
|
|
17
|
+
version: e.getVersion(),
|
|
18
|
+
previousBlockhash: Buffer.from(e.getPrevBlockhash(), "hex").reverse(),
|
|
19
|
+
merkleRoot: Buffer.from(e.getMerkleRoot(), "hex").reverse(),
|
|
20
|
+
timestamp: e.getTimestamp(),
|
|
21
|
+
nbits: e.getNbits(),
|
|
22
|
+
nonce: e.getNonce(),
|
|
23
|
+
hash: Buffer.from(e.getHash(), "hex").reverse()
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const logger = getLogger("EVMBtcRelay: ");
|
|
28
|
+
|
|
29
|
+
export class EVMBtcRelay<B extends BtcBlock>
|
|
30
|
+
extends EVMContractBase<BtcRelayTypechain>
|
|
31
|
+
implements BtcRelay<EVMBtcStoredHeader, EVMTx, B, EVMSigner> {
|
|
32
|
+
|
|
33
|
+
public static GasCosts = {
|
|
34
|
+
GAS_PER_BLOCKHEADER: 30_000,
|
|
35
|
+
GAS_BASE_MAIN: 15_000 + 21_000,
|
|
36
|
+
GAS_PER_BLOCKHEADER_FORK: 65_000,
|
|
37
|
+
GAS_PER_BLOCKHEADER_FORKED: 10_000,
|
|
38
|
+
GAS_BASE_FORK: 25_000 + 21_000
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public async SaveMainHeaders(signer: string, mainHeaders: EVMBtcHeader[], storedHeader: EVMBtcStoredHeader, feeRate: string): Promise<EVMTx> {
|
|
42
|
+
const tx = await this.contract.submitMainBlockheaders.populateTransaction(Buffer.concat([
|
|
43
|
+
storedHeader.serialize(),
|
|
44
|
+
Buffer.concat(mainHeaders.map(header => header.serializeCompact()))
|
|
45
|
+
]));
|
|
46
|
+
tx.from = signer;
|
|
47
|
+
EVMFees.applyFeeRate(tx, EVMBtcRelay.GasCosts.GAS_BASE_MAIN + (EVMBtcRelay.GasCosts.GAS_PER_BLOCKHEADER * mainHeaders.length), feeRate);
|
|
48
|
+
return tx;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public async SaveShortForkHeaders(signer: string, forkHeaders: EVMBtcHeader[], storedHeader: EVMBtcStoredHeader, feeRate: string): Promise<EVMTx> {
|
|
52
|
+
const tx = await this.contract.submitShortForkBlockheaders.populateTransaction(Buffer.concat([
|
|
53
|
+
storedHeader.serialize(),
|
|
54
|
+
Buffer.concat(forkHeaders.map(header => header.serializeCompact()))
|
|
55
|
+
]));
|
|
56
|
+
tx.from = signer;
|
|
57
|
+
EVMFees.applyFeeRate(tx, EVMBtcRelay.GasCosts.GAS_BASE_MAIN + (EVMBtcRelay.GasCosts.GAS_PER_BLOCKHEADER * forkHeaders.length), feeRate);
|
|
58
|
+
return tx;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public async SaveLongForkHeaders(signer: string, forkId: number, forkHeaders: EVMBtcHeader[], storedHeader: EVMBtcStoredHeader, feeRate: string, totalForkHeaders: number = 100): Promise<EVMTx> {
|
|
62
|
+
const tx = await this.contract.submitForkBlockheaders.populateTransaction(forkId, Buffer.concat([
|
|
63
|
+
storedHeader.serialize(),
|
|
64
|
+
Buffer.concat(forkHeaders.map(header => header.serializeCompact()))
|
|
65
|
+
]));
|
|
66
|
+
tx.from = signer;
|
|
67
|
+
EVMFees.applyFeeRate(tx, EVMBtcRelay.GasCosts.GAS_BASE_FORK + (EVMBtcRelay.GasCosts.GAS_PER_BLOCKHEADER_FORK * forkHeaders.length) + (EVMBtcRelay.GasCosts.GAS_PER_BLOCKHEADER_FORKED * totalForkHeaders), feeRate);
|
|
68
|
+
return tx;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
bitcoinRpc: BitcoinRpc<B>;
|
|
72
|
+
|
|
73
|
+
readonly maxHeadersPerTx: number = 100;
|
|
74
|
+
readonly maxForkHeadersPerTx: number = 50;
|
|
75
|
+
readonly maxShortForkHeadersPerTx: number = 100;
|
|
76
|
+
|
|
77
|
+
constructor(
|
|
78
|
+
chainInterface: EVMChainInterface<any>,
|
|
79
|
+
bitcoinRpc: BitcoinRpc<B>,
|
|
80
|
+
bitcoinNetwork: BitcoinNetwork,
|
|
81
|
+
contractAddress: string,
|
|
82
|
+
contractDeploymentHeight?: number
|
|
83
|
+
) {
|
|
84
|
+
super(chainInterface, contractAddress, BtcRelayAbi, contractDeploymentHeight);
|
|
85
|
+
this.bitcoinRpc = bitcoinRpc;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Computes subsequent commited headers as they will appear on the blockchain when transactions
|
|
90
|
+
* are submitted & confirmed
|
|
91
|
+
*
|
|
92
|
+
* @param initialStoredHeader
|
|
93
|
+
* @param syncedHeaders
|
|
94
|
+
* @private
|
|
95
|
+
*/
|
|
96
|
+
private computeCommitedHeaders(initialStoredHeader: EVMBtcStoredHeader, syncedHeaders: EVMBtcHeader[]) {
|
|
97
|
+
const computedCommitedHeaders = [initialStoredHeader];
|
|
98
|
+
for(let blockHeader of syncedHeaders) {
|
|
99
|
+
computedCommitedHeaders.push(computedCommitedHeaders[computedCommitedHeaders.length-1].computeNext(blockHeader));
|
|
100
|
+
}
|
|
101
|
+
return computedCommitedHeaders;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* A common logic for submitting blockheaders in a transaction
|
|
106
|
+
*
|
|
107
|
+
* @param signer
|
|
108
|
+
* @param headers headers to sync to the btc relay
|
|
109
|
+
* @param storedHeader current latest stored block header for a given fork
|
|
110
|
+
* @param tipWork work of the current tip in a given fork
|
|
111
|
+
* @param forkId forkId to submit to, forkId=0 means main chain, forkId=-1 means short fork
|
|
112
|
+
* @param feeRate feeRate for the transaction
|
|
113
|
+
* @param totalForkHeaders Total number of headers in a fork
|
|
114
|
+
* @private
|
|
115
|
+
*/
|
|
116
|
+
private async _saveHeaders(
|
|
117
|
+
signer: string,
|
|
118
|
+
headers: BtcBlock[],
|
|
119
|
+
storedHeader: EVMBtcStoredHeader,
|
|
120
|
+
tipWork: Buffer,
|
|
121
|
+
forkId: number,
|
|
122
|
+
feeRate: string,
|
|
123
|
+
totalForkHeaders: number
|
|
124
|
+
) {
|
|
125
|
+
const blockHeaderObj = headers.map(serializeBlockHeader);
|
|
126
|
+
let tx: EVMTx;
|
|
127
|
+
switch(forkId) {
|
|
128
|
+
case -1:
|
|
129
|
+
tx = await this.SaveShortForkHeaders(signer, blockHeaderObj, storedHeader, feeRate);
|
|
130
|
+
break;
|
|
131
|
+
case 0:
|
|
132
|
+
tx = await this.SaveMainHeaders(signer, blockHeaderObj, storedHeader, feeRate);
|
|
133
|
+
break;
|
|
134
|
+
default:
|
|
135
|
+
tx = await this.SaveLongForkHeaders(signer, forkId, blockHeaderObj, storedHeader, feeRate, totalForkHeaders);
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const computedCommitedHeaders = this.computeCommitedHeaders(storedHeader, blockHeaderObj);
|
|
140
|
+
const lastStoredHeader = computedCommitedHeaders[computedCommitedHeaders.length-1];
|
|
141
|
+
if(forkId!==0 && StatePredictorUtils.gtBuffer(lastStoredHeader.getBlockHash(), tipWork)) {
|
|
142
|
+
//Fork's work is higher than main chain's work, this fork will become a main chain
|
|
143
|
+
forkId = 0;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
forkId: forkId,
|
|
148
|
+
lastStoredHeader,
|
|
149
|
+
tx,
|
|
150
|
+
computedCommitedHeaders
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private async findStoredBlockheaderInTraces(txTrace: EVMTxTrace, commitHash: string): Promise<EVMBtcStoredHeader> {
|
|
155
|
+
if(txTrace.to.toLowerCase() === (await this.contract.getAddress()).toLowerCase()) {
|
|
156
|
+
let dataBuffer: Buffer;
|
|
157
|
+
if(txTrace.type==="CREATE") {
|
|
158
|
+
dataBuffer = Buffer.from(txTrace.input.substring(txTrace.input.length-384, txTrace.input.length-64), "hex");
|
|
159
|
+
} else {
|
|
160
|
+
const result = this.parseCalldata(txTrace.input);
|
|
161
|
+
if(result!=null) {
|
|
162
|
+
if(result.name==="submitMainBlockheaders" || result.name==="submitShortForkBlockheaders") {
|
|
163
|
+
const functionCall: TypedFunctionCall<
|
|
164
|
+
typeof this.contract.submitMainBlockheaders |
|
|
165
|
+
typeof this.contract.submitShortForkBlockheaders
|
|
166
|
+
> = result;
|
|
167
|
+
dataBuffer = Buffer.from(hexlify(functionCall.args[0]).substring(2), "hex");
|
|
168
|
+
} else if(result.name==="submitForkBlockheaders") {
|
|
169
|
+
const functionCall: TypedFunctionCall<typeof this.contract.submitForkBlockheaders> = result;
|
|
170
|
+
dataBuffer = Buffer.from(hexlify(functionCall.args[1]).substring(2), "hex");
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if(dataBuffer!=null) {
|
|
175
|
+
let storedHeader = EVMBtcStoredHeader.deserialize(dataBuffer.subarray(0, 160));
|
|
176
|
+
if(storedHeader.getCommitHash()===commitHash) return storedHeader;
|
|
177
|
+
for(let i = 160; i < dataBuffer.length; i+=48) {
|
|
178
|
+
const blockHeader = EVMBtcHeader.deserialize(dataBuffer.subarray(i, i + 48));
|
|
179
|
+
storedHeader = storedHeader.computeNext(blockHeader);
|
|
180
|
+
if(storedHeader.getCommitHash()===commitHash) return storedHeader;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if(txTrace.calls!=null) {
|
|
186
|
+
for(let call of txTrace.calls) {
|
|
187
|
+
const result = await this.findStoredBlockheaderInTraces(call, commitHash);
|
|
188
|
+
if(result!=null) return result;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private commitHashCache: PromiseLruCache<string, [EVMBtcStoredHeader, string]> = new PromiseLruCache<string, [EVMBtcStoredHeader, string]>(1000);
|
|
196
|
+
private blockHashCache: PromiseLruCache<string, [EVMBtcStoredHeader, string]> = new PromiseLruCache<string, [EVMBtcStoredHeader, string]>(1000);
|
|
197
|
+
|
|
198
|
+
private getBlock(commitHash?: string, blockHash?: Buffer): Promise<[EVMBtcStoredHeader, string] | null> {
|
|
199
|
+
const blockHashString = blockHash==null ? null : "0x"+Buffer.from([...blockHash]).reverse().toString("hex");
|
|
200
|
+
|
|
201
|
+
const generator = () => this.Events.findInContractEvents<[EVMBtcStoredHeader, string], "StoreHeader" | "StoreForkHeader">(
|
|
202
|
+
["StoreHeader", "StoreForkHeader"],
|
|
203
|
+
[
|
|
204
|
+
commitHash,
|
|
205
|
+
blockHashString
|
|
206
|
+
],
|
|
207
|
+
async (event) => {
|
|
208
|
+
const txTrace = await this.Chain.Transactions.traceTransaction(event.transactionHash);
|
|
209
|
+
const storedBlockheader = await this.findStoredBlockheaderInTraces(txTrace, event.args.commitHash);
|
|
210
|
+
if(storedBlockheader==null) return null;
|
|
211
|
+
|
|
212
|
+
this.commitHashCache.set(event.args.commitHash, Promise.resolve([storedBlockheader, event.args.commitHash]));
|
|
213
|
+
this.blockHashCache.set(event.args.blockHash, Promise.resolve([storedBlockheader, event.args.commitHash]));
|
|
214
|
+
return [storedBlockheader, event.args.commitHash];
|
|
215
|
+
}
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
if(commitHash!=null) return this.commitHashCache.getOrComputeAsync(commitHash, generator);
|
|
219
|
+
if(blockHashString!=null) return this.blockHashCache.getOrComputeAsync(blockHashString, generator);
|
|
220
|
+
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private async getBlockHeight(): Promise<number> {
|
|
225
|
+
return Number(await this.contract.getBlockheight());
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Returns data about current main chain tip stored in the btc relay
|
|
230
|
+
*/
|
|
231
|
+
public async getTipData(): Promise<{ commitHash: string; blockhash: string, chainWork: Buffer, blockheight: number }> {
|
|
232
|
+
const commitHash = await this.contract.getTipCommitHash();
|
|
233
|
+
if(commitHash==null || BigInt(commitHash)===BigInt(0)) return null;
|
|
234
|
+
|
|
235
|
+
const result = await this.getBlock(commitHash);
|
|
236
|
+
if(result==null) return null;
|
|
237
|
+
|
|
238
|
+
const storedBlockHeader = result[0];
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
blockheight: storedBlockHeader.getBlockheight(),
|
|
242
|
+
commitHash: commitHash,
|
|
243
|
+
blockhash: storedBlockHeader.getBlockHash().toString("hex"),
|
|
244
|
+
chainWork: storedBlockHeader.getChainWork()
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Retrieves blockheader with a specific blockhash, returns null if requiredBlockheight is provided and
|
|
250
|
+
* btc relay contract is not synced up to the desired blockheight
|
|
251
|
+
*
|
|
252
|
+
* @param blockData
|
|
253
|
+
* @param requiredBlockheight
|
|
254
|
+
*/
|
|
255
|
+
public async retrieveLogAndBlockheight(blockData: {blockhash: string}, requiredBlockheight?: number): Promise<{
|
|
256
|
+
header: EVMBtcStoredHeader,
|
|
257
|
+
height: number
|
|
258
|
+
} | null> {
|
|
259
|
+
//TODO: we can fetch the blockheight and events in parallel
|
|
260
|
+
const blockHeight = await this.getBlockHeight();
|
|
261
|
+
if(requiredBlockheight!=null && blockHeight < requiredBlockheight) {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
const result = await this.getBlock(null, Buffer.from(blockData.blockhash, "hex"));
|
|
265
|
+
if(result==null) return null;
|
|
266
|
+
|
|
267
|
+
const [storedBlockHeader, commitHash] = result;
|
|
268
|
+
|
|
269
|
+
//Check if block is part of the main chain
|
|
270
|
+
const chainCommitment = await this.contract.getCommitHash(storedBlockHeader.blockHeight);
|
|
271
|
+
if(chainCommitment!==commitHash) return null;
|
|
272
|
+
|
|
273
|
+
logger.debug("retrieveLogAndBlockheight(): block found," +
|
|
274
|
+
" commit hash: "+commitHash+" blockhash: "+blockData.blockhash+" current btc relay height: "+blockHeight);
|
|
275
|
+
|
|
276
|
+
return {header: storedBlockHeader, height: blockHeight};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Retrieves blockheader data by blockheader's commit hash,
|
|
281
|
+
*
|
|
282
|
+
* @param commitmentHashStr
|
|
283
|
+
* @param blockData
|
|
284
|
+
*/
|
|
285
|
+
public async retrieveLogByCommitHash(commitmentHashStr: string, blockData: {blockhash: string}): Promise<EVMBtcStoredHeader> {
|
|
286
|
+
const result = await this.getBlock(commitmentHashStr, Buffer.from(blockData.blockhash, "hex"));
|
|
287
|
+
if(result==null) return null;
|
|
288
|
+
|
|
289
|
+
const [storedBlockHeader, commitHash] = result;
|
|
290
|
+
|
|
291
|
+
//Check if block is part of the main chain
|
|
292
|
+
const chainCommitment = await this.contract.getCommitHash(storedBlockHeader.blockHeight);
|
|
293
|
+
if(chainCommitment!==commitHash) return null;
|
|
294
|
+
|
|
295
|
+
logger.debug("retrieveLogByCommitHash(): block found," +
|
|
296
|
+
" commit hash: "+commitmentHashStr+" blockhash: "+blockData.blockhash+" height: "+storedBlockHeader.blockHeight);
|
|
297
|
+
|
|
298
|
+
return storedBlockHeader;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Retrieves latest known stored blockheader & blockheader from bitcoin RPC that is in the main chain
|
|
303
|
+
*/
|
|
304
|
+
public async retrieveLatestKnownBlockLog(): Promise<{
|
|
305
|
+
resultStoredHeader: EVMBtcStoredHeader,
|
|
306
|
+
resultBitcoinHeader: B
|
|
307
|
+
}> {
|
|
308
|
+
const data = await this.Events.findInContractEvents(
|
|
309
|
+
["StoreHeader", "StoreForkHeader"],
|
|
310
|
+
null,
|
|
311
|
+
async (event) => {
|
|
312
|
+
const blockHashHex = Buffer.from(event.args.blockHash.substring(2), "hex").reverse().toString("hex");
|
|
313
|
+
const commitHash = event.args.commitHash;
|
|
314
|
+
|
|
315
|
+
const isInBtcMainChain = await this.bitcoinRpc.isInMainChain(blockHashHex).catch(() => false);
|
|
316
|
+
if(!isInBtcMainChain) return null;
|
|
317
|
+
|
|
318
|
+
const blockHeader = await this.bitcoinRpc.getBlockHeader(blockHashHex);
|
|
319
|
+
|
|
320
|
+
if(commitHash !== await this.contract.getCommitHash(blockHeader.getHeight())) return null;
|
|
321
|
+
|
|
322
|
+
const txTrace = await this.Chain.Transactions.traceTransaction(event.transactionHash);
|
|
323
|
+
const storedHeader = await this.findStoredBlockheaderInTraces(txTrace, commitHash);
|
|
324
|
+
if(storedHeader==null) return null;
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
resultStoredHeader: storedHeader,
|
|
328
|
+
resultBitcoinHeader: blockHeader,
|
|
329
|
+
commitHash: commitHash
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
if(data!=null) logger.debug("retrieveLatestKnownBlockLog(): block found," +
|
|
335
|
+
" commit hash: "+data.commitHash+" blockhash: "+data.resultBitcoinHeader.getHash()+
|
|
336
|
+
" height: "+data.resultStoredHeader.getBlockheight());
|
|
337
|
+
|
|
338
|
+
return data;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Saves blockheaders as a bitcoin main chain to the btc relay
|
|
343
|
+
*
|
|
344
|
+
* @param signer
|
|
345
|
+
* @param mainHeaders
|
|
346
|
+
* @param storedHeader
|
|
347
|
+
* @param feeRate
|
|
348
|
+
*/
|
|
349
|
+
public async saveMainHeaders(signer: string, mainHeaders: BtcBlock[], storedHeader: EVMBtcStoredHeader, feeRate?: string) {
|
|
350
|
+
feeRate ??= await this.Chain.Fees.getFeeRate();
|
|
351
|
+
logger.debug("saveMainHeaders(): submitting main blockheaders, count: "+mainHeaders.length);
|
|
352
|
+
return this._saveHeaders(signer, mainHeaders, storedHeader, null, 0, feeRate, 0);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Creates a new long fork and submits the headers to it
|
|
357
|
+
*
|
|
358
|
+
* @param signer
|
|
359
|
+
* @param forkHeaders
|
|
360
|
+
* @param storedHeader
|
|
361
|
+
* @param tipWork
|
|
362
|
+
* @param feeRate
|
|
363
|
+
*/
|
|
364
|
+
public async saveNewForkHeaders(signer: string, forkHeaders: BtcBlock[], storedHeader: EVMBtcStoredHeader, tipWork: Buffer, feeRate?: string) {
|
|
365
|
+
let forkId: number = Math.floor(Math.random() * 0xFFFFFFFFFFFF);
|
|
366
|
+
feeRate ??= await this.Chain.Fees.getFeeRate();
|
|
367
|
+
|
|
368
|
+
logger.debug("saveNewForkHeaders(): submitting new fork & blockheaders," +
|
|
369
|
+
" count: "+forkHeaders.length+" forkId: 0x"+forkId.toString(16));
|
|
370
|
+
|
|
371
|
+
return await this._saveHeaders(signer, forkHeaders, storedHeader, tipWork, forkId, feeRate, forkHeaders.length);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Continues submitting blockheaders to a given fork
|
|
376
|
+
*
|
|
377
|
+
* @param signer
|
|
378
|
+
* @param forkHeaders
|
|
379
|
+
* @param storedHeader
|
|
380
|
+
* @param forkId
|
|
381
|
+
* @param tipWork
|
|
382
|
+
* @param feeRate
|
|
383
|
+
*/
|
|
384
|
+
public async saveForkHeaders(signer: string, forkHeaders: BtcBlock[], storedHeader: EVMBtcStoredHeader, forkId: number, tipWork: Buffer, feeRate?: string) {
|
|
385
|
+
feeRate ??= await this.Chain.Fees.getFeeRate();
|
|
386
|
+
|
|
387
|
+
logger.debug("saveForkHeaders(): submitting blockheaders to existing fork," +
|
|
388
|
+
" count: "+forkHeaders.length+" forkId: 0x"+forkId.toString(16));
|
|
389
|
+
|
|
390
|
+
return this._saveHeaders(signer, forkHeaders, storedHeader, tipWork, forkId, feeRate, 100);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Submits short fork with given blockheaders
|
|
395
|
+
*
|
|
396
|
+
* @param signer
|
|
397
|
+
* @param forkHeaders
|
|
398
|
+
* @param storedHeader
|
|
399
|
+
* @param tipWork
|
|
400
|
+
* @param feeRate
|
|
401
|
+
*/
|
|
402
|
+
public async saveShortForkHeaders(signer: string, forkHeaders: BtcBlock[], storedHeader: EVMBtcStoredHeader, tipWork: Buffer, feeRate?: string) {
|
|
403
|
+
feeRate ??= await this.Chain.Fees.getFeeRate();
|
|
404
|
+
|
|
405
|
+
logger.debug("saveShortForkHeaders(): submitting short fork blockheaders," +
|
|
406
|
+
" count: "+forkHeaders.length);
|
|
407
|
+
|
|
408
|
+
return this._saveHeaders(signer, forkHeaders, storedHeader, tipWork, -1, feeRate, 0);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Estimate required synchronization fee (worst case) to synchronize btc relay to the required blockheight
|
|
413
|
+
*
|
|
414
|
+
* @param requiredBlockheight
|
|
415
|
+
* @param feeRate
|
|
416
|
+
*/
|
|
417
|
+
public async estimateSynchronizeFee(requiredBlockheight: number, feeRate?: string): Promise<bigint> {
|
|
418
|
+
feeRate ??= await this.Chain.Fees.getFeeRate();
|
|
419
|
+
|
|
420
|
+
const tipData = await this.getTipData();
|
|
421
|
+
const currBlockheight = tipData.blockheight;
|
|
422
|
+
|
|
423
|
+
const blockheightDelta = requiredBlockheight-currBlockheight;
|
|
424
|
+
|
|
425
|
+
if(blockheightDelta<=0) return 0n;
|
|
426
|
+
|
|
427
|
+
const synchronizationFee = (BigInt(blockheightDelta) * await this.getFeePerBlock(feeRate))
|
|
428
|
+
+ EVMFees.getGasFee(EVMBtcRelay.GasCosts.GAS_BASE_MAIN * Math.ceil(blockheightDelta / this.maxHeadersPerTx), feeRate);
|
|
429
|
+
logger.debug("estimateSynchronizeFee(): required blockheight: "+requiredBlockheight+
|
|
430
|
+
" blockheight delta: "+blockheightDelta+" fee: "+synchronizationFee.toString(10));
|
|
431
|
+
|
|
432
|
+
return synchronizationFee;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Returns fee required (in native token) to synchronize a single block to btc relay
|
|
437
|
+
*
|
|
438
|
+
* @param feeRate
|
|
439
|
+
*/
|
|
440
|
+
public async getFeePerBlock(feeRate?: string): Promise<bigint> {
|
|
441
|
+
feeRate ??= await this.Chain.Fees.getFeeRate();
|
|
442
|
+
return EVMFees.getGasFee(EVMBtcRelay.GasCosts.GAS_PER_BLOCKHEADER, feeRate);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Gets fee rate required for submitting blockheaders to the main chain
|
|
447
|
+
*/
|
|
448
|
+
public getMainFeeRate(signer: string | null): Promise<string> {
|
|
449
|
+
return this.Chain.Fees.getFeeRate();
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Gets fee rate required for submitting blockheaders to the specific fork
|
|
454
|
+
*/
|
|
455
|
+
public getForkFeeRate(signer: string, forkId: number): Promise<string> {
|
|
456
|
+
return this.Chain.Fees.getFeeRate();
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
saveInitialHeader(signer: string, header: B, epochStart: number, pastBlocksTimestamps: number[], feeRate?: string): Promise<EVMTx> {
|
|
460
|
+
throw new Error("Not supported, EVM contract is initialized with constructor!");
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Gets committed header, identified by blockhash & blockheight, determines required BTC relay blockheight based on
|
|
465
|
+
* requiredConfirmations
|
|
466
|
+
* If synchronizer is passed & blockhash is not found, it produces transactions to sync up the btc relay to the
|
|
467
|
+
* current chain tip & adds them to the txs array
|
|
468
|
+
*
|
|
469
|
+
* @param signer
|
|
470
|
+
* @param btcRelay
|
|
471
|
+
* @param btcTxs
|
|
472
|
+
* @param txs solana transaction array, in case we need to synchronize the btc relay ourselves the synchronization
|
|
473
|
+
* txns are added here
|
|
474
|
+
* @param synchronizer optional synchronizer to use to synchronize the btc relay in case it is not yet synchronized
|
|
475
|
+
* to the required blockheight
|
|
476
|
+
* @param feeRate Fee rate to use for synchronization transactions
|
|
477
|
+
* @private
|
|
478
|
+
*/
|
|
479
|
+
static async getCommitedHeadersAndSynchronize(
|
|
480
|
+
signer: string,
|
|
481
|
+
btcRelay: EVMBtcRelay<any>,
|
|
482
|
+
btcTxs: {blockheight: number, requiredConfirmations: number, blockhash: string}[],
|
|
483
|
+
txs: EVMTx[],
|
|
484
|
+
synchronizer?: RelaySynchronizer<EVMBtcStoredHeader, EVMTx, any>,
|
|
485
|
+
feeRate?: string
|
|
486
|
+
): Promise<{
|
|
487
|
+
[blockhash: string]: EVMBtcStoredHeader
|
|
488
|
+
}> {
|
|
489
|
+
const leavesTxs: {blockheight: number, requiredConfirmations: number, blockhash: string}[] = [];
|
|
490
|
+
|
|
491
|
+
const blockheaders: {
|
|
492
|
+
[blockhash: string]: EVMBtcStoredHeader
|
|
493
|
+
} = {};
|
|
494
|
+
|
|
495
|
+
for(let btcTx of btcTxs) {
|
|
496
|
+
const requiredBlockheight = btcTx.blockheight+btcTx.requiredConfirmations-1;
|
|
497
|
+
|
|
498
|
+
const result = await tryWithRetries(
|
|
499
|
+
() => btcRelay.retrieveLogAndBlockheight({
|
|
500
|
+
blockhash: btcTx.blockhash
|
|
501
|
+
}, requiredBlockheight)
|
|
502
|
+
);
|
|
503
|
+
|
|
504
|
+
if(result!=null) {
|
|
505
|
+
blockheaders[result.header.getBlockHash().toString("hex")] = result.header;
|
|
506
|
+
} else {
|
|
507
|
+
leavesTxs.push(btcTx);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if(leavesTxs.length===0) return blockheaders;
|
|
512
|
+
|
|
513
|
+
//Need to synchronize
|
|
514
|
+
if(synchronizer==null) return null;
|
|
515
|
+
|
|
516
|
+
//TODO: We don't have to synchronize to tip, only to our required blockheight
|
|
517
|
+
const resp = await synchronizer.syncToLatestTxs(signer.toString(), feeRate);
|
|
518
|
+
logger.debug("getCommitedHeaderAndSynchronize(): BTC Relay not synchronized to required blockheight, "+
|
|
519
|
+
"synchronizing ourselves in "+resp.txs.length+" txs");
|
|
520
|
+
logger.debug("getCommitedHeaderAndSynchronize(): BTC Relay computed header map: ",resp.computedHeaderMap);
|
|
521
|
+
txs.push(...resp.txs);
|
|
522
|
+
|
|
523
|
+
for(let key in resp.computedHeaderMap) {
|
|
524
|
+
const header = resp.computedHeaderMap[key];
|
|
525
|
+
blockheaders[header.getBlockHash().toString("hex")] = header;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
//Check that blockhashes of all the rest txs are included
|
|
529
|
+
for(let btcTx of leavesTxs) {
|
|
530
|
+
if(blockheaders[btcTx.blockhash]==null) return null;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
//Retrieve computed headers
|
|
534
|
+
return blockheaders;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
}
|