Skip to content

Commit 4e85d3e

Browse files
authored
Merge pull request #697 from bvotteler/refactor-move-bitcoin-core-client-to-tests
Refactor: move BitcoinCoreClient to tests
2 parents e3194d2 + d13b832 commit 4e85d3e

16 files changed

Lines changed: 294 additions & 305 deletions

File tree

package.json

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@interlay/interbtc-api",
3-
"version": "2.5.1",
3+
"version": "2.5.2",
44
"description": "JavaScript library to interact with interBTC",
55
"main": "build/src/index.js",
66
"type": "module",
@@ -58,12 +58,10 @@
5858
"@interlay/monetary-js": "0.7.3",
5959
"@polkadot/api": "10.9.1",
6060
"big.js": "6.1.1",
61-
"bitcoin-core": "^3.0.0",
6261
"bitcoinjs-lib": "^5.2.0",
6362
"bn.js": "4.12.0",
6463
"cross-fetch": "^4.0.0",
65-
"isomorphic-fetch": "^3.0.0",
66-
"regtest-client": "^0.2.0"
64+
"yargs": "^17.5.1"
6765
},
6866
"devDependencies": {
6967
"@polkadot/typegen": "10.9.1",
@@ -74,22 +72,21 @@
7472
"@types/yargs": "^17.0.10",
7573
"@typescript-eslint/eslint-plugin": "^5.59.7",
7674
"@typescript-eslint/parser": "^5.59.7",
75+
"bitcoin-core": "^3.0.0",
7776
"cli-table3": "0.6.3",
7877
"eslint": "^8.41.0",
7978
"eslint-config-prettier": "^8.8.0",
8079
"eslint-plugin-unused-imports": "^3.0.0",
8180
"husky": "^8.0.3",
8281
"jest": "^29.6.2",
8382
"npm-run-all": "^4.1.5",
84-
"nyc": "^15.1.0",
8583
"prettier": "^3.0.1",
8684
"shelljs": "0.8.5",
8785
"ts-jest": "^29.1.1",
8886
"ts-node": "10.9.1",
8987
"typedoc": "^0.25.0",
9088
"typedoc-plugin-markdown": "^3.16.0",
91-
"typescript": "5.2.2",
92-
"yargs": "^17.5.1"
89+
"typescript": "5.2.2"
9390
},
9491
"resolutions": {
9592
"bn.js": "4.12.0"

src/parachain/redeem.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414

1515
import { NO_LIQUIDATION_VAULT_FOUND_REJECTION, VaultsAPI } from "./vaults";
1616
import {
17+
allocateAmountsToVaults,
1718
decodeBtcAddress,
1819
decodeFixedPointType,
1920
getTxProof,
@@ -23,7 +24,6 @@ import {
2324
newMonetaryAmount,
2425
newVaultCurrencyPair,
2526
} from "../utils";
26-
import { allocateAmountsToVaults } from "../utils/issueRedeem";
2727
import { ElectrsAPI } from "../external";
2828
import { TransactionAPI } from "./transaction";
2929
import { OracleAPI } from "./oracle";

src/utils/bitcoin.ts

Lines changed: 2 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,8 @@
11
import { Block as BitcoinBlock, payments, Network, TxInput, TxOutput } from "bitcoinjs-lib";
22
import { BufferReader } from "bitcoinjs-lib/src/bufferutils";
33

4-
import { H160 } from "@polkadot/types/interfaces";
4+
import { bufferToHexString } from "../../src/utils";
55
import { BitcoinAddress } from "@polkadot/types/lookup";
6-
import { TypeRegistry } from "@polkadot/types";
7-
8-
import { BTCRelayAPI } from "../parachain";
9-
import {
10-
sleep,
11-
addHexPrefix,
12-
reverseEndiannessHex,
13-
SLEEP_TIME_MS,
14-
BitcoinCoreClient,
15-
bufferToHexString,
16-
} from "../utils";
176
import {
187
HexString,
198
MerkleProof,
@@ -22,7 +11,7 @@ import {
2211
TransactionInputSource,
2312
TransactionLocktime,
2413
TransactionOutput,
25-
} from "../types";
14+
} from "../../src/types";
2615

2716
import { Transaction as BitcoinTransaction } from "bitcoinjs-lib";
2817
import { ElectrsAPI } from "../external";
@@ -79,16 +68,6 @@ function decode<P extends Payable, O>(p: P, f: (payment: P, options?: O) => P):
7968
}
8069
}
8170

82-
export function btcAddressFromParams(
83-
registry: TypeRegistry,
84-
params: { p2pkh: H160 | string } | { p2sh: H160 | string } | { p2wpkhv0: H160 | string }
85-
): BitcoinAddress {
86-
registry.register;
87-
return registry.createType<BitcoinAddress>("BitcoinAddress", {
88-
...params,
89-
});
90-
}
91-
9271
export function decodeBtcAddress(
9372
address: string,
9473
network: Network
@@ -205,26 +184,6 @@ export async function getTxProof(
205184
};
206185
}
207186

208-
export async function waitForBlockRelaying(
209-
btcRelayAPI: BTCRelayAPI,
210-
blockHash: string,
211-
sleepMs = SLEEP_TIME_MS
212-
): Promise<void> {
213-
while (!(await btcRelayAPI.isBlockInRelay(blockHash))) {
214-
console.log(`Blockhash ${blockHash} not yet relayed...`);
215-
await sleep(sleepMs);
216-
}
217-
}
218-
219-
export async function waitForBlockFinalization(
220-
bitcoinCoreClient: BitcoinCoreClient,
221-
btcRelayAPI: BTCRelayAPI
222-
): Promise<void> {
223-
const bestBlockHash = addHexPrefix(reverseEndiannessHex(await bitcoinCoreClient.getBestBlockHash()));
224-
// wait for block to be relayed
225-
await waitForBlockRelaying(btcRelayAPI, bestBlockHash);
226-
}
227-
228187
export class BitcoinMerkleProof {
229188
blockHeader: BitcoinBlock;
230189
transactionsCount: number;

src/utils/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ export * from "./bitcoin";
22
export * from "./currency";
33
export * from "./encoding";
44
export * from "./constants";
5-
export * from "./bitcoin-core-client";
65
export * from "./issueRedeem";
76
export * from "./storage";
87
export * from "./loans";

src/utils/issueRedeem.ts

Lines changed: 3 additions & 238 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,8 @@
1-
import { Hash, EventRecord } from "@polkadot/types/interfaces";
2-
import { ApiTypes, AugmentedEvent } from "@polkadot/api/types";
3-
import type { AnyTuple } from "@polkadot/types/types";
4-
import { ApiPromise } from "@polkadot/api";
5-
import { KeyringPair } from "@polkadot/keyring/types";
6-
import { Bitcoin, BitcoinAmount, InterBtcAmount, MonetaryAmount } from "@interlay/monetary-js";
1+
import { MonetaryAmount } from "@interlay/monetary-js";
72
import { InterbtcPrimitivesVaultId } from "@polkadot/types/lookup";
8-
import { ISubmittableResult } from "@polkadot/types/types";
93

10-
import { newAccountId } from "../utils";
11-
import { BitcoinCoreClient } from "./bitcoin-core-client";
12-
import { stripHexPrefix } from "../utils/encoding";
13-
import { Issue, IssueStatus, Redeem, RedeemStatus, WrappedCurrency } from "../types";
14-
import { waitForBlockFinalization } from "./bitcoin";
15-
import { atomicToBaseAmount, currencyIdToMonetaryCurrency, newMonetaryAmount } from "./currency";
16-
import { InterBtcApi } from "../interbtc-api";
17-
import { sleep, SLEEP_TIME_MS } from "../utils";
18-
19-
export interface IssueResult {
20-
request: Issue;
21-
initialWrappedTokenBalance: MonetaryAmount<WrappedCurrency>;
22-
finalWrappedTokenBalance: MonetaryAmount<WrappedCurrency>;
23-
}
24-
25-
export enum ExecuteRedeem {
26-
False,
27-
Manually,
28-
Auto,
29-
}
30-
31-
/**
32-
* @param events The EventRecord array returned after sending a transaction
33-
* @param methodToCheck The name of the event method whose existence to check
34-
* @returns The id associated with the transaction. If the EventRecord array does not
35-
* contain required events, the function throws an error.
36-
*/
37-
export function getRequestIdsFromEvents(
38-
events: EventRecord[],
39-
eventToFind: AugmentedEvent<ApiTypes, AnyTuple>,
40-
api: ApiPromise
41-
): Hash[] {
42-
const ids = new Array<Hash>();
43-
for (const { event } of events) {
44-
if (eventToFind.is(event)) {
45-
// the redeem id has type H256 and is the first item of the event data array
46-
const id = api.createType("Hash", event.data[0]);
47-
ids.push(id);
48-
}
49-
}
50-
51-
if (ids.length > 0) return ids;
52-
throw new Error("Transaction failed");
53-
}
54-
55-
export const getIssueRequestsFromExtrinsicResult = async (
56-
interBtcApi: InterBtcApi,
57-
result: ISubmittableResult
58-
): Promise<Array<Issue>> => {
59-
const ids = getRequestIdsFromEvents(result.events, interBtcApi.api.events.issue.RequestIssue, interBtcApi.api);
60-
const issueRequests = await interBtcApi.issue.getRequestsByIds(ids);
61-
return issueRequests;
62-
};
63-
64-
export const getRedeemRequestsFromExtrinsicResult = async (
65-
interBtcApi: InterBtcApi,
66-
result: ISubmittableResult
67-
): Promise<Array<Redeem>> => {
68-
const ids = getRequestIdsFromEvents(result.events, interBtcApi.api.events.redeem.RequestRedeem, interBtcApi.api);
69-
const redeemRequests = await interBtcApi.redeem.getRequestsByIds(ids);
70-
return redeemRequests;
71-
};
4+
import { WrappedCurrency } from "../types";
5+
import { newMonetaryAmount } from "./currency";
726

737
/**
748
* Given a list of vaults with availabilities (e.g. collateral for issue, tokens
@@ -117,172 +51,3 @@ export function allocateAmountsToVaults(
11751
}
11852
return allocations;
11953
}
120-
121-
export async function issueSingle(
122-
interBtcApi: InterBtcApi,
123-
bitcoinCoreClient: BitcoinCoreClient,
124-
issuingAccount: KeyringPair,
125-
amount: MonetaryAmount<WrappedCurrency>,
126-
vaultId?: InterbtcPrimitivesVaultId,
127-
autoExecute = true,
128-
triggerRefund = false,
129-
atomic = true
130-
): Promise<IssueResult> {
131-
const prevAccount = interBtcApi.account;
132-
interBtcApi.setAccount(issuingAccount);
133-
try {
134-
const requesterAccountId = newAccountId(interBtcApi.api, issuingAccount.address);
135-
const initialWrappedTokenBalance = (await interBtcApi.tokens.balance(amount.currency, requesterAccountId)).free;
136-
const blocksToMine = 3;
137-
138-
const collateralCurrency = vaultId
139-
? await currencyIdToMonetaryCurrency(interBtcApi.api, vaultId.currencies.collateral)
140-
: undefined;
141-
const { extrinsic, event } = await interBtcApi.issue.request(
142-
amount,
143-
vaultId?.accountId,
144-
collateralCurrency,
145-
atomic
146-
);
147-
148-
const result = await interBtcApi.transaction.sendLogged(extrinsic, event);
149-
const issueRequests = await getIssueRequestsFromExtrinsicResult(interBtcApi, result);
150-
if (issueRequests.length !== 1) {
151-
throw new Error("More than one issue request created");
152-
}
153-
const issueRequest = issueRequests[0];
154-
155-
let amountAsBtc = issueRequest.wrappedAmount.add(issueRequest.bridgeFee);
156-
if (triggerRefund) {
157-
// Send 1 more Btc than needed
158-
amountAsBtc = amountAsBtc.add(new BitcoinAmount(1));
159-
} else if (autoExecute === false) {
160-
// Send 1 less Satoshi than requested
161-
// to trigger the user failsafe and disable auto-execution.
162-
const oneSatoshiInBtc = atomicToBaseAmount(1, Bitcoin);
163-
const oneSatoshi = new BitcoinAmount(oneSatoshiInBtc);
164-
amountAsBtc = amountAsBtc.sub(oneSatoshi);
165-
}
166-
167-
// send btc tx
168-
const vaultBtcAddress = issueRequest.vaultWrappedAddress;
169-
if (vaultBtcAddress === undefined) {
170-
throw new Error("Undefined vault address returned from RequestIssue");
171-
}
172-
173-
const txData = await bitcoinCoreClient.sendBtcTxAndMine(vaultBtcAddress, amountAsBtc, blocksToMine);
174-
175-
if (autoExecute === false) {
176-
console.log("Manually executing, waiting for relay to catchup");
177-
await waitForBlockFinalization(bitcoinCoreClient, interBtcApi.btcRelay);
178-
console.log("Block successfully relayed");
179-
await interBtcApi.electrsAPI.waitForTxInclusion(txData.txid, SLEEP_TIME_MS * 10, SLEEP_TIME_MS);
180-
console.log("Transaction included in electrs");
181-
// execute issue, assuming the selected vault has the `--no-issue-execution` flag enabled
182-
const { extrinsic: executeExtrinsic, event: executeEvent } = await interBtcApi.issue.execute(
183-
issueRequest.id,
184-
txData.txid
185-
);
186-
await interBtcApi.transaction.sendLogged(executeExtrinsic, executeEvent);
187-
} else {
188-
console.log("Auto-executing, waiting for vault to submit proof");
189-
// wait for vault to execute issue
190-
while ((await interBtcApi.issue.getRequestById(issueRequest.id)).status !== IssueStatus.Completed) {
191-
await sleep(SLEEP_TIME_MS);
192-
}
193-
}
194-
195-
const finalWrappedTokenBalance = (await interBtcApi.tokens.balance(amount.currency, requesterAccountId)).free;
196-
return {
197-
request: issueRequest,
198-
initialWrappedTokenBalance,
199-
finalWrappedTokenBalance,
200-
};
201-
} catch (e) {
202-
// IssueCompleted errors occur when multiple vaults attempt to execute the same request
203-
return Promise.reject(new Error(`Issuing failed: ${e}`));
204-
} finally {
205-
if (prevAccount) {
206-
interBtcApi.setAccount(prevAccount);
207-
}
208-
}
209-
}
210-
211-
export async function redeem(
212-
interBtcApi: InterBtcApi,
213-
bitcoinCoreClient: BitcoinCoreClient,
214-
redeemingAccount: KeyringPair,
215-
amount: MonetaryAmount<WrappedCurrency>,
216-
vaultId?: InterbtcPrimitivesVaultId,
217-
autoExecute = ExecuteRedeem.Auto,
218-
atomic = true,
219-
timeout = 5 * 60 * 1000
220-
): Promise<Redeem> {
221-
const prevAccount = interBtcApi.account;
222-
interBtcApi.setAccount(redeemingAccount);
223-
const btcAddress = "bcrt1qujs29q4gkyn2uj6y570xl460p4y43ruayxu8ry";
224-
const { extrinsic, event } = await interBtcApi.redeem.request(amount, btcAddress, vaultId, atomic);
225-
const result = await interBtcApi.transaction.sendLogged(extrinsic, event);
226-
const [redeemRequest] = await getRedeemRequestsFromExtrinsicResult(interBtcApi, result);
227-
228-
switch (autoExecute) {
229-
case ExecuteRedeem.Manually: {
230-
const opreturnData = stripHexPrefix(redeemRequest.id.toString());
231-
const btcTxId = await interBtcApi.electrsAPI.waitForOpreturn(opreturnData, timeout, 5000).catch((_) => {
232-
throw new Error("Redeem request was not executed, timeout expired");
233-
});
234-
// Even if the tx was found, the block needs to be relayed to the parachain before `execute` can be called.
235-
await waitForBlockFinalization(bitcoinCoreClient, interBtcApi.btcRelay);
236-
237-
// manually execute issue
238-
await interBtcApi.redeem.execute(redeemRequest.id.toString(), btcTxId);
239-
break;
240-
}
241-
case ExecuteRedeem.Auto: {
242-
// wait for vault to execute issue
243-
while ((await interBtcApi.redeem.getRequestById(redeemRequest.id)).status !== RedeemStatus.Completed) {
244-
await sleep(SLEEP_TIME_MS);
245-
}
246-
break;
247-
}
248-
}
249-
if (prevAccount) {
250-
interBtcApi.setAccount(prevAccount);
251-
}
252-
return redeemRequest;
253-
}
254-
255-
export async function issueAndRedeem(
256-
InterBtcApi: InterBtcApi,
257-
bitcoinCoreClient: BitcoinCoreClient,
258-
account: KeyringPair,
259-
vaultId?: InterbtcPrimitivesVaultId,
260-
issueAmount: MonetaryAmount<WrappedCurrency> = new InterBtcAmount(0.1),
261-
redeemAmount: MonetaryAmount<WrappedCurrency> = new InterBtcAmount(0.009),
262-
autoExecuteIssue = true,
263-
autoExecuteRedeem = ExecuteRedeem.Auto,
264-
triggerRefund = false,
265-
atomic = true
266-
): Promise<[Issue, Redeem]> {
267-
const issueResult = await issueSingle(
268-
InterBtcApi,
269-
bitcoinCoreClient,
270-
account,
271-
issueAmount,
272-
vaultId,
273-
autoExecuteIssue,
274-
triggerRefund,
275-
atomic
276-
);
277-
278-
const redeemRequest = await redeem(
279-
InterBtcApi,
280-
bitcoinCoreClient,
281-
account,
282-
redeemAmount,
283-
issueResult.request.vaultId,
284-
autoExecuteRedeem,
285-
atomic
286-
);
287-
return [issueResult.request, redeemRequest];
288-
}

0 commit comments

Comments
 (0)