Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement light client verification methods #1116

Open
wants to merge 34 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
a537493
feat: implement light client verification methods
austinabell Apr 24, 2023
39b4575
chore: docs
austinabell Apr 25, 2023
879a7ab
refactor: move light-client APIs to own crate, migrate sha impl
austinabell Apr 26, 2023
17f0cf3
chore: changeset
austinabell Apr 26, 2023
f47b4fa
chore: add docs to validation steps
austinabell Apr 26, 2023
f8612ff
refactor: switch function params to be an object rather than positional
austinabell Apr 26, 2023
b280d8e
chore: revert naj test changes
austinabell Apr 26, 2023
4e81fae
test: adds back light client block verification test
austinabell Apr 26, 2023
09f957f
chore: lint
austinabell Apr 26, 2023
8ad77ae
test: add back execution proof verification in NAJ test
austinabell Apr 26, 2023
153dfa7
test: add execution proof test vectors
austinabell Apr 26, 2023
59b4f14
chore: address comments before refactoring
austinabell Apr 27, 2023
68601e5
refactor: move Enum class to types
austinabell Apr 27, 2023
73644e2
refactor: move light client logic into separate files
austinabell Apr 27, 2023
ab94d18
refactor: move borsh utils to own file
austinabell Apr 27, 2023
e15fad7
Merge branch 'master' into light_client
austinabell Apr 27, 2023
206df23
chore: remove todo from updated types
austinabell Apr 27, 2023
de393fb
chore: update changeset
austinabell Apr 27, 2023
daca5d2
test: move execution verification providers check into accounts
austinabell Apr 27, 2023
f509b3f
Merge branch 'master' into light_client
austinabell Apr 29, 2023
0dde04f
Merge branch 'master' into light_client
austinabell May 4, 2023
29c9ea6
fix: bug with bp signature validation
austinabell May 5, 2023
fe45242
refactor: move block hash generation to after trivial validation
austinabell May 5, 2023
20bff2e
chore: lint fix
austinabell May 5, 2023
0559e23
fix: bn comparison bug
austinabell May 23, 2023
d867179
fix: execution test parameter format from changes
austinabell May 24, 2023
8731572
Merge branch 'master' into light_client
austinabell May 24, 2023
f766671
fix: changes based on review comments
austinabell Jun 2, 2023
c5d1378
Merge branch 'master' into light_client
austinabell Jun 2, 2023
a8e19d8
chore: empty commit to re-trigger flaky CI
austinabell Jun 2, 2023
e7c26ce
Merge branch 'master' into light_client
vikinatora Feb 26, 2024
d97a801
fix: pnpm-lock.yaml
vikinatora Feb 26, 2024
35c12da
chore: lock package version
vikinatora Feb 26, 2024
6aed942
Merge remote-tracking branch 'upstream/master' into light_client
vikinatora Mar 12, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Merge branch 'master' into light_client
  • Loading branch information
vikinatora committed Feb 26, 2024
commit e7c26cea0d2174b902d538513edaae0f8be657e1
13 changes: 7 additions & 6 deletions packages/accounts/package.json
Original file line number Diff line number Diff line change
@@ -33,12 +33,13 @@
"devDependencies": {
"@near-js/keystores": "workspace:*",
"@near-js/light-client": "workspace:*",
"@types/node": "^18.11.18",
"bs58": "^4.0.0",
"jest": "^26.0.1",
"near-hello": "^0.5.1",
"ts-jest": "^26.5.6",
"typescript": "^4.9.4"
"@types/node": "18.11.18",
"bs58": "4.0.0",
"jest": "26.0.1",
"near-hello": "0.5.1",
"ts-jest": "26.5.6",
"typescript": "4.9.4",
"near-workspaces": "3.4.0"
},
"files": [
"lib"
189 changes: 7 additions & 182 deletions packages/accounts/test/providers.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
const { JsonRpcProvider } = require('@near-js/providers');
const { validateExecutionProof } = require('@near-js/light-client');
const BN = require('bn.js');
const base58 = require('bs58');
@@ -12,187 +11,9 @@ jest.setTimeout(60000);
let provider;
let near;

test('txStatus with string hash and buffer hash', withProvider(async (provider) => {
const near = await testUtils.setUpTestConnection();
const sender = await testUtils.createAccount(near);
const receiver = await testUtils.createAccount(near);
const outcome = await sender.sendMoney(receiver.accountId, new BN('1'));

const responseWithString = await provider.txStatus(outcome.transaction.hash, sender.accountId);
const responseWithUint8Array = await provider.txStatus(base58.decode(outcome.transaction.hash), sender.accountId);
expect(responseWithString).toMatchObject(outcome);
expect(responseWithUint8Array).toMatchObject(outcome);
}));

test('txStatusReciept with string hash and buffer hash', withProvider(async (provider) => {
const near = await testUtils.setUpTestConnection();
const sender = await testUtils.createAccount(near);
const receiver = await testUtils.createAccount(near);
const outcome = await sender.sendMoney(receiver.accountId, new BN('1'));
const reciepts = await provider.sendJsonRpc('EXPERIMENTAL_tx_status', [outcome.transaction.hash, sender.accountId]);

const responseWithString = await provider.txStatusReceipts(outcome.transaction.hash, sender.accountId);
const responseWithUint8Array = await provider.txStatusReceipts(base58.decode(outcome.transaction.hash), sender.accountId);
expect('transaction_outcome' in responseWithString).toBeTruthy();
expect('logs' in responseWithString.transaction_outcome.outcome).toBeTruthy();
expect('receipt_ids' in responseWithString.transaction_outcome.outcome).toBeTruthy();
expect('gas_burnt' in responseWithString.transaction_outcome.outcome).toBeTruthy();
expect('tokens_burnt' in responseWithString.transaction_outcome.outcome).toBeTruthy();
expect('executor_id' in responseWithString.transaction_outcome.outcome).toBeTruthy();
expect('status' in responseWithString.transaction_outcome.outcome).toBeTruthy();
expect(responseWithString).toMatchObject(reciepts);
expect(responseWithUint8Array).toMatchObject(reciepts);
}));

test('json rpc query account', withProvider(async (provider) => {
const near = await testUtils.setUpTestConnection();
const account = await testUtils.createAccount(near);
let response = await provider.query(`account/${account.accountId}`, '');
expect(response.code_hash).toEqual('11111111111111111111111111111111');
}));

test('json rpc query view_state', withProvider(async (provider) => {
const near = await testUtils.setUpTestConnection();
const account = await testUtils.createAccount(near);
const contract = await testUtils.deployContract(account, testUtils.generateUniqueString('test'));

await contract.setValue({ args: { value: 'hello' } });

return testUtils.waitFor(async () => {
const response = await provider.query({
request_type: 'view_state',
finality: 'final',
account_id: contract.contractId,
prefix_base64: ''
});
expect(response).toEqual({
block_height: expect.any(Number),
block_hash: expect.any(String),
values: [
{ key: 'bmFtZQ==', value: 'aGVsbG8=', proof: [] }
],
proof: []
});
});
}));

test('json rpc query view_code', withProvider(async (provider) => {
const near = await testUtils.setUpTestConnection();
const account = await testUtils.createAccount(near);
const contract = await testUtils.deployContract(account, testUtils.generateUniqueString('test'));

return testUtils.waitFor(async () => {
const response = await provider.query({
request_type: 'view_code',
finality: 'final',
account_id: contract.contractId
});

expect(response).toEqual({
block_height: expect.any(Number),
block_hash: expect.any(String),
code_base64: expect.any(String),
hash: expect.any(String)
});
});
}));

test('json rpc query call_function', withProvider(async (provider) => {
const near = await testUtils.setUpTestConnection();
const account = await testUtils.createAccount(near);
const contract = await testUtils.deployContract(account, testUtils.generateUniqueString('test'));

await contract.setValue({ args: { value: 'hello' } });

return testUtils.waitFor(async () => {
const response = await provider.query({
request_type: 'call_function',
finality: 'final',
account_id: contract.contractId,
method_name: 'getValue',
args_base64: ''
});
expect(response).toEqual({
block_height: expect.any(Number),
block_hash: expect.any(String),
logs: [],
result: [
34,
104,
101,
108,
108,
111,
34
]
});
});
}));

test('json rpc light client proof', async () => {
const near = await testUtils.setUpTestConnection();
const workingAccount = await testUtils.createAccount(near);
const executionOutcome = await workingAccount.sendMoney(workingAccount.accountId, new BN(10000));
const provider = near.connection.provider;

async function waitForStatusMatching(isMatching) {
const MAX_ATTEMPTS = 10;
for (let i = 0; i < MAX_ATTEMPTS; i++) {
await testUtils.sleep(500);
const nodeStatus = await provider.status();
if (isMatching(nodeStatus)) {
return nodeStatus;
}
}
throw new Error(`Exceeded ${MAX_ATTEMPTS} attempts waiting for matching node status.`);
}

const comittedStatus = await waitForStatusMatching(status =>
status.sync_info.latest_block_hash !== executionOutcome.transaction_outcome.block_hash);
const BLOCKS_UNTIL_FINAL = 2;
const finalizedStatus = await waitForStatusMatching(status =>
status.sync_info.latest_block_height > comittedStatus.sync_info.latest_block_height + BLOCKS_UNTIL_FINAL);

const block = await provider.block({ blockId: finalizedStatus.sync_info.latest_block_hash });
const lightClientHead = block.header.last_final_block;
const finalBlock = await provider.block({ blockId: lightClientHead });
let lightClientRequest = {
type: 'transaction',
light_client_head: lightClientHead,
transaction_hash: executionOutcome.transaction.hash,
sender_id: workingAccount.accountId,
};
const lightClientProof = await provider.lightClientProof(lightClientRequest);
expect('prev_block_hash' in lightClientProof.block_header_lite).toBe(true);
expect('inner_rest_hash' in lightClientProof.block_header_lite).toBe(true);
expect('inner_lite' in lightClientProof.block_header_lite).toBe(true);
expect('timestamp_nanosec' in lightClientProof.block_header_lite.inner_lite).toBe(true);
expect(lightClientProof.outcome_proof.id).toEqual(executionOutcome.transaction_outcome.id);
expect('block_hash' in lightClientProof.outcome_proof).toBe(true);
expect(lightClientProof.outcome_root_proof).toEqual([]);
expect(lightClientProof.block_proof.length).toBeGreaterThan(0);

// Validate the proof against the finalized block
validateExecutionProof({ proof: lightClientProof, blockMerkleRoot: base58.decode(finalBlock.header.block_merkle_root) });

// pass nonexistent hash for light client head will fail
lightClientRequest = {
type: 'transaction',
light_client_head: '11111111111111111111111111111111',
transaction_hash: executionOutcome.transaction.hash,
sender_id: workingAccount.accountId,
};
await expect(provider.lightClientProof(lightClientRequest)).rejects.toThrow('DB Not Found Error');

// Use old block hash as light client head should fail
lightClientRequest = {
type: 'transaction',
light_client_head: executionOutcome.transaction_outcome.block_hash,
transaction_hash: executionOutcome.transaction.hash,
sender_id: workingAccount.accountId,
};

await expect(provider.lightClientProof(lightClientRequest)).rejects.toThrow(/.+ block .+ is ahead of head block .+/);
beforeAll(async () => {
near = await testUtils.setUpTestConnection();
provider = near.connection.provider;
});

describe('providers', () => {
@@ -334,6 +155,7 @@ describe('providers', () => {

const block = await provider.block({ blockId: finalizedStatus.sync_info.latest_block_hash });
const lightClientHead = block.header.last_final_block;
const finalBlock = await provider.block({ blockId: lightClientHead });
let lightClientRequest = {
type: 'transaction',
light_client_head: lightClientHead,
@@ -349,6 +171,9 @@ describe('providers', () => {
expect('block_hash' in lightClientProof.outcome_proof).toBe(true);
expect(lightClientProof.outcome_root_proof).toEqual([]);
expect(lightClientProof.block_proof.length).toBeGreaterThan(0);

// Validate the proof against the finalized block
validateExecutionProof({ proof: lightClientProof, blockMerkleRoot: base58.decode(finalBlock.header.block_merkle_root) });

// pass nonexistent hash for light client head will fail
lightClientRequest = {
4 changes: 2 additions & 2 deletions packages/near-api-js/package.json
Original file line number Diff line number Diff line change
@@ -23,8 +23,8 @@
"@near-js/utils": "workspace:*",
"@near-js/wallet-account": "workspace:*",
"@near-js/light-client": "workspace:*",
"ajv": "^8.11.2",
"ajv-formats": "^2.1.1",
"ajv": "8.11.2",
"ajv-formats": "2.1.1",
"bn.js": "5.2.1",
"borsh": "1.0.0",
"depd": "2.0.0",
Loading
You are viewing a condensed version of this merge commit. You can view the full changes here.