chore: replace jest with mocha

This commit is contained in:
Justin Starry 2021-02-06 10:59:00 +08:00 committed by Justin Starry
parent 612958ece0
commit c675c67c26
41 changed files with 4635 additions and 6665 deletions

View File

@ -7,4 +7,4 @@
/module.flow.js
/.eslintrc.js
/test/.eslintrc.js
/jest-environment.js
/test/dist

6
web3.js/.mocharc.js Normal file
View File

@ -0,0 +1,6 @@
'use strict';
// Configure Node.js tests
module.exports = {
require: ['@babel/register', 'esm'],
};

1
web3.js/.prettierignore Normal file
View File

@ -0,0 +1 @@
test/dist

View File

@ -15,3 +15,4 @@ npm run codecov
make -C examples/bpf-c-noop/
cargo build-bpf --manifest-path examples/bpf-rust-noop/Cargo.toml
npm run test:live-with-test-validator
npm run test:browser-with-test-validator

View File

@ -24,7 +24,7 @@ eslint and flow-type are used.
Helpful link: https://www.saltycrane.com/flow-type-cheat-sheet/latest/
### Testing Framework
https://jestjs.io/
https://mochajs.org/
### API Documentation
ESDoc is used to document the public API. See

View File

@ -5,11 +5,5 @@
],
"plugins": [
"@babel/plugin-proposal-class-properties"
],
"env": {
"test": {
"plugins": ["@babel/plugin-transform-runtime"],
"presets": ["@babel/preset-flow"]
}
}
]
}

File diff suppressed because it is too large Load Diff

View File

@ -1,27 +0,0 @@
const NodeEnvironment = require('jest-environment-node');
class CustomEnvironment extends NodeEnvironment {
constructor(config, context) {
Object.assign(config.globals, {
Uint8Array,
ArrayBuffer,
});
super(config, context);
this.testPath = context.testPath;
this.docblockPragmas = context.docblockPragmas;
}
async setup() {
await super.setup();
}
async teardown() {
await super.teardown();
}
runScript(script) {
return super.runScript(script);
}
}
module.exports = CustomEnvironment;

View File

@ -1,5 +0,0 @@
{
"collectCoverage": true,
"collectCoverageFrom": ["src/**"],
"coverageReporters": ["json", "lcov", "text-summary", "html"]
}

21
web3.js/mocha.html Normal file
View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Mocha Tests</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./node_modules/mocha/mocha.css" />
</head>
<body>
<div id="mocha"></div>
<script src="./node_modules/mocha/mocha.js"></script>
<script class="mocha-init">
mocha.setup('bdd');
mocha.checkLeaks();
</script>
<script type="module" src="./test/dist/bundle.js"></script>
<script type="module" class="mocha-exec">
mocha.run();
</script>
</body>
</html>

View File

@ -46,6 +46,7 @@
"bpf-sdk:install": "npm run clean:fixtures; bin/bpf-sdk-install.sh .",
"bpf-sdk:remove-symlinks": "find bpf-sdk -type l -print -exec cp {} {}.tmp \\; -exec mv {}.tmp {} \\;",
"build": "cross-env NODE_ENV=production rollup -c",
"build:browser-test": "rollup -c test/rollup.config.js",
"build:fixtures": "set -ex; ./test/fixtures/noop-c/build.sh; ./test/fixtures/noop-rust/build.sh",
"clean:fixtures": "make -C examples/bpf-c-noop clean ",
"clean": "rimraf ./coverage ./lib",
@ -70,14 +71,13 @@
"pretty": "prettier --check '{,{examples,src,test}/**/}*.{j,t}s'",
"pretty:fix": "prettier --write '{,{examples,src,test}/**/}*.{j,t}s'",
"re": "semantic-release --repository-url git@github.com:solana-labs/solana-web3.js.git",
"test": "npm run build:fixtures && cross-env NODE_ENV=test jest --useStderr",
"test:cover": "npm run build:fixtures && cross-env NODE_ENV=test jest --coverage --useStderr",
"test:live": "npm run build:fixtures && cross-env NODE_ENV=test TEST_LIVE=1 jest --useStderr",
"test": "npm run build:fixtures && mocha './test/**/*.test.js'",
"test:browser": "TEST_LIVE=1 npm run build:fixtures && npm run build:browser-test && mocha-headless-chrome -f http://localhost:8080/mocha.html --timeout 180000",
"test:browser-with-server": "start-server-and-test 'http-server -p 8080' 8080 test:browser",
"test:browser-with-test-validator": "start-server-and-test 'solana-test-validator --reset --quiet' http://localhost:8899/health test:browser-with-server",
"test:live": "TEST_LIVE=1 npm run test",
"test:live-with-test-validator": "start-server-and-test 'solana-test-validator --reset --quiet' http://localhost:8899/health test:live",
"test:watch": "npm run build:fixtures && cross-env NODE_ENV=test jest --watch --useStderr"
},
"jest": {
"testEnvironment": "./jest-environment"
"test:watch": "npm run build:fixtures && npm run test"
},
"dependencies": {
"@babel/runtime": "^7.12.5",
@ -95,15 +95,19 @@
"tweetnacl": "^1.0.0"
},
"devDependencies": {
"@babel/core": "^7.12.13",
"@babel/plugin-proposal-class-properties": "^7.12.1",
"@babel/plugin-transform-runtime": "^7.12.10",
"@babel/preset-env": "^7.12.11",
"@babel/preset-flow": "^7.12.1",
"@babel/register": "^7.12.13",
"@commitlint/config-conventional": "^11.0.0",
"@commitlint/travis-cli": "^11.0.0",
"@rollup/plugin-alias": "^3.1.2",
"@rollup/plugin-babel": "^5.2.3",
"@rollup/plugin-commonjs": "^17.1.0",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-multi-entry": "^4.0.0",
"@rollup/plugin-node-resolve": "^11.1.1",
"@rollup/plugin-replace": "^2.3.4",
"@solana/spl-token": "^0.0.13",
@ -111,6 +115,8 @@
"@typescript-eslint/parser": "^4.14.2",
"acorn": "^8.0.1",
"babel-eslint": "^11.0.0-beta.2",
"chai": "^4.3.0",
"chai-as-promised": "^7.1.1",
"codecov": "^3.0.4",
"cross-env": "7.0.3",
"elfy": "^1.0.0",
@ -121,22 +127,21 @@
"esdoc-importpath-plugin": "^1.0.2",
"esdoc-inject-style-plugin": "^1.0.0",
"esdoc-standard-plugin": "^1.0.0",
<<<<<<< HEAD
"eslint": "7.19.0",
=======
"eslint": "^7.19.0",
>>>>>>> 37215dbc2... feat: add support for browser es modules
"eslint-config-prettier": "^7.0.0",
"eslint-plugin-flowtype": "^5.2.0",
"eslint-plugin-import": "2.22.1",
"eslint-plugin-jest": "22.19.0",
"eslint-plugin-prettier": "^3.0.0",
"esm": "^3.2.25",
"flow-bin": "0.130.0",
"flow-remove-types": "^2.143.1",
"flow-typed": "3.2.1",
"fs-file-tree": "1.1.1",
"jest": "26.6.3",
"http-server": "^0.12.3",
"marked": "^1.1.0",
"mocha": "^8.2.1",
"mocha-headless-chrome": "^3.1.0",
"mockttp": "^1.1.0",
"mz": "^2.7.0",
"npm-run-all": "^4.1.5",
"prettier": "^2.0.0",
@ -146,8 +151,11 @@
"rollup-plugin-node-polyfills": "^0.2.1",
"rollup-plugin-terser": "^7.0.2",
"semantic-release": "^17.0.2",
"start-server-and-test": "^1.11.6",
"sinon": "^9.2.4",
"start-server-and-test": "^1.12.0",
"typescript": "^4.1.3",
"watch": "^1.0.2"
"watch": "^1.0.2",
"webpack": "^5.21.0",
"webpack-cli": "^4.5.0"
}
}

View File

@ -19,7 +19,11 @@ function generateConfig(configType, format) {
plugins: [
flow(),
commonjs(),
nodeResolve({browser, preferBuiltins: !browser, dedupe: ['bn.js']}),
nodeResolve({
browser,
preferBuiltins: !browser,
dedupe: ['bn.js', 'buffer'],
}),
babel({
exclude: '**/node_modules/**',
babelHelpers: bundle ? 'bundled' : 'runtime',

View File

@ -1,4 +0,0 @@
module.exports = {
// eslint-disable-line import/no-commonjs
extends: ['plugin:jest/recommended', '../.eslintrc.js'],
};

1
web3.js/test/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
dist

View File

@ -1,86 +0,0 @@
// @flow
import fetch from 'node-fetch';
type RpcRequest = {
method: string,
params?: Array<any>,
};
type RpcResponseError = {
message: string,
};
type RpcResponseResult = any;
type RpcResponse = {
error: ?RpcResponseError,
result: ?RpcResponseResult,
};
export const mockRpc: Array<[string, RpcRequest, RpcResponse]> = [];
// Define TEST_LIVE in the environment to test against the real full node
// identified by `url` instead of using the mock
export const mockRpcEnabled = !process.env.TEST_LIVE;
let mockNotice = true;
// Suppress lint: 'JestMockFn' is not defined
// eslint-disable-next-line no-undef
const mock: JestMockFn<any, any> = jest.fn((fetchUrl, fetchOptions) => {
if (!mockRpcEnabled) {
if (mockNotice) {
console.log(
`Note: node-fetch mock is disabled, testing live against ${fetchUrl}`,
);
mockNotice = false;
}
return fetch(fetchUrl, fetchOptions);
}
expect(mockRpc.length).toBeGreaterThanOrEqual(1);
const [mockUrl, mockRequest, mockResponse] = mockRpc.shift();
expect(fetchUrl).toBe(mockUrl);
expect(fetchOptions).toMatchObject({
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
});
expect(fetchOptions.body).toBeDefined();
const body = JSON.parse(fetchOptions.body);
expect(body).toMatchObject(
Object.assign(
{},
{
jsonrpc: '2.0',
method: 'invalid',
},
mockRequest,
),
);
const response = Object.assign(
{},
{
jsonrpc: '2.0',
id: body.id,
error: {
message: 'invalid error message',
},
result: 'invalid response',
},
mockResponse,
);
return {
ok: true,
status: 200,
statusText: 'OK',
text: () => {
return Promise.resolve(JSON.stringify(response));
},
};
});
export default mock;

View File

@ -1,77 +0,0 @@
// @flow
import {Client as LiveClient} from 'rpc-websockets';
import EventEmitter from 'events';
type RpcRequest = {
method: string,
params?: Array<any>,
};
type RpcResponse = {
context: {
slot: number,
},
value: any,
};
// Define TEST_LIVE in the environment to test against the real full node
// identified by `url` instead of using the mock
export const mockRpcEnabled = !process.env.TEST_LIVE;
export const mockRpcSocket: Array<[RpcRequest, RpcResponse]> = [];
class MockClient extends EventEmitter {
mockOpen = false;
subscriptionCounter = 0;
constructor() {
super();
}
connect() {
if (!this.mockOpen) {
this.mockOpen = true;
this.emit('open');
}
}
close() {
if (this.mockOpen) {
this.mockOpen = false;
this.emit('close');
}
}
notify(): Promise<any> {
return Promise.resolve();
}
on(event: string, callback: Function): this {
return super.on(event, callback);
}
call(method: string, params: Array<any>): Promise<Object> {
expect(mockRpcSocket.length).toBeGreaterThanOrEqual(1);
const [mockRequest, mockResponse] = mockRpcSocket.shift();
expect(method).toBe(mockRequest.method);
expect(params).toMatchObject(mockRequest.params);
let id = this.subscriptionCounter++;
const response = {
subscription: id,
result: mockResponse,
};
setImmediate(() => {
const eventName = method.replace('Subscribe', 'Notification');
this.emit(eventName, response);
});
return Promise.resolve(id);
}
}
const Client = mockRpcEnabled ? MockClient : LiveClient;
export {Client};

View File

@ -1,80 +1,84 @@
// @flow
import {Account} from '../src/account';
import {expect} from 'chai';
import {Buffer} from 'buffer';
test('generate new account', () => {
const account = new Account();
expect(account.secretKey).toHaveLength(64);
});
describe('Account', () => {
it('generate new account', () => {
const account = new Account();
expect(account.secretKey).to.have.length(64);
});
test('account from secret key', () => {
const secretKey = Buffer.from([
153,
218,
149,
89,
225,
94,
145,
62,
233,
171,
46,
83,
227,
223,
173,
87,
93,
163,
59,
73,
190,
17,
37,
187,
146,
46,
51,
73,
79,
73,
136,
40,
27,
47,
73,
9,
110,
62,
93,
189,
15,
207,
169,
192,
192,
205,
146,
217,
171,
59,
33,
84,
75,
52,
213,
221,
74,
101,
217,
139,
135,
139,
153,
34,
]);
const account = new Account(secretKey);
expect(account.publicKey.toBase58()).toBe(
'2q7pyhPwAwZ3QMfZrnAbDhnh9mDUqycszcpf86VgQxhF',
);
it('account from secret key', () => {
const secretKey = Buffer.from([
153,
218,
149,
89,
225,
94,
145,
62,
233,
171,
46,
83,
227,
223,
173,
87,
93,
163,
59,
73,
190,
17,
37,
187,
146,
46,
51,
73,
79,
73,
136,
40,
27,
47,
73,
9,
110,
62,
93,
189,
15,
207,
169,
192,
192,
205,
146,
217,
171,
59,
33,
84,
75,
52,
213,
221,
74,
101,
217,
139,
135,
139,
153,
34,
]);
const account = new Account(secretKey);
expect(account.publicKey.toBase58()).to.eq(
'2q7pyhPwAwZ3QMfZrnAbDhnh9mDUqycszcpf86VgQxhF',
);
});
});

View File

@ -1,40 +1,41 @@
// @flow
import {AgentManager, DESTROY_TIMEOUT_MS} from '../src/agent-manager';
import {expect} from 'chai';
import {sleep} from '../src/util/sleep';
jest.setTimeout(10 * 1000);
describe('AgentManager', () => {
it('works', async () => {
const manager = new AgentManager();
const agent = manager._agent;
expect(manager._activeRequests).to.eq(0);
expect(manager._destroyTimeout).to.be.null;
test('agent manager', async () => {
const manager = new AgentManager();
const agent = manager._agent;
expect(manager._activeRequests).toBe(0);
expect(manager._destroyTimeout).toBeNull();
manager.requestStart();
manager.requestStart();
expect(manager._activeRequests).to.eq(1);
expect(manager._destroyTimeout).to.be.null;
expect(manager._activeRequests).toBe(1);
expect(manager._destroyTimeout).toBeNull();
manager.requestEnd();
manager.requestEnd();
expect(manager._activeRequests).to.eq(0);
expect(manager._destroyTimeout).not.to.be.null;
expect(manager._activeRequests).toBe(0);
expect(manager._destroyTimeout).not.toBeNull();
manager.requestStart();
manager.requestStart();
manager.requestStart();
manager.requestStart();
expect(manager._activeRequests).to.eq(2);
expect(manager._destroyTimeout).to.be.null;
expect(manager._activeRequests).toBe(2);
expect(manager._destroyTimeout).toBeNull();
manager.requestEnd();
manager.requestEnd();
manager.requestEnd();
manager.requestEnd();
expect(manager._activeRequests).to.eq(0);
expect(manager._destroyTimeout).not.to.be.null;
expect(manager._agent).to.eq(agent);
expect(manager._activeRequests).toBe(0);
expect(manager._destroyTimeout).not.toBeNull();
expect(manager._agent).toBe(agent);
await sleep(DESTROY_TIMEOUT_MS);
await sleep(DESTROY_TIMEOUT_MS);
expect(manager._agent).not.toBe(agent);
expect(manager._agent).not.to.eq(agent);
}).timeout(2 * DESTROY_TIMEOUT_MS);
});

View File

@ -1,6 +1,8 @@
// @flow
import fs from 'mz/fs';
import {expect, use} from 'chai';
import chaiAsPromised from 'chai-as-promised';
import {
Connection,
@ -9,263 +11,278 @@ import {
sendAndConfirmTransaction,
Account,
} from '../src';
import {mockRpcEnabled} from './__mocks__/node-fetch';
import {url} from './url';
import {newAccountWithLamports} from './new-account-with-lamports';
import {BPF_LOADER_PROGRAM_ID} from '../src/bpf-loader';
import {helpers} from './mocks/rpc-http';
if (!mockRpcEnabled) {
// The default of 5 seconds is too slow for live testing sometimes
jest.setTimeout(240000);
}
use(chaiAsPromised);
test('load BPF C program', async () => {
if (mockRpcEnabled) {
console.log('non-live test skipped');
return;
}
if (process.env.TEST_LIVE) {
describe('BPF Loader', () => {
it('load BPF C program', async () => {
const data = await fs.readFile('test/fixtures/noop-c/noop.so');
const data = await fs.readFile('test/fixtures/noop-c/noop.so');
const connection = new Connection(url, 'singleGossip');
const {feeCalculator} = await connection.getRecentBlockhash();
const fees =
feeCalculator.lamportsPerSignature *
BpfLoader.getMinNumSignatures(data.length);
const payerBalance = await connection.getMinimumBalanceForRentExemption(
0,
);
const executableBalance = await connection.getMinimumBalanceForRentExemption(
data.length,
);
const connection = new Connection(url, 'singleGossip');
const {feeCalculator} = await connection.getRecentBlockhash();
const fees =
feeCalculator.lamportsPerSignature *
BpfLoader.getMinNumSignatures(data.length);
const payerBalance = await connection.getMinimumBalanceForRentExemption(0);
const executableBalance = await connection.getMinimumBalanceForRentExemption(
data.length,
);
const from = await newAccountWithLamports(
connection,
payerBalance + fees + executableBalance,
);
const from = new Account();
await helpers.airdrop({
connection,
address: from.publicKey,
amount: payerBalance + fees + executableBalance,
});
const program = new Account();
await BpfLoader.load(connection, from, program, data, BPF_LOADER_PROGRAM_ID);
// Check that program loading costed exactly `fees + executableBalance`
const fromBalance = await connection.getBalance(from.publicKey);
expect(fromBalance).toEqual(payerBalance);
const transaction = new Transaction().add({
keys: [{pubkey: from.publicKey, isSigner: true, isWritable: true}],
programId: program.publicKey,
});
await sendAndConfirmTransaction(connection, transaction, [from], {
commitment: 'singleGossip',
preflightCommitment: 'singleGossip',
});
});
describe('load BPF Rust program', () => {
if (mockRpcEnabled) {
console.log('non-live test skipped');
return;
}
const connection = new Connection(url, 'singleGossip');
let program: Account;
let payerAccount: Account;
let programData: Buffer;
beforeAll(async () => {
programData = await fs.readFile(
'test/fixtures/noop-rust/solana_bpf_rust_noop.so',
);
const {feeCalculator} = await connection.getRecentBlockhash();
const fees =
feeCalculator.lamportsPerSignature *
BpfLoader.getMinNumSignatures(programData.length);
const payerBalance = await connection.getMinimumBalanceForRentExemption(0);
const executableBalance = await connection.getMinimumBalanceForRentExemption(
programData.length,
);
payerAccount = await newAccountWithLamports(
connection,
payerBalance + fees + executableBalance,
);
// Create program account with low balance
program = await newAccountWithLamports(connection, executableBalance - 1);
// First load will fail part way due to lack of funds
const insufficientPayerAccount = await newAccountWithLamports(
connection,
2 * feeCalculator.lamportsPerSignature * 8,
);
const failedLoad = BpfLoader.load(
connection,
insufficientPayerAccount,
program,
programData,
BPF_LOADER_PROGRAM_ID,
);
await expect(failedLoad).rejects.toThrow();
// Second load will succeed
await BpfLoader.load(
connection,
payerAccount,
program,
programData,
BPF_LOADER_PROGRAM_ID,
);
});
test('get confirmed transaction', async () => {
const transaction = new Transaction().add({
keys: [
{pubkey: payerAccount.publicKey, isSigner: true, isWritable: true},
],
programId: program.publicKey,
});
const signature = await sendAndConfirmTransaction(
connection,
transaction,
[payerAccount],
{
commitment: 'max', // `getParsedConfirmedTransaction` requires max commitment
preflightCommitment: connection.commitment || 'max',
},
);
const parsedTx = await connection.getParsedConfirmedTransaction(signature);
if (parsedTx === null) {
expect(parsedTx).not.toBeNull();
return;
}
const {signatures, message} = parsedTx.transaction;
expect(signatures[0]).toEqual(signature);
const ix = message.instructions[0];
if (ix.parsed) {
expect('parsed' in ix).toBe(false);
} else {
expect(ix.programId.equals(program.publicKey)).toBe(true);
expect(ix.data).toEqual('');
}
});
test('simulate transaction', async () => {
const simulatedTransaction = new Transaction().add({
keys: [
{pubkey: payerAccount.publicKey, isSigner: true, isWritable: true},
],
programId: program.publicKey,
});
const {err, logs} = (
await connection.simulateTransaction(simulatedTransaction, [payerAccount])
).value;
expect(err).toBeNull();
if (logs === null) {
expect(logs).not.toBeNull();
return;
}
expect(logs.length).toBeGreaterThanOrEqual(2);
expect(logs[0]).toEqual(
`Program ${program.publicKey.toBase58()} invoke [1]`,
);
expect(logs[logs.length - 1]).toEqual(
`Program ${program.publicKey.toBase58()} success`,
);
});
test('deprecated - simulate transaction without signature verification', async () => {
const simulatedTransaction = new Transaction().add({
keys: [
{pubkey: payerAccount.publicKey, isSigner: true, isWritable: true},
],
programId: program.publicKey,
});
simulatedTransaction.setSigners(payerAccount.publicKey);
const {err, logs} = (
await connection.simulateTransaction(simulatedTransaction)
).value;
expect(err).toBeNull();
if (logs === null) {
expect(logs).not.toBeNull();
return;
}
expect(logs.length).toBeGreaterThanOrEqual(2);
expect(logs[0]).toEqual(
`Program ${program.publicKey.toBase58()} invoke [1]`,
);
expect(logs[logs.length - 1]).toEqual(
`Program ${program.publicKey.toBase58()} success`,
);
});
test('simulate transaction without signature verification', async () => {
const simulatedTransaction = new Transaction({
feePayer: payerAccount.publicKey,
}).add({
keys: [
{pubkey: payerAccount.publicKey, isSigner: true, isWritable: true},
],
programId: program.publicKey,
});
const {err, logs} = (
await connection.simulateTransaction(simulatedTransaction)
).value;
expect(err).toBeNull();
if (logs === null) {
expect(logs).not.toBeNull();
return;
}
expect(logs.length).toBeGreaterThanOrEqual(2);
expect(logs[0]).toEqual(
`Program ${program.publicKey.toBase58()} invoke [1]`,
);
expect(logs[logs.length - 1]).toEqual(
`Program ${program.publicKey.toBase58()} success`,
);
});
test('simulate transaction with bad programId', async () => {
const simulatedTransaction = new Transaction().add({
keys: [
{pubkey: payerAccount.publicKey, isSigner: true, isWritable: true},
],
programId: new Account().publicKey,
});
simulatedTransaction.setSigners(payerAccount.publicKey);
const {err, logs} = (
await connection.simulateTransaction(simulatedTransaction)
).value;
expect(err).toEqual('ProgramAccountNotFound');
if (logs === null) {
expect(logs).not.toBeNull();
return;
}
expect(logs.length).toEqual(0);
});
test('reload program', async () => {
expect(
const program = new Account();
await BpfLoader.load(
connection,
payerAccount,
from,
program,
programData,
data,
BPF_LOADER_PROGRAM_ID,
),
).toBe(false);
);
// Check that program loading costed exactly `fees + executableBalance`
const fromBalance = await connection.getBalance(from.publicKey);
expect(fromBalance).to.eq(payerBalance);
const transaction = new Transaction().add({
keys: [{pubkey: from.publicKey, isSigner: true, isWritable: true}],
programId: program.publicKey,
});
await sendAndConfirmTransaction(connection, transaction, [from], {
commitment: 'singleGossip',
preflightCommitment: 'singleGossip',
});
}).timeout(5000);
describe('load BPF Rust program', () => {
const connection = new Connection(url, 'singleGossip');
let program = new Account();
let payerAccount = new Account();
let programData: Buffer;
before(async function () {
this.timeout(60_000);
programData = await fs.readFile(
'test/fixtures/noop-rust/solana_bpf_rust_noop.so',
);
const {feeCalculator} = await connection.getRecentBlockhash();
const fees =
feeCalculator.lamportsPerSignature *
BpfLoader.getMinNumSignatures(programData.length);
const payerBalance = await connection.getMinimumBalanceForRentExemption(
0,
);
const executableBalance = await connection.getMinimumBalanceForRentExemption(
programData.length,
);
await helpers.airdrop({
connection,
address: payerAccount.publicKey,
amount: payerBalance + fees + executableBalance,
});
// Create program account with low balance
await helpers.airdrop({
connection,
address: program.publicKey,
amount: executableBalance - 1,
});
// First load will fail part way due to lack of funds
const insufficientPayerAccount = new Account();
await helpers.airdrop({
connection,
address: insufficientPayerAccount.publicKey,
amount: 2 * feeCalculator.lamportsPerSignature * 8,
});
const failedLoad = BpfLoader.load(
connection,
insufficientPayerAccount,
program,
programData,
BPF_LOADER_PROGRAM_ID,
);
await expect(failedLoad).to.be.rejected;
// Second load will succeed
await BpfLoader.load(
connection,
payerAccount,
program,
programData,
BPF_LOADER_PROGRAM_ID,
);
});
it('get confirmed transaction', async () => {
const transaction = new Transaction().add({
keys: [
{pubkey: payerAccount.publicKey, isSigner: true, isWritable: true},
],
programId: program.publicKey,
});
const signature = await sendAndConfirmTransaction(
connection,
transaction,
[payerAccount],
{
commitment: 'max', // `getParsedConfirmedTransaction` requires max commitment
preflightCommitment: connection.commitment || 'max',
},
);
const parsedTx = await connection.getParsedConfirmedTransaction(
signature,
);
if (parsedTx === null) {
expect(parsedTx).not.to.be.null;
return;
}
const {signatures, message} = parsedTx.transaction;
expect(signatures[0]).to.eq(signature);
const ix = message.instructions[0];
if (ix.parsed) {
expect('parsed' in ix).to.eq(false);
} else {
expect(ix.programId).to.eql(program.publicKey);
expect(ix.data).to.eq('');
}
}).timeout(30000);
it('simulate transaction', async () => {
const simulatedTransaction = new Transaction().add({
keys: [
{pubkey: payerAccount.publicKey, isSigner: true, isWritable: true},
],
programId: program.publicKey,
});
const {err, logs} = (
await connection.simulateTransaction(simulatedTransaction, [
payerAccount,
])
).value;
expect(err).to.be.null;
if (logs === null) {
expect(logs).not.to.be.null;
return;
}
expect(logs.length).to.be.at.least(2);
expect(logs[0]).to.eq(
`Program ${program.publicKey.toBase58()} invoke [1]`,
);
expect(logs[logs.length - 1]).to.eq(
`Program ${program.publicKey.toBase58()} success`,
);
});
it('deprecated - simulate transaction without signature verification', async () => {
const simulatedTransaction = new Transaction().add({
keys: [
{pubkey: payerAccount.publicKey, isSigner: true, isWritable: true},
],
programId: program.publicKey,
});
simulatedTransaction.setSigners(payerAccount.publicKey);
const {err, logs} = (
await connection.simulateTransaction(simulatedTransaction)
).value;
expect(err).to.be.null;
if (logs === null) {
expect(logs).not.to.be.null;
return;
}
expect(logs.length).to.be.at.least(2);
expect(logs[0]).to.eq(
`Program ${program.publicKey.toBase58()} invoke [1]`,
);
expect(logs[logs.length - 1]).to.eq(
`Program ${program.publicKey.toBase58()} success`,
);
});
it('simulate transaction without signature verification', async () => {
const simulatedTransaction = new Transaction({
feePayer: payerAccount.publicKey,
}).add({
keys: [
{pubkey: payerAccount.publicKey, isSigner: true, isWritable: true},
],
programId: program.publicKey,
});
const {err, logs} = (
await connection.simulateTransaction(simulatedTransaction)
).value;
expect(err).to.be.null;
if (logs === null) {
expect(logs).not.to.be.null;
return;
}
expect(logs.length).to.be.at.least(2);
expect(logs[0]).to.eq(
`Program ${program.publicKey.toBase58()} invoke [1]`,
);
expect(logs[logs.length - 1]).to.eq(
`Program ${program.publicKey.toBase58()} success`,
);
});
it('simulate transaction with bad programId', async () => {
const simulatedTransaction = new Transaction().add({
keys: [
{pubkey: payerAccount.publicKey, isSigner: true, isWritable: true},
],
programId: new Account().publicKey,
});
simulatedTransaction.setSigners(payerAccount.publicKey);
const {err, logs} = (
await connection.simulateTransaction(simulatedTransaction)
).value;
expect(err).to.eq('ProgramAccountNotFound');
if (logs === null) {
expect(logs).not.to.be.null;
return;
}
expect(logs).to.have.length(0);
});
it('reload program', async () => {
expect(
await BpfLoader.load(
connection,
payerAccount,
program,
programData,
BPF_LOADER_PROGRAM_ID,
),
).to.eq(false);
});
});
});
});
}

View File

@ -1,16 +1,19 @@
// @flow
import {clusterApiUrl} from '../src/util/cluster';
import {expect} from 'chai';
test('invalid', () => {
expect(() => {
// $FlowExpectedError
clusterApiUrl('abc123');
}).toThrow();
});
describe('Cluster Util', () => {
it('invalid', () => {
expect(() => {
// $FlowExpectedError
clusterApiUrl('abc123');
}).to.throw();
});
test('devnet', () => {
expect(clusterApiUrl()).toEqual('https://devnet.solana.com');
expect(clusterApiUrl('devnet')).toEqual('https://devnet.solana.com');
expect(clusterApiUrl('devnet', true)).toEqual('https://devnet.solana.com');
expect(clusterApiUrl('devnet', false)).toEqual('http://devnet.solana.com');
it('devnet', () => {
expect(clusterApiUrl()).to.eq('https://devnet.solana.com');
expect(clusterApiUrl('devnet')).to.eq('https://devnet.solana.com');
expect(clusterApiUrl('devnet', true)).to.eq('https://devnet.solana.com');
expect(clusterApiUrl('devnet', false)).to.eq('http://devnet.solana.com');
});
});

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +0,0 @@
#!/usr/bin/env bash
set -ex
cd "$(dirname "$0")"
make -C ../../../examples/bpf-c-noop/
cp ../../../examples/bpf-c-noop/out/noop.so .

View File

@ -1,7 +0,0 @@
#!/usr/bin/env bash
set -ex
cd "$(dirname "$0")"
cargo build-bpf --manifest-path=../../../examples/bpf-rust-noop/Cargo.toml
cp ../../../examples/bpf-rust-noop/target/deploy/solana_bpf_rust_noop.so .

View File

@ -1,83 +0,0 @@
// @flow
import {Account} from '../src/account';
import {SystemProgram} from '../src/system-program';
import {Transaction} from '../src/transaction';
test('verify getConfirmedBlock', () => {
const account0 = new Account();
const account1 = new Account();
const account2 = new Account();
const account3 = new Account();
const recentBlockhash = account1.publicKey.toBase58(); // Fake recentBlockhash
// Create a couple signed transactions
const transfer0 = SystemProgram.transfer({
fromPubkey: account0.publicKey,
toPubkey: account1.publicKey,
lamports: 123,
});
const transaction0 = new Transaction({recentBlockhash}).add(transfer0);
transaction0.sign(account0);
const transfer1 = SystemProgram.transfer({
fromPubkey: account2.publicKey,
toPubkey: account3.publicKey,
lamports: 456,
});
let transaction1 = new Transaction({recentBlockhash}).add(transfer1);
transaction1.sign(account2);
// Build ConfirmedBlock, with dummy data for blockhashes, balances
const confirmedBlock = {
blockhash: recentBlockhash,
previousBlockhash: recentBlockhash,
transactions: [
{
transaction: transaction0,
meta: {
fee: 0,
preBalances: [100000, 100000, 1, 1, 1],
postBalances: [99877, 100123, 1, 1, 1],
status: {Ok: null},
err: null,
},
},
{
transaction: transaction1,
meta: {
fee: 0,
preBalances: [100000, 100000, 1, 1, 1],
postBalances: [99544, 100456, 1, 1, 1],
status: {Ok: null},
err: null,
},
},
],
rewards: [],
};
// Verify signatures in ConfirmedBlock
for (const transactionWithMeta of confirmedBlock.transactions) {
expect(transactionWithMeta.transaction.verifySignatures()).toBe(true);
}
const bogusSignature = {
signature: Buffer.alloc(64, 9),
publicKey: account2.publicKey,
};
transaction1.signatures[0] = bogusSignature;
let badConfirmedBlock = confirmedBlock;
badConfirmedBlock.transactions[1].transaction = transaction1;
// Verify signatures in ConfirmedBlock
const verifications = badConfirmedBlock.transactions.map(
transactionWithMeta => transactionWithMeta.transaction.verifySignatures(),
);
expect(
verifications.reduce(
(accumulator, currentValue) => accumulator && currentValue,
),
).toBe(false);
});

View File

@ -1,19 +0,0 @@
// @flow
import type {TransactionSignature} from '../../src/transaction';
import {mockRpcSocket} from '../__mocks__/rpc-websockets';
export function mockConfirmTransaction(signature: TransactionSignature) {
mockRpcSocket.push([
{
method: 'signatureSubscribe',
params: [signature, {commitment: 'singleGossip'}],
},
{
context: {
slot: 11,
},
value: {err: null},
},
]);
}

View File

@ -1,36 +0,0 @@
// @flow
import {Account} from '../../src';
import type {Commitment} from '../../src/connection';
import {url} from '../url';
import {mockRpc} from '../__mocks__/node-fetch';
export function mockGetRecentBlockhash(commitment: ?Commitment) {
const recentBlockhash = new Account();
const params = [];
if (commitment) {
params.push({commitment});
}
mockRpc.push([
url,
{
method: 'getRecentBlockhash',
params,
},
{
error: null,
result: {
context: {
slot: 11,
},
value: {
blockhash: recentBlockhash.publicKey.toBase58(),
feeCalculator: {
lamportsPerSignature: 42,
},
},
},
},
]);
}

View File

@ -0,0 +1,170 @@
// @flow
import bs58 from 'bs58';
import BN from 'bn.js';
import * as mockttp from 'mockttp';
import {mockRpcMessage} from './rpc-websockets';
import {Connection} from '../../src';
import type {Commitment} from '../../src/connection';
export const mockServer = process.env.TEST_LIVE || mockttp.getLocal();
let uniqueCounter = 0;
export const uniqueSignature = () => {
return bs58.encode(new BN(++uniqueCounter).toArray(null, 64));
};
export const uniqueBlockhash = () => {
return bs58.encode(new BN(++uniqueCounter).toArray(null, 32));
};
export const mockErrorMessage = 'Invalid';
export const mockErrorResponse = {
code: -32602,
message: mockErrorMessage,
};
export const mockRpcResponse = async ({
method,
params,
value,
error,
withContext,
}: {
method: string,
params: Array<any>,
value: any,
error: any,
withContext?: boolean,
}) => {
if (process.env.TEST_LIVE) return;
let result = withContext
? {
context: {
slot: 11,
},
value,
}
: value;
await mockServer
.post('/')
.withJsonBodyIncluding({
jsonrpc: '2.0',
method,
params,
})
.thenReply(
200,
JSON.stringify({
jsonrpc: '2.0',
id: '',
error,
result,
}),
);
};
const recentBlockhash = async ({
connection,
commitment,
}: {
connection: Connection,
commitment: ?Commitment,
}) => {
const blockhash = uniqueBlockhash();
const params = [];
if (commitment) {
params.push({commitment});
}
await mockRpcResponse({
method: 'getRecentBlockhash',
params,
value: {
blockhash,
feeCalculator: {
lamportsPerSignature: 42,
},
},
withContext: true,
});
return await connection.getRecentBlockhash(commitment);
};
const processTransaction = async ({
connection,
transaction,
signers,
commitment,
err,
}: {
connection: Connection,
transaction: Transaction,
signers: Array<Account>,
commitment: Commitment,
err?: any,
}) => {
const blockhash = (await recentBlockhash({connection})).blockhash;
transaction.recentBlockhash = blockhash;
transaction.sign(...signers);
const encoded = transaction.serialize().toString('base64');
const signature = bs58.encode(transaction.signature);
await mockRpcResponse({
method: 'sendTransaction',
params: [encoded],
value: signature,
});
const sendOptions = err
? {
skipPreflight: true,
}
: {
preflightCommitment: commitment,
};
await connection.sendEncodedTransaction(encoded, sendOptions);
await mockRpcMessage({
method: 'signatureSubscribe',
params: [signature, {commitment}],
result: {err: err || null},
});
return await connection.confirmTransaction(signature, commitment);
};
const airdrop = async ({
connection,
address,
amount,
}: {
connection: Connection,
address: PublicKey,
amount: number,
}) => {
await mockRpcResponse({
method: 'requestAirdrop',
params: [address.toBase58(), amount],
value: uniqueSignature(),
});
const signature = await connection.requestAirdrop(address, amount);
await mockRpcMessage({
method: 'signatureSubscribe',
params: [signature, {commitment: 'singleGossip'}],
result: {err: null},
});
await connection.confirmTransaction(signature, 'singleGossip');
return signature;
};
export const helpers = {
airdrop,
processTransaction,
recentBlockhash,
};

View File

@ -0,0 +1,106 @@
// @flow
import {Client as LiveClient} from 'rpc-websockets';
import {expect} from 'chai';
import sinon from 'sinon';
import {Connection} from '../../src';
type RpcRequest = {
method: string,
params?: Array<any>,
};
type RpcResponse = {
context: {
slot: number,
},
value: any,
};
const mockRpcSocket: Array<[RpcRequest, RpcResponse]> = [];
const sandbox = sinon.createSandbox();
export const mockRpcMessage = ({
method,
params,
result,
}: {
method: string,
params: Array<any>,
result: any,
}) => {
mockRpcSocket.push([
{method, params},
{
context: {slot: 11},
value: result,
},
]);
};
export const stubRpcWebSocket = (connection: Connection) => {
const rpcWebSocket = connection._rpcWebSocket;
const mockClient = new MockClient(rpcWebSocket);
sandbox.stub(rpcWebSocket, 'connect').callsFake(() => {
mockClient.connect();
});
sandbox.stub(rpcWebSocket, 'close').callsFake(() => {
mockClient.close();
});
sandbox.stub(rpcWebSocket, 'call').callsFake((method, params) => {
return mockClient.call(method, params);
});
};
export const restoreRpcWebSocket = (connection: Connection) => {
connection._rpcWebSocket.close();
if (connection._rpcWebSocketIdleTimeout !== null) {
clearTimeout(connection._rpcWebSocketIdleTimeout);
connection._rpcWebSocketIdleTimeout = null;
}
sandbox.restore();
};
class MockClient {
mockOpen = false;
subscriptionCounter = 0;
constructor(rpcWebSocket: LiveClient) {
this.client = rpcWebSocket;
}
connect() {
if (!this.mockOpen) {
this.mockOpen = true;
this.client.emit('open');
}
}
close() {
if (this.mockOpen) {
this.mockOpen = false;
this.client.emit('close');
}
}
call(method: string, params: Array<any>): Promise<Object> {
expect(mockRpcSocket.length).to.be.at.least(1);
const [mockRequest, mockResponse] = mockRpcSocket.shift();
expect(method).to.eq(mockRequest.method);
expect(params).to.eql(mockRequest.params);
let id = ++this.subscriptionCounter;
const response = {
subscription: id,
result: mockResponse,
};
setImmediate(() => {
const eventName = method.replace('Subscribe', 'Notification');
this.client.emit(eventName, response);
});
return Promise.resolve(id);
}
}

View File

@ -1,36 +0,0 @@
// @flow
import {Account, Connection} from '../src';
import {mockRpc} from './__mocks__/node-fetch';
import {url} from './url';
export async function newAccountWithLamports(
connection: Connection,
lamports: number = 1000000,
): Promise<Account> {
const account = new Account();
{
mockRpc.push([
url,
{
method: 'requestAirdrop',
params: [account.publicKey.toBase58(), lamports],
},
{
error: null,
// Signature doesn't matter
result:
'3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
},
]);
}
const signature = await connection.requestAirdrop(
account.publicKey,
lamports,
);
await connection.confirmTransaction(signature, 'singleGossip');
return account;
}

View File

@ -1,6 +1,8 @@
// @flow
import bs58 from 'bs58';
import {Buffer} from 'buffer';
import {expect} from 'chai';
import {
Account,
@ -10,15 +12,9 @@ import {
PublicKey,
} from '../src';
import {NONCE_ACCOUNT_LENGTH} from '../src/nonce-account';
import {mockRpc, mockRpcEnabled} from './__mocks__/node-fetch';
import {mockGetRecentBlockhash} from './mockrpc/get-recent-blockhash';
import {url} from './url';
import {mockConfirmTransaction} from './mockrpc/confirm-transaction';
if (!mockRpcEnabled) {
// Testing max commitment level takes around 20s to complete
jest.setTimeout(30000);
}
import {MOCK_PORT, url} from './url';
import {helpers, mockRpcResponse, mockServer} from './mocks/rpc-http';
import {stubRpcWebSocket, restoreRpcWebSocket} from './mocks/rpc-websockets';
const expectedData = (authorizedPubkey: PublicKey): [string, string] => {
const expectedData = Buffer.alloc(NONCE_ACCOUNT_LENGTH);
@ -31,257 +27,154 @@ const expectedData = (authorizedPubkey: PublicKey): [string, string] => {
return [expectedData.toString('base64'), 'base64'];
};
test('create and query nonce account', async () => {
const from = new Account();
const nonceAccount = new Account();
const connection = new Connection(url, 'singleGossip');
describe('Nonce', () => {
let connection: Connection;
beforeEach(() => {
connection = new Connection(url);
});
mockRpc.push([
url,
{
if (!process.env.TEST_LIVE) {
beforeEach(() => {
mockServer.start(MOCK_PORT);
stubRpcWebSocket(connection);
});
afterEach(() => {
mockServer.stop();
restoreRpcWebSocket(connection);
});
}
it('create and query nonce account', async () => {
const from = new Account();
const nonceAccount = new Account();
await mockRpcResponse({
method: 'getMinimumBalanceForRentExemption',
params: [NONCE_ACCOUNT_LENGTH, {commitment: 'singleGossip'}],
},
{
error: null,
result: 50,
},
]);
params: [NONCE_ACCOUNT_LENGTH],
value: 50,
});
const minimumAmount = await connection.getMinimumBalanceForRentExemption(
NONCE_ACCOUNT_LENGTH,
);
const minimumAmount = await connection.getMinimumBalanceForRentExemption(
NONCE_ACCOUNT_LENGTH,
);
mockRpc.push([
url,
{
method: 'requestAirdrop',
params: [from.publicKey.toBase58(), minimumAmount * 2],
},
{
error: null,
result:
'1WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
},
]);
await helpers.airdrop({
connection,
address: from.publicKey,
amount: minimumAmount * 2,
});
const signature = await connection.requestAirdrop(
from.publicKey,
minimumAmount * 2,
);
mockConfirmTransaction(signature);
await connection.confirmTransaction(signature, 'singleGossip');
const transaction = new Transaction().add(
SystemProgram.createNonceAccount({
fromPubkey: from.publicKey,
noncePubkey: nonceAccount.publicKey,
authorizedPubkey: from.publicKey,
lamports: minimumAmount,
}),
);
mockRpc.push([
url,
{
method: 'getBalance',
params: [from.publicKey.toBase58(), {commitment: 'singleGossip'}],
},
{
error: null,
result: {
context: {
slot: 11,
},
value: minimumAmount * 2,
},
},
]);
await helpers.processTransaction({
connection,
transaction,
signers: [from, nonceAccount],
commitment: 'singleGossip',
});
const balance = await connection.getBalance(from.publicKey);
expect(balance).toBe(minimumAmount * 2);
mockGetRecentBlockhash('max');
mockRpc.push([
url,
{
method: 'sendTransaction',
},
{
error: null,
result:
'3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
},
]);
const transaction = new Transaction().add(
SystemProgram.createNonceAccount({
fromPubkey: from.publicKey,
noncePubkey: nonceAccount.publicKey,
authorizedPubkey: from.publicKey,
lamports: minimumAmount,
}),
);
const nonceSignature = await connection.sendTransaction(
transaction,
[from, nonceAccount],
{
skipPreflight: true,
},
);
mockConfirmTransaction(nonceSignature);
await connection.confirmTransaction(nonceSignature, 'singleGossip');
mockRpc.push([
url,
{
await mockRpcResponse({
method: 'getAccountInfo',
params: [
nonceAccount.publicKey.toBase58(),
{encoding: 'base64', commitment: 'singleGossip'},
],
},
{
error: null,
result: {
context: {
slot: 11,
},
value: {
owner: '11111111111111111111111111111111',
lamports: minimumAmount,
data: expectedData(from.publicKey),
executable: false,
},
value: {
owner: '11111111111111111111111111111111',
lamports: minimumAmount,
data: expectedData(from.publicKey),
executable: false,
},
},
]);
//
const nonceAccountData = await connection.getNonce(nonceAccount.publicKey);
if (nonceAccountData === null) {
expect(nonceAccountData).not.toBeNull();
return;
}
expect(nonceAccountData.authorizedPubkey).toEqual(from.publicKey);
expect(bs58.decode(nonceAccountData.nonce).length).toBeGreaterThan(30);
});
withContext: true,
});
test('create and query nonce account with seed', async () => {
const from = new Account();
const seed = 'seed';
const noncePubkey = await PublicKey.createWithSeed(
from.publicKey,
seed,
SystemProgram.programId,
);
const connection = new Connection(url, 'singleGossip');
mockRpc.push([
url,
{
method: 'getMinimumBalanceForRentExemption',
params: [NONCE_ACCOUNT_LENGTH, {commitment: 'singleGossip'}],
},
{
error: null,
result: 50,
},
]);
const minimumAmount = await connection.getMinimumBalanceForRentExemption(
NONCE_ACCOUNT_LENGTH,
);
mockRpc.push([
url,
{
method: 'requestAirdrop',
params: [from.publicKey.toBase58(), minimumAmount * 2],
},
{
error: null,
result:
'1WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
},
]);
const signature = await connection.requestAirdrop(
from.publicKey,
minimumAmount * 2,
);
mockConfirmTransaction(signature);
await connection.confirmTransaction(signature, 'singleGossip');
mockRpc.push([
url,
{
method: 'getBalance',
params: [from.publicKey.toBase58(), {commitment: 'singleGossip'}],
},
{
error: null,
result: {
context: {
slot: 11,
},
value: minimumAmount * 2,
},
},
]);
const balance = await connection.getBalance(from.publicKey);
expect(balance).toBe(minimumAmount * 2);
mockGetRecentBlockhash('max');
mockRpc.push([
url,
{
method: 'sendTransaction',
},
{
error: null,
result:
'3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
},
]);
const transaction = new Transaction().add(
SystemProgram.createNonceAccount({
fromPubkey: from.publicKey,
noncePubkey: noncePubkey,
basePubkey: from.publicKey,
seed,
authorizedPubkey: from.publicKey,
lamports: minimumAmount,
}),
);
const nonceSignature = await connection.sendTransaction(transaction, [from], {
skipPreflight: true,
const nonceAccountData = await connection.getNonce(
nonceAccount.publicKey,
'singleGossip',
);
if (nonceAccountData === null) {
expect(nonceAccountData).not.to.be.null;
return;
}
expect(nonceAccountData.authorizedPubkey).to.eql(from.publicKey);
expect(bs58.decode(nonceAccountData.nonce).length).to.be.greaterThan(30);
});
mockConfirmTransaction(nonceSignature);
await connection.confirmTransaction(nonceSignature, 'singleGossip');
mockRpc.push([
url,
{
it('create and query nonce account with seed', async () => {
const from = new Account();
const seed = 'seed';
const noncePubkey = await PublicKey.createWithSeed(
from.publicKey,
seed,
SystemProgram.programId,
);
await mockRpcResponse({
method: 'getMinimumBalanceForRentExemption',
params: [NONCE_ACCOUNT_LENGTH],
value: 50,
});
const minimumAmount = await connection.getMinimumBalanceForRentExemption(
NONCE_ACCOUNT_LENGTH,
);
await helpers.airdrop({
connection,
address: from.publicKey,
amount: minimumAmount * 2,
});
const transaction = new Transaction().add(
SystemProgram.createNonceAccount({
fromPubkey: from.publicKey,
noncePubkey: noncePubkey,
basePubkey: from.publicKey,
seed,
authorizedPubkey: from.publicKey,
lamports: minimumAmount,
}),
);
await helpers.processTransaction({
connection,
transaction,
signers: [from],
commitment: 'singleGossip',
});
await mockRpcResponse({
method: 'getAccountInfo',
params: [
noncePubkey.toBase58(),
{encoding: 'base64', commitment: 'singleGossip'},
],
},
{
error: null,
result: {
context: {
slot: 11,
},
value: {
owner: '11111111111111111111111111111111',
lamports: minimumAmount,
data: expectedData(from.publicKey),
executable: false,
},
value: {
owner: '11111111111111111111111111111111',
lamports: minimumAmount,
data: expectedData(from.publicKey),
executable: false,
},
},
]);
//
const nonceAccountData = await connection.getNonce(noncePubkey);
if (nonceAccountData === null) {
expect(nonceAccountData).not.toBeNull();
return;
}
expect(nonceAccountData.authorizedPubkey).toEqual(from.publicKey);
expect(bs58.decode(nonceAccountData.nonce).length).toBeGreaterThan(30);
withContext: true,
});
const nonceAccountData = await connection.getNonce(
noncePubkey,
'singleGossip',
);
if (nonceAccountData === null) {
expect(nonceAccountData).not.to.be.null;
return;
}
expect(nonceAccountData.authorizedPubkey).to.eql(from.publicKey);
expect(bs58.decode(nonceAccountData.nonce).length).to.be.greaterThan(30);
});
});

View File

@ -1,11 +1,77 @@
// @flow
import BN from 'bn.js';
import {Buffer} from 'buffer';
import {PublicKey, MAX_SEED_LENGTH} from '../src/publickey';
import {expect, use} from 'chai';
import chaiAsPromised from 'chai-as-promised';
test('invalid', () => {
expect(() => {
new PublicKey([
use(chaiAsPromised);
describe('PublicKey', function () {
it('invalid', () => {
expect(() => {
new PublicKey([
3,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
]);
}).to.throw();
expect(() => {
new PublicKey(
'0x300000000000000000000000000000000000000000000000000000000000000000000',
);
}).to.throw();
expect(() => {
new PublicKey(
'0x300000000000000000000000000000000000000000000000000000000000000',
);
}).to.throw();
expect(() => {
new PublicKey(
'135693854574979916511997248057056142015550763280047535983739356259273198796800000',
);
}).to.throw();
expect(() => {
new PublicKey('12345');
}).to.throw();
});
it('equals', () => {
const arrayKey = new PublicKey([
3,
0,
0,
@ -38,286 +104,228 @@ test('invalid', () => {
0,
0,
0,
]);
const base58Key = new PublicKey(
'CiDwVBFgWV9E5MvXWoLgnEgn2hK7rJikbvfWavzAQz3',
);
expect(arrayKey.equals(base58Key)).to.be.true;
});
it('toBase58', () => {
const key = new PublicKey('CiDwVBFgWV9E5MvXWoLgnEgn2hK7rJikbvfWavzAQz3');
expect(key.toBase58()).to.eq('CiDwVBFgWV9E5MvXWoLgnEgn2hK7rJikbvfWavzAQz3');
expect(key.toString()).to.eq('CiDwVBFgWV9E5MvXWoLgnEgn2hK7rJikbvfWavzAQz3');
const key2 = new PublicKey('1111111111111111111111111111BukQL');
expect(key2.toBase58()).to.eq('1111111111111111111111111111BukQL');
expect(key2.toString()).to.eq('1111111111111111111111111111BukQL');
const key3 = new PublicKey('11111111111111111111111111111111');
expect(key3.toBase58()).to.eq('11111111111111111111111111111111');
const key4 = new PublicKey([
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
]);
}).toThrow();
expect(key4.toBase58()).to.eq('11111111111111111111111111111111');
});
expect(() => {
new PublicKey(
'0x300000000000000000000000000000000000000000000000000000000000000000000',
it('toBuffer', () => {
const key = new PublicKey('CiDwVBFgWV9E5MvXWoLgnEgn2hK7rJikbvfWavzAQz3');
expect(key.toBuffer()).to.have.length(32);
expect(key.toBase58()).to.eq('CiDwVBFgWV9E5MvXWoLgnEgn2hK7rJikbvfWavzAQz3');
const key2 = new PublicKey('11111111111111111111111111111111');
expect(key2.toBuffer()).to.have.length(32);
expect(key2.toBase58()).to.eq('11111111111111111111111111111111');
const key3 = new PublicKey(0);
expect(key3.toBuffer()).to.have.length(32);
expect(key3.toBase58()).to.eq('11111111111111111111111111111111');
});
it('equals (II)', () => {
const key1 = new PublicKey([
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
1,
]);
const key2 = new PublicKey(key1.toBuffer());
expect(key1.equals(key2)).to.be.true;
});
it('createWithSeed', async () => {
const defaultPublicKey = new PublicKey('11111111111111111111111111111111');
const derivedKey = await PublicKey.createWithSeed(
defaultPublicKey,
'limber chicken: 4/45',
defaultPublicKey,
);
}).toThrow();
expect(() => {
new PublicKey(
'0x300000000000000000000000000000000000000000000000000000000000000',
expect(
derivedKey.equals(
new PublicKey('9h1HyLCW5dZnBVap8C5egQ9Z6pHyjsh5MNy83iPqqRuq'),
),
).to.be.true;
});
it('createProgramAddress', async () => {
const programId = new PublicKey(
'BPFLoader1111111111111111111111111111111111',
);
}).toThrow();
expect(() => {
new PublicKey(
'135693854574979916511997248057056142015550763280047535983739356259273198796800000',
const publicKey = new PublicKey(
'SeedPubey1111111111111111111111111111111111',
);
}).toThrow();
expect(() => {
new PublicKey('12345');
}).toThrow();
});
test('equals', () => {
const arrayKey = new PublicKey([
3,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
]);
const base58Key = new PublicKey(
'CiDwVBFgWV9E5MvXWoLgnEgn2hK7rJikbvfWavzAQz3',
);
expect(arrayKey.equals(base58Key)).toBe(true);
});
test('toBase58', () => {
const key = new PublicKey('CiDwVBFgWV9E5MvXWoLgnEgn2hK7rJikbvfWavzAQz3');
expect(key.toBase58()).toBe('CiDwVBFgWV9E5MvXWoLgnEgn2hK7rJikbvfWavzAQz3');
expect(key.toString()).toBe('CiDwVBFgWV9E5MvXWoLgnEgn2hK7rJikbvfWavzAQz3');
const key2 = new PublicKey('1111111111111111111111111111BukQL');
expect(key2.toBase58()).toBe('1111111111111111111111111111BukQL');
expect(key2.toString()).toBe('1111111111111111111111111111BukQL');
const key3 = new PublicKey('11111111111111111111111111111111');
expect(key3.toBase58()).toBe('11111111111111111111111111111111');
const key4 = new PublicKey([
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
]);
expect(key4.toBase58()).toBe('11111111111111111111111111111111');
});
test('toBuffer', () => {
const key = new PublicKey('CiDwVBFgWV9E5MvXWoLgnEgn2hK7rJikbvfWavzAQz3');
expect(key.toBuffer()).toHaveLength(32);
expect(key.toBase58()).toBe('CiDwVBFgWV9E5MvXWoLgnEgn2hK7rJikbvfWavzAQz3');
const key2 = new PublicKey('11111111111111111111111111111111');
expect(key2.toBuffer()).toHaveLength(32);
expect(key2.toBase58()).toBe('11111111111111111111111111111111');
const key3 = new PublicKey(0);
expect(key3.toBuffer()).toHaveLength(32);
expect(key3.toBase58()).toBe('11111111111111111111111111111111');
});
test('equals (II)', () => {
const key1 = new PublicKey([
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
1,
]);
const key2 = new PublicKey(key1.toBuffer());
expect(key1.equals(key2)).toBe(true);
});
test('createWithSeed', async () => {
const defaultPublicKey = new PublicKey('11111111111111111111111111111111');
const derivedKey = await PublicKey.createWithSeed(
defaultPublicKey,
'limber chicken: 4/45',
defaultPublicKey,
);
expect(
derivedKey.equals(
new PublicKey('9h1HyLCW5dZnBVap8C5egQ9Z6pHyjsh5MNy83iPqqRuq'),
),
).toBe(true);
});
test('createProgramAddress', async () => {
const programId = new PublicKey(
'BPFLoader1111111111111111111111111111111111',
);
const publicKey = new PublicKey(
'SeedPubey1111111111111111111111111111111111',
);
let programAddress = await PublicKey.createProgramAddress(
[Buffer.from('', 'utf8'), Buffer.from([1])],
programId,
);
expect(
programAddress.equals(
new PublicKey('3gF2KMe9KiC6FNVBmfg9i267aMPvK37FewCip4eGBFcT'),
),
).toBe(true);
programAddress = await PublicKey.createProgramAddress(
[Buffer.from('☉', 'utf8')],
programId,
);
expect(
programAddress.equals(
new PublicKey('7ytmC1nT1xY4RfxCV2ZgyA7UakC93do5ZdyhdF3EtPj7'),
),
).toBe(true);
programAddress = await PublicKey.createProgramAddress(
[Buffer.from('Talking', 'utf8'), Buffer.from('Squirrels', 'utf8')],
programId,
);
expect(
programAddress.equals(
new PublicKey('HwRVBufQ4haG5XSgpspwKtNd3PC9GM9m1196uJW36vds'),
),
).toBe(true);
programAddress = await PublicKey.createProgramAddress(
[publicKey.toBuffer()],
programId,
);
expect(
programAddress.equals(
new PublicKey('GUs5qLUfsEHkcMB9T38vjr18ypEhRuNWiePW2LoK4E3K'),
),
).toBe(true);
const programAddress2 = await PublicKey.createProgramAddress(
[Buffer.from('Talking', 'utf8')],
programId,
);
expect(programAddress.equals(programAddress2)).toBe(false);
await expect(
PublicKey.createProgramAddress(
[Buffer.alloc(MAX_SEED_LENGTH + 1)],
let programAddress = await PublicKey.createProgramAddress(
[Buffer.from('', 'utf8'), Buffer.from([1])],
programId,
),
).rejects.toThrow('Max seed length exceeded');
// https://github.com/solana-labs/solana/issues/11950
{
let seeds = [
new PublicKey('H4snTKK9adiU15gP22ErfZYtro3aqR9BTMXiH3AwiUTQ').toBuffer(),
new BN(2).toArrayLike(Buffer, 'le', 8),
];
let programId = new PublicKey(
'4ckmDgGdxQoPDLUkDT3vHgSAkzA3QRdNq5ywwY4sUSJn',
);
programAddress = await PublicKey.createProgramAddress(seeds, programId);
expect(
programAddress.equals(
new PublicKey('12rqwuEgBYiGhBrDJStCiqEtzQpTTiZbh7teNVLuYcFA'),
new PublicKey('3gF2KMe9KiC6FNVBmfg9i267aMPvK37FewCip4eGBFcT'),
),
).toBe(true);
}
});
).to.be.true;
test('findProgramAddress', async () => {
const programId = new PublicKey(
'BPFLoader1111111111111111111111111111111111',
);
let [programAddress, nonce] = await PublicKey.findProgramAddress(
[Buffer.from('', 'utf8')],
programId,
);
expect(
programAddress.equals(
await PublicKey.createProgramAddress(
[Buffer.from('', 'utf8'), Buffer.from([nonce])],
programAddress = await PublicKey.createProgramAddress(
[Buffer.from('☉', 'utf8')],
programId,
);
expect(
programAddress.equals(
new PublicKey('7ytmC1nT1xY4RfxCV2ZgyA7UakC93do5ZdyhdF3EtPj7'),
),
).to.be.true;
programAddress = await PublicKey.createProgramAddress(
[Buffer.from('Talking', 'utf8'), Buffer.from('Squirrels', 'utf8')],
programId,
);
expect(
programAddress.equals(
new PublicKey('HwRVBufQ4haG5XSgpspwKtNd3PC9GM9m1196uJW36vds'),
),
).to.be.true;
programAddress = await PublicKey.createProgramAddress(
[publicKey.toBuffer()],
programId,
);
expect(
programAddress.equals(
new PublicKey('GUs5qLUfsEHkcMB9T38vjr18ypEhRuNWiePW2LoK4E3K'),
),
).to.be.true;
const programAddress2 = await PublicKey.createProgramAddress(
[Buffer.from('Talking', 'utf8')],
programId,
);
expect(programAddress.equals(programAddress2)).to.eq(false);
await expect(
PublicKey.createProgramAddress(
[Buffer.alloc(MAX_SEED_LENGTH + 1)],
programId,
),
),
).toBe(true);
).to.be.rejectedWith('Max seed length exceeded');
// https://github.com/solana-labs/solana/issues/11950
{
let seeds = [
new PublicKey(
'H4snTKK9adiU15gP22ErfZYtro3aqR9BTMXiH3AwiUTQ',
).toBuffer(),
new BN(2).toArrayLike(Buffer, 'le', 8),
];
let programId = new PublicKey(
'4ckmDgGdxQoPDLUkDT3vHgSAkzA3QRdNq5ywwY4sUSJn',
);
programAddress = await PublicKey.createProgramAddress(seeds, programId);
expect(
programAddress.equals(
new PublicKey('12rqwuEgBYiGhBrDJStCiqEtzQpTTiZbh7teNVLuYcFA'),
),
).to.be.true;
}
});
it('findProgramAddress', async () => {
const programId = new PublicKey(
'BPFLoader1111111111111111111111111111111111',
);
let [programAddress, nonce] = await PublicKey.findProgramAddress(
[Buffer.from('', 'utf8')],
programId,
);
expect(
programAddress.equals(
await PublicKey.createProgramAddress(
[Buffer.from('', 'utf8'), Buffer.from([nonce])],
programId,
),
),
).to.be.true;
});
});

View File

@ -0,0 +1,76 @@
import alias from '@rollup/plugin-alias';
import babel from '@rollup/plugin-babel';
import commonjs from '@rollup/plugin-commonjs';
import flowRemoveTypes from 'flow-remove-types';
import json from '@rollup/plugin-json';
import multi from '@rollup/plugin-multi-entry';
import nodeResolve from '@rollup/plugin-node-resolve';
import nodePolyfills from 'rollup-plugin-node-polyfills';
import replace from '@rollup/plugin-replace';
export default {
input: {
// include: [
// 'test/account.test.js',
// 'test/cluster.test.js',
// 'test/stake-program.test.js',
// ],
include: ['test/**/*.test.js'],
exclude: ['test/agent-manager.test.js', 'test/bpf-loader.test.js'],
},
external: ['node-forge', 'http2', '_stream_wrap'],
output: {
file: 'test/dist/bundle.js',
format: 'es',
sourcemap: true,
},
plugins: [
flow(),
multi(),
commonjs(),
nodeResolve({
browser: true,
preferBuiltins: false,
dedupe: ['bn.js', 'buffer'],
}),
babel({
exclude: '**/node_modules/**',
babelHelpers: 'runtime',
plugins: ['@babel/plugin-transform-runtime'],
}),
nodePolyfills(),
replace({
'process.env.BROWSER': 'true',
'process.env.TEST_LIVE': 'true',
}),
alias({
entries: [
{
find: /^\.\.\/src\/.*\.js$/,
replacement: './lib/index.browser.esm.js',
},
],
}),
json(),
],
onwarn: function (warning, rollupWarn) {
if (warning.code !== 'CIRCULAR_DEPENDENCY' && warning.code !== 'EVAL') {
rollupWarn(warning);
}
},
treeshake: {
moduleSideEffects: path => path.endsWith('test.js'),
},
};
// Using this instead of rollup-plugin-flow due to
// https://github.com/leebyron/rollup-plugin-flow/issues/5
function flow() {
return {
name: 'flow-remove-types',
transform: code => ({
code: flowRemoveTypes(code).toString(),
map: null,
}),
};
}

View File

@ -1,88 +1,82 @@
// @flow
import {keccak_256} from 'js-sha3';
import secp256k1 from 'secp256k1';
import {randomBytes} from 'crypto';
import {Buffer} from 'buffer';
import createKeccakHash from 'keccak';
import {privateKeyVerify, ecdsaSign, publicKeyCreate} from 'secp256k1';
import {Secp256k1Program} from '../src/secp256k1-program';
import {mockRpcEnabled} from './__mocks__/node-fetch';
import {url} from './url';
import {
Connection,
Account,
sendAndConfirmTransaction,
LAMPORTS_PER_SOL,
Transaction,
Secp256k1Program,
} from '../src';
import {url} from './url';
import {helpers} from './mocks/rpc-http';
const {privateKeyVerify, ecdsaSign, publicKeyCreate} = secp256k1;
const randomPrivateKey = () => {
let privateKey;
do {
privateKey = new Account().secretKey.slice(0, 32);
} while (!privateKeyVerify(privateKey));
return privateKey;
};
if (!mockRpcEnabled) {
jest.setTimeout(20000);
if (process.env.TEST_LIVE) {
describe('secp256k1', () => {
it('create secp256k1 instruction with public key', async () => {
const privateKey = randomPrivateKey();
const publicKey = publicKeyCreate(privateKey, false);
const message = Buffer.from('This is a message');
const messageHash = createKeccakHash('keccak256')
.update(message)
.digest();
const {signature, recid: recoveryId} = ecdsaSign(messageHash, privateKey);
const connection = new Connection(url, 'singleGossip');
const from = new Account();
await connection.confirmTransaction(
await connection.requestAirdrop(from.publicKey, 2 * LAMPORTS_PER_SOL),
'singleGossip',
);
const transaction = new Transaction().add(
Secp256k1Program.createInstructionWithPublicKey({
publicKey,
message,
signature,
recoveryId,
}),
);
await sendAndConfirmTransaction(connection, transaction, [from], {
commitment: 'singleGossip',
preflightCommitment: 'singleGossip',
});
});
it('create secp256k1 instruction with private key', async () => {
const privateKey = randomPrivateKey();
const connection = new Connection(url, 'singleGossip');
const from = new Account();
await connection.confirmTransaction(
await connection.requestAirdrop(from.publicKey, 2 * LAMPORTS_PER_SOL),
'singleGossip',
);
const transaction = new Transaction().add(
Secp256k1Program.createInstructionWithPrivateKey({
privateKey,
message: Buffer.from('Test 123'),
}),
);
await sendAndConfirmTransaction(connection, transaction, [from], {
commitment: 'singleGossip',
preflightCommitment: 'singleGossip',
});
});
});
}
test('live create secp256k1 instruction with public key', async () => {
if (mockRpcEnabled) {
console.log('non-live test skipped');
return;
}
const message = Buffer.from('This is a message');
let privateKey;
do {
privateKey = randomBytes(32);
} while (!privateKeyVerify(privateKey));
const publicKey = publicKeyCreate(privateKey, false);
const messageHash = Buffer.from(keccak_256.update(message).digest());
const {signature, recid: recoveryId} = ecdsaSign(messageHash, privateKey);
const instruction = Secp256k1Program.createInstructionWithPublicKey({
publicKey,
message,
signature,
recoveryId,
});
const transaction = new Transaction();
transaction.add(instruction);
const connection = new Connection(url, 'recent');
const from = new Account();
await connection.requestAirdrop(from.publicKey, 2 * LAMPORTS_PER_SOL);
await sendAndConfirmTransaction(connection, transaction, [from], {
commitment: 'single',
skipPreflight: true,
});
});
test('live create secp256k1 instruction with private key', async () => {
if (mockRpcEnabled) {
console.log('non-live test skipped');
return;
}
let privateKey;
do {
privateKey = randomBytes(32);
} while (!privateKeyVerify(privateKey));
const instruction = Secp256k1Program.createInstructionWithPrivateKey({
privateKey,
message: Buffer.from('Test 123'),
});
const transaction = new Transaction();
transaction.add(instruction);
const connection = new Connection(url, 'recent');
const from = new Account();
await connection.requestAirdrop(from.publicKey, 2 * LAMPORTS_PER_SOL);
await sendAndConfirmTransaction(connection, transaction, [from], {
commitment: 'single',
skipPreflight: true,
});
});

View File

@ -1,38 +1,13 @@
// @flow
import {expect} from 'chai';
import {decodeLength, encodeLength} from '../src/util/shortvec-encoding';
function checkDecodedArray(array: Array<number>, expectedValue: number) {
expect(decodeLength(array)).toEqual(expectedValue);
expect(array.length).toEqual(0);
expect(decodeLength(array)).to.eq(expectedValue);
expect(array).to.have.length(0);
}
test('shortvec decodeLength', () => {
let array = [];
checkDecodedArray(array, 0);
array = [5];
checkDecodedArray(array, 5);
array = [0x7f];
checkDecodedArray(array, 0x7f);
array = [0x80, 0x01];
checkDecodedArray(array, 0x80);
array = [0xff, 0x01];
checkDecodedArray(array, 0xff);
array = [0x80, 0x02];
checkDecodedArray(array, 0x100);
array = [0xff, 0xff, 0x01];
checkDecodedArray(array, 0x7fff);
array = [0x80, 0x80, 0x80, 0x01];
checkDecodedArray(array, 0x200000);
});
function checkEncodedArray(
array: Array<number>,
len: number,
@ -41,31 +16,59 @@ function checkEncodedArray(
expectedArray: Array<number>,
) {
encodeLength(array, len);
expect(array.length).toEqual(prevLength);
expect(array.slice(-addedLength)).toEqual(expectedArray);
expect(array).to.have.length(prevLength);
expect(array.slice(-addedLength)).to.eql(expectedArray);
}
test('shortvec encodeLength', () => {
let array = [];
let prevLength = 1;
checkEncodedArray(array, 0, prevLength, 1, [0]);
describe('shortvec', () => {
it('decodeLength', () => {
let array = [];
checkDecodedArray(array, 0);
checkEncodedArray(array, 5, (prevLength += 1), 1, [5]);
array = [5];
checkDecodedArray(array, 5);
checkEncodedArray(array, 0x7f, (prevLength += 1), 1, [0x7f]);
array = [0x7f];
checkDecodedArray(array, 0x7f);
checkEncodedArray(array, 0x80, (prevLength += 2), 2, [0x80, 0x01]);
array = [0x80, 0x01];
checkDecodedArray(array, 0x80);
checkEncodedArray(array, 0xff, (prevLength += 2), 2, [0xff, 0x01]);
array = [0xff, 0x01];
checkDecodedArray(array, 0xff);
checkEncodedArray(array, 0x100, (prevLength += 2), 2, [0x80, 0x02]);
array = [0x80, 0x02];
checkDecodedArray(array, 0x100);
checkEncodedArray(array, 0x7fff, (prevLength += 3), 3, [0xff, 0xff, 0x01]);
array = [0xff, 0xff, 0x01];
checkDecodedArray(array, 0x7fff);
prevLength = checkEncodedArray(array, 0x200000, (prevLength += 4), 4, [
0x80,
0x80,
0x80,
0x01,
]);
array = [0x80, 0x80, 0x80, 0x01];
checkDecodedArray(array, 0x200000);
});
it('encodeLength', () => {
let array = [];
let prevLength = 1;
checkEncodedArray(array, 0, prevLength, 1, [0]);
checkEncodedArray(array, 5, (prevLength += 1), 1, [5]);
checkEncodedArray(array, 0x7f, (prevLength += 1), 1, [0x7f]);
checkEncodedArray(array, 0x80, (prevLength += 2), 2, [0x80, 0x01]);
checkEncodedArray(array, 0xff, (prevLength += 2), 2, [0xff, 0x01]);
checkEncodedArray(array, 0x100, (prevLength += 2), 2, [0x80, 0x02]);
checkEncodedArray(array, 0x7fff, (prevLength += 3), 3, [0xff, 0xff, 0x01]);
prevLength = checkEncodedArray(array, 0x200000, (prevLength += 4), 4, [
0x80,
0x80,
0x80,
0x01,
]);
});
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,7 @@
// @flow
import {expect} from 'chai';
import {
Account,
Connection,
@ -6,202 +9,136 @@ import {
SystemProgram,
LAMPORTS_PER_SOL,
} from '../src';
import {mockRpc, mockRpcEnabled} from './__mocks__/node-fetch';
import {mockGetRecentBlockhash} from './mockrpc/get-recent-blockhash';
import {url} from './url';
import {mockConfirmTransaction} from './mockrpc/confirm-transaction';
import {MOCK_PORT, url} from './url';
if (!mockRpcEnabled) {
// The default of 5 seconds is too slow for live testing sometimes
jest.setTimeout(30000);
}
import {
helpers,
mockErrorMessage,
mockErrorResponse,
uniqueSignature,
uniqueBlockhash,
mockRpcResponse,
mockServer,
} from './mocks/rpc-http';
import {
stubRpcWebSocket,
restoreRpcWebSocket,
mockRpcMessage,
} from './mocks/rpc-websockets';
import base58 from 'bs58';
test('transaction-payer', async () => {
const accountPayer = new Account();
const accountFrom = new Account();
const accountTo = new Account();
const connection = new Connection(url, 'singleGossip');
describe('Transaction Payer', () => {
let connection: Connection;
beforeEach(() => {
connection = new Connection(url);
});
mockRpc.push([
url,
{
method: 'getMinimumBalanceForRentExemption',
params: [0, {commitment: 'singleGossip'}],
},
{
error: null,
result: 50,
},
]);
if (!process.env.TEST_LIVE) {
beforeEach(() => {
mockServer.start(MOCK_PORT);
stubRpcWebSocket(connection);
});
const minimumAmount = await connection.getMinimumBalanceForRentExemption(
0,
'singleGossip',
);
mockRpc.push([
url,
{
method: 'requestAirdrop',
params: [accountPayer.publicKey.toBase58(), LAMPORTS_PER_SOL],
},
{
error: null,
result:
'8WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
},
]);
let signature = await connection.requestAirdrop(
accountPayer.publicKey,
LAMPORTS_PER_SOL,
);
mockConfirmTransaction(signature);
await connection.confirmTransaction(signature, 'singleGossip');
mockRpc.push([
url,
{
method: 'requestAirdrop',
params: [accountFrom.publicKey.toBase58(), minimumAmount + 12],
},
{
error: null,
result:
'8WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
},
]);
signature = await connection.requestAirdrop(
accountFrom.publicKey,
minimumAmount + 12,
);
mockConfirmTransaction(signature);
await connection.confirmTransaction(signature, 'singleGossip');
mockRpc.push([
url,
{
method: 'requestAirdrop',
params: [accountTo.publicKey.toBase58(), minimumAmount + 21],
},
{
error: null,
result:
'8WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
},
]);
signature = await connection.requestAirdrop(
accountTo.publicKey,
minimumAmount + 21,
);
mockConfirmTransaction(signature);
await connection.confirmTransaction(signature, 'singleGossip');
mockGetRecentBlockhash('max');
mockRpc.push([
url,
{
method: 'sendTransaction',
},
{
error: null,
result:
'3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
},
]);
const transaction = new Transaction().add(
SystemProgram.transfer({
fromPubkey: accountFrom.publicKey,
toPubkey: accountTo.publicKey,
lamports: 10,
}),
);
signature = await connection.sendTransaction(
transaction,
[accountPayer, accountFrom],
{skipPreflight: true},
);
mockConfirmTransaction(signature);
await connection.confirmTransaction(signature, 'singleGossip');
mockRpc.push([
url,
{
method: 'getSignatureStatuses',
params: [
[
'3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
],
],
},
{
error: null,
result: {
context: {
slot: 11,
},
value: [
{
slot: 0,
confirmations: 11,
status: {Ok: null},
err: null,
},
],
},
},
]);
const {value} = await connection.getSignatureStatus(signature);
if (value !== null) {
expect(typeof value.slot).toEqual('number');
expect(value.err).toBeNull();
} else {
expect(value).not.toBeNull();
afterEach(() => {
mockServer.stop();
restoreRpcWebSocket(connection);
});
}
mockRpc.push([
url,
{
it('transaction-payer', async () => {
const accountPayer = new Account();
const accountFrom = new Account();
const accountTo = new Account();
await helpers.airdrop({
connection,
address: accountPayer.publicKey,
amount: LAMPORTS_PER_SOL,
});
await mockRpcResponse({
method: 'getMinimumBalanceForRentExemption',
params: [0],
value: 50,
});
const minimumAmount = await connection.getMinimumBalanceForRentExemption(0);
await helpers.airdrop({
connection,
address: accountFrom.publicKey,
amount: minimumAmount + 12,
});
await helpers.airdrop({
connection,
address: accountTo.publicKey,
amount: minimumAmount + 21,
});
const transaction = new Transaction().add(
SystemProgram.transfer({
fromPubkey: accountFrom.publicKey,
toPubkey: accountTo.publicKey,
lamports: 10,
}),
);
await helpers.processTransaction({
connection,
transaction,
signers: [accountPayer, accountFrom],
commitment: 'singleGossip',
});
const signature = base58.encode(transaction.signature);
await mockRpcResponse({
method: 'getSignatureStatuses',
params: [[signature]],
value: [
{
slot: 0,
confirmations: 11,
status: {Ok: null},
err: null,
},
],
withContext: true,
});
const {value} = await connection.getSignatureStatus(signature);
if (value !== null) {
expect(typeof value.slot).to.eq('number');
expect(value.err).to.be.null;
} else {
expect(value).not.to.be.null;
}
await mockRpcResponse({
method: 'getBalance',
params: [accountPayer.publicKey.toBase58(), {commitment: 'singleGossip'}],
},
{
error: null,
result: {
context: {
slot: 11,
},
value: LAMPORTS_PER_SOL - 1,
},
},
]);
value: LAMPORTS_PER_SOL - 1,
withContext: true,
});
// accountPayer should be less than LAMPORTS_PER_SOL as it paid for the transaction
// (exact amount less depends on the current cluster fees)
const balance = await connection.getBalance(accountPayer.publicKey);
expect(balance).toBeGreaterThan(0);
expect(balance).toBeLessThanOrEqual(LAMPORTS_PER_SOL);
// accountPayer should be less than LAMPORTS_PER_SOL as it paid for the transaction
// (exact amount less depends on the current cluster fees)
const balance = await connection.getBalance(
accountPayer.publicKey,
'singleGossip',
);
expect(balance).to.be.greaterThan(0);
expect(balance).to.be.at.most(LAMPORTS_PER_SOL);
// accountFrom should have exactly 2, since it didn't pay for the transaction
mockRpc.push([
url,
{
// accountFrom should have exactly 2, since it didn't pay for the transaction
await mockRpcResponse({
method: 'getBalance',
params: [accountFrom.publicKey.toBase58(), {commitment: 'singleGossip'}],
},
{
error: null,
result: {
context: {
slot: 11,
},
value: minimumAmount + 2,
},
},
]);
expect(await connection.getBalance(accountFrom.publicKey)).toBe(
minimumAmount + 2,
);
value: minimumAmount + 2,
withContext: true,
});
expect(
await connection.getBalance(accountFrom.publicKey, 'singleGossip'),
).to.eq(minimumAmount + 2);
});
});

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,10 @@
* The connection url to use when running unit tests against a live cluster
*/
export const url = 'http://localhost:8899/';
export const MOCK_PORT = 9999;
export const url = process.env.TEST_LIVE
? 'http://localhost:8899/'
: 'http://localhost:9999/';
//export const url = 'https://devnet.solana.com/';
//export const url = 'http://devnet.solana.com/';

View File

@ -1,36 +1,40 @@
// @flow
import {Buffer} from 'buffer';
import {expect} from 'chai';
import nacl from 'tweetnacl';
import {PublicKey} from '../src/publickey';
import {ValidatorInfo} from '../src/validator-info';
test('from config account data', () => {
const keypair = nacl.sign.keyPair.fromSeed(
Uint8Array.from(Array(32).fill(8)),
);
describe('ValidatorInfo', () => {
it('from config account data', () => {
const keypair = nacl.sign.keyPair.fromSeed(
Uint8Array.from(Array(32).fill(8)),
);
const expectedValidatorInfo = new ValidatorInfo(
new PublicKey(keypair.publicKey),
{
name: 'Validator',
keybaseUsername: 'validator_id',
},
);
const expectedValidatorInfo = new ValidatorInfo(
new PublicKey(keypair.publicKey),
{
name: 'Validator',
keybaseUsername: 'validator_id',
},
);
// Config data string steps:
// 1) Generate a keypair
// 2) Airdrop lamports to the account
// 3) Modify the `solana-validator-info` tool
// a) Remove the keybase id verification step
// b) Print base64 account data in the `get --all` codepath
// c) Add `println!("Account data: {:?}", base64::encode(&account.data));`
// 4) Use modified `solana-validator-info` tool to publish validator info
// 5) And then use it again to fetch the data! (feel free to trim some A's)
const configData = Buffer.from(
'AgdRlwF0SPKsXcI8nrx6x4wKJyV6xhRFjeCk8W+AAAAAABOY9ixtGkV8UbpqS189vS9p/KkyFiGNyJl+QWvRfZPKATUAAAAAAAAAeyJrZXliYXNlVXNlcm5hbWUiOiJ2YWxpZGF0b3JfaWQiLCJuYW1lIjoiVmFsaWRhdG9yIn0',
'base64',
);
const info = ValidatorInfo.fromConfigData(configData);
// Config data string steps:
// 1) Generate a keypair
// 2) Airdrop lamports to the account
// 3) Modify the `solana-validator-info` tool
// a) Remove the keybase id verification step
// b) Print base64 account data in the `get --all` codepath
// c) Add `println!("Account data: {:?}", base64::encode(&account.data));`
// 4) Use modified `solana-validator-info` tool to publish validator info
// 5) And then use it again to fetch the data! (feel free to trim some A's)
const configData = Buffer.from(
'AgdRlwF0SPKsXcI8nrx6x4wKJyV6xhRFjeCk8W+AAAAAABOY9ixtGkV8UbpqS189vS9p/KkyFiGNyJl+QWvRfZPKATUAAAAAAAAAeyJrZXliYXNlVXNlcm5hbWUiOiJ2YWxpZGF0b3JfaWQiLCJuYW1lIjoiVmFsaWRhdG9yIn0',
'base64',
);
const info = ValidatorInfo.fromConfigData(configData);
expect(info).toEqual(expectedValidatorInfo);
expect(info).to.eql(expectedValidatorInfo);
});
});

View File

@ -1,68 +1,68 @@
// @flow
import bs58 from 'bs58';
import {Buffer} from 'buffer';
import {expect, use} from 'chai';
import chaiAsPromised from 'chai-as-promised';
import {Connection} from '../src';
import {url} from './url';
import {mockRpcEnabled} from './__mocks__/node-fetch';
import {sleep} from '../src/util/sleep';
describe('websocket', () => {
if (mockRpcEnabled) {
test('no-op', () => {});
console.log('non-live test skipped');
return;
}
use(chaiAsPromised);
const connection = new Connection(url);
if (process.env.TEST_LIVE) {
describe('websocket', () => {
const connection = new Connection(url);
test('connect and disconnect', async () => {
const testSignature = bs58.encode(Buffer.alloc(64));
const id = connection.onSignature(testSignature, () => {});
it('connect and disconnect', async () => {
const testSignature = bs58.encode(Buffer.alloc(64));
const id = connection.onSignature(testSignature, () => {});
// wait for websocket to connect
await sleep(100);
expect(connection._rpcWebSocketConnected).toBe(true);
expect(connection._rpcWebSocketHeartbeat).not.toBe(null);
// wait for websocket to connect
await sleep(100);
expect(connection._rpcWebSocketConnected).to.be.true;
expect(connection._rpcWebSocketHeartbeat).not.to.eq(null);
// test if socket is open
await connection._rpcWebSocket.notify('ping');
// test if socket is open
await connection._rpcWebSocket.notify('ping');
await connection.removeSignatureListener(id);
expect(connection._rpcWebSocketConnected).toBe(false);
expect(connection._rpcWebSocketHeartbeat).not.toBe(null);
expect(connection._rpcWebSocketIdleTimeout).not.toBe(null);
await connection.removeSignatureListener(id);
expect(connection._rpcWebSocketConnected).to.eq(false);
expect(connection._rpcWebSocketHeartbeat).not.to.eq(null);
expect(connection._rpcWebSocketIdleTimeout).not.to.eq(null);
// wait for websocket to disconnect
await sleep(1100);
expect(connection._rpcWebSocketConnected).toBe(false);
expect(connection._rpcWebSocketHeartbeat).toBe(null);
expect(connection._rpcWebSocketIdleTimeout).toBe(null);
// wait for websocket to disconnect
await sleep(1100);
expect(connection._rpcWebSocketConnected).to.eq(false);
expect(connection._rpcWebSocketHeartbeat).to.eq(null);
expect(connection._rpcWebSocketIdleTimeout).to.eq(null);
// test if socket is closed
await expect(connection._rpcWebSocket.notify('ping')).rejects.toThrow(
'socket not ready',
);
// test if socket is closed
await expect(connection._rpcWebSocket.notify('ping')).to.be.rejectedWith(
'socket not ready',
);
});
it('idle timeout', async () => {
const testSignature = bs58.encode(Buffer.alloc(64));
const id = connection.onSignature(testSignature, () => {});
// wait for websocket to connect
await sleep(100);
expect(connection._rpcWebSocketIdleTimeout).to.eq(null);
await connection.removeSignatureListener(id);
expect(connection._rpcWebSocketIdleTimeout).not.to.eq(null);
const nextId = connection.onSignature(testSignature, () => {});
expect(connection._rpcWebSocketIdleTimeout).to.eq(null);
await connection.removeSignatureListener(nextId);
expect(connection._rpcWebSocketIdleTimeout).not.to.eq(null);
// wait for websocket to disconnect
await sleep(1100);
expect(connection._rpcWebSocketIdleTimeout).to.eq(null);
});
});
test('idle timeout', async () => {
const testSignature = bs58.encode(Buffer.alloc(64));
const id = connection.onSignature(testSignature, () => {});
// wait for websocket to connect
await sleep(100);
expect(connection._rpcWebSocketIdleTimeout).toBe(null);
await connection.removeSignatureListener(id);
expect(connection._rpcWebSocketIdleTimeout).not.toBe(null);
const nextId = connection.onSignature(testSignature, () => {});
expect(connection._rpcWebSocketIdleTimeout).toBe(null);
await connection.removeSignatureListener(nextId);
expect(connection._rpcWebSocketIdleTimeout).not.toBe(null);
// wait for websocket to disconnect
await sleep(1100);
expect(connection._rpcWebSocketIdleTimeout).toBe(null);
});
});
}