From 5456df137ceb76e76ab2e63b217143ffa68523ce Mon Sep 17 00:00:00 2001 From: robertlincecum Date: Mon, 26 Aug 2024 10:33:05 -0500 Subject: [PATCH 1/5] update transactions --- .env.test | 7 + .gitignore | 4 +- package.json | 2 +- src/_tests/integration/create-provider.ts | 7 +- .../providerdata.integration.test.ts | 11 +- .../providererrror.integration.test.ts | 7 +- .../integration/qrc20.integration.test.ts | 7 +- .../rpcendpoints.integration.test.ts | 7 +- .../integration/sendquai.integration.test.ts | 7 +- .../simplestorage.integration.test.ts | 7 +- src/providers/format.ts | 54 ++++- src/providers/formatting.ts | 91 +++++++- src/providers/provider.ts | 215 +++++++++++++++++- start-test-containers.sh | 58 +++++ stop-test-containers.sh | 4 + 15 files changed, 465 insertions(+), 23 deletions(-) create mode 100644 .env.test create mode 100755 start-test-containers.sh create mode 100755 stop-test-containers.sh diff --git a/.env.test b/.env.test new file mode 100644 index 00000000..7dab2b74 --- /dev/null +++ b/.env.test @@ -0,0 +1,7 @@ +CYPRUS1_PRIVKEY_1="0x88391c9aa998c810322838623728b5421ec906171a19ea15da25e0d15d0cfa09" +CYPRUS1_ADDR_1="0x00048b6FE7D7f18b142ea2133A6B8F42B75d5e3F" +CYPRUS1_PRIVKEY_2="0xc44d26c6f48e7990d124395a9b3584627a2dbc862d6d4dc71af6e83ed9220b86" +CYPRUS1_ADDR_2="0x00055f033Dc133eB44D8690836c0d5a97fF3ad3a" + +PRIMEURL="http://localhost:9100/" +CYPRUS1URL="http://localhost:9200/" diff --git a/.gitignore b/.gitignore index b028861d..bd1b20fd 100644 --- a/.gitignore +++ b/.gitignore @@ -8,5 +8,7 @@ dist/*.gz .idea/** lib/commonjs/** lib/esm/** -.env +.env* +!.env.test .DS_Store +quai-local-node diff --git a/package.json b/package.json index 9d5f72d7..25dc7a59 100644 --- a/package.json +++ b/package.json @@ -134,7 +134,7 @@ "clean": "rm -rf dist lib/esm lib/commonjs && cp -r misc/basedirs/* .", "update-symlink": "npm run clean && npm run build-all && npm rm -g quais-6 -g && npm link", "stats": "echo 'Dependencies' && npm ls --all --omit=dev", - "test": "npm run test-esm", + "test": "./start-test-containers.sh && NODE_ENV=test npm run test-esm; ./stop-test-containers.sh", "test-browser": "node lib/esm/_tests/browser/test-browser.js", "test-commonjs": "mocha --reporter ./reporter.cjs ./lib/commonjs/_tests/**/*.test.js", "test-unit": "mocha --reporter ./reporter.cjs ./lib/commonjs/_tests/unit/*.unit.test.js", diff --git a/src/_tests/integration/create-provider.ts b/src/_tests/integration/create-provider.ts index aa87bfa3..99687d7e 100644 --- a/src/_tests/integration/create-provider.ts +++ b/src/_tests/integration/create-provider.ts @@ -2,7 +2,12 @@ import { isError, JsonRpcProvider } from '../../index.js'; import type { AbstractProvider } from '../../index.js'; import dotenv from 'dotenv'; -dotenv.config(); +const env = process.env.NODE_ENV || 'development'; + +dotenv.config({ path: `.env.${env}` }); + +// Or fallback to .env if NODE_ENV specific file doesn't exist +dotenv.config({ path: `.env`, override: false }); interface ProviderCreator { name: string; diff --git a/src/_tests/integration/providerdata.integration.test.ts b/src/_tests/integration/providerdata.integration.test.ts index 5a2d94d4..1312b773 100644 --- a/src/_tests/integration/providerdata.integration.test.ts +++ b/src/_tests/integration/providerdata.integration.test.ts @@ -11,7 +11,12 @@ import axios from 'axios'; import { stall } from '../utils.js'; import dotenv from 'dotenv'; import { Shard } from '../../constants/index.js'; -dotenv.config(); +const env = process.env.NODE_ENV || 'development'; + +dotenv.config({ path: `.env.${env}` }); + +// Or fallback to .env if NODE_ENV specific file doesn't exist +dotenv.config({ path: `.env`, override: false }); // import { // networkFeatureAtBlock, networkNames, // testAddress, testBlock, testReceipt, testTransaction @@ -22,9 +27,7 @@ dotenv.config(); //setupProviders(); const providerC1 = new quais.JsonRpcProvider(process.env.RPC_URL); -const privateKey = process.env.CYPRUS1PK; -console.log(privateKey); -const wallet = new quais.Wallet(process.env.CYPRUS1PK || '', providerC1); +const wallet = new quais.Wallet(process.env.CYPRUS1_PRIVKEY_1 || '', providerC1); const destinationC1 = '0x0047f9CEa7662C567188D58640ffC48901cde02a'; const destinationC2 = '0x011ae0a1Bd5B71b4F16F8FdD3AEF278C3D042449'; diff --git a/src/_tests/integration/providererrror.integration.test.ts b/src/_tests/integration/providererrror.integration.test.ts index 58f30998..3edd81c7 100644 --- a/src/_tests/integration/providererrror.integration.test.ts +++ b/src/_tests/integration/providererrror.integration.test.ts @@ -7,7 +7,12 @@ import { stall } from '../utils.js'; import dotenv from 'dotenv'; import { QuaiTransactionResponse } from '../../providers/provider.js'; -dotenv.config(); +const env = process.env.NODE_ENV || 'development'; + +dotenv.config({ path: `.env.${env}` }); + +// Or fallback to .env if NODE_ENV specific file doesn't exist +dotenv.config({ path: `.env`, override: false }); type TestCustomError = { name: string; diff --git a/src/_tests/integration/qrc20.integration.test.ts b/src/_tests/integration/qrc20.integration.test.ts index f07a27d0..20041c8f 100644 --- a/src/_tests/integration/qrc20.integration.test.ts +++ b/src/_tests/integration/qrc20.integration.test.ts @@ -3,7 +3,12 @@ import { quais, Contract } from '../../index.js'; import dotenv from 'dotenv'; import QRC20 from './contracts/QRC20.js'; -dotenv.config(); +const env = process.env.NODE_ENV || 'development'; + +dotenv.config({ path: `.env.${env}` }); + +// Or fallback to .env if NODE_ENV specific file doesn't exist +dotenv.config({ path: `.env`, override: false }); describe('Tests ERC20 contract deployment and integration', function () { this.timeout(120000); diff --git a/src/_tests/integration/rpcendpoints.integration.test.ts b/src/_tests/integration/rpcendpoints.integration.test.ts index 22d5c39a..572ab090 100644 --- a/src/_tests/integration/rpcendpoints.integration.test.ts +++ b/src/_tests/integration/rpcendpoints.integration.test.ts @@ -1,6 +1,11 @@ import assert from 'assert'; import dotenv from 'dotenv'; -dotenv.config(); +const env = process.env.NODE_ENV || 'development'; + +dotenv.config({ path: `.env.${env}` }); + +// Or fallback to .env if NODE_ENV specific file doesn't exist +dotenv.config({ path: `.env`, override: false }); import { JsonRpcProvider, Shard } from '../../index.js'; diff --git a/src/_tests/integration/sendquai.integration.test.ts b/src/_tests/integration/sendquai.integration.test.ts index 376e8ccf..b318799c 100644 --- a/src/_tests/integration/sendquai.integration.test.ts +++ b/src/_tests/integration/sendquai.integration.test.ts @@ -4,7 +4,12 @@ import { JsonRpcProvider, Wallet } from '../../index.js'; import dotenv from 'dotenv'; import { QuaiTransactionResponse } from '../../providers/provider.js'; -dotenv.config(); +const env = process.env.NODE_ENV || 'development'; + +dotenv.config({ path: `.env.${env}` }); + +// Or fallback to .env if NODE_ENV specific file doesn't exist +dotenv.config({ path: `.env`, override: false }); const testCases = [ { diff --git a/src/_tests/integration/simplestorage.integration.test.ts b/src/_tests/integration/simplestorage.integration.test.ts index ae048ecf..f4d71392 100644 --- a/src/_tests/integration/simplestorage.integration.test.ts +++ b/src/_tests/integration/simplestorage.integration.test.ts @@ -5,7 +5,12 @@ import { JsonRpcProvider, Wallet, ContractFactory, Contract, ContractRunner } fr import SimpleStorageContract from './contracts/SimpleStorageContract.js'; import dotenv from 'dotenv'; -dotenv.config(); +const env = process.env.NODE_ENV || 'development'; + +dotenv.config({ path: `.env.${env}` }); + +// Or fallback to .env if NODE_ENV specific file doesn't exist +dotenv.config({ path: `.env`, override: false }); describe('Test Contract SimpleStorage', function () { this.timeout(60000); diff --git a/src/providers/format.ts b/src/providers/format.ts index 5e5dfc0a..d6ee938f 100644 --- a/src/providers/format.ts +++ b/src/providers/format.ts @@ -24,6 +24,7 @@ import type { EtxParams, QiTransactionResponseParams, QuaiTransactionResponseParams, + ExternalTransactionResponseParams, } from './formatting.js'; const BN_0 = BigInt(0); @@ -208,7 +209,7 @@ const _formatBlock = object({ if (typeof tx === 'string') { return formatHash(tx); } - return formatTransactionResponse(tx); + return formatExternalTransactionResponse(tx); }), hash: formatHash, header: _formatHeader, @@ -235,11 +236,11 @@ export function formatBlock(value: any): BlockParams { } return formatTransactionResponse(tx); }); - result.extTransactions = value.extTransactions.map((tx: string | TransactionResponseParams) => { + result.extTransactions = value.extTransactions.map((tx: string | ExternalTransactionResponseParams) => { if (typeof tx === 'string') { return tx; } - return formatTransactionResponse(tx); + return formatExternalTransactionResponse(tx); }); return result; } @@ -321,6 +322,51 @@ export function formatTransactionReceipt(value: any): TransactionReceiptParams { return result; } +export function formatExternalTransactionResponse(value: any): ExternalTransactionResponseParams { + const result = object( + { + hash: formatHash, + type: (value: any) => { + if (value === '0x' || value == null) { + return 0; + } + return parseInt(value, 16); + }, + accessList: allowNull(accessListify, null), + blockHash: allowNull(formatHash, null), + blockNumber: allowNull((value: any) => (value ? parseInt(value, 16) : null), null), + index: allowNull((value: any) => (value ? BigInt(value) : null), null), + from: allowNull(getAddress, null), + sender: allowNull(getAddress, null), + maxPriorityFeePerGas: allowNull((value: any) => (value ? BigInt(value) : null)), + maxFeePerGas: allowNull((value: any) => (value ? BigInt(value) : null)), + gasLimit: allowNull((value: any) => (value ? BigInt(value) : null), null), + to: allowNull(getAddress, null), + value: allowNull((value: any) => (value ? BigInt(value) : null), null), + nonce: allowNull((value: any) => (value ? parseInt(value, 10) : null), null), + creates: allowNull(getAddress, null), + chainId: allowNull((value: any) => (value ? BigInt(value) : null), null), + isCoinbase: allowNull((value: any) => (value ? parseInt(value, 10) : null), null), + originatingTxHash: allowNull(formatHash, null), + etxIndex: allowNull((value: any) => (value ? parseInt(value, 10) : null), null), + etxType: allowNull((value: any) => value, null), + data: (value: any) => value, + }, + { + data: ['input'], + gasLimit: ['gas'], + index: ['transactionIndex'], + }, + )(value) as ExternalTransactionResponseParams; + + // 0x0000... should actually be null + if (result.blockHash && getBigInt(result.blockHash) === BN_0) { + result.blockHash = null; + } + + return result; +} + export function formatTransactionResponse(value: any): TransactionResponseParams { // Determine if it is a Quai or Qi transaction based on the type const transactionType = parseInt(value.type, 16); @@ -352,6 +398,8 @@ export function formatTransactionResponse(value: any): TransactionResponseParams nonce: allowNull((value: any) => (value ? parseInt(value, 10) : null), null), creates: allowNull(getAddress, null), chainId: allowNull((value: any) => (value ? BigInt(value) : null), null), + etxType: allowNull((value: any) => value, null), + originatingTxHash: allowNull(formatHash, null), data: (value: any) => value, }, { diff --git a/src/providers/formatting.ts b/src/providers/formatting.ts index 27647e4b..aef7e886 100644 --- a/src/providers/formatting.ts +++ b/src/providers/formatting.ts @@ -10,7 +10,7 @@ import type { AccessList, TxInput, TxOutput } from '../transaction/index.js'; * @category Providers */ export interface BlockParams { - extTransactions: ReadonlyArray; + extTransactions: ReadonlyArray; hash: string; header: BlockHeaderParams; interlinkHashes: Array; // New parameter @@ -299,6 +299,89 @@ export interface TransactionReceiptParams { etxs: ReadonlyArray; } +export interface ExternalTransactionResponseParams { + /** + * The block number of the block that included this transaction. + */ + blockNumber: null | number; + + /** + * The block hash of the block that included this transaction. + */ + blockHash: null | string; + + /** + * The transaction hash. + */ + hash: string; + + /** + * The transaction index. + */ + index: bigint; + + /** + * The transaction type. Quai transactions are always type 0. + */ + type: number; + + /** + * The target of the transaction. If `null`, the `data` is initcode and this transaction is a deployment + * transaction. + */ + to: null | string; + + etxIndex: number; + + /** + * The sender of the transaction. + */ + from: string; + + /** + * The nonce of the transaction, used for replay protection. + */ + nonce: number; + + /** + * The maximum amount of gas this transaction is authorized to consume. + */ + gasLimit: bigint; + + /** + * The transaction data. + */ + data: string; + + /** + * The transaction value (in wei). + */ + value: bigint; + + /** + * The chain ID this transaction is valid on. + */ + chainId: bigint; + + /** + * The signature of the transaction. + */ + signature: Signature; + + /** + * The transaction access list. + */ + accessList: null | AccessList; + + originatingTxHash: null | string; + + isCoinbase: null | number; + + etxType: null | string; + + sender: string; +} + /** * A **TransactionResponseParams** encodes the minimal required properties for a formatted transaction response for * either a Qi or Quai transaction. @@ -397,6 +480,12 @@ export interface QuaiTransactionResponseParams { * The transaction access list. */ accessList: null | AccessList; + + etxType: null | string; + + sender: null | string; + + originatingTxHash: null | string; } /** diff --git a/src/providers/provider.ts b/src/providers/provider.ts index b13d14ce..80a2db29 100644 --- a/src/providers/provider.ts +++ b/src/providers/provider.ts @@ -44,6 +44,7 @@ export type BlockTag = BigNumberish | string; import { BlockHeaderParams, BlockParams, + ExternalTransactionResponseParams, LogParams, QiTransactionResponseParams, QuaiTransactionResponseParams, @@ -633,7 +634,7 @@ export class WoHeader implements WoHeaderParams { * @category Providers */ export class Block implements BlockParams, Iterable { - readonly #extTransactions!: Array; + readonly #extTransactions!: Array; readonly hash: string; readonly header: BlockHeader; readonly interlinkHashes: Array; // New parameter @@ -668,7 +669,7 @@ export class Block implements BlockParams, Iterable { this.#extTransactions = block.extTransactions.map((tx) => { if (typeof tx !== 'string') { - return new QuaiTransactionResponse(tx, provider); + return new ExternalTransactionResponse(tx, provider); } return tx; }); @@ -752,7 +753,7 @@ export class Block implements BlockParams, Iterable { * @returns {TransactionResponse[]} The list of prefetched extended transactions. * @throws {Error} If the transactions were not prefetched. */ - get prefetchedExtTransactions(): Array { + get prefetchedExtTransactions(): Array { const txs = this.#extTransactions.slice(); // Doesn't matter... @@ -770,7 +771,7 @@ export class Block implements BlockParams, Iterable { }, ); - return >txs; + return >txs; } /** @@ -889,9 +890,9 @@ export class Block implements BlockParams, Iterable { * @returns {Promise} A promise resolving to the extended transaction. * @throws {Error} If the extended transaction is not found. */ - async getExtTransaction(indexOrHash: number | string): Promise { + async getExtTransaction(indexOrHash: number | string): Promise { // Find the internal value by its index or hash - let tx: string | TransactionResponse | undefined = undefined; + let tx: string | ExternalTransactionResponse | undefined = undefined; if (typeof indexOrHash === 'number') { tx = this.#extTransactions[indexOrHash]; } else { @@ -917,7 +918,7 @@ export class Block implements BlockParams, Iterable { } if (typeof tx === 'string') { - return await this.provider.getTransaction(tx); + throw new Error("External Transaction isn't prefetched"); } else { return tx; } @@ -1511,6 +1512,194 @@ export interface QiMinedTransactionResponse extends QiTransactionResponse { date: Date; } +export class ExternalTransactionResponse implements QuaiTransactionLike, ExternalTransactionResponseParams { + /** + * The provider this is connected to, which will influence how its methods will resolve its async inspection + * methods. + */ + readonly provider: Provider; + + /** + * The block number of the block that this transaction was included in. + * + * This is `null` for pending transactions. + */ + readonly blockNumber: null | number; + + /** + * The blockHash of the block that this transaction was included in. + * + * This is `null` for pending transactions. + */ + readonly blockHash: null | string; + + /** + * The index within the block that this transaction resides at. + */ + readonly index!: bigint; + + /** + * The transaction hash. + */ + readonly hash!: string; + + /** + * The [EIP-2718](https://eips.ethereum.org/EIPS/eip-2718) transaction envelope type. This is `0` for legacy + * transactions types. + */ + readonly type!: number; + + /** + * The receiver of this transaction. + * + * If `null`, then the transaction is an initcode transaction. This means the result of executing the + * {@link ExternalTransactionResponse.data | **data** } will be deployed as a new contract on chain (assuming it does + * not revert) and the address may be computed using [getCreateAddress](../functions/getCreateAddress). + */ + readonly to!: null | string; + + /** + * The sender of this transaction. It is implicitly computed from the transaction pre-image hash (as the digest) and + * the {@link QuaiTransactionResponse.signature | **signature** } using ecrecover. + */ + readonly from!: string; + + /** + * The nonce, which is used to prevent replay attacks and offer a method to ensure transactions from a given sender + * are explicitly ordered. + * + * When sending a transaction, this must be equal to the number of transactions ever sent by + * {@link ExternalTransactionResponse.from | **from** }. + */ + readonly nonce!: number; + + /** + * The maximum units of gas this transaction can consume. If execution exceeds this, the entries transaction is + * reverted and the sender is charged for the full amount, despite not state changes being made. + */ + readonly gasLimit!: bigint; + + /** + * The data. + */ + readonly data!: string; + + /** + * The value, in wei. Use [formatEther](../functions/formatEther) to format this value as ether. + */ + readonly value!: bigint; + + /** + * The chain ID. + */ + readonly chainId!: bigint; + + /** + * The signature. + */ + readonly signature!: Signature; + + /** + * The [EIP-2930](https://eips.ethereum.org/EIPS/eip-2930) access list for transaction types that support it, + * otherwise `null`. + */ + readonly accessList!: null | AccessList; + + readonly etxType!: null | string; + + readonly isCoinbase!: null | number; + + readonly originatingTxHash!: null | string; + + readonly sender!: string; + + readonly etxIndex!: number; + + protected startBlock: number; + + /** + * @ignore + */ + constructor(tx: ExternalTransactionResponseParams, provider: Provider) { + this.provider = provider; + + this.blockNumber = tx.blockNumber != null ? tx.blockNumber : null; + this.blockHash = tx.blockHash != null ? tx.blockHash : null; + + this.hash = tx.hash; + this.index = tx.index; + + this.type = tx.type; + + this.from = tx.from; + this.to = tx.to || null; + + this.gasLimit = tx.gasLimit; + this.nonce = tx.nonce; + this.data = tx.data; + this.value = tx.value; + + this.chainId = tx.chainId; + this.signature = tx.signature; + + this.accessList = tx.accessList != null ? tx.accessList : null; + this.startBlock = -1; + this.originatingTxHash = tx.originatingTxHash != null ? tx.originatingTxHash : null; + this.isCoinbase = tx.isCoinbase != null ? tx.isCoinbase : null; + this.etxType = tx.etxType != null ? tx.etxType : null; + this.sender = tx.sender; + this.etxIndex = tx.etxIndex; + } + + /** + * Returns a JSON-compatible representation of this transaction. + */ + toJSON(): any { + const { + blockNumber, + blockHash, + index, + hash, + type, + to, + from, + nonce, + data, + signature, + accessList, + etxType, + isCoinbase, + originatingTxHash, + etxIndex, + sender, + } = this; + const result = { + _type: 'TransactionReceipt', + accessList, + blockNumber, + blockHash, + chainId: toJson(this.chainId), + data, + from, + gasLimit: toJson(this.gasLimit), + hash, + nonce, + signature, + to, + index, + type, + etxType, + isCoinbase, + originatingTxHash, + sender, + etxIndex, + value: toJson(this.value), + }; + + return result; + } +} + /** * A **TransactionResponse** is an interface representing either a Quai or Qi transaction that has been mined into a * block. @@ -1632,6 +1821,12 @@ export class QuaiTransactionResponse implements QuaiTransactionLike, QuaiTransac */ readonly accessList!: null | AccessList; + readonly etxType!: null | string; + + readonly sender!: null | string; + + readonly originatingTxHash!: null | string; + protected startBlock: number; /** @@ -1664,6 +1859,12 @@ export class QuaiTransactionResponse implements QuaiTransactionLike, QuaiTransac this.accessList = tx.accessList != null ? tx.accessList : null; this.startBlock = -1; + + this.etxType = tx.etxType != null ? tx.etxType : null; + + this.sender = tx.sender != null ? tx.sender : null; + + this.originatingTxHash = tx.originatingTxHash != null ? tx.originatingTxHash : null; } /** diff --git a/start-test-containers.sh b/start-test-containers.sh new file mode 100755 index 00000000..b33fe404 --- /dev/null +++ b/start-test-containers.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +# Check if the quai-local-node directory exists, if not, clone it +if [ ! -d "./quai-local-node" ]; then + echo "quai-local-node directory not found. Cloning from GitHub..." + git clone git@github.com:dominant-strategies/quai-local-node.git + if [ $? -ne 0 ]; then + echo "Failed to clone quai-local-node repository. Exiting." + exit 1 + fi + echo "Successfully cloned quai-local-node." +fi + +# Bring up the Docker containers +docker-compose -f ./quai-local-node/docker-compose.yml up -d + +# Function to check if the API is responding correctly +check_service() { + local url=$1 + local data=$2 + local attempts=0 + local max_attempts=30 # Maximum number of attempts to avoid infinite loops + + while [ $attempts -lt $max_attempts ]; do + response=$(curl -s -X POST -H "Content-Type: application/json" -d "$data" $url) + connection_refused=$(echo $response | grep -c "Failed to connect") + valid_response=$(echo $response | grep -c '"hash"') + + if [ $connection_refused -eq 0 ] && [ $valid_response -gt 0 ]; then + echo "Service at $url is up and returned a valid response with a hash" + return 0 + else + echo "Waiting for $url to return a valid response with a hash..." + ((attempts++)) + sleep 2 # Wait for 2 seconds before checking again + fi + done + + echo "Error: $url did not return a valid response with a hash after $attempts attempts." + echo "Last response: $response" + return 1 +} + +# JSON payload to send in the POST request +payload='{ + "jsonrpc": "2.0", + "method": "quai_getBlockByNumber", + "params": [ + "0x1", + false + ], + "id": 1 +}' + +# Wait for the service on localhost:9200 to respond correctly +check_service "http://localhost:9200" "$payload" || exit 1 + +echo "Test containers are up and running." diff --git a/stop-test-containers.sh b/stop-test-containers.sh new file mode 100755 index 00000000..125ac77b --- /dev/null +++ b/stop-test-containers.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +# Bring down the Docker containers +docker-compose -f ./quai-local-node/docker-compose.yml down From c527743978e4da4a81a7224a7ab8ea57e22a8b72 Mon Sep 17 00:00:00 2001 From: robertlincecum Date: Mon, 26 Aug 2024 15:14:39 -0500 Subject: [PATCH 2/5] make extin external transaction type --- src/providers/format.ts | 18 +++++++++++------- src/providers/formatting.ts | 6 +----- src/providers/provider.ts | 33 ++++++++++++++++++++++----------- src/utils/errors.ts | 3 ++- 4 files changed, 36 insertions(+), 24 deletions(-) diff --git a/src/providers/format.ts b/src/providers/format.ts index d6ee938f..03681f05 100644 --- a/src/providers/format.ts +++ b/src/providers/format.ts @@ -230,12 +230,17 @@ const _formatBlock = object({ 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); - }); + result.transactions = value.transactions.map( + (tx: string | TransactionResponseParams | ExternalTransactionResponseParams) => { + if (typeof tx === 'string') { + return tx; + } + if ('originatingTxHash' in tx) { + return formatExternalTransactionResponse(tx); + } + return formatTransactionResponse(tx); + }, + ); result.extTransactions = value.extTransactions.map((tx: string | ExternalTransactionResponseParams) => { if (typeof tx === 'string') { return tx; @@ -399,7 +404,6 @@ export function formatTransactionResponse(value: any): TransactionResponseParams creates: allowNull(getAddress, null), chainId: allowNull((value: any) => (value ? BigInt(value) : null), null), etxType: allowNull((value: any) => value, null), - originatingTxHash: allowNull(formatHash, null), data: (value: any) => value, }, { diff --git a/src/providers/formatting.ts b/src/providers/formatting.ts index aef7e886..02490c1b 100644 --- a/src/providers/formatting.ts +++ b/src/providers/formatting.ts @@ -18,7 +18,7 @@ export interface BlockParams { size: bigint; subManifest: Array | null; totalEntropy: bigint; - transactions: ReadonlyArray; + transactions: ReadonlyArray; uncles: Array | null; woHeader: WoHeaderParams; // New nested parameter structure } @@ -482,10 +482,6 @@ export interface QuaiTransactionResponseParams { accessList: null | AccessList; etxType: null | string; - - sender: null | string; - - originatingTxHash: null | string; } /** diff --git a/src/providers/provider.ts b/src/providers/provider.ts index 80a2db29..4d34c75c 100644 --- a/src/providers/provider.ts +++ b/src/providers/provider.ts @@ -642,7 +642,9 @@ export class Block implements BlockParams, Iterable { readonly size!: bigint; readonly subManifest!: Array | null; readonly totalEntropy!: bigint; - readonly #transactions!: Array; + readonly #transactions!: Array< + string | QuaiTransactionResponse | QiTransactionResponse | ExternalTransactionResponse + >; readonly uncles!: Array | null; readonly woHeader: WoHeader; // New nested parameter structure @@ -661,10 +663,16 @@ export class Block implements BlockParams, Iterable { */ constructor(block: BlockParams, provider: Provider) { this.#transactions = block.transactions.map((tx) => { - if (typeof tx !== 'string') { + if (typeof tx === 'string') { + return tx; + } + if ('originatingTxHash' in tx) { + return new ExternalTransactionResponse(tx as ExternalTransactionResponseParams, provider); + } + if ('from' in tx) { return new QuaiTransactionResponse(tx, provider); } - return tx; + return new QiTransactionResponse(tx as QiTransactionResponseParams, provider); }); this.#extTransactions = block.extTransactions.map((tx) => { @@ -849,9 +857,9 @@ export class Block implements BlockParams, Iterable { * @returns {Promise} A promise resolving to the transaction. * @throws {Error} If the transaction is not found. */ - async getTransaction(indexOrHash: number | string): Promise { + async getTransaction(indexOrHash: number | string): Promise { // Find the internal value by its index or hash - let tx: string | TransactionResponse | undefined = undefined; + let tx: string | TransactionResponse | ExternalTransactionResponse | undefined = undefined; if (typeof indexOrHash === 'number') { tx = this.#transactions[indexOrHash]; } else { @@ -1698,6 +1706,13 @@ export class ExternalTransactionResponse implements QuaiTransactionLike, Externa return result; } + + replaceableTransaction(startBlock: number): ExternalTransactionResponse { + assertArgument(Number.isInteger(startBlock) && startBlock >= 0, 'invalid startBlock', 'startBlock', startBlock); + const tx = new ExternalTransactionResponse(this, this.provider); + tx.startBlock = startBlock; + return tx; + } } /** @@ -1861,10 +1876,6 @@ export class QuaiTransactionResponse implements QuaiTransactionLike, QuaiTransac this.startBlock = -1; this.etxType = tx.etxType != null ? tx.etxType : null; - - this.sender = tx.sender != null ? tx.sender : null; - - this.originatingTxHash = tx.originatingTxHash != null ? tx.originatingTxHash : null; } /** @@ -2039,7 +2050,7 @@ export class QuaiTransactionResponse implements QuaiTransactionLike, QuaiTransac // Search for the transaction that replaced us for (let i = 0; i < block.length; i++) { - const tx: TransactionResponse = await block.getTransaction(i); + const tx: TransactionResponse | ExternalTransactionResponse = await block.getTransaction(i); if ('from' in tx && tx.from === this.from && tx.nonce === this.nonce) { // Get the receipt @@ -2070,7 +2081,7 @@ export class QuaiTransactionResponse implements QuaiTransactionLike, QuaiTransac cancelled: reason === 'replaced' || reason === 'cancelled', reason, replacement: tx.replaceableTransaction(startBlock), - hash: tx.hash, + hash: (tx as QuaiTransactionResponse).hash, receipt, }); } diff --git a/src/utils/errors.ts b/src/utils/errors.ts index d29dd2ea..8d08d65b 100644 --- a/src/utils/errors.ts +++ b/src/utils/errors.ts @@ -13,6 +13,7 @@ import { defineProperties } from './properties.js'; import type { TransactionRequest, TransactionReceipt, TransactionResponse } from '../providers/index.js'; import type { FetchRequest, FetchResponse } from './fetch.js'; +import { ExternalTransactionResponse } from '../providers/provider'; /** * An error may contain additional properties, but those must not conflict with any implicit properties. @@ -538,7 +539,7 @@ export interface TransactionReplacedError extends quaisError<'TRANSACTION_REPLAC /** * The transaction that replaced the transaction. */ - replacement: TransactionResponse; + replacement: TransactionResponse | ExternalTransactionResponse; /** * The receipt of the transaction that replace the transaction. From c818ca04292ad288f8f2a43802babab111ef94fa Mon Sep 17 00:00:00 2001 From: robertlincecum Date: Mon, 26 Aug 2024 15:22:36 -0500 Subject: [PATCH 3/5] increase test container max connection attempts --- start-test-containers.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/start-test-containers.sh b/start-test-containers.sh index b33fe404..1ed50ddf 100755 --- a/start-test-containers.sh +++ b/start-test-containers.sh @@ -19,7 +19,7 @@ check_service() { local url=$1 local data=$2 local attempts=0 - local max_attempts=30 # Maximum number of attempts to avoid infinite loops + local max_attempts=100 # Maximum number of attempts to avoid infinite loops while [ $attempts -lt $max_attempts ]; do response=$(curl -s -X POST -H "Content-Type: application/json" -d "$data" $url) From e27fcc84e0f068d342c12137a7425fe92a556dfb Mon Sep 17 00:00:00 2001 From: robertlincecum Date: Thu, 29 Aug 2024 14:04:09 -0500 Subject: [PATCH 4/5] wip --- .gitignore | 2 +- package.json | 5 ++++- src/_tests/unit/formatter.unit.test.ts | 2 +- src/providers/abstract-provider.ts | 2 +- src/providers/provider-websocket.ts | 2 +- 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index bd1b20fd..573cd42e 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,4 @@ lib/esm/** .env* !.env.test .DS_Store -quai-local-node +quai-local-node* diff --git a/package.json b/package.json index 25dc7a59..689af17f 100644 --- a/package.json +++ b/package.json @@ -134,7 +134,10 @@ "clean": "rm -rf dist lib/esm lib/commonjs && cp -r misc/basedirs/* .", "update-symlink": "npm run clean && npm run build-all && npm rm -g quais-6 -g && npm link", "stats": "echo 'Dependencies' && npm ls --all --omit=dev", - "test": "./start-test-containers.sh && NODE_ENV=test npm run test-esm; ./stop-test-containers.sh", + "test": "NODE_ENV=test npm run test-esm", + "test-containers": "npm run containers-up && NODE_ENV=test npm run test; npm run containers-down", + "containers-up": "./start-test-containers.sh", + "containers-down": "./stop-test-containers.sh", "test-browser": "node lib/esm/_tests/browser/test-browser.js", "test-commonjs": "mocha --reporter ./reporter.cjs ./lib/commonjs/_tests/**/*.test.js", "test-unit": "mocha --reporter ./reporter.cjs ./lib/commonjs/_tests/unit/*.unit.test.js", diff --git a/src/_tests/unit/formatter.unit.test.ts b/src/_tests/unit/formatter.unit.test.ts index 21b1d7c3..43a6549e 100644 --- a/src/_tests/unit/formatter.unit.test.ts +++ b/src/_tests/unit/formatter.unit.test.ts @@ -1,6 +1,6 @@ // import { expect } from 'chai'; import assert from 'assert'; -import { formatTransactionReceipt } from '../../providers/format'; +import { formatTransactionReceipt } from '../../providers/format.js'; // Mock objects similar to what would be returned by your JSON RPC response const inZoneTxReceipt = { diff --git a/src/providers/abstract-provider.ts b/src/providers/abstract-provider.ts index fda72003..85947b33 100644 --- a/src/providers/abstract-provider.ts +++ b/src/providers/abstract-provider.ts @@ -86,7 +86,7 @@ import { PollingTransactionSubscriber, } from './subscriber-polling.js'; import { getNodeLocationFromZone, getZoneFromNodeLocation } from '../utils/shards.js'; -import { fromShard } from '../constants/shards'; +import { fromShard } from '../constants/shards.js'; type Timer = ReturnType; diff --git a/src/providers/provider-websocket.ts b/src/providers/provider-websocket.ts index 4d23fec2..4bf5e1aa 100644 --- a/src/providers/provider-websocket.ts +++ b/src/providers/provider-websocket.ts @@ -5,7 +5,7 @@ import { SocketProvider } from './provider-socket.js'; import type { JsonRpcApiProviderOptions } from './provider-jsonrpc.js'; import type { Networkish } from './network.js'; import { Shard, toShard } from '../constants/index.js'; -import { fromShard } from '../constants/shards'; +import { fromShard } from '../constants/shards.js'; /** * A generic interface to a Websocket-like object. From c766f1e48708a6160ebda6849e30930a9c12de75 Mon Sep 17 00:00:00 2001 From: robertlincecum Date: Fri, 30 Aug 2024 11:51:16 -0500 Subject: [PATCH 5/5] add tests to cicd --- .github/workflows/dev-build-manual.yml | 24 ++++++++++++++++++++++++ .github/workflows/dev-build.yml | 5 +++-- package.json | 1 + 3 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/dev-build-manual.yml diff --git a/.github/workflows/dev-build-manual.yml b/.github/workflows/dev-build-manual.yml new file mode 100644 index 00000000..ff0047b5 --- /dev/null +++ b/.github/workflows/dev-build-manual.yml @@ -0,0 +1,24 @@ +name: Manual Dev Workflow +on: + workflow_dispatch: + inputs: + branch: + description: 'Branch to use' + required: true + default: 'main' +jobs: + call-common-workflow: + uses: dominant-strategies/quai-cicd/.github/workflows/deploy-dev-common.yml@main + with: + needs_build: true + install_command: "npm ci" + build_command: "npm run build-clean && npm run test-unit && npm run test-integ-containers" + cloud_deploy: false + skip_deploy: true + update_version: false + needs_docker: false + include_chart: false + needs_docker_compose: true + secrets: + GH_PAT: ${{ secrets.GH_PAT }} + BUILD_ARGS: '' diff --git a/.github/workflows/dev-build.yml b/.github/workflows/dev-build.yml index 5faa271c..474b4333 100644 --- a/.github/workflows/dev-build.yml +++ b/.github/workflows/dev-build.yml @@ -2,19 +2,20 @@ name: Auto Dev Workflow on: pull_request: types: [closed] - branches: [ "master" ] + branches: [ "alpha" ] jobs: call-common-workflow: uses: dominant-strategies/quai-cicd/.github/workflows/deploy-dev-common.yml@main with: needs_build: true install_command: "npm ci" - build_command: "npm run build-clean" + build_command: "npm run build-clean && npm run test-unit && npm run test-integ-containers" cloud_deploy: false skip_deploy: true update_version: false needs_docker: false include_chart: false + needs_docker_compose: true secrets: GH_PAT: ${{ secrets.GH_PAT }} BUILD_ARGS: '' diff --git a/package.json b/package.json index 689af17f..5dd3c2aa 100644 --- a/package.json +++ b/package.json @@ -136,6 +136,7 @@ "stats": "echo 'Dependencies' && npm ls --all --omit=dev", "test": "NODE_ENV=test npm run test-esm", "test-containers": "npm run containers-up && NODE_ENV=test npm run test; npm run containers-down", + "test-integ-containers": "npm run containers-up && NODE_ENV=test npm run test-integ; npm run containers-down", "containers-up": "./start-test-containers.sh", "containers-down": "./stop-test-containers.sh", "test-browser": "node lib/esm/_tests/browser/test-browser.js",