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

Commit

Permalink
XRP - replace WebSocket with RPC
Browse files Browse the repository at this point in the history
  • Loading branch information
hakim-adamik committed Apr 28, 2022
1 parent bcd322c commit 1a3fa87
Show file tree
Hide file tree
Showing 11 changed files with 465 additions and 716 deletions.
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

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;
};

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

1 comment on commit 1a3fa87

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 2 txs ($51.83) for Bot 'XRP on Mère Denis'

Details of the 2 mutations

Spec XRP (5)

Spec XRP found 5 XRP accounts. Will use XRP 2.2.2 on nanoS 2.0.0
(1966ms) XRP 1 cross: 10.098 XRP (200ops) (rGc49k7KBc1woDkg5951sZbTizyR4RnH9k on 44'/144'/0'/0/0) #0 js:2:ripple:rGc49k7KBc1woDkg5951sZbTizyR4RnH9k:
(1527ms) XRP 2: 11.979 XRP (200ops) (rJzU8Eajm6ZNoh3bCm8AQ682GT1GFJ5zsv on 44'/144'/1'/0/0) #1 js:2:ripple:rJzU8Eajm6ZNoh3bCm8AQ682GT1GFJ5zsv:
(1379ms) XRP 3: 10.761 XRP (200ops) (rJBGZo8JfXJhXnjoc57ygFZccdprzm2bR2 on 44'/144'/2'/0/0) #2 js:2:ripple:rJBGZo8JfXJhXnjoc57ygFZccdprzm2bR2:
(689ms) XRP 4: 47.6 XRP (19ops) (rP2VnCuAZYKxtkCcCAztF62oB6Za1v78pd on 44'/144'/3'/0/0) #3 js:2:ripple:rP2VnCuAZYKxtkCcCAztF62oB6Za1v78pd:
(309ms) XRP 5: 0 XRP (0ops) (raJsignX8f8SGqc2FpUv3wmrArY8eqW1Vy on 44'/144'/4'/0/0) #4 js:2:ripple:raJsignX8f8SGqc2FpUv3wmrArY8eqW1Vy:
all accounts sync in 731ms
▬ XRP 2.2.2 on nanoS 2.0.0
→ FROM XRP 2: 11.979 XRP (200ops) (rJzU8Eajm6ZNoh3bCm8AQ682GT1GFJ5zsv on 44'/144'/1'/0/0) #1 js:2:ripple:rJzU8Eajm6ZNoh3bCm8AQ682GT1GFJ5zsv: (! sum of ops -11.206703 XRP)
max spendable ~1.9795
★ using mutation 'move ~50%'
→ TO XRP 4: 47.6 XRP (19ops) (rP2VnCuAZYKxtkCcCAztF62oB6Za1v78pd on 44'/144'/3'/0/0) #3 js:2:ripple:rP2VnCuAZYKxtkCcCAztF62oB6Za1v78pd:
✔️ transaction 
SEND 0.9534 XRP
TO rP2VnCuAZYKxtkCcCAztF62oB6Za1v78pd
with fee=0.00001 XRP
  tag=123
STATUS (1253ms)
  amount: 0.9534 XRP
  estimated fees: 0.00001 XRP
  total spent: 0.95341 XRP
✔️ has been signed! (3.6s) 
✔️ broadcasted! (242ms) optimistic operation: 
  -0.9534 XRP        OUT        36D501BBF87A278F75BF6075A060C04151F9D0196494A6E7359EB0860BC00AA3 2022-04-28T20:27
✔️ operation confirmed (5.6s): 
  -0.95341 XRP       OUT        36D501BBF87A278F75BF6075A060C04151F9D0196494A6E7359EB0860BC00AA3 2022-04-28T20:27
✔️ XRP 2: 11.026 XRP (201ops) (rJzU8Eajm6ZNoh3bCm8AQ682GT1GFJ5zsv on 44'/144'/1'/0/0) #1 js:2:ripple:rJzU8Eajm6ZNoh3bCm8AQ682GT1GFJ5zsv: (! sum of ops -12.160113 XRP)
(final state reached in 5.6s)

all accounts sync in 755ms
▬ XRP 2.2.2 on nanoS 2.0.0
→ FROM XRP 3: 10.761 XRP (200ops) (rJBGZo8JfXJhXnjoc57ygFZccdprzm2bR2 on 44'/144'/2'/0/0) #2 js:2:ripple:rJBGZo8JfXJhXnjoc57ygFZccdprzm2bR2: (! sum of ops -9.717794 XRP)
max spendable ~0.7614
★ using mutation 'move ~50%'
→ TO XRP 2: 11.026 XRP (201ops) (rJzU8Eajm6ZNoh3bCm8AQ682GT1GFJ5zsv on 44'/144'/1'/0/0) #1 js:2:ripple:rJzU8Eajm6ZNoh3bCm8AQ682GT1GFJ5zsv:
✔️ transaction 
SEND 0.384723 XRP
TO rJzU8Eajm6ZNoh3bCm8AQ682GT1GFJ5zsv
with fee=0.00001 XRP
  tag=123
STATUS (1180ms)
  amount: 0.384723 XRP
  estimated fees: 0.00001 XRP
  total spent: 0.384733 XRP
✔️ has been signed! (3.5s) 
✔️ broadcasted! (155ms) optimistic operation: 
  -0.384723 XRP      OUT        9F293B32B2B5F9C41365A912053A72924D732DE83F807EE5046A1B50DEEC08C7 2022-04-28T20:27
✔️ operation confirmed (5.7s): 
  -0.384733 XRP      OUT        9F293B32B2B5F9C41365A912053A72924D732DE83F807EE5046A1B50DEEC08C7 2022-04-28T20:27
✔️ XRP 3: 10.376 XRP (201ops) (rJBGZo8JfXJhXnjoc57ygFZccdprzm2bR2 on 44'/144'/2'/0/0) #2 js:2:ripple:rJBGZo8JfXJhXnjoc57ygFZccdprzm2bR2: (! sum of ops -10.102527 XRP)
(final state reached in 5.7s)


Portfolio ($51.83)

Details of the 1 currencies
Spec (accounts) Operations Balance funds?
XRP (3) 623 (+4) 80.44 XRP (- 0.00002) ($51.83) 💪 rGc49k7KBc1woDkg5951sZbTizyR4RnH9k

Please sign in to comment.