Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

avoid unnecessary binary search in gas estimation #876

Merged
merged 9 commits into from
Nov 13, 2023
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
6 changes: 3 additions & 3 deletions examples/docker-compose-bodhi-stack.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
services:
mandala-node:
image: ghcr.io/acalanetwork/mandala-node:sha-fe67fd1
image: ghcr.io/acalanetwork/mandala-node:sha-37d9e36
container_name: mandala-node
ports:
- 9944:9944
Expand All @@ -22,7 +22,7 @@ services:
POSTGRES_PASSWORD: postgres

subquery-node:
image: acala/evm-subql:2.7.11
image: acala/evm-subql:2.7.13
container_name: subquery-node
ports:
- 3000:3000
Expand Down Expand Up @@ -69,7 +69,7 @@ services:
- --indexer=http://subquery-node:3000

eth-rpc-adapter-server:
image: acala/eth-rpc-adapter:2.7.11
image: acala/eth-rpc-adapter:2.7.13
container_name: eth-rpc-adapter-server
restart: always
depends_on:
Expand Down
65 changes: 17 additions & 48 deletions packages/bodhi/src/BodhiSigner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { Deferrable } from '@ethersproject/properties';
import { KeyringPair } from '@polkadot/keyring/types';
import { Logger } from '@ethersproject/logger';
import { MultiSigner } from './MultiSigner';
import { Signer, SubmittableExtrinsic } from '@polkadot/api/types';
import { Signer } from '@polkadot/api/types';
import { SubmittableResult } from '@polkadot/api';
import { SubstrateSigner } from './SubstrateSigner';
import { TransactionRequest, TransactionResponse } from '@ethersproject/abstract-provider';
Expand Down Expand Up @@ -208,58 +208,27 @@ export class BodhiSigner extends AbstractSigner implements TypedDataSigner {
..._transaction,
};

const resources = await this.provider.estimateResources(transaction);

let gasLimit: BigNumber;
let storageLimit: BigNumber;

let totalLimit = await transaction.gasLimit;

if (totalLimit === null || totalLimit === undefined) {
gasLimit = resources.gasLimit;
storageLimit = resources.usedStorage;
totalLimit = resources.gasLimit.add(resources.usedStorage);
} else {
const estimateTotalLimit = resources.gasLimit.add(resources.usedStorage);
gasLimit = BigNumber.from(totalLimit).mul(resources.gasLimit).div(estimateTotalLimit).add(1);
storageLimit = BigNumber.from(totalLimit).mul(resources.usedStorage).div(estimateTotalLimit).add(1);
}

transaction.gasLimit = totalLimit;
const { gasLimit, usedStorage: storageLimit } = await this.provider.estimateResources(transaction);

const tx = await this.populateTransaction(transaction);

const data = tx.data?.toString() ?? '0x';
const from = tx.from;

if (!data) {
return logger.throwError('Request data not found');
}

if (!from) {
return logger.throwError('Request from not found');
}
const createParams = [
data,
toBN(tx.value),
toBN(gasLimit),
toBN(storageLimit.isNegative() ? 0 : storageLimit),
(tx.accessList as any) || [],
] as const;

let extrinsic: SubmittableExtrinsic<'promise'>;
const callParams = [
tx.to,
...createParams,
] as const;

if (!tx.to) {
extrinsic = this.provider.api.tx.evm.create(
data,
toBN(tx.value),
toBN(gasLimit),
toBN(storageLimit.isNegative() ? 0 : storageLimit),
(tx.accessList as any) || []
);
} else {
extrinsic = this.provider.api.tx.evm.call(
tx.to,
data,
toBN(tx.value),
toBN(gasLimit),
toBN(storageLimit.isNegative() ? 0 : storageLimit),
(tx.accessList as any) || []
);
}
const extrinsic = tx.to
? this.provider.api.tx.evm.call(...callParams)
: this.provider.api.tx.evm.create(...createParams);

await extrinsic.signAsync(this.substrateAddress);

Expand All @@ -270,7 +239,7 @@ export class BodhiSigner extends AbstractSigner implements TypedDataSigner {
.then(() => {
resolve({
hash: extrinsic.hash.toHex(),
from: from || '',
from: tx.from || '',
confirmations: 0,
nonce: toBN(tx.nonce).toNumber(),
gasLimit: BigNumber.from(tx.gasLimit || '0'),
Expand Down
2 changes: 1 addition & 1 deletion packages/eth-providers/src/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ describe('eth call error handling', () => {

describe('checkEvmExecutionError', () => {
const commonData = {
used_gas: 0,
used_gas: '0x0',
used_storage: 0,
logs: [],
};
Expand Down
89 changes: 53 additions & 36 deletions packages/eth-providers/src/base-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ export interface CallInfo {
fatal?: any;
};
value: string;
used_gas: number;
used_gas: string;
used_storage: number;
logs: Log[];
};
Expand Down Expand Up @@ -689,7 +689,7 @@ export abstract class BaseProvider extends AbstractProvider {
const blockTag = await this._ensureSafeModeBlockTagFinalization(await parseBlockTag(_blockTag));

const [address, blockHash] = await Promise.all([
this._getAddress(addressOrName),
addressOrName,
this._getBlockHash(blockTag),
]);

Expand Down Expand Up @@ -740,7 +740,7 @@ export abstract class BaseProvider extends AbstractProvider {
): Promise<number> => {
const resolvedBlockTag = await blockTag;

const address = await this._getAddress(addressOrName);
const address = await addressOrName;
const [substrateAddress, blockHash] = await Promise.all([
this.getSubstrateAddress(address),
this._getBlockHash(blockTag),
Expand All @@ -765,7 +765,7 @@ export abstract class BaseProvider extends AbstractProvider {
if (blockTag === 'pending') return '0x';

const [address, blockHash] = await Promise.all([
this._getAddress(addressOrName),
addressOrName,
this._getBlockHash(blockTag),
]);

Expand Down Expand Up @@ -862,7 +862,7 @@ export abstract class BaseProvider extends AbstractProvider {
const blockTag = await this._ensureSafeModeBlockTagFinalization(await parseBlockTag(_blockTag));

const [address, blockHash, resolvedPosition] = await Promise.all([
this._getAddress(addressOrName),
addressOrName,
this._getBlockHash(blockTag),
Promise.resolve(position).then(hexValue),
]);
Expand Down Expand Up @@ -1108,44 +1108,66 @@ export abstract class BaseProvider extends AbstractProvider {
storageLimit: STORAGE_LIMIT,
};

const { used_gas: usedGas, used_storage: usedStorage } = await this._ethCall(txRequest);
const gasInfo = await this._ethCall(txRequest);
const usedGas = BigNumber.from(gasInfo.used_gas).toNumber();
const usedStorage = gasInfo.used_storage;

/* ----------
try using a gasLimit slightly more than actual used gas
if it already works, which should be the usual case
we don't need to waste time doing binary search
---------- */
let gasLimit = Math.floor(usedGas * 1.2);
let gasAlreadyWorks = true;
try {
await this._ethCall({
...txRequest,
gasLimit,
});
} catch {
gasAlreadyWorks = false;
}

// binary search the best passing gasLimit
let lowest = MIN_GAS_LIMIT;
let highest = MAX_GAS_LIMIT;
let mid = Math.min(usedGas * 3, Math.floor((lowest + highest) / 2));
let prevHighest = highest;
while (highest - lowest > 1) {
try {
await this._ethCall({
...txRequest,
gasLimit: mid,
});
highest = mid;

if ((prevHighest - highest) / prevHighest < 0.1) break;
prevHighest = highest;
} catch (e: any) {
if ((e.message as string).includes('revert') || (e.message as string).includes('outOfGas')) {
lowest = mid;
} else {
throw e;
if (!gasAlreadyWorks) {
// need to binary search the best passing gasLimit
let lowest = MIN_GAS_LIMIT;
let highest = MAX_GAS_LIMIT;
let mid = Math.min(usedGas * 3, Math.floor((lowest + highest) / 2));
let prevHighest = highest;
while (highest - lowest > 1) {
try {
await this._ethCall({
...txRequest,
gasLimit: mid,
});
highest = mid;

if ((prevHighest - highest) / prevHighest < 0.1) break;
prevHighest = highest;
} catch (e: any) {
if ((e.message as string).includes('revert') || (e.message as string).includes('outOfGas')) {
lowest = mid;
} else {
throw e;
}
}

mid = Math.floor((highest + lowest) / 2);
}

mid = Math.floor((highest + lowest) / 2);
gasLimit = highest;
}

return {
usedGas: BigNumber.from(usedGas), // actual used gas
gasLimit: BigNumber.from(highest), // gasLimit to pass execution
usedGas: BigNumber.from(usedGas), // actual used gas
gasLimit: BigNumber.from(gasLimit), // gasLimit to pass execution
usedStorage: BigNumber.from(usedStorage),
};
};

getSubstrateAddress = async (addressOrName: string, blockTag?: BlockTag | Promise<BlockTag>): Promise<string> => {
const [address, blockHash] = await Promise.all([
this._getAddress(addressOrName),
addressOrName,
this._getBlockHash(blockTag),
]);

Expand All @@ -1171,7 +1193,7 @@ export abstract class BaseProvider extends AbstractProvider {
}

const [address, blockHash] = await Promise.all([
this._getAddress(addressOrName),
addressOrName,
this._getBlockHash(blockTag),
]);

Expand Down Expand Up @@ -1628,11 +1650,6 @@ export abstract class BaseProvider extends AbstractProvider {
}
};

_getAddress = async (addressOrName: string | Promise<string>): Promise<string> => {
addressOrName = await addressOrName;
return addressOrName;
};

// from chain only
getReceiptAtBlockFromChain = async (
txHash: string | Promise<string>,
Expand Down
Loading