Refactor Block object and provider getBlock operations.

This commit is contained in:
Richard Moore 2022-11-30 15:41:17 -05:00
parent e1c8b99307
commit 35cd8135d4
7 changed files with 102 additions and 102 deletions

@ -72,7 +72,7 @@ export class ContractUnknownEventPayload extends EventPayload<ContractEventName
defineProperties<ContractUnknownEventPayload>(this, { log }); defineProperties<ContractUnknownEventPayload>(this, { log });
} }
async getBlock(): Promise<Block<string>> { async getBlock(): Promise<Block> {
return await this.log.getBlock(); return await this.log.getBlock();
} }

@ -26,8 +26,7 @@ import {
import { EnsResolver } from "./ens-resolver.js"; import { EnsResolver } from "./ens-resolver.js";
import { import {
formatBlock, formatBlockWithTransactions, formatLog, formatTransactionReceipt, formatBlock, formatLog, formatTransactionReceipt, formatTransactionResponse
formatTransactionResponse
} from "./format.js"; } from "./format.js";
import { Network } from "./network.js"; import { Network } from "./network.js";
import { copyRequest, Block, FeeData, Log, TransactionReceipt, TransactionResponse } from "./provider.js"; import { copyRequest, Block, FeeData, Log, TransactionReceipt, TransactionResponse } from "./provider.js";
@ -473,14 +472,10 @@ export class AbstractProvider implements Provider {
}); });
} }
_wrapBlock(value: BlockParams<string>, network: Network): Block<string> { _wrapBlock(value: BlockParams, network: Network): Block {
return new Block(formatBlock(value), this); return new Block(formatBlock(value), this);
} }
_wrapBlockWithTransactions(value: BlockParams<TransactionResponseParams>, network: Network): Block<TransactionResponse> {
return new Block(formatBlockWithTransactions(value), this);
}
_wrapLog(value: LogParams, network: Network): Log { _wrapLog(value: LogParams, network: Network): Log {
return new Log(formatLog(value), this); return new Log(formatLog(value), this);
} }
@ -884,26 +879,16 @@ export class AbstractProvider implements Provider {
} }
// Queries // Queries
async getBlock(block: BlockTag | string): Promise<null | Block<string>> { async getBlock(block: BlockTag | string, prefetchTxs?: boolean): Promise<null | Block> {
const { network, params } = await resolveProperties({ const { network, params } = await resolveProperties({
network: this.getNetwork(), network: this.getNetwork(),
params: this.#getBlock(block, false) params: this.#getBlock(block, !!prefetchTxs)
}); });
if (params == null) { return null; } if (params == null) { return null; }
return this._wrapBlock(formatBlock(params), network); return this._wrapBlock(formatBlock(params), network);
} }
async getBlockWithTransactions(block: BlockTag | string): Promise<null | Block<TransactionResponse>> {
const { network, params } = await resolveProperties({
network: this.getNetwork(),
params: this.#getBlock(block, true)
});
if (params == null) { return null; }
return this._wrapBlockWithTransactions(formatBlockWithTransactions(params), network);
}
async getTransaction(hash: string): Promise<null | TransactionResponse> { async getTransaction(hash: string): Promise<null | TransactionResponse> {
const { network, params } = await resolveProperties({ const { network, params } = await resolveProperties({
network: this.getNetwork(), network: this.getNetwork(),
@ -1033,7 +1018,7 @@ export class AbstractProvider implements Provider {
}); });
} }
async waitForBlock(blockTag?: BlockTag): Promise<Block<string>> { async waitForBlock(blockTag?: BlockTag): Promise<Block> {
throw new Error(); throw new Error();
//return new Block(<any><unknown>{ }, this); //return new Block(<any><unknown>{ }, this);
} }

@ -14,10 +14,6 @@ import type {
TransactionReceiptParams, TransactionResponseParams, TransactionReceiptParams, TransactionResponseParams,
} from "./formatting.js"; } from "./formatting.js";
import type {
TransactionResponse
} from "./provider.js";
const BN_0 = BigInt(0); const BN_0 = BigInt(0);
@ -111,38 +107,31 @@ export function formatLog(value: any): LogParams {
return _formatLog(value); return _formatLog(value);
} }
function _formatBlockWith(txFunc: FormatFunc): FormatFunc { const _formatBlock = object({
return object({ hash: allowNull(formatHash),
hash: allowNull(formatHash), parentHash: formatHash,
parentHash: formatHash, number: getNumber,
number: getNumber,
timestamp: getNumber, timestamp: getNumber,
nonce: allowNull(formatData), nonce: allowNull(formatData),
difficulty: getBigInt, difficulty: getBigInt,
gasLimit: getBigInt, gasLimit: getBigInt,
gasUsed: getBigInt, gasUsed: getBigInt,
miner: allowNull(getAddress), miner: allowNull(getAddress),
extraData: formatData, extraData: formatData,
transactions: arrayOf(txFunc), baseFeePerGas: allowNull(getBigInt)
});
baseFeePerGas: allowNull(getBigInt) export function formatBlock(value: any): BlockParams {
const result = _formatBlock(value);
result.transactions = value.transactions.map((tx: string | TransactionResponseParams) => {
if (typeof(tx) === "string") { return tx; }
return formatTransactionResponse(tx);
}); });
} return result;
const _formatBlock = _formatBlockWith(formatHash);
export function formatBlock(value: any): BlockParams<string> {
return _formatBlock(value);
}
const _formatBlockWithTransactions = _formatBlockWith(formatTransactionResponse);
export function formatBlockWithTransactions(value: any): BlockParams<TransactionResponse> {
return _formatBlockWithTransactions(value);
} }
const _formatReceiptLog = object({ const _formatReceiptLog = object({

@ -72,7 +72,7 @@ export interface PreparedTransactionRequest {
////////////////////// //////////////////////
// Block // Block
export interface BlockParams<T extends string | TransactionResponseParams> { export interface BlockParams {
hash?: null | string; hash?: null | string;
number: number; number: number;
@ -91,7 +91,7 @@ export interface BlockParams<T extends string | TransactionResponseParams> {
baseFeePerGas: null | bigint; baseFeePerGas: null | bigint;
transactions: ReadonlyArray<T>; transactions: ReadonlyArray<string | TransactionResponseParams>;
}; };

@ -2,10 +2,8 @@ import { defineProperties } from "../utils/properties.js";
import { assertArgument } from "../utils/index.js"; import { assertArgument } from "../utils/index.js";
import type { BlockParams, TransactionResponseParams } from "./formatting.js";
import type { import type {
Block, FeeData, Provider, TransactionResponse FeeData, Provider
} from "./provider.js"; } from "./provider.js";
@ -142,7 +140,7 @@ export class FeeDataNetworkPlugin extends NetworkPlugin {
return new FeeDataNetworkPlugin(this.#feeDataFunc); return new FeeDataNetworkPlugin(this.#feeDataFunc);
} }
} }
/*
export class CustomBlockNetworkPlugin extends NetworkPlugin { export class CustomBlockNetworkPlugin extends NetworkPlugin {
readonly #blockFunc: (provider: Provider, block: BlockParams<string>) => Block<string>; readonly #blockFunc: (provider: Provider, block: BlockParams<string>) => Block<string>;
readonly #blockWithTxsFunc: (provider: Provider, block: BlockParams<TransactionResponseParams>) => Block<TransactionResponse>; readonly #blockWithTxsFunc: (provider: Provider, block: BlockParams<TransactionResponseParams>) => Block<TransactionResponse>;
@ -157,7 +155,7 @@ export class CustomBlockNetworkPlugin extends NetworkPlugin {
return await this.#blockFunc(provider, block); return await this.#blockFunc(provider, block);
} }
async getBlockWithTransactions(provider: Provider, block: BlockParams<TransactionResponseParams>): Promise<Block<TransactionResponse>> { async getBlockions(provider: Provider, block: BlockParams<TransactionResponseParams>): Promise<Block<TransactionResponse>> {
return await this.#blockWithTxsFunc(provider, block); return await this.#blockWithTxsFunc(provider, block);
} }
@ -165,3 +163,4 @@ export class CustomBlockNetworkPlugin extends NetworkPlugin {
return new CustomBlockNetworkPlugin(this.#blockFunc, this.#blockWithTxsFunc); return new CustomBlockNetworkPlugin(this.#blockFunc, this.#blockWithTxsFunc);
} }
} }
*/

@ -358,10 +358,7 @@ export class FallbackProvider extends AbstractProvider {
return await provider.getBalance(req.address, req.blockTag); return await provider.getBalance(req.address, req.blockTag);
case "getBlock": { case "getBlock": {
const block = ("blockHash" in req) ? req.blockHash: req.blockTag; const block = ("blockHash" in req) ? req.blockHash: req.blockTag;
if (req.includeTransactions) { return await provider.getBlock(block, req.includeTransactions);
return await provider.getBlockWithTransactions(block);
}
return await provider.getBlock(block);
} }
case "getBlockNumber": case "getBlockNumber":
return await provider.getBlockNumber(); return await provider.getBlockNumber();

@ -242,7 +242,7 @@ export interface BlockParams<T extends string | TransactionResponseParams> {
}; };
*/ */
export interface MinedBlock<T extends string | TransactionResponse = string> extends Block<T> { export interface MinedBlock extends Block {
readonly number: number; readonly number: number;
readonly hash: string; readonly hash: string;
readonly timestamp: number; readonly timestamp: number;
@ -254,7 +254,7 @@ export interface MinedBlock<T extends string | TransactionResponse = string> ext
* A **Block** represents the data associated with a full block on * A **Block** represents the data associated with a full block on
* Ethereum. * Ethereum.
*/ */
export class Block<T extends string | TransactionResponse> implements BlockParams<T>, Iterable<T> { export class Block implements BlockParams, Iterable<string> {
/** /**
* The provider connected to the block used to fetch additional details * The provider connected to the block used to fetch additional details
* if necessary. * if necessary.
@ -333,7 +333,7 @@ export class Block<T extends string | TransactionResponse> implements BlockParam
*/ */
readonly baseFeePerGas!: null | bigint; readonly baseFeePerGas!: null | bigint;
readonly #transactions: ReadonlyArray<T>; readonly #transactions: Array<string | TransactionResponse>;
/** /**
* Create a new **Block** object. * Create a new **Block** object.
@ -341,16 +341,16 @@ export class Block<T extends string | TransactionResponse> implements BlockParam
* This should generally not be necessary as the unless implementing a * This should generally not be necessary as the unless implementing a
* low-level library. * low-level library.
*/ */
constructor(block: BlockParams<T>, provider: Provider) { constructor(block: BlockParams, provider: Provider) {
this.#transactions = Object.freeze(block.transactions.map((tx) => { this.#transactions = block.transactions.map((tx) => {
if (typeof(tx) !== "string" && tx.provider !== provider) { if (typeof(tx) !== "string") {
return <T>(new TransactionResponse(tx, provider)); return new TransactionResponse(tx, provider);
} }
return <T>tx; return tx;
}));; });
defineProperties<Block<T>>(this, { defineProperties<Block>(this, {
provider, provider,
hash: getValue(block.hash), hash: getValue(block.hash),
@ -373,9 +373,14 @@ export class Block<T extends string | TransactionResponse> implements BlockParam
} }
/** /**
* Returns the list of transactions. * Returns the list of transaction hashes.
*/ */
get transactions(): ReadonlyArray<T> { return this.#transactions; } get transactions(): ReadonlyArray<string> {
return this.#transactions.map((tx) => {
if (typeof(tx) === "string") { return tx; }
return tx.hash;
});
}
/** /**
* Returns a JSON-friendly value. * Returns a JSON-friendly value.
@ -398,13 +403,14 @@ export class Block<T extends string | TransactionResponse> implements BlockParam
}; };
} }
[Symbol.iterator](): Iterator<T> { [Symbol.iterator](): Iterator<string> {
let index = 0; let index = 0;
const txs = this.transactions;
return { return {
next: () => { next: () => {
if (index < this.length) { if (index < this.length) {
return { return {
value: this.transactions[index++], done: false value: txs[index++], done: false
} }
} }
return { value: undefined, done: true }; return { value: undefined, done: true };
@ -415,7 +421,7 @@ export class Block<T extends string | TransactionResponse> implements BlockParam
/** /**
* The number of transactions in this block. * The number of transactions in this block.
*/ */
get length(): number { return this.transactions.length; } get length(): number { return this.#transactions.length; }
/** /**
* The [date](link-js-data) this block was included at. * The [date](link-js-data) this block was included at.
@ -424,14 +430,31 @@ export class Block<T extends string | TransactionResponse> implements BlockParam
if (this.timestamp == null) { return null; } if (this.timestamp == null) { return null; }
return new Date(this.timestamp * 1000); return new Date(this.timestamp * 1000);
} }
// @TODO: Don't have 2 block types?
// just populate this with all the values? simplifies provider
/** /**
* Get the transaction at %%indexe%% within this block. * Get the transaction at %%indexe%% within this block.
*/ */
async getTransaction(index: number): Promise<TransactionResponse> { async getTransaction(indexOrHash: number | string): Promise<TransactionResponse> {
const tx = this.transactions[index]; // Find the internal value by its index or hash
let tx: string | TransactionResponse | undefined = undefined;
if (typeof(indexOrHash) === "number") {
tx = this.#transactions[indexOrHash];
} else {
const hash = indexOrHash.toLowerCase();
for (const v of this.#transactions) {
if (typeof(v) === "string") {
if (v !== hash) { continue; }
tx = v;
break;
} else {
if (v.hash === hash) { continue; }
tx = v;
break;
}
}
}
if (tx == null) { throw new Error("no such tx"); } if (tx == null) { throw new Error("no such tx"); }
if (typeof(tx) === "string") { if (typeof(tx) === "string") {
return <TransactionResponse>(await this.provider.getTransaction(tx)); return <TransactionResponse>(await this.provider.getTransaction(tx));
} else { } else {
@ -445,12 +468,12 @@ export class Block<T extends string | TransactionResponse> implements BlockParam
* If true, the block has been typed-gaurded that all mined * If true, the block has been typed-gaurded that all mined
* properties are non-null. * properties are non-null.
*/ */
isMined(): this is MinedBlock<T> { return !!this.hash; } isMined(): this is MinedBlock { return !!this.hash; }
/** /**
* *
*/ */
isLondon(): this is (Block<T> & { baseFeePerGas: bigint }) { isLondon(): this is (Block & { baseFeePerGas: bigint }) {
return !!this.baseFeePerGas; return !!this.baseFeePerGas;
} }
@ -536,16 +559,22 @@ export class Log implements LogParams {
}; };
} }
async getBlock(): Promise<Block<string>> { async getBlock(): Promise<Block> {
return <Block<string>>(await this.provider.getBlock(this.blockHash)); const block = await this.provider.getBlock(this.blockHash);
assert(!!block, "failed to find transaction", "UNKNOWN_ERROR", { });
return block;
} }
async getTransaction(): Promise<TransactionResponse> { async getTransaction(): Promise<TransactionResponse> {
return <TransactionResponse>(await this.provider.getTransaction(this.transactionHash)); const tx = await this.provider.getTransaction(this.transactionHash);
assert(!!tx, "failed to find transaction", "UNKNOWN_ERROR", { });
return tx;
} }
async getTransactionReceipt(): Promise<TransactionReceipt> { async getTransactionReceipt(): Promise<TransactionReceipt> {
return <TransactionReceipt>(await this.provider.getTransactionReceipt(this.transactionHash)); const receipt = await this.provider.getTransactionReceipt(this.transactionHash);
assert(!!receipt, "failed to find transaction receipt", "UNKNOWN_ERROR", { });
return receipt;
} }
removedEvent(): OrphanFilter { removedEvent(): OrphanFilter {
@ -697,7 +726,7 @@ export class TransactionReceipt implements TransactionReceiptParams, Iterable<Lo
return this.gasUsed * this.gasPrice; return this.gasUsed * this.gasPrice;
} }
async getBlock(): Promise<Block<string>> { async getBlock(): Promise<Block> {
const block = await this.provider.getBlock(this.blockHash); const block = await this.provider.getBlock(this.blockHash);
if (block == null) { throw new Error("TODO"); } if (block == null) { throw new Error("TODO"); }
return block; return block;
@ -863,7 +892,7 @@ export class TransactionResponse implements TransactionLike<string>, Transaction
}; };
} }
async getBlock(): Promise<null | Block<string>> { async getBlock(): Promise<null | Block> {
let blockNumber = this.blockNumber; let blockNumber = this.blockNumber;
if (blockNumber == null) { if (blockNumber == null) {
const tx = await this.getTransaction(); const tx = await this.getTransaction();
@ -919,15 +948,19 @@ export class TransactionResponse implements TransactionLike<string>, Transaction
// Get the next block to scan // Get the next block to scan
if (stopScanning) { return null; } if (stopScanning) { return null; }
const block = await this.provider.getBlockWithTransactions(nextScan); const block = await this.provider.getBlock(nextScan, true);
// This should not happen; but we'll try again shortly // This should not happen; but we'll try again shortly
if (block == null) { return; } if (block == null) { return; }
for (const tx of block.transactions) { // We were mined; no replacement
for (const hash of block) {
if (hash === this.hash) { return; }
}
// We were mined; no replacement // Search for the transaction that replaced us
if (tx.hash === this.hash) { return; } for (let i = 0; i < block.length; i++) {
const tx: TransactionResponse = await block.getTransaction(i);
if (tx.from === this.from && tx.nonce === this.nonce) { if (tx.from === this.from && tx.nonce === this.nonce) {
// Get the receipt // Get the receipt
@ -1284,16 +1317,13 @@ export interface Provider extends ContractRunner, EventEmitterable<ProviderEvent
/** /**
* Resolves to the block for %%blockHashOrBlockTag%%. * Resolves to the block for %%blockHashOrBlockTag%%.
*/
getBlock(blockHashOrBlockTag: BlockTag | string): Promise<null | Block<string>>;
/**
* Resolves to the block for %%blockHashOrBlockTag%%, including each
* full transaction.
* *
* If a block is unknonw, this resolved to ``null``. * If %%prefetchTxs%%, and the backend supports including transactions
* with block requests, all transactions will be included and the
* [[Block]] object will not need to make remote calls for getting
* transactions.
*/ */
getBlockWithTransactions(blockHashOrBlockTag: BlockTag | string): Promise<null | Block<TransactionResponse>> getBlock(blockHashOrBlockTag: BlockTag | string, prefetchTxs?: boolean): Promise<null | Block>;
/** /**
* Resolves to the transaction for %%hash%%. * Resolves to the transaction for %%hash%%.
@ -1360,5 +1390,5 @@ export interface Provider extends ContractRunner, EventEmitterable<ProviderEvent
* This can be useful for waiting some number of blocks by using * This can be useful for waiting some number of blocks by using
* the ``currentBlockNumber + N``. * the ``currentBlockNumber + N``.
*/ */
waitForBlock(blockTag?: BlockTag): Promise<Block<string>>; waitForBlock(blockTag?: BlockTag): Promise<Block>;
} }