From 78997dd20d11cdbdb2612ad9b477544e5c2bccb7 Mon Sep 17 00:00:00 2001 From: "Rodrigo Q. Saramago" Date: Sun, 7 Aug 2022 21:53:49 +0200 Subject: [PATCH 01/13] Promisify solc-js downloader --- downloadCurrentVersion.ts | 79 ++++++++------------------------------- downloader.ts | 64 +++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 63 deletions(-) create mode 100755 downloader.ts diff --git a/downloadCurrentVersion.ts b/downloadCurrentVersion.ts index 6b524a49..23ef4356 100755 --- a/downloadCurrentVersion.ts +++ b/downloadCurrentVersion.ts @@ -3,73 +3,26 @@ // This is used to download the correct binary version // as part of the prepublish step. -import * as fs from 'fs'; -import { https } from 'follow-redirects'; -import MemoryStream from 'memorystream'; -import { keccak256 } from 'js-sha3'; +import downloader from './downloader'; const pkg = require('./package.json'); -function getVersionList (cb) { - console.log('Retrieving available version list...'); - - const mem = new MemoryStream(null, { readable: false }); - https.get('https://binaries.soliditylang.org/bin/list.json', function (response) { - if (response.statusCode !== 200) { - console.log('Error downloading file: ' + response.statusCode); - process.exit(1); +async function download () { + try { + const list = JSON.parse(await downloader.getVersionList()); + const wanted = pkg.version.match(/^(\d+\.\d+\.\d+)$/)[1]; + const releaseFileName = list.releases[wanted]; + const expectedFile = list.builds.filter(function (entry) { return entry.path === releaseFileName; })[0]; + if (!expectedFile) { + throw new Error('Requested version not found. Version list is invalid or corrupted?'); } - response.pipe(mem); - response.on('end', function () { - cb(mem.toString()); - }); - }); -} - -function downloadBinary (outputName, version, expectedHash) { - console.log('Downloading version', version); - - // Remove if existing - if (fs.existsSync(outputName)) { - fs.unlinkSync(outputName); - } - - process.on('SIGINT', function () { - console.log('Interrupted, removing file.'); - fs.unlinkSync(outputName); + const expectedHash = expectedFile.keccak256; + await downloader.downloadBinary('soljson.js', releaseFileName, expectedHash); + process.exit(); + } catch (err) { + console.log(err.message); process.exit(1); - }); - - const file = fs.createWriteStream(outputName, { encoding: 'binary' }); - https.get('https://binaries.soliditylang.org/bin/' + version, function (response) { - if (response.statusCode !== 200) { - console.log('Error downloading file: ' + response.statusCode); - process.exit(1); - } - response.pipe(file); - file.on('finish', function () { - file.close(function () { - const hash = '0x' + keccak256(fs.readFileSync(outputName, { encoding: 'binary' })); - if (expectedHash !== hash) { - console.log('Hash mismatch: ' + expectedHash + ' vs ' + hash); - process.exit(1); - } - console.log('Done.'); - }); - }); - }); + } } console.log('Downloading correct solidity binary...'); - -getVersionList(function (list) { - list = JSON.parse(list); - const wanted = pkg.version.match(/^(\d+\.\d+\.\d+)$/)[1]; - const releaseFileName = list.releases[wanted]; - const expectedFile = list.builds.filter(function (entry) { return entry.path === releaseFileName; })[0]; - if (!expectedFile) { - console.log('Version list is invalid or corrupted?'); - process.exit(1); - } - const expectedHash = expectedFile.keccak256; - downloadBinary('soljson.js', releaseFileName, expectedHash); -}); +download(); diff --git a/downloader.ts b/downloader.ts new file mode 100755 index 00000000..dbfc2c8f --- /dev/null +++ b/downloader.ts @@ -0,0 +1,64 @@ +import * as fs from 'fs'; +import { https } from 'follow-redirects'; +import MemoryStream from 'memorystream'; +import { keccak256 } from 'js-sha3'; + +function getVersionList () { + console.log('Retrieving available version list...'); + + return new Promise((resolve, reject) => { + const mem = new MemoryStream(null, { readable: false }); + https.get('https://binaries.soliditylang.org/bin/list.json', function (response) { + if (response.statusCode !== 200) { + reject(new Error('Error downloading file: ' + response.statusCode)); + } + response.pipe(mem); + response.on('end', function () { + resolve(mem.toString()); + }); + }).on('error', (err) => { + reject(err); + }); + }); +} + +function downloadBinary (outputName, version, expectedHash) { + console.log('Downloading version', version); + + return new Promise((resolve, reject) => { + // Remove if existing + if (fs.existsSync(outputName)) { + fs.unlinkSync(outputName); + } + + process.on('SIGINT', function () { + fs.unlinkSync(outputName); + reject(new Error('Interrupted, removing file.')); + }); + + const file = fs.createWriteStream(outputName, { encoding: 'binary' }); + https.get('https://binaries.soliditylang.org/bin/' + version, function (response) { + if (response.statusCode !== 200) { + reject(new Error('Error downloading file: ' + response.statusCode)); + } + response.pipe(file); + file.on('finish', function () { + file.close(); + const hash = '0x' + keccak256(fs.readFileSync(outputName, { encoding: 'binary' })); + if (expectedHash !== hash) { + reject(new Error('Hash mismatch: ' + expectedHash + ' vs ' + hash)); + } else { + console.log('Done.'); + resolve(); + } + }); + }).on('error', (err) => { + reject(err); + }); + }); +} + +export = { + getVersionList, + downloadBinary +}; From 52316914feee598d52a582bac726b7297e1416b4 Mon Sep 17 00:00:00 2001 From: "Rodrigo Q. Saramago" Date: Sun, 7 Aug 2022 21:59:41 +0200 Subject: [PATCH 02/13] Test solc-js download --- test/downloader.ts | 38 ++++++++++++++++++++++++++++++++++++++ test/index.ts | 1 + 2 files changed, 39 insertions(+) create mode 100644 test/downloader.ts diff --git a/test/downloader.ts b/test/downloader.ts new file mode 100644 index 00000000..91c23ff6 --- /dev/null +++ b/test/downloader.ts @@ -0,0 +1,38 @@ +import tape from 'tape'; +import * as semver from 'semver'; +import * as tmp from 'tmp'; +import wrapper from '../wrapper'; +import downloader from '../downloader'; + +const pkg = require('../package.json'); + +tape('Download latest binary', function (t) { + t.test('checking whether the current version is the latest available for download', async function (st) { + try { + const list = JSON.parse(await downloader.getVersionList()); + const wanted = pkg.version.match(/^(\d+\.\d+\.\d+)$/)[1]; + if (semver.neq(wanted, list.latestRelease)) { + st.fail(`Version ${wanted} is not the latest release ${list.latestRelease}`); + } + + const releaseFileName = list.releases[wanted]; + const expectedFile = list.builds.filter(function (entry) { return entry.path === releaseFileName; })[0]; + if (!expectedFile) { + st.fail(`Version ${wanted} not found. Version list is invalid or corrupted?`); + } + + const tempDir = tmp.dirSync({ unsafeCleanup: true, prefix: 'solc-js-compiler-test-' }).name; + const solcjsBin = `${tempDir}/${expectedFile.path}`; + await downloader.downloadBinary(solcjsBin, releaseFileName, expectedFile.keccak256); + + const solc = wrapper(require(solcjsBin)); + if (semver.neq(solc.version(), wanted)) { + st.fail('Downloaded version differs from package version'); + } + st.pass(`Version ${wanted} successfully downloaded`); + } catch (err) { + st.fail(err.message); + } + st.end(); + }); +}); diff --git a/test/index.ts b/test/index.ts index 9815fa16..1e45c4a5 100644 --- a/test/index.ts +++ b/test/index.ts @@ -6,6 +6,7 @@ import('./compiler'); import('./smtcallback'); import('./smtchecker'); import('./abi'); +import('./downloader'); // The CLI doesn't support Node 4 if (semver.gte(process.version, '5.0.0')) { From 53803216c4964c43c3db425041b5070ac4312012 Mon Sep 17 00:00:00 2001 From: "Rodrigo Q. Saramago" Date: Fri, 16 Sep 2022 19:45:00 +0200 Subject: [PATCH 03/13] Add download unit tests --- downloadCurrentVersion.ts | 17 +-- downloader.ts | 10 +- package.json | 1 + test/downloader.ts | 198 +++++++++++++++++++++++--- test/resources/assets/README.md | 9 ++ test/resources/assets/cert.pem | 20 +++ test/resources/assets/csr.pem | 17 +++ test/resources/assets/dummy-list.json | 33 +++++ test/resources/assets/key.pem | 27 ++++ 9 files changed, 296 insertions(+), 36 deletions(-) create mode 100644 test/resources/assets/README.md create mode 100644 test/resources/assets/cert.pem create mode 100644 test/resources/assets/csr.pem create mode 100644 test/resources/assets/dummy-list.json create mode 100644 test/resources/assets/key.pem diff --git a/downloadCurrentVersion.ts b/downloadCurrentVersion.ts index 23ef4356..41a4a995 100755 --- a/downloadCurrentVersion.ts +++ b/downloadCurrentVersion.ts @@ -5,24 +5,25 @@ import downloader from './downloader'; const pkg = require('./package.json'); +const BIN_URL = 'https://binaries.soliditylang.org/bin'; -async function download () { +async function download (version, binURL = BIN_URL) { try { - const list = JSON.parse(await downloader.getVersionList()); - const wanted = pkg.version.match(/^(\d+\.\d+\.\d+)$/)[1]; - const releaseFileName = list.releases[wanted]; + const list = JSON.parse(await downloader.getVersionList(`${binURL}/list.json`)); + const releaseFileName = list.releases[version]; const expectedFile = list.builds.filter(function (entry) { return entry.path === releaseFileName; })[0]; + if (!expectedFile) { throw new Error('Requested version not found. Version list is invalid or corrupted?'); } + const expectedHash = expectedFile.keccak256; - await downloader.downloadBinary('soljson.js', releaseFileName, expectedHash); - process.exit(); + await downloader.downloadBinary(binURL, 'soljson.js', releaseFileName, expectedHash); } catch (err) { console.log(err.message); process.exit(1); } -} +}; console.log('Downloading correct solidity binary...'); -download(); +download(pkg.version.match(/^(\d+\.\d+\.\d+)$/)[1]); diff --git a/downloader.ts b/downloader.ts index dbfc2c8f..e09d0a78 100755 --- a/downloader.ts +++ b/downloader.ts @@ -3,12 +3,12 @@ import { https } from 'follow-redirects'; import MemoryStream from 'memorystream'; import { keccak256 } from 'js-sha3'; -function getVersionList () { +function getVersionList (url: string): Promise { console.log('Retrieving available version list...'); return new Promise((resolve, reject) => { const mem = new MemoryStream(null, { readable: false }); - https.get('https://binaries.soliditylang.org/bin/list.json', function (response) { + https.get(url, function (response) { if (response.statusCode !== 200) { reject(new Error('Error downloading file: ' + response.statusCode)); } @@ -22,8 +22,8 @@ function getVersionList () { }); } -function downloadBinary (outputName, version, expectedHash) { - console.log('Downloading version', version); +function downloadBinary (binURL: string, outputName: string, releaseFile: string, expectedHash: string): Promise { + console.log('Downloading version', releaseFile); return new Promise((resolve, reject) => { // Remove if existing @@ -37,7 +37,7 @@ function downloadBinary (outputName, version, expectedHash) { }); const file = fs.createWriteStream(outputName, { encoding: 'binary' }); - https.get('https://binaries.soliditylang.org/bin/' + version, function (response) { + https.get(`${binURL}/${releaseFile}`, function (response) { if (response.statusCode !== 200) { reject(new Error('Error downloading file: ' + response.statusCode)); } diff --git a/package.json b/package.json index 8a85ef3f..1df545d6 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "eslint-plugin-import": "^2.25.3", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^5.1.1", + "nock": "^13.2.9", "nyc": "^15.1.0", "tape": "^4.11.0", "tape-spawn": "^1.4.2", diff --git a/test/downloader.ts b/test/downloader.ts index 91c23ff6..960caed9 100644 --- a/test/downloader.ts +++ b/test/downloader.ts @@ -1,38 +1,190 @@ -import tape from 'tape'; -import * as semver from 'semver'; import * as tmp from 'tmp'; -import wrapper from '../wrapper'; +import tape from 'tape'; +import nock from 'nock'; +import fs from 'fs'; +import path from 'path'; +import { keccak256 } from 'js-sha3'; +import { https } from 'follow-redirects'; import downloader from '../downloader'; -const pkg = require('../package.json'); +const assets = path.resolve(__dirname, 'resources/assets'); + +tape.onFinish(() => { + if (!nock.isDone()) { + throw Error('expected requests were not performed'); + } +}); + +function hash (filePath: string): string { + return '0x' + keccak256(fs.readFileSync(filePath, { encoding: 'binary' })); +} + +function generateTestFile (t: tape.Test, content: string): tmp.FileResult { + // As the `keep` option is set to true the removeCallback must be called by the caller + // to cleanup the files after the test. + const file = tmp.fileSync({ template: 'soljson-XXXXXX.js', keep: true }); + try { + fs.writeFileSync(file.name, content); + } catch (err) { + t.fail('error writing test file'); + } + + return file; +} + +function versionListMock (url: string): nock.Interceptor { + return nock(url).get('/bin/list.json'); +} + +function downloadBinaryMock (url: string, filename: string): nock.Interceptor { + return nock(url).get(`/bin/${path.basename(filename)}`); +} + +function defaultListener (req, res) { + res.writeHead(200); + res.end('OK'); +}; + +async function startMockServer (listener = defaultListener) { + const server = https.createServer({ + key: fs.readFileSync(path.resolve(assets, 'key.pem')), + cert: fs.readFileSync(path.resolve(assets, 'cert.pem')) + }, listener); + + await new Promise(resolve => server.listen(resolve)); + server.port = server.address().port; + server.origin = `https://localhost:${server.port}`; + return server; +} + +tape('Download version list', async function (t) { + const server = await startMockServer(); + + t.teardown(function () { + server.close(); + nock.cleanAll(); + }); + + t.test('successfully get version list', async function (st) { + const dummyListPath = path.resolve(assets, 'dummy-list.json'); + versionListMock(server.origin).replyWithFile(200, dummyListPath, { + 'Content-Type': 'application/json' + }); -tape('Download latest binary', function (t) { - t.test('checking whether the current version is the latest available for download', async function (st) { try { - const list = JSON.parse(await downloader.getVersionList()); - const wanted = pkg.version.match(/^(\d+\.\d+\.\d+)$/)[1]; - if (semver.neq(wanted, list.latestRelease)) { - st.fail(`Version ${wanted} is not the latest release ${list.latestRelease}`); - } + const list = JSON.parse( + await downloader.getVersionList(`${server.origin}/bin/list.json`) + ); + const expected = require(dummyListPath); + st.deepEqual(list, expected, 'list should match'); + st.equal(list.latestRelease, expected.latestRelease, 'latest release should be equal'); + } catch (err) { + st.fail(err.message); + } + st.end(); + }); - const releaseFileName = list.releases[wanted]; - const expectedFile = list.builds.filter(function (entry) { return entry.path === releaseFileName; })[0]; - if (!expectedFile) { - st.fail(`Version ${wanted} not found. Version list is invalid or corrupted?`); - } + t.test('should throw an exception when version list not found', async function (st) { + versionListMock(server.origin).reply(404); + + try { + await downloader.getVersionList(`${server.origin}/bin/list.json`); + st.fail('should throw file not found error'); + } catch (err) { + st.equal(err.message, 'Error downloading file: 404', 'should throw file not found error'); + } + st.end(); + }); +}); + +tape('Download latest binary', async function (t) { + const server = await startMockServer(); + const content = '() => {}'; + const tmpDir = tmp.dirSync({ unsafeCleanup: true, prefix: 'solcjs-download-test-' }).name; + + t.teardown(function () { + server.close(); + nock.cleanAll(); + }); + + t.test('successfully download binary', async function (st) { + const targetFilename = `${tmpDir}/target-success.js`; + const file = generateTestFile(st, content); - const tempDir = tmp.dirSync({ unsafeCleanup: true, prefix: 'solc-js-compiler-test-' }).name; - const solcjsBin = `${tempDir}/${expectedFile.path}`; - await downloader.downloadBinary(solcjsBin, releaseFileName, expectedFile.keccak256); + st.teardown(function () { + file.removeCallback(); + }); - const solc = wrapper(require(solcjsBin)); - if (semver.neq(solc.version(), wanted)) { - st.fail('Downloaded version differs from package version'); + downloadBinaryMock(server.origin, file.name) + .replyWithFile(200, file.name, { + 'content-type': 'application/javascript', + 'content-length': content.length.toString() + }); + + try { + await downloader.downloadBinary( + `${server.origin}/bin`, + targetFilename, + file.name, + hash(file.name) + ); + + if (!fs.existsSync(targetFilename)) { + st.fail('download failed'); } - st.pass(`Version ${wanted} successfully downloaded`); + + const got = fs.readFileSync(targetFilename, { encoding: 'binary' }); + const expected = fs.readFileSync(file.name, { encoding: 'binary' }); + st.equal(got.length, expected.length, 'should download the correct file'); } catch (err) { st.fail(err.message); } st.end(); }); + + t.test('should throw an exception when file not found', async function (st) { + const targetFilename = `${tmpDir}/target-fail404.js`; + downloadBinaryMock(server.origin, 'test.js').reply(404); + + try { + await downloader.downloadBinary( + `${server.origin}/bin`, + targetFilename, + 'test.js', + `0x${keccak256('something')}` + ); + st.fail('should throw file not found error'); + } catch (err) { + st.equal(err.message, 'Error downloading file: 404', 'should throw file not found error'); + } + st.end(); + }); + + t.test('should throw an exception if hashes do not match', async function (st) { + const targetFilename = `${tmpDir}/target-fail-hash.js`; + const file = generateTestFile(st, content); + + st.teardown(function () { + file.removeCallback(); + }); + + downloadBinaryMock(server.origin, file.name) + .replyWithFile(200, file.name, { + 'content-type': 'application/javascript', + 'content-length': content.length.toString() + }); + + try { + await downloader.downloadBinary( + `${server.origin}/bin`, + targetFilename, + file.name, + `0x${keccak256('something')}` + ); + st.fail('should throw hash mismatch error'); + } catch (err) { + st.match(err.message, /Hash mismatch/, 'should detect hash mismatch'); + } + st.end(); + }); }); diff --git a/test/resources/assets/README.md b/test/resources/assets/README.md new file mode 100644 index 00000000..643c87e3 --- /dev/null +++ b/test/resources/assets/README.md @@ -0,0 +1,9 @@ +The certificates in this folder are only for testing purposes and are **not** valid certificates. + +They were generated using the following command: +- Generate an RSA private key: +`openssl genrsa -out key.pem` +- Generate a csr using all default options and common name as "localhost": +`openssl req -new -key key.pem -out csr.pem` +- Self-sign the certificate: +`openssl x509 -req -days 9999 -in csr.pem -signkey key.pem -out cert.pem` \ No newline at end of file diff --git a/test/resources/assets/cert.pem b/test/resources/assets/cert.pem new file mode 100644 index 00000000..ca6a5f25 --- /dev/null +++ b/test/resources/assets/cert.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDOzCCAiMCFHpFewtcvLRCZbPPcl04e2vNaFtBMA0GCSqGSIb3DQEBCwUAMFkx +CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl +cm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMMCWxvY2FsaG9zdDAgFw0yMjA5 +MTYxNjExMzRaGA8yMDUwMDEzMTE2MTEzNFowWTELMAkGA1UEBhMCQVUxEzARBgNV +BAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0 +ZDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAqx1uPfkuqDqphLOwAMhvQqYWec7037WOrpO/5mHees9fVAX2QyGxV+Dg +zTCHJ4Jn4ATkw3i4yfgEwa4cuHhBuopmK7WCR3F0x8HLR6XA3nupWUbplT6L8BqH +9YOrT8XRnbQuIV5+y7p9AkTh4WaLFQXUfnlU9Pvu3qIp9eYt1QDMY9NVF2IMPXsk +2CigcqwBPVya6PHNbPYZiEW7B6Hh+rIk1yoG/bNdh3ZCLPBjX6tfpmxH8KZgfAZ4 +Q83naav7ABoTL2FQIwwMBKYinvVkzissY/s9cwDmSS3q1pc1CIRLwjuEr9OfdUIk +JNYqM6Bu2J8mpL3tyJTMTsx6OdtrNQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCT +X/hdb5S7s4Nzg8m/DsfaazftqwAtJVP7ZhXDewJ9/QKpA85mqcQDAZg+1EOVXRmW +VVSrEeOPm88T1y/aNBEYOL86koKHmvu5KwVvkVZGbebDKdSljdp8B1FEncGeQjXF +3lusP8DLCkzR0Lk4fORiVn9cdbgo8qM5rQMlF2DOJSVXgtEsp6HUJ3CZ9kdJ8QHO +/nL5R1ADTIkQHL73JmIxrAU6GQdMU0YqYybaZa0uOcYHBm8F9c/nGRj2Whm8SQ84 +WOA9cA/vdbdqSr80LOQ2kvx9Na9tGBRvOaHZT3q6CfysmQdvoogqLFLIwof8Sz2O +PfL6Z1Y7lAFnd+ThVK/z +-----END CERTIFICATE----- diff --git a/test/resources/assets/csr.pem b/test/resources/assets/csr.pem new file mode 100644 index 00000000..4e0e9f99 --- /dev/null +++ b/test/resources/assets/csr.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICnjCCAYYCAQAwWTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx +ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbG9j +YWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqx1uPfkuqDqp +hLOwAMhvQqYWec7037WOrpO/5mHees9fVAX2QyGxV+DgzTCHJ4Jn4ATkw3i4yfgE +wa4cuHhBuopmK7WCR3F0x8HLR6XA3nupWUbplT6L8BqH9YOrT8XRnbQuIV5+y7p9 +AkTh4WaLFQXUfnlU9Pvu3qIp9eYt1QDMY9NVF2IMPXsk2CigcqwBPVya6PHNbPYZ +iEW7B6Hh+rIk1yoG/bNdh3ZCLPBjX6tfpmxH8KZgfAZ4Q83naav7ABoTL2FQIwwM +BKYinvVkzissY/s9cwDmSS3q1pc1CIRLwjuEr9OfdUIkJNYqM6Bu2J8mpL3tyJTM +Tsx6OdtrNQIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAH49b7ktMCNIScGNIgy1 +XeF+sUrXtOYv67vhIPxF8y9ep7Lm6nHOGTzEmoHqf0ogHTb4cGclgakdaRAH65eU +BZvIaxEUFdV7G787ESkXdyvtvZB6cc6/ZJSUPsnpoGkS08kNrzZBXfMBapoawyTN +g31zyerxjRWTEKEBB7u74cTyamjNEgml0cOa7VB6CkPI9PM2/VkYLCpqZNLTbzSE +GHXzouzE7ajZ4dxx3aEsXXjZLypWimjZJA3zcvl9Jv1p6zEkDwnjofOcGiIDmdgu +sdlj3v9Efv/0A03Q7z/rr/Gl83lskFQB5VTU1DBrkFJ4D6VueWjwBrqff3rGKKhm +CvQ= +-----END CERTIFICATE REQUEST----- diff --git a/test/resources/assets/dummy-list.json b/test/resources/assets/dummy-list.json new file mode 100644 index 00000000..579a9cc4 --- /dev/null +++ b/test/resources/assets/dummy-list.json @@ -0,0 +1,33 @@ +{ + "builds": [ + { + "path": "soljson-v0.8.16+commit.07a7930e.js", + "version": "0.8.16", + "build": "commit.07a7930e", + "longVersion": "0.8.16+commit.07a7930e", + "keccak256": "0x331f4bc6de3d44d87b68629e83f711105325b482da7e9ca9bdbdd01371fee438", + "sha256": "0x27b2820ef93805a65c76b7945a49432582d306fd17a28985709a51e6403677c2", + "urls": [ + "bzzr://af0d70945c85865298732ac2bfdacdf2774fb4daf793c94fafe135b839a60a5c", + "dweb:/ipfs/QmWzBJ8gdccvRSSB5YsMKiF2qt3RFmAP2X25QEWqqQnR4y" + ] + }, + { + "path": "soljson-v0.8.17+commit.8df45f5f.js", + "version": "0.8.17", + "build": "commit.8df45f5f", + "longVersion": "0.8.17+commit.8df45f5f", + "keccak256": "0x3f2be218cf4545b4d2e380417c6da1e008fdc4255ab38c9ee12f64c0e3f55ea9", + "sha256": "0x617828e63be485c7cc2dbcbdd5a22b582b40fafaa41016ad595637b83c90656c", + "urls": [ + "bzzr://fe8da5b2531d31e4b67acdce09c81eccba1100550a7222722152ffdb16ea85ef", + "dweb:/ipfs/QmTedx1wBKSUaLatuqXYngjfKQLD2cGqPKjdLYCnbMYwiz" + ] + } + ], + "releases": { + "0.8.17": "soljson-v0.8.17+commit.8df45f5f.js", + "0.8.16": "soljson-v0.8.16+commit.07a7930e.js" + }, + "latestRelease": "0.8.17" +} \ No newline at end of file diff --git a/test/resources/assets/key.pem b/test/resources/assets/key.pem new file mode 100644 index 00000000..ac62aa32 --- /dev/null +++ b/test/resources/assets/key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAqx1uPfkuqDqphLOwAMhvQqYWec7037WOrpO/5mHees9fVAX2 +QyGxV+DgzTCHJ4Jn4ATkw3i4yfgEwa4cuHhBuopmK7WCR3F0x8HLR6XA3nupWUbp +lT6L8BqH9YOrT8XRnbQuIV5+y7p9AkTh4WaLFQXUfnlU9Pvu3qIp9eYt1QDMY9NV +F2IMPXsk2CigcqwBPVya6PHNbPYZiEW7B6Hh+rIk1yoG/bNdh3ZCLPBjX6tfpmxH +8KZgfAZ4Q83naav7ABoTL2FQIwwMBKYinvVkzissY/s9cwDmSS3q1pc1CIRLwjuE +r9OfdUIkJNYqM6Bu2J8mpL3tyJTMTsx6OdtrNQIDAQABAoIBAQCiRYi0UMiEVQ1J +gTXZqDqK1ALghPkH5Z9nw0nq6skXYe0fO4AsbZlGo0XMkMa7GxnzAfEHxkZHkmLA +YaNu9OwwQOtfbAQeSGPcw9VJHtFmq5MPzQXauLPwgNiKZdFiF1EP4z0T/A72mOXP +7w7euGYRC7RWNC3zHa+LhfGQ9jZklY1nIFBThGXs5yGUENnmH6AF5wTXRmHev0Fd +qmNfKVff3Hf7ndqwNBQ5zglkR43/CpsHrp410lGyKKpDgmum/OCfMBJr2VP7/BKv +fouvBOc48Pl8Iq3rFia4VU8XOTgXvfGat/l9ZmDDIfWTTrC92H6prgrZHC421Vl3 +SNTVt9XJAoGBAN+qCy5fcawLcJ4CIVl3d2sChmS3fo1HLSuAi+L1ZDYR41Wrd1l8 +n8v9lEtVLbyaIUyMu1g+2UCpz+wLR4icMm2QDZPNp+WuNd+/u99oNsrF3DBesHMM +XFsX/rb37mt4X2sNFAHSmmgn+OJvFPWn2hl9O28HZYu9fgH/axrIzg8DAoGBAMPa +gT91VKcSLg5IUpDS6ieaFO8MJJR10mtvfHttU7ZmWwf7AuIJ3hOYMPRrZF1omjzP +EDgQkhEuFVA4xOrtb8Rm+cxzJiKbMXDBLIzKWEIj9HQvjZfrK1hYKKwR9xPAca9A +WxbbR6Psd/jjTWB8aeyXiIGu3Q+XhrqSmBTvBMtnAoGAHwI2NqGo4s9bN6zX7s1v +UviwpToDY5bgnk1eF4Mp940XVYNGMrPie6eWbbNLegBsaW6BcsST3jEs0G7n5fqx +yxrTpxA2HNPASFsOsvSB1C8GTfZyxCCMlVyIRdmtxlyT7gWMJoyKN7KDAPGhmEVD +KZGBMe12mJ8W3zQIJ1h7qP0CgYBIiRR1PC8t8T29bGun13WBVT+LiSPDGB/mSlQA +Im1ukFoGiqB2ttDY0PTQkTWKosL84nNPUFPIqRibrWrA108dnkXFwp/NEU0uRV0K +h6ZIWKignzwBl8dl4MCvebfYSN3R2jPiMzRFMb79HrH5mhpg1X5pCSo2LBhp4QES +P7O24QKBgQCOcF9xYV7hqPci4EW8GDLe27GDP4qJsMm5tbwgMXKz8+bCOaVFHkqM +XvDk+6RQ0TUEMicZlO69EPlQ5Ev1jd+gO17/hchhjVw3kCstSknFlMTkHJMPdu5B +Gn1UbFJkNnpM3po0nKgGCx7M78aw1WBYqQI9k97Zw07nwGoBp4yZfA== +-----END RSA PRIVATE KEY----- From f9200767016bc8ab7d034a2bcc51296132fbc4c3 Mon Sep 17 00:00:00 2001 From: "Rodrigo Q. Saramago" Date: Sat, 17 Sep 2022 11:07:36 +0200 Subject: [PATCH 04/13] Minor adjusts --- test/downloader.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/downloader.ts b/test/downloader.ts index 960caed9..a57650b3 100644 --- a/test/downloader.ts +++ b/test/downloader.ts @@ -40,12 +40,12 @@ function downloadBinaryMock (url: string, filename: string): nock.Interceptor { return nock(url).get(`/bin/${path.basename(filename)}`); } -function defaultListener (req, res) { +function defaultListener (req: any, res: any): void { res.writeHead(200); res.end('OK'); }; -async function startMockServer (listener = defaultListener) { +async function startMockServer (listener = defaultListener): Promise { const server = https.createServer({ key: fs.readFileSync(path.resolve(assets, 'key.pem')), cert: fs.readFileSync(path.resolve(assets, 'cert.pem')) @@ -97,7 +97,7 @@ tape('Download version list', async function (t) { }); }); -tape('Download latest binary', async function (t) { +tape('Download binary', async function (t) { const server = await startMockServer(); const content = '() => {}'; const tmpDir = tmp.dirSync({ unsafeCleanup: true, prefix: 'solcjs-download-test-' }).name; From 8b4c2e5346080fd837a90bb1273901974db9fce7 Mon Sep 17 00:00:00 2001 From: "Rodrigo Q. Saramago" Date: Sun, 18 Sep 2022 12:04:12 +0200 Subject: [PATCH 05/13] Fix host url --- downloadCurrentVersion.ts | 8 ++++---- downloader.ts | 8 ++++---- test/downloader.ts | 18 +++++++++--------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/downloadCurrentVersion.ts b/downloadCurrentVersion.ts index 41a4a995..425be16b 100755 --- a/downloadCurrentVersion.ts +++ b/downloadCurrentVersion.ts @@ -5,11 +5,11 @@ import downloader from './downloader'; const pkg = require('./package.json'); -const BIN_URL = 'https://binaries.soliditylang.org/bin'; +const DEFAULT_HOST = 'https://binaries.soliditylang.org'; -async function download (version, binURL = BIN_URL) { +async function download (version, host = DEFAULT_HOST) { try { - const list = JSON.parse(await downloader.getVersionList(`${binURL}/list.json`)); + const list = JSON.parse(await downloader.getVersionList(host)); const releaseFileName = list.releases[version]; const expectedFile = list.builds.filter(function (entry) { return entry.path === releaseFileName; })[0]; @@ -18,7 +18,7 @@ async function download (version, binURL = BIN_URL) { } const expectedHash = expectedFile.keccak256; - await downloader.downloadBinary(binURL, 'soljson.js', releaseFileName, expectedHash); + await downloader.downloadBinary(host, 'soljson.js', releaseFileName, expectedHash); } catch (err) { console.log(err.message); process.exit(1); diff --git a/downloader.ts b/downloader.ts index e09d0a78..d36c34a7 100755 --- a/downloader.ts +++ b/downloader.ts @@ -3,12 +3,12 @@ import { https } from 'follow-redirects'; import MemoryStream from 'memorystream'; import { keccak256 } from 'js-sha3'; -function getVersionList (url: string): Promise { +function getVersionList (host: string): Promise { console.log('Retrieving available version list...'); return new Promise((resolve, reject) => { const mem = new MemoryStream(null, { readable: false }); - https.get(url, function (response) { + https.get(`${host}/bin/list.json`, function (response) { if (response.statusCode !== 200) { reject(new Error('Error downloading file: ' + response.statusCode)); } @@ -22,7 +22,7 @@ function getVersionList (url: string): Promise { }); } -function downloadBinary (binURL: string, outputName: string, releaseFile: string, expectedHash: string): Promise { +function downloadBinary (host: string, outputName: string, releaseFile: string, expectedHash: string): Promise { console.log('Downloading version', releaseFile); return new Promise((resolve, reject) => { @@ -37,7 +37,7 @@ function downloadBinary (binURL: string, outputName: string, releaseFile: string }); const file = fs.createWriteStream(outputName, { encoding: 'binary' }); - https.get(`${binURL}/${releaseFile}`, function (response) { + https.get(`${host}/bin/${releaseFile}`, function (response) { if (response.statusCode !== 200) { reject(new Error('Error downloading file: ' + response.statusCode)); } diff --git a/test/downloader.ts b/test/downloader.ts index a57650b3..2f3d55e5 100644 --- a/test/downloader.ts +++ b/test/downloader.ts @@ -32,12 +32,12 @@ function generateTestFile (t: tape.Test, content: string): tmp.FileResult { return file; } -function versionListMock (url: string): nock.Interceptor { - return nock(url).get('/bin/list.json'); +function versionListMock (host: string): nock.Interceptor { + return nock(host).get('/bin/list.json'); } -function downloadBinaryMock (url: string, filename: string): nock.Interceptor { - return nock(url).get(`/bin/${path.basename(filename)}`); +function downloadBinaryMock (host: string, filename: string): nock.Interceptor { + return nock(host).get(`/bin/${path.basename(filename)}`); } function defaultListener (req: any, res: any): void { @@ -73,7 +73,7 @@ tape('Download version list', async function (t) { try { const list = JSON.parse( - await downloader.getVersionList(`${server.origin}/bin/list.json`) + await downloader.getVersionList(server.origin) ); const expected = require(dummyListPath); st.deepEqual(list, expected, 'list should match'); @@ -88,7 +88,7 @@ tape('Download version list', async function (t) { versionListMock(server.origin).reply(404); try { - await downloader.getVersionList(`${server.origin}/bin/list.json`); + await downloader.getVersionList(server.origin); st.fail('should throw file not found error'); } catch (err) { st.equal(err.message, 'Error downloading file: 404', 'should throw file not found error'); @@ -123,7 +123,7 @@ tape('Download binary', async function (t) { try { await downloader.downloadBinary( - `${server.origin}/bin`, + server.origin, targetFilename, file.name, hash(file.name) @@ -148,7 +148,7 @@ tape('Download binary', async function (t) { try { await downloader.downloadBinary( - `${server.origin}/bin`, + server.origin, targetFilename, 'test.js', `0x${keccak256('something')}` @@ -176,7 +176,7 @@ tape('Download binary', async function (t) { try { await downloader.downloadBinary( - `${server.origin}/bin`, + server.origin, targetFilename, file.name, `0x${keccak256('something')}` From e054e7026ef25b9d49e537de92c61be04507114a Mon Sep 17 00:00:00 2001 From: r0qs Date: Wed, 21 Sep 2022 14:29:37 +0200 Subject: [PATCH 06/13] Update downloader.ts Co-authored-by: matheusaaguiar <95899911+matheusaaguiar@users.noreply.github.com> --- downloader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/downloader.ts b/downloader.ts index d36c34a7..344ac9b1 100755 --- a/downloader.ts +++ b/downloader.ts @@ -46,7 +46,7 @@ function downloadBinary (host: string, outputName: string, releaseFile: string, file.close(); const hash = '0x' + keccak256(fs.readFileSync(outputName, { encoding: 'binary' })); if (expectedHash !== hash) { - reject(new Error('Hash mismatch: ' + expectedHash + ' vs ' + hash)); + reject(new Error('Hash mismatch: expected ' + expectedHash + ' but got ' + hash)); } else { console.log('Done.'); resolve(); From 515ec2b24a4a432cda2fb6a90dea32639de82013 Mon Sep 17 00:00:00 2001 From: "Rodrigo Q. Saramago" Date: Wed, 21 Sep 2022 14:38:55 +0200 Subject: [PATCH 07/13] Print error message in test failure assertion --- test/downloader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/downloader.ts b/test/downloader.ts index 2f3d55e5..0d0c069e 100644 --- a/test/downloader.ts +++ b/test/downloader.ts @@ -26,7 +26,7 @@ function generateTestFile (t: tape.Test, content: string): tmp.FileResult { try { fs.writeFileSync(file.name, content); } catch (err) { - t.fail('error writing test file'); + t.fail(`error writing test file: ${err.message}`); } return file; From ad40b4cdb1b48e385b6fcefe2f09c0d65d599c62 Mon Sep 17 00:00:00 2001 From: "Rodrigo Q. Saramago" Date: Wed, 21 Sep 2022 16:23:28 +0200 Subject: [PATCH 08/13] Move hashFile function to common place --- common/helpers.ts | 12 ++++++++++++ downloader.ts | 4 ++-- test/downloader.ts | 9 +++------ 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/common/helpers.ts b/common/helpers.ts index c21a91c6..78543649 100644 --- a/common/helpers.ts +++ b/common/helpers.ts @@ -1,3 +1,6 @@ +import fs from 'fs'; +import { keccak256 } from 'js-sha3'; + /** * Returns true if and only if the value is null or undefined. * @@ -18,3 +21,12 @@ export function isObject (value: any): boolean { // to confirm it's just an object. return typeof value === 'object' && !Array.isArray(value); } + +/** + * Returns the keccak256 hash of a file. + * + * @param path The path to the file to be hashed. + */ +export function hashFile (path: string): string { + return '0x' + keccak256(fs.readFileSync(path, { encoding: 'binary' })); +} diff --git a/downloader.ts b/downloader.ts index 344ac9b1..a246b195 100755 --- a/downloader.ts +++ b/downloader.ts @@ -1,7 +1,7 @@ import * as fs from 'fs'; import { https } from 'follow-redirects'; import MemoryStream from 'memorystream'; -import { keccak256 } from 'js-sha3'; +import { hashFile } from './common/helpers'; function getVersionList (host: string): Promise { console.log('Retrieving available version list...'); @@ -44,7 +44,7 @@ function downloadBinary (host: string, outputName: string, releaseFile: string, response.pipe(file); file.on('finish', function () { file.close(); - const hash = '0x' + keccak256(fs.readFileSync(outputName, { encoding: 'binary' })); + const hash = hashFile(outputName); if (expectedHash !== hash) { reject(new Error('Hash mismatch: expected ' + expectedHash + ' but got ' + hash)); } else { diff --git a/test/downloader.ts b/test/downloader.ts index 0d0c069e..d443db04 100644 --- a/test/downloader.ts +++ b/test/downloader.ts @@ -3,9 +3,10 @@ import tape from 'tape'; import nock from 'nock'; import fs from 'fs'; import path from 'path'; -import { keccak256 } from 'js-sha3'; import { https } from 'follow-redirects'; import downloader from '../downloader'; +import { keccak256 } from 'js-sha3'; +import { hashFile } from '../common/helpers'; const assets = path.resolve(__dirname, 'resources/assets'); @@ -15,10 +16,6 @@ tape.onFinish(() => { } }); -function hash (filePath: string): string { - return '0x' + keccak256(fs.readFileSync(filePath, { encoding: 'binary' })); -} - function generateTestFile (t: tape.Test, content: string): tmp.FileResult { // As the `keep` option is set to true the removeCallback must be called by the caller // to cleanup the files after the test. @@ -126,7 +123,7 @@ tape('Download binary', async function (t) { server.origin, targetFilename, file.name, - hash(file.name) + hashFile(file.name) ); if (!fs.existsSync(targetFilename)) { From 5893e2e4b000934b4cc56e9a26d206d65fce2ef9 Mon Sep 17 00:00:00 2001 From: r0qs Date: Wed, 2 Nov 2022 09:09:44 -0300 Subject: [PATCH 09/13] Update downloadCurrentVersion.ts Co-authored-by: matheusaaguiar <95899911+matheusaaguiar@users.noreply.github.com> --- downloadCurrentVersion.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/downloadCurrentVersion.ts b/downloadCurrentVersion.ts index 425be16b..c75cff38 100755 --- a/downloadCurrentVersion.ts +++ b/downloadCurrentVersion.ts @@ -14,7 +14,7 @@ async function download (version, host = DEFAULT_HOST) { const expectedFile = list.builds.filter(function (entry) { return entry.path === releaseFileName; })[0]; if (!expectedFile) { - throw new Error('Requested version not found. Version list is invalid or corrupted?'); + throw new Error('Requested version not found. Version list is invalid or corrupted.'); } const expectedHash = expectedFile.keccak256; From 26a0167c7970ee6d5209b7ce6ea7fb17ba48021b Mon Sep 17 00:00:00 2001 From: r0qs Date: Wed, 2 Nov 2022 09:16:09 -0300 Subject: [PATCH 10/13] Update downloader.ts Co-authored-by: matheusaaguiar <95899911+matheusaaguiar@users.noreply.github.com> --- downloader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/downloader.ts b/downloader.ts index a246b195..6e151bc7 100755 --- a/downloader.ts +++ b/downloader.ts @@ -33,7 +33,7 @@ function downloadBinary (host: string, outputName: string, releaseFile: string, process.on('SIGINT', function () { fs.unlinkSync(outputName); - reject(new Error('Interrupted, removing file.')); + reject(new Error('Interrupted... file removed')); }); const file = fs.createWriteStream(outputName, { encoding: 'binary' }); From 461fa71deaac95a77372c48f51aafb64a50b4331 Mon Sep 17 00:00:00 2001 From: r0qs Date: Wed, 2 Nov 2022 09:17:16 -0300 Subject: [PATCH 11/13] Update test/downloader.ts Co-authored-by: matheusaaguiar <95899911+matheusaaguiar@users.noreply.github.com> --- test/downloader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/downloader.ts b/test/downloader.ts index d443db04..56935991 100644 --- a/test/downloader.ts +++ b/test/downloader.ts @@ -23,7 +23,7 @@ function generateTestFile (t: tape.Test, content: string): tmp.FileResult { try { fs.writeFileSync(file.name, content); } catch (err) { - t.fail(`error writing test file: ${err.message}`); + t.fail(`Error writing test file: ${err.message}`); } return file; From 6e7f694df8798c71d632fd1c891bd117e7954393 Mon Sep 17 00:00:00 2001 From: "Rodrigo Q. Saramago" Date: Wed, 2 Nov 2022 09:23:10 -0300 Subject: [PATCH 12/13] Replace filter by find --- downloadCurrentVersion.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/downloadCurrentVersion.ts b/downloadCurrentVersion.ts index c75cff38..f668f7a5 100755 --- a/downloadCurrentVersion.ts +++ b/downloadCurrentVersion.ts @@ -11,7 +11,7 @@ async function download (version, host = DEFAULT_HOST) { try { const list = JSON.parse(await downloader.getVersionList(host)); const releaseFileName = list.releases[version]; - const expectedFile = list.builds.filter(function (entry) { return entry.path === releaseFileName; })[0]; + const expectedFile = list.builds.find((entry) => entry.path === releaseFileName); if (!expectedFile) { throw new Error('Requested version not found. Version list is invalid or corrupted.'); From 1b16bb93ff690f990cc23e24426d65167de491a3 Mon Sep 17 00:00:00 2001 From: "Rodrigo Q. Saramago" Date: Wed, 2 Nov 2022 09:34:30 -0300 Subject: [PATCH 13/13] Change exception message when expected mock requests are not performed --- test/downloader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/downloader.ts b/test/downloader.ts index 56935991..0710e1d0 100644 --- a/test/downloader.ts +++ b/test/downloader.ts @@ -12,7 +12,7 @@ const assets = path.resolve(__dirname, 'resources/assets'); tape.onFinish(() => { if (!nock.isDone()) { - throw Error('expected requests were not performed'); + throw Error('Expected download requests were not performed'); } });