Skip to content
This repository has been archived by the owner on Jul 15, 2022. It is now read-only.

Feature Branch for Ripple (XRP) family #1764

Merged
merged 1 commit into from
May 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions .github/workflows/bot-xrp-mere-denis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
name: Bot 'XRP on Mère Denis'
on:
push:
branches:
- family/ripple
This conversation was marked as resolved.
Show resolved Hide resolved

jobs:
start-runner:
name: "start ec2 instance (Linux)"
if: ${{ always() }}
uses: ledgerhq/actions/.github/workflows/start-linux-runner.yml@main
secrets:
CI_BOT_TOKEN: ${{ secrets.CI_BOT_TOKEN }}

stop-runner:
name: "stop ec2 instance (Linux)"
needs: [start-runner, run-bot]
uses: ledgerhq/actions/.github/workflows/stop-linux-runner.yml@main
if: ${{ always() }}
with:
label: ${{ needs.start-runner.outputs.label }}
ec2-instance-id: ${{ needs.start-runner.outputs.ec2-instance-id }}
secrets:
CI_BOT_TOKEN: ${{ secrets.CI_BOT_TOKEN }}

run-bot:
needs: [start-runner]
runs-on: ${{ needs.start-runner.outputs.label }}
steps:
- name: prepare runner
run: |
sudo growpart /dev/nvme0n1 1
sudo resize2fs /dev/nvme0n1p1
- uses: actions/checkout@v2
- name: Retrieving coin apps
uses: actions/checkout@v2
with:
repository: LedgerHQ/coin-apps
token: ${{ secrets.PAT }}
path: coin-apps
- uses: actions/setup-node@master
with:
node-version: 14.x
- name: install yarn
run: npm i -g yarn
- name: pull docker image
run: docker pull ghcr.io/ledgerhq/speculos
- name: kill apt-get
run: sudo killall -w apt-get apt || echo OK
- name: Install linux deps
run: sudo apt-get install -y libusb-1.0-0-dev jq
- name: Install dependencies
run: |
yarn global add yalc
yarn --frozen-lockfile
yarn ci-setup-cli
- name: BOT
env:
SEED: ${{ secrets.SEED1 }}
VERBOSE_FILE: bot-tests.txt
GITHUB_SHA: ${GITHUB_SHA}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_RUN_ID: ${{ github.run_id }}
GITHUB_WORKFLOW: ${{ github.workflow }}
SLACK_API_TOKEN: ${{ secrets.SLACK_API_TOKEN }}
SLACK_CHANNEL: ci-xrp-ll
BOT_FILTER_FAMILY: ripple
EXPERIMENTAL_CURRENCIES_JS_BRIDGE: ripple
run: COINAPPS=$PWD/coin-apps yarn ci-test-bot
timeout-minutes: 120
- name: Run coverage
if: failure() || success()
run: CODECOV_TOKEN=${{ secrets.CODECOV_TOKEN }} npx codecov
- name: upload logs
if: failure() || success()
uses: actions/upload-artifact@v1
with:
name: bot-tests.txt
path: bot-tests.txt
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@
"ripemd160": "^2.0.2",
"ripple-binary-codec": "^1.3.0",
"ripple-bs58check": "^2.0.2",
"ripple-lib": "1.10.0",
"rlp": "^3.0.0",
"rxjs": "6",
"rxjs-compat": "^6.6.7",
Expand Down
255 changes: 95 additions & 160 deletions src/api/Ripple.ts
Original file line number Diff line number Diff line change
@@ -1,185 +1,120 @@
import { BigNumber } from "bignumber.js";
import {
parseCurrencyUnit,
getCryptoCurrencyById,
formatCurrencyUnit,
} from "../currencies";
import { getEnv } from "../env";
import { RippleAPI } from "ripple-lib";
import { Payment } from "ripple-lib/dist/npm/transaction/payment";
import { TransactionsOptions } from "ripple-lib/dist/npm/ledger/transactions";
import network from "../network";
import { parseCurrencyUnit, getCryptoCurrencyById } from "../currencies";

type AsyncApiFunction = (api: RippleAPI) => Promise<any>;

type XRPInstruction = {
fee: string;
maxLedgerVersionOffset: number;
};

const rippleUnit = getCryptoCurrencyById("ripple").units[0];

const defaultEndpoint = () => getEnv("API_RIPPLE_WS");
const defaultEndpoint = () => getEnv("API_RIPPLE_RPC");

export const connectionTimeout = 30 * 1000; // default connectionTimeout is 2s and make the specs bot failed

const WEBSOCKET_DEBOUNCE_DELAY = 30000;
let api;
let pendingQueries: Promise<any>[] = [];
let apiDisconnectTimeout;

/**
* Connects to Substrate Node, executes calls then disconnects
*
* @param {*} execute - the calls to execute on api
*/
async function withApi(
execute: AsyncApiFunction,
endpointConfig: string | null | undefined = null
): Promise<any> {
const server = endpointConfig || defaultEndpoint();

// If client is instanciated already, ensure it is connected & ready
if (api) {
try {
if (!(await api.isConnected)) {
throw new Error("XRP WS is not connected");
}
} catch (err) {
// definitely not connected...
api = null;
pendingQueries = [];
}
}

if (!api) {
api = new RippleAPI({
server,
});
// https://github.com/ripple/ripple-lib/issues/1196#issuecomment-583156895
// We can't add connectionTimeout to the constructor
// We need to add this config to allow the bot to not timeout on github action
// but it will throw a 'additionalProperty "connectionTimeout" exists'
// during the preparePayment
api.connection._config.connectionTimeout = connectionTimeout;
api.on("error", (errorCode, errorMessage) => {
console.warn(`Ripple API error: ${errorCode}: ${errorMessage}`);
});
await api.connect();
}

cancelDebouncedDisconnect();

try {
const query = execute(api);
pendingQueries.push(query.catch((err) => err));
const res = await query;
return res;
} finally {
debouncedDisconnect();
}
}

/**
* Disconnects Websocket API client after all pending queries are flushed.
*/
export const disconnect = async (): Promise<void> => {
cancelDebouncedDisconnect();

if (api) {
const disconnecting = api;
const pending = pendingQueries;
api = undefined;
pendingQueries = [];
await Promise.all(pending);
await disconnecting.disconnect();
}
};

const cancelDebouncedDisconnect = () => {
if (apiDisconnectTimeout) {
clearTimeout(apiDisconnectTimeout);
apiDisconnectTimeout = null;
}
};

/**
* Disconnects Websocket client after a delay.
*/
const debouncedDisconnect = () => {
cancelDebouncedDisconnect();
apiDisconnectTimeout = setTimeout(disconnect, WEBSOCKET_DEBOUNCE_DELAY);
};
const rippleUnit = getCryptoCurrencyById("ripple").units[0];

export const parseAPIValue = (value: string): BigNumber =>
parseCurrencyUnit(rippleUnit, value);

export const parseAPICurrencyObject = ({
currency,
value,
}: {
currency: string;
value: string;
}): BigNumber => {
if (currency !== "XRP") {
console.warn(`RippleJS: attempt to parse unknown currency ${currency}`);
return new BigNumber(0);
}

return parseAPIValue(value);
export const submit = async (signature: string): Promise<any> => {
const res = await network({
method: "POST",
url: `${defaultEndpoint()}`,
data: {
method: "submit",
params: [
{
tx_blob: signature,
},
],
},
});
return res.data.result;
};

export const formatAPICurrencyXRP = (
amount: BigNumber
): { currency: "XRP"; value: string } => {
const value = formatCurrencyUnit(rippleUnit, amount, {
showAllDigits: true,
disableRounding: true,
useGrouping: false,
});
return {
currency: "XRP",
value,
type AccountInfo = {
account_data: {
Account: string;
Balance: string;
Flags: number;
LedgerEntryType: string;
OwnerCount: number;
PreviousTxnID: string;
PreviousTxnLgrSeq: number;
Sequence: number;
index: string;
};
error: string;
};
This conversation was marked as resolved.
Show resolved Hide resolved

export const preparePayment = async (
address: string,
payment: Payment,
instruction: XRPInstruction
): Promise<any> =>
withApi(async (api: RippleAPI) => {
return api.preparePayment(address, payment, instruction);
});

export const submit = async (signature: string): Promise<any> =>
withApi(async (api: RippleAPI) => {
return api.request("submit", {
tx_blob: signature,
});
});
// endpointConfig does not seem to be undestood by linter

export const getAccountInfo = async (
recipient: string,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
endpointConfig?: string | null | undefined
): Promise<any> =>
withApi(async (api: RippleAPI) => {
return api.getAccountInfo(recipient);
current?: boolean
): Promise<AccountInfo> => {
const res = await network({
method: "POST",
url: `${defaultEndpoint()}`,
data: {
method: "account_info",
params: [
{
account: recipient,
ledger_index: current ? "current" : "validated",
},
],
},
});
return res.data.result;
};

export const getServerInfo = async (
// eslint-disable-next-line @typescript-eslint/no-unused-vars
endpointConfig?: string | null | undefined
): Promise<any> =>
withApi(async (api: RippleAPI) => {
return api.getServerInfo();
): Promise<any> => {
const res = await network({
method: "POST",
url: endpointConfig ?? `${defaultEndpoint()}`,
data: {
method: "server_info",
params: [
{
ledger_index: "validated",
},
],
},
});

/* eslint-enable no-unused-vars */
return res.data.result;
};

export const getTransactions = async (
address: string,
options: TransactionsOptions | undefined
): Promise<any> =>
withApi(async (api: RippleAPI) => {
return api.getTransactions(address, options);
options: any | undefined
): Promise<any> => {
const res = await network({
method: "POST",
url: `${defaultEndpoint()}`,
data: {
method: "account_tx",
params: [
{
account: address,
ledger_index: "validated",
...options,
},
],
},
});
return res.data.result.transactions;
};

export default async function getLedgerIndex(): Promise<number> {
const ledgerResponse = await network({
method: "POST",
url: `${defaultEndpoint()}`,
data: {
method: "ledger",
params: [
{
ledger_index: "validated",
},
],
},
});
return ledgerResponse.data.result.ledger_index;
}
2 changes: 0 additions & 2 deletions src/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { disconnect as rippleApiDisconnect } from "./Ripple";
import { disconnect as polkadotApiDisconnect } from "../families/polkadot/api";

export async function disconnectAll(): Promise<void> {
await rippleApiDisconnect();
await polkadotApiDisconnect();
}
6 changes: 3 additions & 3 deletions src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@ const envDefinitions = {
parser: stringParser,
desc: "Node API to use for cosmos_testnet (COSMOS_NODE or STARGATE_NODE are known)",
},
API_RIPPLE_WS: {
API_RIPPLE_RPC: {
parser: stringParser,
def: "wss://xrplcluster.com/ledgerlive",
desc: "XRP Ledger full history open WebSocket endpoint",
def: "https://xrplcluster.com/ledgerlive",
desc: "XRP Ledger full history open JSON-RPC endpoint",
},
API_FILECOIN_ENDPOINT: {
parser: stringParser,
Expand Down
Loading