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

Commit

Permalink
isNil MAX gas gwei cached gas price
Browse files Browse the repository at this point in the history
  • Loading branch information
dekz committed May 20, 2020
1 parent 1608a50 commit d61bc6c
Show file tree
Hide file tree
Showing 10 changed files with 112 additions and 78 deletions.
16 changes: 10 additions & 6 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,12 +172,12 @@ export const RFQT_SKIP_BUY_REQUESTS: boolean = _.isEmpty(process.env.RFQT_SKIP_B
: assertEnvVarType('RFQT_SKIP_BUY_REQUESTS', process.env.RFQT_SKIP_BUY_REQUESTS, EnvVarType.Boolean);

// Whitelisted 0x API keys that can use the meta-txn /submit endpoint
export const WHITELISTED_API_KEYS_META_TXN_SUBMIT: string[] =
process.env.WHITELISTED_API_KEYS_META_TXN_SUBMIT === undefined
export const META_TXN_SUBMIT_WHITELISTED_API_KEYS: string[] =
process.env.META_TXN_SUBMIT_WHITELISTED_API_KEYS === undefined
? []
: assertEnvVarType(
'WHITELISTED_API_KEYS_META_TXN_SUBMIT',
process.env.WHITELISTED_API_KEYS_META_TXN_SUBMIT,
'META_TXN_SUBMIT_WHITELISTED_API_KEYS',
process.env.META_TXN_SUBMIT_WHITELISTED_API_KEYS,
EnvVarType.APIKeys,
);

Expand All @@ -196,9 +196,13 @@ export const META_TXN_RELAY_EXPECTED_MINED_SEC: number = _.isEmpty(process.env.M
);
// Should TransactionWatcherSignerService sign transactions
// tslint:disable-next-line:boolean-naming
export const ENABLE_TRANSACTION_SIGNING: boolean = _.isEmpty(process.env.ENABLE_TRANSACTION_SIGNING)
export const META_TXN_SIGNING_ENABLED: boolean = _.isEmpty(process.env.META_TXN_SIGNING_ENABLED)
? true
: assertEnvVarType('ENABLE_TRANSACTION_SIGNING', process.env.ENABLE_TRANSACTION_SIGNING, EnvVarType.Boolean);
: assertEnvVarType('META_TXN_SIGNING_ENABLED', process.env.META_TXN_SIGNING_ENABLED, EnvVarType.Boolean);
// The maximum gas price (in gwei) the service will allow
export const META_TXN_MAX_GAS_PRICE_GWEI: BigNumber = _.isEmpty(process.env.META_TXN_MAX_GAS_PRICE_GWEI)
? new BigNumber(50)
: assertEnvVarType('META_TXN_MAX_GAS_PRICE_GWEI', process.env.META_TXN_MAX_GAS_PRICE_GWEI, EnvVarType.UnitAmount);

// Whether or not prometheus metrics should be enabled.
// tslint:disable-next-line:boolean-naming
Expand Down
2 changes: 1 addition & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,6 @@ export const NUMBER_OF_BLOCKS_UNTIL_CONFIRMED = 3;
export const TX_WATCHER_UPDATE_METRICS_INTERVAL_MS = 30 * 1000;
export const ETH_DECIMALS = 18;
export const GWEI_DECIMALS = 9;
export const SIGNER_ETH_BALANCE_CONSIDERED_CRITICAL = 0.1;
export const META_TXN_MIN_SIGNER_ETH_BALANCE = 0.1;
export const SIGNER_STATUS_DB_KEY = 'signer_status';
export const SIGNER_KILL_SWITCH_KEY = 'signer_kill_switch_on';
2 changes: 1 addition & 1 deletion src/handlers/meta_transaction_handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { SwapQuoterError } from '@0x/asset-swapper';
import { BigNumber } from '@0x/utils';
import * as express from 'express';
import * as HttpStatus from 'http-status-codes';
import * as _ from 'lodash';
import * as isValidUUID from 'uuid-validate';

import { CHAIN_ID } from '../config';
Expand Down Expand Up @@ -236,6 +235,7 @@ export class MetaTransactionHandlers {
res.status(HttpStatus.OK).send({
ethereumTransactionHash,
signedEthereumTransaction,
zeroExTransactionHash,
});
} else {
const ethereumTxn = await this._metaTransactionService.generatePartialExecuteTransactionEthereumTransactionAsync(
Expand Down
16 changes: 11 additions & 5 deletions src/services/meta_transaction_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ import {
ASSET_SWAPPER_MARKET_ORDERS_OPTS,
CHAIN_ID,
LIQUIDITY_POOL_REGISTRY_ADDRESS,
META_TXN_MAX_GAS_PRICE_GWEI,
META_TXN_RELAY_EXPECTED_MINED_SEC,
WHITELISTED_API_KEYS_META_TXN_SUBMIT,
META_TXN_SUBMIT_WHITELISTED_API_KEYS,
} from '../config';
import {
GWEI_DECIMALS,
ONE_GWEI,
ONE_SECOND_MS,
PUBLIC_ADDRESS_FOR_ETH_CALLS,
Expand Down Expand Up @@ -53,7 +55,7 @@ export class MetaTransactionService {
private readonly _kvRepository: Repository<KeyValueEntity>;

public static isEligibleForFreeMetaTxn(apiKey: string): boolean {
return WHITELISTED_API_KEYS_META_TXN_SUBMIT.includes(apiKey);
return META_TXN_SUBMIT_WHITELISTED_API_KEYS.includes(apiKey);
}
private static _calculateProtocolFee(numOrders: number, gasPrice: BigNumber): BigNumber {
return new BigNumber(150000).times(gasPrice).times(numOrders);
Expand Down Expand Up @@ -221,8 +223,11 @@ export class MetaTransactionService {
const gasPrice = zeroExTransaction.gasPrice;
const currentFastGasPrice = await ethGasStationUtils.getGasPriceOrThrowAsync();
// Make sure gasPrice is not 3X the current fast EthGasStation gas price
// tslint:disable-next-line:custom-no-magic-numbers
if (currentFastGasPrice.lt(gasPrice) && gasPrice.minus(currentFastGasPrice).gt(currentFastGasPrice.times(3))) {
if (
Web3Wrapper.toUnitAmount(gasPrice, GWEI_DECIMALS).gte(META_TXN_MAX_GAS_PRICE_GWEI) ||
// tslint:disable-next-line:custom-no-magic-numbers
(currentFastGasPrice.lt(gasPrice) && gasPrice.minus(currentFastGasPrice).gte(currentFastGasPrice.times(3)))
) {
throw new Error('Gas price too high');
}

Expand Down Expand Up @@ -314,6 +319,7 @@ export class MetaTransactionService {
return {
ethereumTransactionHash,
signedEthereumTransaction,
zeroExTransactionHash,
};
}
public async isSignerLiveAsync(): Promise<boolean> {
Expand Down Expand Up @@ -353,7 +359,7 @@ export class MetaTransactionService {
return utils.runWithTimeout(async () => {
while (true) {
const tx = await this._transactionEntityRepository.findOne(txEntity.refHash);
if (tx !== undefined && tx.txHash !== undefined && tx.signedTx !== undefined) {
if (!utils.isNil(tx) && !utils.isNil(tx.txHash) && !utils.isNil(tx.signedTx)) {
return { ethereumTransactionHash: tx.txHash, signedEthereumTransaction: tx.signedTx };
}

Expand Down
108 changes: 57 additions & 51 deletions src/services/transaction_watcher_signer_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,17 @@ import { Counter, Gauge, Summary } from 'prom-client';
import { Connection, Not, Repository } from 'typeorm';

import {
ENABLE_PROMETHEUS_METRICS,
ENABLE_TRANSACTION_SIGNING,
ETHEREUM_RPC_URL,
META_TXN_RELAY_EXPECTED_MINED_SEC,
META_TXN_RELAY_PRIVATE_KEYS,
META_TXN_SIGNING_ENABLED,
} from '../config';
import {
ETH_DECIMALS,
GWEI_DECIMALS,
META_TXN_MIN_SIGNER_ETH_BALANCE,
NUMBER_OF_BLOCKS_UNTIL_CONFIRMED,
ONE_SECOND_MS,
SIGNER_ETH_BALANCE_CONSIDERED_CRITICAL,
SIGNER_STATUS_DB_KEY,
TX_HASH_RESPONSE_WAIT_TIME_MS,
TX_WATCHER_POLLING_INTERVAL_MS,
Expand All @@ -29,6 +28,7 @@ import { logger } from '../logger';
import { TransactionStates, TransactionWatcherSignerStatus } from '../types';
import { ethGasStationUtils } from '../utils/gas_station_utils';
import { Signer } from '../utils/signer';
import { utils } from '../utils/utils';

const SIGNER_ADDRESS_LABEL = 'signer_address';
const TRANSACTION_STATUS_LABEL = 'status';
Expand All @@ -39,19 +39,19 @@ export class TransactionWatcherSignerService {
private readonly _provider: SupportedProvider;
private readonly _web3Wrapper: Web3Wrapper;
private readonly _transactionWatcherTimer: NodeJS.Timer;
private readonly _signers: Map<string, Signer>;
private readonly _signerBalances: Map<string, number>;
private readonly _signers: Map<string, Signer> = new Map();
private readonly _signerBalancesEth: Map<string, number> = new Map();
private readonly _availableSignerPublicAddresses: string[];
private readonly _metricsUpdateTimer: NodeJS.Timer;
private readonly _signerBalancesGauge: Gauge<string>;
private readonly _transactionsUpdateCounter: Counter<string>;
private readonly _gasPriceSummary: Summary<string>;

public static getSortedSignersByAvailability(signerMap: Map<string, { balance: number; count: number }>): string[] {
return [...signerMap.entries()]
return Array.from(signerMap.entries())
.sort((a, b) => {
const aSigner = a[1];
const bSigner = b[1];
const [, aSigner] = a;
const [, bSigner] = b;
// if the number of pending transactions is the same, we sort
// the signers by their known balance.
if (aSigner.count === bSigner.count) {
Expand All @@ -60,7 +60,7 @@ export class TransactionWatcherSignerService {
// otherwise we sort by the least amount of pending transactions.
return aSigner.count - bSigner.count;
})
.map(entry => entry[0]);
.map(([address]) => address);
}
private static _createWeb3Provider(rpcHost: string): SupportedProvider {
const providerEngine = new Web3ProviderEngine();
Expand All @@ -69,9 +69,7 @@ export class TransactionWatcherSignerService {
return providerEngine;
}
private static _isUnsubmittedTxExpired(tx: TransactionEntity): boolean {
const now = new Date();
const shouldBeSubmittedBy = new Date(tx.createdAt.getTime() + TX_HASH_RESPONSE_WAIT_TIME_MS);
return tx.status === TransactionStates.Unsubmitted && now > shouldBeSubmittedBy;
return tx.status === TransactionStates.Unsubmitted && Date.now() > tx.expectedAt.getTime();
}
constructor(dbConnection: Connection) {
this._provider = TransactionWatcherSignerService._createWeb3Provider(ETHEREUM_RPC_URL);
Expand All @@ -84,6 +82,18 @@ export class TransactionWatcherSignerService {
this._signers.set(signer.publicAddress, signer);
return signer.publicAddress;
});
this._metricsUpdateTimer = intervalUtils.setAsyncExcludingInterval(
async () => {
await this._updateLiveSatusAsync();
},
TX_WATCHER_UPDATE_METRICS_INTERVAL_MS,
(err: Error) => {
logger.error({
message: `transaction watcher failed to update metrics and heartbeat: ${JSON.stringify(err)}`,
err: err.stack,
});
},
);
this._transactionWatcherTimer = intervalUtils.setAsyncExcludingInterval(
async () => {
logger.trace('syncing transaction status');
Expand Down Expand Up @@ -113,23 +123,6 @@ export class TransactionWatcherSignerService {
help: 'Observed gas prices by the signer in gwei',
labelNames: [SIGNER_ADDRESS_LABEL],
});
if (ENABLE_PROMETHEUS_METRICS) {
this._metricsUpdateTimer = intervalUtils.setAsyncExcludingInterval(
async () => {
logger.trace('updating metrics');
await this._updateSignerBalancesAsync();
logger.trace('heartbeat');
await this._updateSignerStatusAsync();
},
TX_WATCHER_UPDATE_METRICS_INTERVAL_MS,
(err: Error) => {
logger.error({
message: `transaction watcher failed to update metrics and heartbeat: ${JSON.stringify(err)}`,
err: err.stack,
});
},
);
}
}
public stop(): void {
intervalUtils.clearAsyncExcludingInterval(this._transactionWatcherTimer);
Expand Down Expand Up @@ -181,7 +174,7 @@ export class TransactionWatcherSignerService {
txEntity.nonce = web3WrapperUtils.convertHexToNumber(ethereumTxnParams.nonce);
txEntity.from = ethereumTxnParams.from;
this._gasPriceSummary.observe(
{ signer_address: txEntity.from },
{ [SIGNER_ADDRESS_LABEL]: txEntity.from },
Web3Wrapper.toUnitAmount(txEntity.gasPrice, GWEI_DECIMALS).toNumber(),
);
await this._updateTxEntityAsync(txEntity);
Expand Down Expand Up @@ -267,7 +260,10 @@ export class TransactionWatcherSignerService {
});
for (const tx of transactionsToAbort) {
tx.status = TransactionStates.Aborted;
this._transactionsUpdateCounter.inc({ signer_address: tx.from, status: tx.status }, 1);
this._transactionsUpdateCounter.inc(
{ [SIGNER_ADDRESS_LABEL]: tx.from, [TRANSACTION_STATUS_LABEL]: tx.status },
1,
);
await this._transactionRepository.save(tx);
}

Expand All @@ -285,7 +281,7 @@ export class TransactionWatcherSignerService {
for (const tx of unsignedTransactions) {
if (TransactionWatcherSignerService._isUnsubmittedTxExpired(tx)) {
logger.error({
message: `found a transaction in an unsubmitted state waiting longer that ${TX_HASH_RESPONSE_WAIT_TIME_MS}ms`,
message: `found a transaction in an unsubmitted state waiting longer than ${TX_HASH_RESPONSE_WAIT_TIME_MS}ms`,
refHash: tx.refHash,
from: tx.from,
});
Expand Down Expand Up @@ -314,12 +310,12 @@ export class TransactionWatcherSignerService {
return signer;
}
private async _getNextSignerAsync(): Promise<Signer> {
const sortedSigners = await this._getSortedSignerPublicAddressesByAvailabilityAsync();
const [selectedSigner] = await this._getSortedSignerPublicAddressesByAvailabilityAsync();
// TODO(oskar) - add random choice for top signers to better distribute
// the fees.
const signer = this._signers.get(sortedSigners[0]);
const signer = this._signers.get(selectedSigner);
if (signer === undefined) {
throw new Error(`signer with public address: ${sortedSigners[0]} is not available`);
throw new Error(`signer with public address: ${selectedSigner} is not available`);
}

return signer;
Expand All @@ -328,7 +324,7 @@ export class TransactionWatcherSignerService {
const signerMap = new Map<string, { count: number; balance: number }>();
this._availableSignerPublicAddresses.forEach(signerAddress => {
const count = 0;
const balance = this._signerBalances.get(signerAddress) || 0;
const balance = this._signerBalancesEth.get(signerAddress) || 0;
signerMap.set(signerAddress, { count, balance });
});
// TODO(oskar) - move to query builder?
Expand Down Expand Up @@ -383,7 +379,7 @@ export class TransactionWatcherSignerService {
});
continue;
}
if (tx.gasPrice !== undefined && tx.gasPrice.isGreaterThanOrEqualTo(targetGasPrice)) {
if (!utils.isNil(tx.gasPrice) && tx.gasPrice.isGreaterThanOrEqualTo(targetGasPrice)) {
logger.warn({
message:
'unsticking of transaction skipped as the targetGasPrice is less than or equal to the gas price it was submitted with',
Expand Down Expand Up @@ -454,7 +450,10 @@ export class TransactionWatcherSignerService {
}
}
private async _updateTxEntityAsync(txEntity: TransactionEntity): Promise<TransactionEntity> {
this._transactionsUpdateCounter.inc({ signer_address: txEntity.from, status: txEntity.status }, 1);
this._transactionsUpdateCounter.inc(
{ [SIGNER_ADDRESS_LABEL]: txEntity.from, [TRANSACTION_STATUS_LABEL]: txEntity.status },
1,
);
return this._transactionRepository.save(txEntity);
}
private async _updateSignerBalancesAsync(): Promise<void> {
Expand All @@ -473,16 +472,23 @@ export class TransactionWatcherSignerService {
private async _updateSignerBalanceAsync(signerAddress: string): Promise<void> {
const signerBalance = await this._web3Wrapper.getBalanceInWeiAsync(signerAddress);
const balanceInETH = Web3Wrapper.toUnitAmount(signerBalance, ETH_DECIMALS).toNumber();
this._signerBalancesGauge.set({ signer_address: signerAddress }, balanceInETH);
this._signerBalances.set(signerAddress, balanceInETH);
this._signerBalancesGauge.set({ [SIGNER_ADDRESS_LABEL]: signerAddress }, balanceInETH);
this._signerBalancesEth.set(signerAddress, balanceInETH);
}
private _isSignerLive(): boolean {
// TODO: better signer liveliness checks, we just check if any address
// has more than 0.1 ETH available or signing has been explicitly disabled.
return (
[...this._signerBalances.values()].filter(val => val > SIGNER_ETH_BALANCE_CONSIDERED_CRITICAL).length > 0 &&
ENABLE_TRANSACTION_SIGNING
);
const hasAvailableBalance =
Array.from(this._signerBalancesEth.values()).filter(val => val > META_TXN_MIN_SIGNER_ETH_BALANCE).length >
0;
const isEnabled = META_TXN_SIGNING_ENABLED;
return hasAvailableBalance && isEnabled;
}
private async _updateLiveSatusAsync(): Promise<void> {
logger.trace('updating metrics');
await this._updateSignerBalancesAsync();
logger.trace('heartbeat');
await this._updateSignerStatusAsync();
}
private async _updateSignerStatusAsync(): Promise<void> {
// TODO: do we need to find the entity first, for UPDATE?
Expand All @@ -493,13 +499,13 @@ export class TransactionWatcherSignerService {
const statusContent: TransactionWatcherSignerStatus = {
live: this._isSignerLive(),
// tslint:disable-next-line:no-inferred-empty-object-type
balances: [...this._signerBalances.entries()].reduce((acc: object, signerBalance: [string, number]): Record<
string,
number
> => {
const [from, balance] = signerBalance;
return { ...acc, [from]: balance };
}, {}),
balances: Array.from(this._signerBalancesEth.entries()).reduce(
(acc: object, signerBalance: [string, number]): Record<string, number> => {
const [from, balance] = signerBalance;
return { ...acc, [from]: balance };
},
{},
),
};
statusKV.value = JSON.stringify(statusContent);
await this._kvRepository.save(statusKV);
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,7 @@ export interface CalculateMetaTransactionPriceResponse {
export interface PostTransactionResponse {
ethereumTransactionHash: string;
signedEthereumTransaction: string;
zeroExTransactionHash: string;
}

export interface ZeroExTransactionWithoutDomain {
Expand Down
Loading

0 comments on commit d61bc6c

Please sign in to comment.