Skip to content

Commit

Permalink
chore: blk-01 (#1794)
Browse files Browse the repository at this point in the history
* chore: blk-01
  • Loading branch information
claytonneal authored Feb 12, 2025
1 parent e2d2d5e commit 5121de3
Show file tree
Hide file tree
Showing 26 changed files with 274 additions and 203 deletions.
9 changes: 9 additions & 0 deletions docker/rpc-proxy/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ RUN /bin/sh ./adjust-packages.sh ./
# Stage 2: Serve the app using node
FROM node:20.17.0-alpine3.20 AS runner

# Update package list and upgrade OpenSSL
RUN apk update && \
apk add --no-cache openssl && \
apk upgrade --no-cache openssl && \
# Verify OpenSSL installation
openssl version && \
# Clean up
rm -rf /var/cache/apk/*

# Copy only the built files and essential runtime files from the builder stage
## rpc-proxy
COPY --from=builder /app/packages/rpc-proxy/dist /app/packages/rpc-proxy/dist
Expand Down
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
11 changes: 10 additions & 1 deletion packages/errors/src/available-errors/provider/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,14 @@ class JSONRPCInternalError extends JSONRPCProviderError {
}
}

/**
* Invalid default block.
*
* WHEN TO USE:
* * When converting default block to vechain revision
*/
class JSONRPCInvalidDefaultBlock extends VechainSDKError<string> {}

/**
* Server error.
*
Expand All @@ -143,5 +151,6 @@ export {
JSONRPCParseError,
JSONRPCProviderError,
JSONRPCServerError,
ProviderMethodError
ProviderMethodError,
JSONRPCInvalidDefaultBlock
};
89 changes: 51 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,59 @@
import { Hex } from '@vechain/sdk-core';
import { type BlockQuantityInputRPC } from '../../rpc-mapper/types';
import { HexUInt, Revision } from '@vechain/sdk-core';
import { JSONRPCInvalidDefaultBlock } from '@vechain/sdk-errors';

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)) {
const defaultBlockValue = defaultBlock.toString();
throw new JSONRPCInvalidDefaultBlock(
'DefaultBlockToRevision',
`Invalid default block: ${defaultBlockValue}`,
defaultBlockValue,
null
);
}
// 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 All @@ -31,7 +30,7 @@ const ethEstimateGas = async (
params: unknown[]
): Promise<string> => {
// Input validation
if (![1, 2].includes(params.length) || typeof params[0] !== 'object')
if (params.length !== 2 || typeof params[0] !== 'object')
throw new JSONRPCInvalidParams(
'eth_estimateGas',
`Invalid input params for "eth_estimateGas" method. See ${RPC_DOCUMENTATION_URL} for details.`,
Expand All @@ -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
Loading

1 comment on commit 5121de3

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test Coverage

Summary

Lines Statements Branches Functions
Coverage: 98%
98.96% (4395/4441) 97.01% (1398/1441) 98.9% (906/916)
Title Tests Skipped Failures Errors Time
core 839 0 πŸ’€ 0 ❌ 0 πŸ”₯ 2m 32s ⏱️
network 728 0 πŸ’€ 0 ❌ 0 πŸ”₯ 5m 10s ⏱️
errors 40 0 πŸ’€ 0 ❌ 0 πŸ”₯ 18.033s ⏱️
logging 3 0 πŸ’€ 0 ❌ 0 πŸ”₯ 19.175s ⏱️
hardhat-plugin 19 0 πŸ’€ 0 ❌ 0 πŸ”₯ 1m 12s ⏱️
aws-kms-adapter 23 0 πŸ’€ 0 ❌ 0 πŸ”₯ 1m 40s ⏱️
ethers-adapter 5 0 πŸ’€ 0 ❌ 0 πŸ”₯ 1m 19s ⏱️
rpc-proxy 37 0 πŸ’€ 0 ❌ 0 πŸ”₯ 1m 26s ⏱️

Please sign in to comment.