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

Commit

Permalink
near: add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
heyitaki committed Jan 1, 2023
1 parent 74f792b commit b945a8e
Show file tree
Hide file tree
Showing 10 changed files with 310 additions and 142 deletions.
74 changes: 15 additions & 59 deletions watcher/scripts/backfillNear.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,9 @@
import { CONTRACTS } from '@certusone/wormhole-sdk/lib/cjs/utils/consts';
import {
INITIAL_DEPLOYMENT_BLOCK_BY_CHAIN,
sleep,
} from '@wormhole-foundation/wormhole-monitor-common';
import axios from 'axios';
import { connect } from 'near-api-js';
import { Provider } from 'near-api-js/lib/providers';
import { ChainName, CONTRACTS } from '@certusone/wormhole-sdk/lib/cjs/utils/consts';
import { INITIAL_DEPLOYMENT_BLOCK_BY_CHAIN } from '@wormhole-foundation/wormhole-monitor-common';
import { BlockResult } from 'near-api-js/lib/providers/provider';
import { initDb } from '../src/databases/utils';
import {
NearExplorerTransactionRequestParams,
NearExplorerTransactionResponse,
} from '../src/types/near';
import { NearWatcher } from '../src/watchers/NearWatcher';
import { getArchivalRpcProvider, getTransactionsByAccountId } from '../src/utils/near';
import { getMessagesFromBlockResults } from '../src/watchers/NearWatcher';

// This script exists because NEAR RPC nodes do not support querying blocks older than 5 epochs
// (~2.5 days): https://docs.near.org/api/rpc/setup#querying-historical-data. This script fetches
Expand All @@ -23,58 +14,24 @@ import { NearWatcher } from '../src/watchers/NearWatcher';
// Otherwise, the script will backfill the local JSON database.

const BATCH_SIZE = 1000;
const NEAR_ARCHIVE_RPC = 'https://archival-rpc.mainnet.near.org';
const NEAR_EXPLORER_TRANSACTION_URL =
'https://backend-mainnet-1713.onrender.com/trpc/transaction.listByAccountId';

const getArchivalRpcProvider = async (): Promise<Provider> => {
const connection = await connect({ nodeUrl: NEAR_ARCHIVE_RPC, networkId: 'mainnet' });
const provider = connection.connection.provider;

// sleep for 100ms between each request (do not parallelize calls with Promise.all)
for (const propName of Object.getOwnPropertyNames(Object.getPrototypeOf(provider))) {
if (typeof (provider as any)[propName] === 'function') {
(provider as any)[propName] = async (...args: any[]) => {
await sleep(100); // respect rate limits: 600req/min
return (provider as any)[propName](...args);
};
}
}

return provider;
};

const getExplorerTransactionsUrl = (timestamp: number, batchSize: number): string => {
const params: NearExplorerTransactionRequestParams = {
accountId: CONTRACTS.MAINNET.near.core,
limit: batchSize,
cursor: {
timestamp,
indexInChunk: 0,
},
};
return `${NEAR_EXPLORER_TRANSACTION_URL}?batch=1&input={"0":${JSON.stringify(params)}}`;
};

(async () => {
const db = initDb();
const watcher = new NearWatcher();
const chain: ChainName = 'near';
const provider = await getArchivalRpcProvider();
const fromBlock = Number(
(await db.getLastBlockByChain(watcher.chain)) ??
INITIAL_DEPLOYMENT_BLOCK_BY_CHAIN[watcher.chain] ??
0
(await db.getLastBlockByChain(chain)) ?? INITIAL_DEPLOYMENT_BLOCK_BY_CHAIN[chain] ?? 0
);

// fetch all transactions for core bridge contract from explorer:
// https://github.com/near/near-explorer/blob/beead42ba2a91ad8d2ac3323c29b1148186eec98/backend/src/router/transaction/list.ts#L127
// fetch all transactions for core bridge contract from explorer
const toBlock = await provider.block({ finality: 'final' });
const transactions = (
(await axios.get(getExplorerTransactionsUrl(toBlock.header.timestamp, BATCH_SIZE)))
.data as NearExplorerTransactionResponse
)[0].result.data.items.filter((tx) => tx.status === 'success');
const transactions = await getTransactionsByAccountId(
CONTRACTS.MAINNET.near.core,
BATCH_SIZE,
toBlock.header.timestamp
);

// filter out transactions that are not in the given block range
// filter out transactions that precede last seen block
const blocks: BlockResult[] = [];
const blockHashes = [...new Set(transactions.map((tx) => tx.blockHash))];
blockHashes.forEach(async (hash) => {
Expand All @@ -84,7 +41,6 @@ const getExplorerTransactionsUrl = (timestamp: number, batchSize: number): strin
}
});

watcher.provider = provider;
const vaasByBlock = await watcher.getMessagesFromBlockResults(blocks);
await db.storeVaasByBlock(watcher.chain, vaasByBlock);
const vaasByBlock = await getMessagesFromBlockResults(provider, blocks);
await db.storeVaasByBlock(chain, vaasByBlock);
})();
6 changes: 6 additions & 0 deletions watcher/src/consts.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ChainName, CONTRACTS } from '@certusone/wormhole-sdk/lib/cjs/utils/consts';
import { AxiosRequestConfig } from 'axios';

export const TIMEOUT = 0.5 * 1000;

Expand Down Expand Up @@ -53,3 +54,8 @@ export const DB_SOURCE = process.env.DB_SOURCE || 'local';
export const JSON_DB_FILE = process.env.JSON_DB_FILE || '../server/db.json';
export const DB_LAST_BLOCK_FILE =
process.env.DB_LAST_BLOCK_FILE || '../server/lastBlockByChain.json';

// without this, axios request will error `Z_BUF_ERROR`: https://github.com/axios/axios/issues/5346
export const AXIOS_CONFIG_JSON: AxiosRequestConfig = {
headers: { 'Accept-Encoding': 'application/json' },
};
118 changes: 92 additions & 26 deletions watcher/src/types/near.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,43 +16,109 @@ export type WormholePublishEventLog = {
block: number;
};

export const isWormholePublishEventLog = (log: EventLog): log is WormholePublishEventLog => {
return log.standard === 'wormhole' && log.event === 'publish';
};

export type NearExplorerTransactionResponse = {
id: string | null;
result: {
type: string;
data: {
items: NearExplorerTransaction[];
};
};
}[];
export type GetTransactionsByAccountIdResponse = [
| {
id: string | null;
result: {
type: string;
data: {
items: Transaction[];
};
};
}
| {
id: string | null;
error: {
message: string;
code: number;
data: {
code: string;
httpStatus: number;
path: string;
};
};
}
];

export type NearExplorerTransaction = {
export type Transaction = {
hash: string;
signerId: string;
receiverId: string;
blockHash: string;
blockTimestamp: number;
actions: {
kind: string;
args: {
methodName: string;
args: string;
gas: number;
deposit: string;
};
}[];
status: string;
actions: Action[];
status: 'unknown' | 'failure' | 'success';
};

export type NearExplorerTransactionRequestParams = {
export type GetTransactionsByAccountIdRequestParams = {
accountId: string;
limit: number;
cursor?: {
timestamp: number; // paginate with timestamp
timestamp: string; // paginate with timestamp
indexInChunk: number;
};
};

type Action =
| {
kind: 'createAccount';
args: {};
}
| {
kind: 'deployContract';
args: {
code: string;
};
}
| {
kind: 'functionCall';
args: {
methodName: string;
args: string;
gas: number;
deposit: string;
};
}
| {
kind: 'transfer';
args: {
deposit: string;
};
}
| {
kind: 'stake';
args: {
stake: string;
publicKey: string;
};
}
| {
kind: 'addKey';
args: {
publicKey: string;
accessKey: {
nonce: number;
permission:
| {
type: 'fullAccess';
}
| {
type: 'functionCall';
contractId: string;
methodNames: string[];
};
};
};
}
| {
kind: 'deleteKey';
args: {
publicKey: string;
};
}
| {
kind: 'deleteAccount';
args: {
beneficiaryId: string;
};
};
56 changes: 56 additions & 0 deletions watcher/src/utils/near.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { sleep } from '@wormhole-foundation/wormhole-monitor-common';
import axios from 'axios';
import { connect } from 'near-api-js';
import { JsonRpcProvider, Provider } from 'near-api-js/lib/providers';
import { AXIOS_CONFIG_JSON } from '../consts';
import {
GetTransactionsByAccountIdRequestParams,
GetTransactionsByAccountIdResponse,
Transaction,
} from '../types/near';

const NEAR_ARCHIVE_RPC = 'https://archival-rpc.mainnet.near.org';
const NEAR_EXPLORER_TRANSACTION_URL =
'https://backend-mainnet-1713.onrender.com/trpc/transaction.listByAccountId';
export const ARCHIVAL_NODE_RATE_LIMIT_MS = 100;

export const getArchivalRpcProvider = async (): Promise<Provider> => {
const connection = await connect({ nodeUrl: NEAR_ARCHIVE_RPC, networkId: 'mainnet' });
const provider = connection.connection.provider as JsonRpcProvider;
const originalFn = provider.sendJsonRpc;
provider.sendJsonRpc = async function <T>(method: string, params: object) {
await sleep(ARCHIVAL_NODE_RATE_LIMIT_MS); // respect rate limits: 600req/min
return originalFn.call(this, method, params) as Promise<T>;
};

return provider;
};

export const getTransactionsByAccountId = async (
accountId: string,
batchSize: number,
timestamp: string
): Promise<Transaction[]> => {
const params: GetTransactionsByAccountIdRequestParams = {
accountId,
limit: batchSize,
cursor: {
timestamp,
indexInChunk: 0,
},
};

// using this api: https://github.com/near/near-explorer/blob/beead42ba2a91ad8d2ac3323c29b1148186eec98/backend/src/router/transaction/list.ts#L127
const res = (
(
await axios.get(
`${NEAR_EXPLORER_TRANSACTION_URL}?batch=1&input={"0":${JSON.stringify(params)}}`,
AXIOS_CONFIG_JSON
)
).data as GetTransactionsByAccountIdResponse
)[0];
if ('error' in res) throw new Error(res.error.message);
return res.result.data.items.filter(
(tx) => tx.status === 'success' && tx.actions.some((a) => a.kind === 'functionCall') // other actions don't generate logs
);
};
5 changes: 5 additions & 0 deletions watcher/src/utils/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { EventLog, WormholePublishEventLog } from '../types/near';

export const isWormholePublishEventLog = (log: EventLog): log is WormholePublishEventLog => {
return log.standard === 'wormhole' && log.event === 'publish';
};
16 changes: 5 additions & 11 deletions watcher/src/watchers/EVMWatcher.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Implementation__factory } from '@certusone/wormhole-sdk/lib/cjs/ethers-contracts/factories/Implementation__factory';
import { CONTRACTS, EVMChainName } from '@certusone/wormhole-sdk/lib/cjs/utils/consts';
import { Log } from '@ethersproject/abstract-provider';
import axios, { AxiosRequestConfig } from 'axios';
import axios from 'axios';
import { BigNumber } from 'ethers';
import { RPCS_BY_CHAIN } from '../consts';
import { AXIOS_CONFIG_JSON, RPCS_BY_CHAIN } from '../consts';
import { VaasByBlock } from '../databases/types';
import { makeBlockKey, makeVaaKey } from '../databases/utils';
import { Watcher } from './Watcher';
Expand All @@ -12,12 +12,6 @@ import { Watcher } from './Watcher';
// https://github.com/wormhole-foundation/wormhole/blob/main/ethereum/contracts/Implementation.sol#L12
export const LOG_MESSAGE_PUBLISHED_TOPIC =
'0x6eb224fb001ed210e379b335e35efe88672a8ce935d981a6896b27ffdf52a3b2';

// without this, Oasis will `Z_BUF_ERROR`
export const EVM_AXIOS_CONFIG: AxiosRequestConfig = {
headers: { 'Accept-Encoding': 'application/json' },
};

export const wormholeInterface = Implementation__factory.createInterface();

export type BlockTag = 'finalized' | 'safe' | 'latest';
Expand Down Expand Up @@ -59,7 +53,7 @@ export class EVMWatcher extends Watcher {
],
},
],
EVM_AXIOS_CONFIG
AXIOS_CONFIG_JSON
)
)?.data?.[0]?.result;
if (result && result.hash && result.number && result.timestamp) {
Expand Down Expand Up @@ -88,7 +82,7 @@ export class EVMWatcher extends Watcher {
params: [`0x${blockNumber.toString(16)}`, false],
});
}
const results = (await axios.post(rpc, reqs, EVM_AXIOS_CONFIG))?.data;
const results = (await axios.post(rpc, reqs, AXIOS_CONFIG_JSON))?.data;
if (results && results.length) {
// Convert to Ethers compatible type
return results.map((response: undefined | { result?: Block }, idx: number) => {
Expand Down Expand Up @@ -141,7 +135,7 @@ export class EVMWatcher extends Watcher {
],
},
],
EVM_AXIOS_CONFIG
AXIOS_CONFIG_JSON
)
)?.data?.[0]?.result;
if (result) {
Expand Down
6 changes: 3 additions & 3 deletions watcher/src/watchers/MoonbeamWatcher.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { sleep } from '@wormhole-foundation/wormhole-monitor-common';
import axios from 'axios';
import { RPCS_BY_CHAIN } from '../consts';
import { EVMWatcher, EVM_AXIOS_CONFIG } from './EVMWatcher';
import { AXIOS_CONFIG_JSON, RPCS_BY_CHAIN } from '../consts';
import { EVMWatcher } from './EVMWatcher';

export class MoonbeamWatcher extends EVMWatcher {
constructor() {
Expand Down Expand Up @@ -30,7 +30,7 @@ export class MoonbeamWatcher extends EVMWatcher {
params: [blockFromNumber.hash],
},
],
EVM_AXIOS_CONFIG
AXIOS_CONFIG_JSON
)
)?.data?.[0]?.result || false;
} catch (e) {
Expand Down

0 comments on commit b945a8e

Please sign in to comment.