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