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

chore: blk-01 #1794

Merged
merged 11 commits into from
Feb 12, 2025
Merged
Show file tree
Hide file tree
Changes from 2 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
85 changes: 47 additions & 38 deletions packages/core/src/vcdm/Revision.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,44 @@
import { InvalidDataType } from '@vechain/sdk-errors';
import { Hex } from './Hex';
import { HexUInt } from './HexUInt';
import { Txt } from './Txt';

/**
* Represents a revision for a Thor transaction or block.
*
* @remarks The string representation of the revision is always expressed as a number in base 10.
* Represents a revision for a Thor transaction or block
* Revision strings can be one of the following:
* - "best": indicating the best revision
* - "finalized": indicating a finalized revision
* - "next": indicating the next revision
* - "justified": indicating the justified revision
* - A hex string prefixed with "0x" indicating a specific block id
* - A positive number indicating a specific block number
*
* @extends Txt
*/
class Revision extends Txt {
/**
* Regular expression pattern for revision strings.
* Revision strings can be one of the following:
* - "best": indicating the best revision
* - "finalized": indicating a finalized revision
* - A positive numeric string indicating a specific revision
*
* @type {RegExp}
*/
private static readonly REGEX_DECIMAL_REVISION = /^(best|finalized|\d+)$/;
private static readonly VALID_REVISION_REGEX =
/^(best|finalized|next|justified|0x[a-fA-F0-9]+|\d+)$/;

/**
* Determines if the given value is valid.
* This is true if the given value is
* - "best" string or {@link Txt}: indicating the best revision;
* - "finalized" string or {@link Txt}: indicating a finalized revision;
* - a positive number;
* - a positive numeric decimal or `0x` prefixed hexadecimal string indicating a specific revision,
*
* @param {bigint | number | string | Hex | Txt} value - The value to be validated.
* Determines if the given value is a valid revision.
* @param {bigint| number | string | Hex} value - The value to be validated.
* @returns {boolean} - Returns `true` if the value is valid, `false` otherwise.
*/
public static isValid(value: number | string): boolean {
public static isValid(value: bigint | number | string | Hex): boolean {
if (typeof value === 'number') {
return Number.isInteger(value) && value >= 0;
}
return (
HexUInt.isValid0x(value) ||
Revision.REGEX_DECIMAL_REVISION.test(value)
);
if (typeof value === 'bigint') {
return value >= BigInt(0);
}
if (value instanceof Hex) {
return Revision.isValid(value.bi);
}
return Revision.VALID_REVISION_REGEX.test(value);
}

/**
Expand All @@ -59,24 +57,25 @@ class Revision extends Txt {
*/
public static of(value: bigint | number | string | Uint8Array | Hex): Txt {
try {
let txt: string;
if (value instanceof Hex) {
txt = value.bi.toString();
} else if (
typeof value === 'bigint' ||
typeof value === 'number' ||
typeof value === 'string'
) {
txt = `${value}`;
} else {
txt = Txt.of(value).toString();
// handle Uint8Array which is needed to extend Txt.of
if (ArrayBuffer.isView(value)) {
const txtValue = Txt.of(value).toString();
if (Revision.isValid(txtValue)) {
return new Revision(txtValue);
} else {
throw new InvalidDataType('Revision.of', 'not a revision', {
value: `${value}`
});
}
}
if (Revision.isValid(txt)) {
return new Revision(txt);
// handle other types
if (Revision.isValid(value)) {
return new Revision(`${value}`);
} else {
throw new InvalidDataType('Revision.of', 'not a revision', {
value: `${value}`
});
}
throw new InvalidDataType('Revision.of', 'not a revision', {
value: `${value}`
});
} catch (e) {
throw new InvalidDataType('Revision.of', 'not a revision', {
value: `${value}`,
Expand All @@ -94,6 +93,16 @@ class Revision extends Txt {
* Return the `finalized` revision instance.
*/
public static readonly FINALIZED: Revision = Revision.of('finalized');

/**
* Return the `next` revision instance.
*/
public static readonly NEXT: Revision = Revision.of('next');

/**
* Return the `justified` revision instance.
*/
public static readonly JUSTIFIED: Revision = Revision.of('justified');
}

export { Revision };
5 changes: 4 additions & 1 deletion packages/core/tests/vcdm/Revision.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ describe('Revision class tests', () => {
expect(Revision.isValid('123.57')).toBeFalsy();
});

test('Return false for not numeric nor `best` nor `finalized` value', () => {
test('Return false for not numeric not a block tag', () => {
expect(Revision.isValid('ABadBabe')).toBeFalsy();
});

Expand All @@ -54,6 +54,9 @@ describe('Revision class tests', () => {
test('Return true for `finalized` value', () => {
expect(Revision.isValid('finalized')).toBeTruthy();
});
test('Return true for `next` value', () => {
expect(Revision.isValid('next')).toBeTruthy();
});
});
});

Expand Down
82 changes: 44 additions & 38 deletions packages/network/src/provider/utils/const/blocks/blocks.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,52 @@
import { Hex } from '@vechain/sdk-core';
import { type BlockQuantityInputRPC } from '../../rpc-mapper/types';
import { HexUInt, Revision } from '@vechain/sdk-core';

type DefaultBlock =
| `0x${string}`
| 'latest'
| 'earliest'
| 'pending'
| 'safe'
| 'finalized';
const defaultBlockTags: DefaultBlock[] = [
'latest',
'earliest',
'pending',
'safe',
'finalized'
];

/**
* Get the correct block number for the given block number.
*
* @param block - The block tag to get.
* 'latest' or 'earliest' or 'pending' or 'safe' or 'finalized'
* or an object: { blockNumber: number } or { blockHash: string }
* Maps the Ethereum "default block" type to VeChainThor Revision type.
* Ethereum "default block" can be:
* - 'latest' or 'earliest' or 'pending' or 'safe' or 'finalized'
* - a hexadecimal block number
* VeChainThor revision type can be:
* - 'best', 'next', 'justified', 'finalized'
* - a hexadecimal block Id
* - a integer block number
*
* @note
* * Currently VeChainThor supports 'earliest', 'latest' and 'finalized' as block tags.
* So 'pending' and 'safe' are converted to 'best' which is the alias for 'latest' and 'finalized' in VeChainThor.
* @param defaultBlock - The Ethereum default block type to convert
* @returns The VeChainThor revision type
*/
const getCorrectBlockNumberRPCToVeChain = (
block: BlockQuantityInputRPC
): string => {
// Tag block number
if (typeof block === 'string') {
// Latest, Finalized, Safe blocks
if (
block === 'latest' ||
block === 'finalized' ||
block === 'safe' ||
block === 'pending'
)
// 'best' is the alias for 'latest', 'finalized' and 'safe' in VeChainThor
return 'best';

// Earliest block
if (block === 'earliest') return Hex.of(0).toString();

// Hex number of block
return block;
const DefaultBlockToRevision = (defaultBlock: DefaultBlock): Revision => {
// if valid hex then return integer block number
if (HexUInt.isValid(defaultBlock)) {
return Revision.of(HexUInt.of(defaultBlock).n.toString());
}

// Object with block number
if (block.blockNumber !== undefined) {
return Hex.of(block.blockNumber).toString();
// check if default block is a valid block tag
if (!defaultBlockTags.includes(defaultBlock)) {
throw new Error(`Invalid block tag: ${defaultBlock}`);
}
// map block tag to VeChainThor revision
if (defaultBlock === 'earliest') {
return Revision.of(HexUInt.of(0));
} else if (defaultBlock === 'safe') {
return Revision.of('justified');
} else if (defaultBlock === 'finalized') {
return Revision.of('finalized');
} else {
return Revision.of('best');
}

// Object with block hash - Default case
return block.blockHash;
};

export { getCorrectBlockNumberRPCToVeChain };
export { type DefaultBlock, DefaultBlockToRevision };
1 change: 0 additions & 1 deletion packages/network/src/provider/utils/rpc-mapper/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export * from './methods';
export * from './rpc-mapper';
export type * from './types.d';
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ import {
JSONRPCInvalidParams,
stringifyData
} from '@vechain/sdk-errors';
import { getCorrectBlockNumberRPCToVeChain } from '../../../const';
import { type DefaultBlock, DefaultBlockToRevision } from '../../../const';
import { type TransactionObjectInput } from './types';
import { type BlockQuantityInputRPC } from '../../types';
import {
type SimulateTransactionClause,
type SimulateTransactionOptions,
Expand Down Expand Up @@ -41,7 +40,7 @@ const ethCall = async (
try {
const [inputOptions, block] = params as [
TransactionObjectInput,
BlockQuantityInputRPC
DefaultBlock
];

// Simulate transaction
Expand All @@ -54,7 +53,7 @@ const ethCall = async (
} satisfies SimulateTransactionClause
],
{
revision: getCorrectBlockNumberRPCToVeChain(block),
revision: DefaultBlockToRevision(block).toString(),
gas:
inputOptions.gas !== undefined
? parseInt(inputOptions.gas, 16)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ import {
type SimulateTransactionClause,
type ThorClient
} from '../../../../../thor-client';
import { getCorrectBlockNumberRPCToVeChain } from '../../../const';
import { type BlockQuantityInputRPC } from '../../types';
import { type DefaultBlock, DefaultBlockToRevision } from '../../../const';
import { RPC_DOCUMENTATION_URL } from '../../../../../utils';

/**
Expand Down Expand Up @@ -42,10 +41,11 @@ const ethEstimateGas = async (
// NOTE: The standard requires block parameter.
// Here it is ignored and can be added in the future compatibility reasons.
// (INPUT CHECK TAKE CARE OF THIS)
const [inputOptions, revision] = params as [
const [inputOptions, defaultBlock] = params as [
TransactionObjectInput,
BlockQuantityInputRPC?
DefaultBlock
];
const revision = DefaultBlockToRevision(defaultBlock);

const estimatedGas = await thorClient.gas.estimateGas(
[
Expand All @@ -57,10 +57,7 @@ const ethEstimateGas = async (
],
inputOptions.from,
{
revision:
revision !== undefined
? getCorrectBlockNumberRPCToVeChain(revision)
: undefined
revision: revision.toString()
}
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ import {
JSONRPCInvalidParams,
stringifyData
} from '@vechain/sdk-errors';
import type { BlockQuantityInputRPC } from '../../types';
import { getCorrectBlockNumberRPCToVeChain } from '../../../const';
import { type DefaultBlock, DefaultBlockToRevision } from '../../../const';
import { RPC_DOCUMENTATION_URL } from '../../../../../utils';
import { Address, Revision } from '@vechain/sdk-core';
import { Address } from '@vechain/sdk-core';

/**
* RPC Method eth_getBalance implementation
Expand Down Expand Up @@ -40,13 +39,13 @@ const ethGetBalance = async (
);

try {
const [address, block] = params as [string, BlockQuantityInputRPC];
const [address, block] = params as [string, DefaultBlock];

// Get the account details
const accountDetails = await thorClient.accounts.getAccount(
Address.of(address),
{
revision: Revision.of(getCorrectBlockNumberRPCToVeChain(block))
revision: DefaultBlockToRevision(block)
}
);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { ThorId } from '@vechain/sdk-core';
import { RPC_DOCUMENTATION_URL } from '../../../../../utils';
import { ethGetBlockByNumber } from '../eth_getBlockByNumber';
import {
JSONRPCInternalError,
JSONRPCInvalidParams,
stringifyData
} from '@vechain/sdk-errors';
import { type BlocksRPC } from '../../../formatter';
import { blocksFormatter, type BlocksRPC } from '../../../formatter';
import { type ThorClient } from '../../../../../thor-client';
import { ethChainId } from '../eth_chainId';

/**
* RPC Method eth_getBlockByHash implementation
Expand All @@ -19,6 +19,7 @@ import { type ThorClient } from '../../../../../thor-client';
* * params[0]: The block hash of block to get.
* * params[1]: The transaction hydrated detail flag. If true, the block will contain the transaction details, otherwise it will only contain the transaction hashes.
* @returns the block at the given block hash formatted to the RPC standard or null if the block does not exist.
* @note Ethereum block hash is passed to Thor as the block id.
* @throws {JSONRPCInvalidParams, JSONRPCInternalError}
*/
const ethGetBlockByHash = async (
Expand All @@ -39,8 +40,22 @@ const ethGetBlockByHash = async (
);

try {
// Return the block by number (in this case, the block hash is the block number)
return await ethGetBlockByNumber(thorClient, params);
const [blockHash, isTxDetail] = params as [string, boolean];

let chainId: string = '0x0';

// If the transaction detail flag is set, we need to get the chain id
if (isTxDetail) {
chainId = await ethChainId(thorClient);
}

const block = isTxDetail
? await thorClient.blocks.getBlockExpanded(blockHash)
: await thorClient.blocks.getBlockCompressed(blockHash);

return block !== null
? blocksFormatter.formatToRPCStandard(block, chainId)
: null;
} catch (e) {
throw new JSONRPCInternalError(
'eth_getBlockByHash()',
Expand Down
Loading
Loading