Merge pull request #16 from Lioncat2002/main

Added typescript tests
This commit is contained in:
galactus 2022-12-08 21:00:07 +01:00 committed by GitHub
commit 2e1e99b117
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 2960 additions and 74 deletions

2
.gitignore vendored
View File

@ -1 +1,3 @@
/target
/node_modules
/test-ledger

View File

@ -23,5 +23,10 @@ This project is currently based on an unstable feature of block subscription of
```
cargo run --bin lite-rpc -- --port 9000 --subscription-port 9001 --url http://localhost:8899
cargo run --bin lite-rpc -- run --port 9000 --subscription-port 9001 --rpc-url http://localhost:8899
```
## Tests
```
cargo run --bin lite-rpc -- test
```

8
jest.config.js Normal file
View File

@ -0,0 +1,8 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
transform: {
'^.+\\.ts?$': 'ts-jest',
},
transformIgnorePatterns: ['<rootDir>/node_modules/'],
};

23
package.json Normal file
View File

@ -0,0 +1,23 @@
{
"name": "lightrpc-test",
"version": "1.0.0",
"repository": "",
"author": "",
"license": "MIT",
"private": true,
"dependencies": {
"@solana/web3.js": "^1.66.2",
"@types/jest": "^29.2.3",
"jest": "^29.3.1",
"start-server-and-test": "^1.14.0",
"ts-jest": "^29.0.3"
},
"scripts": {
"test": "jest --detectOpenHandles",
"start:lite-rpc": "lite-rpc",
"test:test-validator": "start-server-and-test 'solana-test-validator --reset --quiet & target/debug/lite-rpc run --port 9000 --rpc-url http://localhost:8899 --subscription-port 9001 --websocket-url ws://localhost:8900/' http://localhost:8899/health test"
},
"devDependencies": {
"typescript": "^4.8.4"
}
}

View File

@ -1,4 +1,6 @@
use {clap::Parser, solana_cli_config::ConfigInput, std::net::SocketAddr};
use clap::Subcommand;
use clap::Parser;
/// Holds the configuration for a single run of the benchmark
#[derive(Parser, Debug)]
@ -11,18 +13,24 @@ use {clap::Parser, solana_cli_config::ConfigInput, std::net::SocketAddr};
"
)]
pub struct Args {
#[arg(short, long, default_value_t = SocketAddr::from(([127, 0, 0, 1], 8899)))]
pub port: SocketAddr,
#[arg(short, long, default_value_t = SocketAddr::from(([127, 0, 0, 1], 8900)))]
pub subscription_port: SocketAddr,
#[arg(short, long, default_value_t = String::new())]
#[clap(subcommand)]
pub command: Command,
/*
#[arg(short, long, default_value_t = String::from("8899"))]
pub port: String,
#[arg(short, long, default_value_t = String::from("8900"))]
pub subscription_port: String,
#[arg(short, long, default_value_t = String::from("http://localhost:8899"))]
pub rpc_url: String,
#[arg(short, long, default_value_t = String::new())]
pub websocket_url: String,
*/
}
/*
impl Args {
pub fn resolve_address(&mut self) {
if self.rpc_url.is_empty() {
let (_, rpc_url) = ConfigInput::compute_json_rpc_url_setting(
self.rpc_url.as_str(),
@ -40,4 +48,20 @@ impl Args {
self.websocket_url = ws_url;
}
}
}
*/
#[derive(Subcommand, Debug)]
pub enum Command {
Run {
#[arg(short, long, default_value_t = String::from("8899"))]
port: String,
#[arg(short, long, default_value_t = String::from("8900"))]
subscription_port: String,
#[arg(short, long, default_value_t = String::from("http://localhost:8899"))]
rpc_url: String,
#[arg(short, long, default_value_t = String::new())]
websocket_url: String,
},
Test,
}

View File

@ -1,10 +1,11 @@
use std::sync::Arc;
use std::{net::SocketAddr, sync::Arc};
use clap::Parser;
use context::LiteRpcSubsrciptionControl;
use jsonrpc_core::MetaIoHandler;
use jsonrpc_http_server::{hyper, AccessControlAllowOrigin, DomainsValidation, ServerBuilder};
use pubsub::LitePubSubService;
use solana_cli_config::ConfigInput;
use solana_perf::thread::renice_this_thread;
use tokio::sync::broadcast;
@ -23,21 +24,31 @@ mod rpc;
use cli::Args;
pub fn main() {
let mut cli_config = Args::parse();
cli_config.resolve_address();
fn run(port: String, subscription_port: String, rpc_url: String, websocket_url: String) {
let rpc_url = if rpc_url.is_empty() {
let (_, rpc_url) = ConfigInput::compute_json_rpc_url_setting(
rpc_url.as_str(),
&ConfigInput::default().json_rpc_url,
);
rpc_url
} else {
rpc_url
};
let websocket_url = if websocket_url.is_empty() {
let (_, ws_url) = ConfigInput::compute_websocket_url_setting(
&websocket_url.as_str(),
"",
rpc_url.as_str(),
"",
);
ws_url
} else {
websocket_url
};
println!(
"Using rpc server {} and ws server {}",
cli_config.rpc_url, cli_config.websocket_url
rpc_url, websocket_url
);
let Args {
rpc_url: json_rpc_url,
websocket_url,
port: rpc_addr,
subscription_port,
..
} = &cli_config;
let performance_counter = PerformanceCounter::new();
launch_performance_updating_thread(performance_counter.clone());
@ -49,14 +60,16 @@ pub fn main() {
notification_reciever,
));
let subscription_port = format!("127.0.0.1:{subscription_port}")
.parse::<SocketAddr>()
.expect("Invalid subscription port");
// start websocket server
let (_trigger, websocket_service) = LitePubSubService::new(
pubsub_control.clone(),
*subscription_port,
subscription_port,
performance_counter.clone(),
);
// start recieving notifications and broadcast them
{
let pubsub_control = pubsub_control.clone();
std::thread::Builder::new()
@ -71,12 +84,11 @@ pub fn main() {
io.extend_with(lite_rpc.to_delegate());
let mut request_processor = LightRpcRequestProcessor::new(
json_rpc_url,
websocket_url,
rpc_url.as_str(),
&websocket_url,
notification_sender,
performance_counter.clone(),
);
let runtime = Arc::new(
tokio::runtime::Builder::new_multi_thread()
.worker_threads(1)
@ -87,7 +99,7 @@ pub fn main() {
.expect("Runtime"),
);
let max_request_body_size: usize = 50 * (1 << 10);
let socket_addr = *rpc_addr;
let socket_addr = port.parse::<SocketAddr>().unwrap();
{
let request_processor = request_processor.clone();
@ -109,3 +121,40 @@ pub fn main() {
request_processor.free();
websocket_service.close().unwrap();
}
fn ts_test() {
let res = std::process::Command::new("yarn")
.args(["run", "test:test-validator"])
.output()
.unwrap();
println!("{}", String::from_utf8_lossy(&res.stdout));
println!("{}", String::from_utf8_lossy(&res.stderr));
}
pub fn main() {
let cli_command = Args::parse();
match cli_command.command {
cli::Command::Run {
port,
subscription_port,
rpc_url,
websocket_url,
} => run(port, subscription_port, rpc_url, websocket_url),
cli::Command::Test => ts_test(),
}
//cli_config.resolve_address();
//println!(
// "Using rpc server {} and ws server {}",
// cli_config.rpc_url, cli_config.websocket_url
//);
//let Args {
// rpc_url: json_rpc_url,
// websocket_url,
// port: rpc_addr,
// subscription_port,
// ..
//} = &cli_config;
// start recieving notifications and broadcast them
}

View File

@ -195,7 +195,6 @@ impl LightRpcRequestProcessor {
for signature in signatures {
match signature_status.entry(signature.clone()) {
dashmap::mapref::entry::Entry::Occupied(mut x) => {
let signature_notification = SignatureNotification {
signature: Signature::from_str(signature.as_str())
.unwrap(),
@ -270,7 +269,7 @@ pub mod lite_rpc {
use itertools::Itertools;
use solana_sdk::{fee_calculator::FeeCalculator, pubkey::Pubkey};
use solana_transaction_status::{TransactionStatus, TransactionConfirmationStatus};
use solana_transaction_status::{TransactionConfirmationStatus, TransactionStatus};
use super::*;
#[rpc]
@ -329,7 +328,6 @@ pub mod lite_rpc {
signature_strs: Vec<String>,
config: Option<RpcSignatureStatusConfig>,
) -> Result<RpcResponse<Vec<Option<TransactionStatus>>>>;
}
pub struct LightRpc;
impl Lite for LightRpc {
@ -461,17 +459,15 @@ pub mod lite_rpc {
match k_value {
Some(value) => match *value {
Some(commitment_for_signature) => {
Ok(RpcResponse {
context: RpcResponseContext::new(slot),
value: if commitment.eq(&CommitmentLevel::Finalized) {
commitment_for_signature.eq(&CommitmentLevel::Finalized)
} else {
commitment_for_signature.eq(&CommitmentLevel::Finalized)
|| commitment_for_signature.eq(&CommitmentLevel::Confirmed)
},
})
}
Some(commitment_for_signature) => Ok(RpcResponse {
context: RpcResponseContext::new(slot),
value: if commitment.eq(&CommitmentLevel::Finalized) {
commitment_for_signature.eq(&CommitmentLevel::Finalized)
} else {
commitment_for_signature.eq(&CommitmentLevel::Finalized)
|| commitment_for_signature.eq(&CommitmentLevel::Confirmed)
},
}),
None => Ok(RpcResponse {
context: RpcResponseContext::new(slot),
value: false,
@ -502,43 +498,51 @@ pub mod lite_rpc {
signature_strs: Vec<String>,
_config: Option<RpcSignatureStatusConfig>,
) -> Result<RpcResponse<Vec<Option<TransactionStatus>>>> {
let confirmed_slot = meta.context.confirmed_block_info.slot.load(Ordering::Relaxed);
let status = signature_strs.iter().map(|x| {
let singature_status = meta.context.signature_status.get(x);
let k_value = singature_status;
match k_value {
Some(value) => match *value {
Some(commitment_for_signature) => {
let slot = meta.context
let confirmed_slot = meta
.context
.confirmed_block_info
.slot
.load(Ordering::Relaxed);
let status = signature_strs
.iter()
.map(|x| {
let singature_status = meta.context.signature_status.get(x);
let k_value = singature_status;
match k_value {
Some(value) => match *value {
Some(commitment_for_signature) => {
let slot = meta
.context
.confirmed_block_info
.slot
.load(Ordering::Relaxed);
meta.performance_counter
.update_confirm_transaction_counter();
meta.performance_counter
.update_confirm_transaction_counter();
let status = match commitment_for_signature {
CommitmentLevel::Finalized => TransactionConfirmationStatus::Finalized,
_ => TransactionConfirmationStatus::Confirmed,
};
Some(TransactionStatus {
slot,
confirmations: Some(1),
status: Ok(()),
err: None,
confirmation_status : Some(status)
})
}
let status = match commitment_for_signature {
CommitmentLevel::Finalized => {
TransactionConfirmationStatus::Finalized
}
_ => TransactionConfirmationStatus::Confirmed,
};
Some(TransactionStatus {
slot,
confirmations: Some(1),
status: Ok(()),
err: None,
confirmation_status: Some(status),
})
}
None => None,
},
None => None,
},
None => None
}
}).collect_vec();
Ok(
RpcResponse {
context : RpcResponseContext::new(confirmed_slot),
value: status,
}
)
}
})
.collect_vec();
Ok(RpcResponse {
context: RpcResponseContext::new(confirmed_slot),
value: status,
})
}
fn request_airdrop(

31
tests/main.test.ts Normal file
View File

@ -0,0 +1,31 @@
import { Connection, Keypair, LAMPORTS_PER_SOL, Message, VersionedTransaction } from "@solana/web3.js";
import { url } from "./urls";
test('send and confirm transaction', async () => {
const connection = new Connection(url, 'confirmed');
const payer = Keypair.generate();
await connection.requestAirdrop(payer.publicKey, LAMPORTS_PER_SOL);
const recentBlockhash = (await connection.getLatestBlockhash('confirmed')).blockhash;
const versionedTx = new VersionedTransaction(
new Message({
header: {
numRequiredSignatures: 1,
numReadonlySignedAccounts: 0,
numReadonlyUnsignedAccounts: 0,
},
recentBlockhash,
instructions: [],
accountKeys: [payer.publicKey.toBase58()],
}),
);
versionedTx.sign([payer]);
const signature = await connection.sendTransaction(versionedTx);
const latestBlockHash = await connection.getLatestBlockhash();
await connection.confirmTransaction({
blockhash: latestBlockHash.blockhash,
lastValidBlockHeight: latestBlockHash.lastValidBlockHeight,
signature: signature,
});
});

1
tests/urls.ts Normal file
View File

@ -0,0 +1 @@
export const url = "http://127.0.0.1:9000";

82
tsconfig.json Normal file
View File

@ -0,0 +1,82 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "commonjs", /* Specify what module code is generated. */
// "rootDir": "./", /* Specify the root folder within your source files. */
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
// "resolveJsonModule": true, /* Enable importing .json files. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
// "outDir": "./", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}

2657
yarn.lock Normal file

File diff suppressed because it is too large Load Diff