From 4d69211eed94fa67ec8331f5024586f4e17d78e8 Mon Sep 17 00:00:00 2001 From: Austin Abell Date: Sun, 7 May 2023 20:48:35 -0400 Subject: [PATCH 1/2] test: include test vector submodule and runner for execution proofs --- .github/workflows/contracts-eth.yml | 1 + .gitmodules | 3 ++ contracts/eth/nearprover/package.json | 3 +- contracts/eth/nearprover/test/NearProver.js | 45 +++++++++++++++++++++ near-light-client-tests | 1 + utils/proof-vector-utils.js | 22 ++++++++++ 6 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 .gitmodules create mode 160000 near-light-client-tests create mode 100644 utils/proof-vector-utils.js diff --git a/.github/workflows/contracts-eth.yml b/.github/workflows/contracts-eth.yml index 063e20060..812c09757 100644 --- a/.github/workflows/contracts-eth.yml +++ b/.github/workflows/contracts-eth.yml @@ -49,6 +49,7 @@ jobs: - name: Clone the repository uses: actions/checkout@v3 with: + submodules: 'true' lfs: 'true' - name: Execute diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..041e50a7b --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "near-light-client-tests"] + path = near-light-client-tests + url = https://github.com/austinabell/near-light-client-tests.git diff --git a/contracts/eth/nearprover/package.json b/contracts/eth/nearprover/package.json index 57d1495c0..57928d2fb 100644 --- a/contracts/eth/nearprover/package.json +++ b/contracts/eth/nearprover/package.json @@ -27,7 +27,8 @@ "readline-sync": "^1.4.10", "solc": "0.8.3", "solidity-coverage": "^0.7.16", - "solium": "^1.2.5" + "solium": "^1.2.5", + "bs58": "^4.0.1" }, "scripts": { "build": "hardhat compile", diff --git a/contracts/eth/nearprover/test/NearProver.js b/contracts/eth/nearprover/test/NearProver.js index c30f04e1e..fa0913036 100644 --- a/contracts/eth/nearprover/test/NearProver.js +++ b/contracts/eth/nearprover/test/NearProver.js @@ -3,6 +3,8 @@ const { ethers } = require('hardhat'); const { borshifyOutcomeProof } = require(`rainbow-bridge-utils`); const fs = require('fs').promises; const { computeMerkleRoot } = require('../utils/utils'); +const bs58 = require('bs58'); +const { getAllJsonFilesRecursive } = require('../../../../utils/proof-vector-utils'); let NearProver, NearBridgeMock; @@ -15,6 +17,49 @@ beforeEach(async function () { ); }); +async function runTestVectors(testVectors) { + for (const test of testVectors) { + const { + description, + params: { proof, block_merkle_root }, + expected: { is_valid, error }, + } = test; + let wasValid; + let executionError; + try { + const blockMerkleRoot = bs58.decode(block_merkle_root).toString('hex'); + // Note, the height shouldn't matter here, defaulting to 100 + const height = 100; + expect(blockMerkleRoot === computeMerkleRoot(proof).toString('hex')).to.be.true; + await NearBridgeMock.setBlockMerkleRoot(height, "0x" + blockMerkleRoot); + wasValid = await NearProver.proveOutcome(borshifyOutcomeProof(proof), height); + } catch (error) { + wasValid = false; + executionError = error; + } + if (wasValid !== is_valid) { + const prefix = `Test Case "${description}": FAILED - expected`; + throw new Error( + `${prefix} ${is_valid + ? `valid, got error ${executionError}` + : `invalid result${error ? ` with error "${error}"` : ""}` + }` + ); + } + } +} + +describe('execution outcome proof vectors', async function () { + const files = getAllJsonFilesRecursive("../../../near-light-client-tests/test-vectors/executions"); + + for (const file of files) { + const fileName = file.split('\\').pop().split('/').pop(); + it(`execution vector file "${fileName}"`, async function () { + await runTestVectors(require("../" + file)); + }); + } +}); + async function testProof(merkleRoot, height, proofPath) { let proof = require(proofPath); console.log(computeMerkleRoot(proof).toString('hex')); diff --git a/near-light-client-tests b/near-light-client-tests new file mode 160000 index 000000000..c1b8b5a3f --- /dev/null +++ b/near-light-client-tests @@ -0,0 +1 @@ +Subproject commit c1b8b5a3fa498848b82448e92b672e2ab33165c4 diff --git a/utils/proof-vector-utils.js b/utils/proof-vector-utils.js new file mode 100644 index 000000000..135c5ee1b --- /dev/null +++ b/utils/proof-vector-utils.js @@ -0,0 +1,22 @@ +const { join } = require("path"); +const { readdirSync, statSync } = require("fs"); + +function getAllJsonFilesRecursive(dirPath) { + let arrayOfFiles = []; + const files = readdirSync(dirPath); + + files.forEach((file) => { + if (statSync(join(dirPath, file)).isDirectory()) { + const subDirFiles = getAllJsonFilesRecursive(join(dirPath, file)); + arrayOfFiles = arrayOfFiles.concat(subDirFiles); + } else if (file.endsWith(".json")) { + arrayOfFiles.push(join(dirPath, file)); + } + }); + + return arrayOfFiles; +} + +module.exports = { + getAllJsonFilesRecursive, +} \ No newline at end of file From a91865c9c00f028e6b0c399b53d75b9f6f3c2305 Mon Sep 17 00:00:00 2001 From: Austin Abell Date: Mon, 8 May 2023 20:17:14 -0400 Subject: [PATCH 2/2] add runner for lc block vectors --- contracts/eth/nearbridge/test/BlockVectors.js | 76 +++++++++++++++++++ contracts/eth/nearbridge/yarn.lock | 36 ++++++--- 2 files changed, 101 insertions(+), 11 deletions(-) create mode 100644 contracts/eth/nearbridge/test/BlockVectors.js diff --git a/contracts/eth/nearbridge/test/BlockVectors.js b/contracts/eth/nearbridge/test/BlockVectors.js new file mode 100644 index 000000000..3cb320de4 --- /dev/null +++ b/contracts/eth/nearbridge/test/BlockVectors.js @@ -0,0 +1,76 @@ +// const { expect } = require('chai'); +const { ethers } = require('hardhat'); +const { getAllJsonFilesRecursive } = require('../../../../utils/proof-vector-utils'); +const { borshify, borshifyInitialValidators } = require('rainbow-bridge-utils'); + +async function runTestVectors(testVectors) { + const Ed25519 = await (await ethers.getContractFactory('Ed25519')).deploy(); + for (const test of testVectors) { + let { + description, + params: { previous_block, current_bps, new_block }, + expected: { is_valid, error }, + } = test; + + if (description == "Invalid approval message signature at index 2") { + // Signature validation isn't done because too slow (see below). This test case must + // be skipped because block valid outside of signature. + continue; + } + + let wasValid; + let executionError; + try { + const NearBridge = await (await ethers.getContractFactory('NearBridge')).deploy( + Ed25519.address, + ethers.BigNumber.from('1000000000000000000'), // 1e18 + ethers.BigNumber.from('360'), // lock duration + ethers.BigNumber.from('362627730000'), // replace duration + await (await ethers.getSigners())[0].getAddress(), + 0, + ); + await NearBridge.deposit({ value: ethers.utils.parseEther('1') }); + + await NearBridge.initWithValidators(borshifyInitialValidators(current_bps)); + // Note: hacky workaround since the initWithBlock method requires `approvals_after_next` + // and `next_block_inner_hash`, which it does not use and `next_bps` that is a bit + // redundant with the validators initialization. + previous_block.approvals_after_next = new_block.approvals_after_next; + previous_block.next_bps = current_bps; + previous_block.next_block_inner_hash = new_block.next_block_inner_hash; + await NearBridge.initWithBlock(borshify(previous_block)); + await NearBridge.addLightClientBlock(borshify(new_block)); + + // Note: this validation is really slow and stalls the test suite, but worked until it did + // for (let j = 0; j < new_block.approvals_after_next.length; j++) { + // if (new_block.approvals_after_next[j]) { + // expect(await NearBridge.checkBlockProducerSignatureInHead(j)).to.be.true; + // } + // } + wasValid = true; + } catch (error) { + wasValid = false; + executionError = error; + } + if (wasValid !== is_valid) { + const prefix = `Test Case "${description}": FAILED - expected`; + throw new Error( + `${prefix} ${is_valid + ? `valid, got error ${executionError}` + : `invalid result${error ? ` with error "${error}"` : ''}` + }`, + ); + } + } +} + +describe('light client block vectors', async function () { + const files = getAllJsonFilesRecursive('../../../near-light-client-tests/test-vectors/blocks'); + + for (const file of files) { + const fileName = file.split('\\').pop().split('/').pop(); + it(`block vector file "${fileName}"`, async function () { + await runTestVectors(require('../' + file)); + }); + } +}); \ No newline at end of file diff --git a/contracts/eth/nearbridge/yarn.lock b/contracts/eth/nearbridge/yarn.lock index d7f986e06..25319b305 100644 --- a/contracts/eth/nearbridge/yarn.lock +++ b/contracts/eth/nearbridge/yarn.lock @@ -2118,6 +2118,11 @@ bn.js@4.11.6: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" integrity sha1-UzRK2xRhehP26N0s4okF0cC6MhU= +bn.js@5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" + integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== + bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.10.0, bn.js@^4.11.0, bn.js@^4.11.6, bn.js@^4.11.8, bn.js@^4.11.9, bn.js@^4.8.0: version "4.12.0" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" @@ -2160,6 +2165,15 @@ body-parser@^1.16.0: raw-body "2.4.2" type-is "~1.6.18" +borsh@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/borsh/-/borsh-0.7.0.tgz#6e9560d719d86d90dc589bca60ffc8a6c51fec2a" + integrity sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA== + dependencies: + bn.js "^5.2.0" + bs58 "^4.0.0" + text-encoding-utf-8 "^1.0.2" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -3380,7 +3394,7 @@ error-ex@^1.2.0: dependencies: is-arrayish "^0.2.1" -error-polyfill@^0.1.2: +error-polyfill@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/error-polyfill/-/error-polyfill-0.1.3.tgz#df848b61ad8834f7a5db69a70b9913df86721d15" integrity sha512-XHJk60ufE+TG/ydwp4lilOog549iiQF2OAPhkk9DdiYWMrltz5yhDz/xnKuenNwP7gy3dsibssO5QpVhkrSzzg== @@ -6997,20 +7011,20 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= -near-api-js@^0.26.0: - version "0.26.0" - resolved "https://registry.yarnpkg.com/near-api-js/-/near-api-js-0.26.0.tgz#1d13571e0ec107aaf61b908466c38110ba5bf97b" - integrity sha512-aeRU2oWo6qKJF2oM7IcZ10vEqohxk6palTTx7LBUBsaKjF8561gBNIPhnkndhSrpsVSA9NBibX80WfOmK1tqrw== +near-api-js@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/near-api-js/-/near-api-js-1.1.0.tgz#907e807f052c1f043c6fbf28f61872de3c02235a" + integrity sha512-qYKv1mYsaDZc2uYndhS+ttDhR9+60qFc+ZjD6lWsAxr3ZskMjRwPffDGQZYhC7BRDQMe1HEbk6d5mf+TVm0Lqg== dependencies: - "@types/bn.js" "^4.11.5" - bn.js "^5.0.0" + bn.js "5.2.1" + borsh "^0.7.0" bs58 "^4.0.0" depd "^2.0.0" - error-polyfill "^0.1.2" + error-polyfill "^0.1.3" http-errors "^1.7.2" js-sha256 "^0.9.0" mustache "^4.0.0" - node-fetch "^2.3.0" + node-fetch "^2.6.1" text-encoding-utf-8 "^1.0.2" tweetnacl "^1.0.1" @@ -7062,7 +7076,7 @@ node-environment-flags@1.0.6: object.getownpropertydescriptors "^2.0.3" semver "^5.7.0" -node-fetch@^2.3.0, node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.7: +node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.7: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== @@ -7882,7 +7896,7 @@ queue-microtask@^1.2.2: eth-object "https://github.com/aurora-is-near/eth-object#a84d1420fdde87a768b96c6a3b3d0ee435998f72" eth-util-lite near/eth-util-lite#master lodash "^4.17.20" - near-api-js "^0.26.0" + near-api-js "^1.1.0" web3 "^1.6.0" randomatic@^3.0.0: