Skip to content

Commit

Permalink
Merge branch 'l2fixes' of github.com:leolambo/bitcore
Browse files Browse the repository at this point in the history
  • Loading branch information
kajoseph committed May 24, 2024
2 parents cb82858 + ddda3b1 commit 9bc0294
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 118 deletions.
86 changes: 51 additions & 35 deletions packages/bitcore-node/src/providers/chain-state/evm/api/ecsp.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { CryptoRpc } from 'crypto-rpc';
import _ from 'lodash';
import { Readable, Transform } from 'stream';
import { Readable } from 'stream';
import Web3 from 'web3';
import Config from '../../../../config';
import logger from '../../../../logger';
Expand All @@ -20,7 +20,7 @@ import {
import { unixToDate } from '../../../../utils/convert';
import { StatsUtil } from '../../../../utils/stats';
import MoralisAPI from '../../external/providers/moralis';
import { ExternalApiStream } from '../../external/streams/apiStream';
import { ExternalApiStream, MergedStream } from '../../external/streams/apiStream';
import { NodeQueryStream } from '../../external/streams/nodeStream';
import { InternalStateProvider } from '../../internal/internal';
import { EVMTransactionStorage } from '../models/transaction';
Expand Down Expand Up @@ -85,10 +85,14 @@ export class BaseEVMExternalStateProvider extends InternalStateProvider implemen
return rpcObj;
}

async getChainId({ network }) {
const { web3 } = await this.getWeb3(network);
return await web3.eth.getChainId();
}

async getLocalTip({ chain, network, includeTxs = false }): Promise<IBlock> {
const { web3 } = await this.getWeb3(network);
const block = await web3.eth.getBlock('latest');
// timestamp is incorrect. do we want to spend an api call just to get the date?
return ECSP.transformBlockData({ chain, network, block, includeTxs }) as IBlock;
}

Expand All @@ -98,12 +102,19 @@ export class BaseEVMExternalStateProvider extends InternalStateProvider implemen
const latestBlock = await web3.eth.getBlockNumber();
// Getting the 25th percentile gas prices from the last 4k blocks
const feeHistory = await web3.eth.getFeeHistory(20 * 200, latestBlock, [25]);
const gasPrices = feeHistory.reward.map(reward => parseInt(reward[0])).sort((a, b) => b - a);
const whichQuartile = Math.min(target, 4) || 1;
const quartileMedian = StatsUtil.getNthQuartileMedian(gasPrices, whichQuartile);
const roundedGwei = (quartileMedian / 1e9).toFixed(6); // increased precision to handle chains with lower fees
const gwei = Number(roundedGwei) || 0;
const feerate = gwei * 1e9;
const gasPrices: Array<BigInt> = [];
const length = Math.min(feeHistory.baseFeePerGas.length, feeHistory.reward.length);
for (let i = 0; i < length; i++) {
// Adds base fee with reward / priority fee to get total gas price in wei
// For pre-type2 blocks the gas prices are returned as rewards and zeroes are returned for the base fee per gas
gasPrices.push(BigInt(feeHistory.baseFeePerGas[i]) + BigInt(feeHistory.reward[i][0]));
}
// Sort descending with an appropriate BigInt comparator
gasPrices.sort((a, b) => (a < b ? 1 : a > b ? -1 : 0));
const whichQuartile: number = Math.min(target, 4) || 1;
const quartileMedian: BigInt = StatsUtil.getNthQuartileMedian(gasPrices, whichQuartile);
const feerate = quartileMedian.toString();
// Fee returned in wei as a string
return { feerate, blocks: target };
}

Expand Down Expand Up @@ -146,8 +157,8 @@ export class BaseEVMExternalStateProvider extends InternalStateProvider implemen
return Promise.all(blockPromises)
.then(blockPromises.map(blockTransform) as any)
.catch(error => error);
} catch (err) {
logger.error('Error getting blocks from historical node: %o', err);
} catch (err: any) {
logger.error('Error getting blocks from historical node: %o', err.log || err);
}
return undefined;
}
Expand All @@ -162,8 +173,8 @@ export class BaseEVMExternalStateProvider extends InternalStateProvider implemen
const tip = await this.getLocalTip(params);
const tipHeight = tip ? tip.height : 0;
return await this._getTransaction({ chain, network, txId, tipHeight, web3 });
} catch (err) {
logger.error('Error getting transactions from historical node %o', err);
} catch (err: any) {
logger.error('Error getting transactions from historical node %o', err.log || err);
}
return undefined;
}
Expand Down Expand Up @@ -210,10 +221,10 @@ export class BaseEVMExternalStateProvider extends InternalStateProvider implemen
// stream results into response
const result = await NodeQueryStream.onStream(txStream, req!, res!);
if (result?.success === false) {
logger.error('Error mid-stream (streamTransactions): %o', result.error);
logger.error('Error mid-stream (streamTransactions): %o', result.error?.log || result.error);
}
} catch (err) {
logger.error('Error streaming transactions from historical node %o', err);
} catch (err: any) {
logger.error('Error streaming transactions from historical node %o', err.log || err);
throw err;
}
}
Expand All @@ -222,22 +233,23 @@ export class BaseEVMExternalStateProvider extends InternalStateProvider implemen
const { req, res, args, chain, network, address } = params;
const { tokenAddress } = args;
try {
// Calculate confirmations with tip height
let result;
const chainId = await this.getChainId({ network });
// Calculate confirmations with tip height
const tip = await this.getLocalTip(params);
args.tipHeight = tip ? tip.height : 0;
if (!args.tokenAddress) {
const txStream = MoralisAPI.streamTransactionsByAddress({ chain, network, address, args });
const txStream = MoralisAPI.streamTransactionsByAddress({ chainId, chain, network, address, args });
result = await ExternalApiStream.onStream(txStream, req!, res!);
} else {
const tokenTransfers = MoralisAPI.streamERC20TransactionsByAddress({ chain, network, address, tokenAddress, args });
const tokenTransfers = MoralisAPI.streamERC20TransactionsByAddress({ chainId, chain, network, address, tokenAddress, args });
result = await ExternalApiStream.onStream(tokenTransfers, req!, res!);
}
if (result?.success === false) {
logger.error('Error mid-stream (streamAddressTransactions): %o', result.error);
logger.error('Error mid-stream (streamAddressTransactions): %o', result.error?.log || result.error);
}
} catch (err) {
logger.error('Error streaming address transactions from external provider: %o', err);
} catch (err: any) {
logger.error('Error streaming address transactions from external provider: %o', err.log || err);
throw err;
}
}
Expand All @@ -248,46 +260,47 @@ export class BaseEVMExternalStateProvider extends InternalStateProvider implemen
if (!wallet?._id) {
throw new Error('Missing wallet');
}
const chainId = await this.getChainId({ network });
// Calculate confirmations with tip height
const tip = await this.getLocalTip(params);
args.tipHeight = tip ? tip.height : 0;
const walletAddresses = (await this.getWalletAddresses(wallet._id!)).map(addy => addy.address);
const mergedStream = new Transform();
const mergedStream = new MergedStream();
const txStreams: Readable[] = [];
// Only mergedStream writes to res object
const _mergedStream = ExternalApiStream.onStream(mergedStream, req!, res!);

// Default to pulling only the first 10 transactions per address
for (let i = 0; i < walletAddresses.length; i++) {
// args / query params are processed at the api provider level
txStreams.push(MoralisAPI.streamTransactionsByAddress({ chain, network, address: walletAddresses[i], args }));
txStreams.push(MoralisAPI.streamTransactionsByAddress({ chainId, chain, network, address: walletAddresses[i], args }));
}
// Pipe all txStreams to the mergedStream
ExternalApiStream.mergeStreams(txStreams, mergedStream);
// Ensure mergeStream resolves
const result = await _mergedStream;
if (result?.success === false) {
logger.error('Error mid-stream (streamWalletTransactions): %o', result.error);
logger.error('Error mid-stream (streamWalletTransactions): %o', result.error?.log || result.error);
}
} catch (err) {
logger.error('Error streaming wallet transactions from external provider: %o', err);
} catch (err: any) {
logger.error('Error streaming wallet transactions from external provider: %o', err.log || err);
throw err;
}
}

async getWalletBalanceAtTime(params: GetWalletBalanceAtTimeParams): Promise<{ confirmed: number; unconfirmed: number; balance: number }> {
const { chain, network, time, wallet } = params;
const { network, time, wallet } = params;
try {
if (!wallet?._id) {
throw new Error('Missing wallet');
}
const addresses = (await this.getWalletAddresses(wallet._id!)).map(addy => addy.address);
// get block number based on time
const block = await this.getBlockNumberByDate({ network, date: time });
const result: any = await this.getNativeBalanceByBlock({ chain, network, block, addresses });
const result: any = await this.getNativeBalanceByBlock({ network, block, addresses });
return { unconfirmed: 0, confirmed: result?.total_balance, balance: result?.total_balance };
} catch (err) {
logger.error('Error getting wallet balance at time from external provider %o', err);
} catch (err: any) {
logger.error('Error getting wallet balance at time from external provider %o', err.log || err);
throw err;
}
}
Expand Down Expand Up @@ -382,19 +395,22 @@ export class BaseEVMExternalStateProvider extends InternalStateProvider implemen
}

async getBlockNumberByDate({ date, network }) {
const res = await MoralisAPI.getBlockByDate({ chain: this.chain, network, date });
const chainId = await this.getChainId({ network });
const res = await MoralisAPI.getBlockByDate({ chainId, date });
const block = JSON.parse((res as any).body);
return block.number ? Number((block as any).number) : undefined;
}

async getBlockNumberByBlockId({ blockId, network }) {
const res = await MoralisAPI.getBlockByHash({ chain: this.chain, network, blockId });
const chainId = await this.getChainId({ network })
const res = await MoralisAPI.getBlockByHash({ chainId, blockId });
const block = JSON.parse((res as any).body);
return block.number ? Number((block as any).number) : undefined;
}

async getNativeBalanceByBlock({ chain, network, block, addresses }) {
const res = await MoralisAPI.getNativeBalanceByBlock({ chain, network, block, addresses });
async getNativeBalanceByBlock({ network, block, addresses }) {
const chainId = await this.getChainId({ network });
const res = await MoralisAPI.getNativeBalanceByBlock({ chainId, block, addresses });
return JSON.parse((res as any).body);
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { isDateValid } from '../../../../utils/check';
import { EVMTransactionStorage } from '../../evm/models/transaction';
import { ErigonTraceResponse } from '../../evm/p2p/rpcs/erigonRpc';
import { EVMTransactionJSON, Transaction } from '../../evm/types';
import moralisChains from '../defaults';
import { ExternalApiStream as apiStream } from '../streams/apiStream';

const baseUrl = 'https://deep-index.moralis.io/api/v2.2';
Expand All @@ -13,12 +12,15 @@ const headers = {
'X-API-Key': config.externalProviders?.moralis?.apiKey,
};

const getBlockByDate = async ({ chain, network, date }) => {
const getBlockByDate = async ({ chainId, date }) => {
if (!date || !isDateValid(date)) {
throw new Error('Invalid date');
}
if (!chainId) {
throw new Error('Invalid chainId');
}

const query = transformQueryParams({ chain, network, args: { date } });
const query = transformQueryParams({ chainId: formatChainId(chainId), args: { date } });
const queryStr = buildQueryString(query);

return new Promise((resolve, reject) => {
Expand All @@ -35,12 +37,13 @@ const getBlockByDate = async ({ chain, network, date }) => {
});
}

const getBlockByHash = async ({ chain, network, blockId }) => {
const getBlockByHash = async ({ chainId, blockId }) => {
if (!blockId) {
throw new Error('Invalid block number or hash string');
}

const chainId = getMoralisChainId(chain, network);
if (!chainId) {
throw new Error('Invalid chainId');
}

return new Promise((resolve, reject) => {
request({
Expand All @@ -56,13 +59,13 @@ const getBlockByHash = async ({ chain, network, blockId }) => {
});
}

const getNativeBalanceByBlock = async ({ chain, network, block, addresses }) => {
const getNativeBalanceByBlock = async ({ chainId, block, addresses }) => {
if (!block) {
throw new Error('Invalid block number or hash string');
}
// 25 wallet addresses max per Moralis API docs
const queryStr = buildQueryString({
chain: getMoralisChainId(chain, network),
chain: chainId,
wallet_address: addresses
})

Expand All @@ -80,12 +83,15 @@ const getNativeBalanceByBlock = async ({ chain, network, block, addresses }) =>
});
}

const streamTransactionsByAddress = ({ chain, network, address, args }): any => {
const streamTransactionsByAddress = ({ chainId, chain, network, address, args }): any => {
if (!address) {
throw new Error('Missing address');
}
if (!chainId) {
throw new Error('Invalid chainId');
}

const query = transformQueryParams({ chain, network, args }); // throws if no chain or network
const query = transformQueryParams({ chainId, args }); // throws if no chain or network
const queryStr = buildQueryString({
...query,
order: args.order || 'DESC', // default to descending order
Expand All @@ -107,15 +113,18 @@ const streamTransactionsByAddress = ({ chain, network, address, args }): any =>
)
}

const streamERC20TransactionsByAddress = ({ chain, network, address, tokenAddress, args }): any => {
const streamERC20TransactionsByAddress = ({ chainId, chain, network, address, tokenAddress, args }): any => {
if (!address) {
throw new Error('Missing address');
}
if (!tokenAddress) {
throw new Error('Missing token address');
}
if (!chainId) {
throw new Error('Invalid chainId');
}

const queryTransform = transformQueryParams({ chain, network, args }); // throws if no chain or network
const queryTransform = transformQueryParams({ chainId, args }); // throws if no chain or network
const queryStr = buildQueryString({
...queryTransform,
order: args.order || 'DESC', // default to descending order
Expand Down Expand Up @@ -194,9 +203,9 @@ const transformTokenTransfer = (transfer) => {
}

const transformQueryParams = (params) => {
const { chain, network, args } = params;
const { chainId, args } = params;
let query = {
chain: getMoralisChainId(chain, network),
chain: formatChainId(chainId),
} as any;
if (args) {
if (args.startBlock || args.endBlock) {
Expand Down Expand Up @@ -224,27 +233,6 @@ const transformQueryParams = (params) => {
return query;
}

const getMoralisChainId = (chain, network): string | Error => {
if (!chain) {
throw new Error('Missing chain');
}
if (!network) {
throw new Error('Missing network');
}

chain = chain.toUpperCase();
network = network.toLowerCase();

if (network === 'testnet') {
network = moralisChains[chain]?.testnet;
}
if (!moralisChains[chain][network]) {
throw new Error(`${chain}:${network} is not supported`);
}

return moralisChains[chain][network];
}

const calculateConfirmations = (tx, tip) => {
let confirmations = 0;
if (tx.blockHeight && tx.blockHeight >= 0) {
Expand All @@ -270,6 +258,10 @@ const buildQueryString = (params: Record<string, any>): string => {
return query.length ? `?${query.join('&')}` : '';
}

const formatChainId = (chainId) => {
return '0x' + parseInt(chainId).toString(16)
}

const MoralisAPI = {
getBlockByDate,
getBlockByHash,
Expand Down
Loading

0 comments on commit 9bc0294

Please sign in to comment.