diff --git a/.cargo/config b/.cargo/config index 3527f8783469aade58728a6a2c86c2cc4c8dfd4a..3b8aaea1d5e245c71373fa095819c8cec1a131ca 100644 --- a/.cargo/config +++ b/.cargo/config @@ -1,5 +1,6 @@ [alias] cucumber = "test -p duniter-end2end-tests --test cucumber_tests --" +smoldot = "test -p duniter-end2end-tests --test smoldot_tests -- --nocapture" tu = "test --workspace --exclude duniter-end2end-tests" xtask = "run --package xtask --" diff --git a/Cargo.lock b/Cargo.lock index ed959f0a8d777e426dd888ebcff93591b964e697..fae8265cf222e6d24dfd372c574b40fe244677a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1361,7 +1361,9 @@ dependencies = [ "notify", "parity-scale-codec", "portpicker", + "serde", "serde_json", + "sp-core", "sp-keyring", "subxt", "tokio", diff --git a/end2end-tests/Cargo.toml b/end2end-tests/Cargo.toml index 8d7b445eeded82e13bb22a059caefa39ad27fd7d..7fb726eaca779df264258f55e10121f02e34cc54 100644 --- a/end2end-tests/Cargo.toml +++ b/end2end-tests/Cargo.toml @@ -17,7 +17,9 @@ env_logger = "0.9.0" notify = "4.0" parity-scale-codec = "2.3.1" portpicker = "0.1.1" +serde = "1.0" serde_json = "1.0.64" +sp-core = { git = "https://github.com/librelois/substrate.git", branch = "duniter-monthly-2022-02" } sp-keyring = { git = "https://github.com/librelois/substrate.git", branch = "duniter-monthly-2022-02" } subxt = { git = 'https://github.com/librelois/subxt.git', branch = 'duniter-monthly-2022-02' } tokio = { version = "1.15.0", features = ["macros"] } @@ -25,3 +27,7 @@ tokio = { version = "1.15.0", features = ["macros"] } [[test]] name = "cucumber_tests" harness = false # allows Cucumber to print output instead of libtest + +[[test]] +name = "smoldot_tests" +harness = true diff --git a/end2end-tests/smoldot-server/index.js b/end2end-tests/smoldot-server/index.js new file mode 100644 index 0000000000000000000000000000000000000000..39ba36490bbfebe980fd70155070aee78034e0ad --- /dev/null +++ b/end2end-tests/smoldot-server/index.js @@ -0,0 +1,139 @@ +// This file launches a WebSocket server that exposes the JSON-RPC API of a smoldot light client. + +import * as smoldot from "@substrate/smoldot-light"; +import { default as websocket } from "websocket"; +import * as http from "node:http"; +import * as process from "node:process"; +import * as fs from "node:fs"; +import * as util from "node:util"; + +// File logger +const logFile = fs.createWriteStream('smoldot.log', {flags : 'w'}); +const logStdout = process.stdout; +const fileLogger = function () { + logFile.write(util.format.apply(null, arguments) + '\n'); +} +console.log = function () { + logFile.write(util.format.apply(null, arguments) + '\n'); + logStdout.write(util.format.apply(null, arguments) + '\n'); +} +console.error = console.log; + +// Chain spec +const gdev = fs.readFileSync(process.env.CHAIN_SPEC_FILEPATH || "../../node/specs/gdev-raw.json", "utf8"); + +const client = smoldot.start({ + maxLogLevel: 4, // Can be increased for more verbosity + forbidTcp: false, + forbidWs: false, + forbidNonLocalWs: false, + forbidWss: false, + logCallback: (level, target, message) => { + // As incredible as it seems, there is currently no better way to print the current time + // formatted in a certain way. + const now = new Date(); + const hours = ("0" + now.getHours()).slice(-2); + const minutes = ("0" + now.getMinutes()).slice(-2); + const seconds = ("0" + now.getSeconds()).slice(-2); + const milliseconds = ("00" + now.getMilliseconds()).slice(-3); + if (level <= 3) { + console.log( + "[%s:%s:%s.%s] [%s] %s", + hours, + minutes, + seconds, + milliseconds, + target, + message + ); + } else { + fileLogger( + "[%s:%s:%s.%s] [%s] %s", + hours, + minutes, + seconds, + milliseconds, + target, + message + ); + } + }, +}); + +client + .addChain({ chainSpec: gdev }) + .catch((error) => { + console.error("Error while adding chain: " + error); + process.exit(1); + }); + +// Start the WebSocket server listening on port 9943. +let server = http.createServer(function (request, response) { + response.writeHead(404); + response.end(); +}); +let ws_port = process.env.WS_PORT || 9943; +server.listen(ws_port, function () { + console.log("JSON-RPC server now listening on port " + ws_port); + console.log( + "Please visit: https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A" + ws_port + ); + console.log(""); +}); +let wsServer = new websocket.server({ + httpServer: server, + autoAcceptConnections: false, +}); + +wsServer.on("request", function (request) { + let chain = (async () => { + return { + gdev: await client.addChain({ + chainSpec: gdev, + jsonRpcCallback: (resp) => { + connection.sendUTF(resp); + }, + }), + }; + })(); + + const connection = request.accept( + request.requestedProtocols[0], + request.origin + ); + console.log("New JSON-RPC client connected: " + request.remoteAddress + "."); + + chain.catch((error) => { + console.error("Error while adding chain gdev: " + error); + connection.close(400); + }); + + // Receiving a message from the connection. This is a JSON-RPC request. + connection.on("message", function (message) { + if (message.type === "utf8") { + chain + .then((chain) => { + if (chain.gdev) chain.gdev.sendJsonRpc(message.utf8Data); + else console.error("Unknown chain"); + }) + .catch((error) => { + console.error("Error during JSON-RPC request: " + error); + process.exit(1); + }); + } else { + connection.close(400); + } + }); + + // When the connection closes, remove the chains that have been added. + connection.on("close", function (reasonCode, description) { + console.log( + "JSON-RPC client " + connection.remoteAddress + " disconnected." + ); + chain + .then((chain) => { + if (chain.gdev) chain.gdev.remove(); + }) + .catch(() => {}); + }); +}); diff --git a/end2end-tests/smoldot-server/package-lock.json b/end2end-tests/smoldot-server/package-lock.json new file mode 100644 index 0000000000000000000000000000000000000000..a2e7cb5439ca639c85a4e9ddaa637a1de0bbc019 --- /dev/null +++ b/end2end-tests/smoldot-server/package-lock.json @@ -0,0 +1,459 @@ +{ + "name": "smoldot-test-server", + "version": "0.0.1", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "smoldot-test-server", + "version": "0.0.1", + "dependencies": { + "@substrate/smoldot-light": "^0.6.17", + "websocket": "^1.0.34" + }, + "devDependencies": { + "@types/node": "^17.0.38", + "@types/websocket": "^1.0.5", + "typescript": "^4.7.2" + } + }, + "node_modules/@substrate/smoldot-light": { + "version": "0.6.17", + "resolved": "https://registry.npmjs.org/@substrate/smoldot-light/-/smoldot-light-0.6.17.tgz", + "integrity": "sha512-C8KYYvocfxO7I+9WdSoqfu+bbpM0a09c8f6PGfPphMPLm4hjuNPBo00WyJhCFcCMQBfPu3FprCUPDImFYELTdA==", + "dependencies": { + "buffer": "^6.0.1", + "pako": "^2.0.4", + "websocket": "^1.0.32" + } + }, + "node_modules/@types/node": { + "version": "17.0.38", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.38.tgz", + "integrity": "sha512-5jY9RhV7c0Z4Jy09G+NIDTsCZ5G0L5n+Z+p+Y7t5VJHM30bgwzSjVtlcBxqAj+6L/swIlvtOSzr8rBk/aNyV2g==", + "dev": true + }, + "node_modules/@types/websocket": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.5.tgz", + "integrity": "sha512-NbsqiNX9CnEfC1Z0Vf4mE1SgAJ07JnRYcNex7AJ9zAVzmiGHmjKFEk7O4TJIsgv2B1sLEb6owKFZrACwdYngsQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/bufferutil": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.6.tgz", + "integrity": "sha512-jduaYOYtnio4aIAyc6UbvPCVcgq7nYpVnucyxr6eCYg/Woad9Hf/oxxBRDnGGjPfjUm6j5O/uBWhIu4iLebFaw==", + "hasInstallScript": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dependencies": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/es5-ext": { + "version": "0.10.61", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.61.tgz", + "integrity": "sha512-yFhIqQAzu2Ca2I4SE2Au3rxVfmohU9Y7wqGR+s7+H7krk26NXhIRAZDgqd6xqjCEFUomDEA3/Bo/7fKmIkW1kA==", + "hasInstallScript": true, + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "dependencies": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "node_modules/ext": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz", + "integrity": "sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==", + "dependencies": { + "type": "^2.5.0" + } + }, + "node_modules/ext/node_modules/type": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.6.0.tgz", + "integrity": "sha512-eiDBDOmkih5pMbo9OqsqPRGMljLodLcwd5XD5JbtNB0o89xZAwynY9EdCDsJU7LtcVCClu9DvM7/0Ep1hYX3EQ==" + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" + }, + "node_modules/node-gyp-build": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.4.0.tgz", + "integrity": "sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/pako": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.0.4.tgz", + "integrity": "sha512-v8tweI900AUkZN6heMU/4Uy4cXRc2AYNRggVmTR+dEncawDJgCdLMximOVA2p4qO57WMynangsfGRb5WD6L1Bg==" + }, + "node_modules/type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/typescript": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.2.tgz", + "integrity": "sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/utf-8-validate": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.9.tgz", + "integrity": "sha512-Yek7dAy0v3Kl0orwMlvi7TPtiCNrdfHNd7Gcc/pLq4BLXqfAmd0J7OWMizUQnTTJsyjKn02mU7anqwfmUP4J8Q==", + "hasInstallScript": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/websocket": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz", + "integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==", + "dependencies": { + "bufferutil": "^4.0.1", + "debug": "^2.2.0", + "es5-ext": "^0.10.50", + "typedarray-to-buffer": "^3.1.5", + "utf-8-validate": "^5.0.2", + "yaeti": "^0.0.6" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/yaeti": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", + "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=", + "engines": { + "node": ">=0.10.32" + } + } + }, + "dependencies": { + "@substrate/smoldot-light": { + "version": "0.6.17", + "resolved": "https://registry.npmjs.org/@substrate/smoldot-light/-/smoldot-light-0.6.17.tgz", + "integrity": "sha512-C8KYYvocfxO7I+9WdSoqfu+bbpM0a09c8f6PGfPphMPLm4hjuNPBo00WyJhCFcCMQBfPu3FprCUPDImFYELTdA==", + "requires": { + "buffer": "^6.0.1", + "pako": "^2.0.4", + "websocket": "^1.0.32" + } + }, + "@types/node": { + "version": "17.0.38", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.38.tgz", + "integrity": "sha512-5jY9RhV7c0Z4Jy09G+NIDTsCZ5G0L5n+Z+p+Y7t5VJHM30bgwzSjVtlcBxqAj+6L/swIlvtOSzr8rBk/aNyV2g==", + "dev": true + }, + "@types/websocket": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.5.tgz", + "integrity": "sha512-NbsqiNX9CnEfC1Z0Vf4mE1SgAJ07JnRYcNex7AJ9zAVzmiGHmjKFEk7O4TJIsgv2B1sLEb6owKFZrACwdYngsQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "bufferutil": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.6.tgz", + "integrity": "sha512-jduaYOYtnio4aIAyc6UbvPCVcgq7nYpVnucyxr6eCYg/Woad9Hf/oxxBRDnGGjPfjUm6j5O/uBWhIu4iLebFaw==", + "requires": { + "node-gyp-build": "^4.3.0" + } + }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "es5-ext": { + "version": "0.10.61", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.61.tgz", + "integrity": "sha512-yFhIqQAzu2Ca2I4SE2Au3rxVfmohU9Y7wqGR+s7+H7krk26NXhIRAZDgqd6xqjCEFUomDEA3/Bo/7fKmIkW1kA==", + "requires": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "next-tick": "^1.1.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "requires": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "ext": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz", + "integrity": "sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==", + "requires": { + "type": "^2.5.0" + }, + "dependencies": { + "type": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.6.0.tgz", + "integrity": "sha512-eiDBDOmkih5pMbo9OqsqPRGMljLodLcwd5XD5JbtNB0o89xZAwynY9EdCDsJU7LtcVCClu9DvM7/0Ep1hYX3EQ==" + } + } + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" + }, + "node-gyp-build": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.4.0.tgz", + "integrity": "sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ==" + }, + "pako": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.0.4.tgz", + "integrity": "sha512-v8tweI900AUkZN6heMU/4Uy4cXRc2AYNRggVmTR+dEncawDJgCdLMximOVA2p4qO57WMynangsfGRb5WD6L1Bg==" + }, + "type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "requires": { + "is-typedarray": "^1.0.0" + } + }, + "typescript": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.2.tgz", + "integrity": "sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A==", + "dev": true + }, + "utf-8-validate": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.9.tgz", + "integrity": "sha512-Yek7dAy0v3Kl0orwMlvi7TPtiCNrdfHNd7Gcc/pLq4BLXqfAmd0J7OWMizUQnTTJsyjKn02mU7anqwfmUP4J8Q==", + "requires": { + "node-gyp-build": "^4.3.0" + } + }, + "websocket": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz", + "integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==", + "requires": { + "bufferutil": "^4.0.1", + "debug": "^2.2.0", + "es5-ext": "^0.10.50", + "typedarray-to-buffer": "^3.1.5", + "utf-8-validate": "^5.0.2", + "yaeti": "^0.0.6" + } + }, + "yaeti": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", + "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=" + } + } +} diff --git a/end2end-tests/smoldot-server/package.json b/end2end-tests/smoldot-server/package.json new file mode 100644 index 0000000000000000000000000000000000000000..fb0f67f73457b969822e96f0e49f618dbb5bf0f6 --- /dev/null +++ b/end2end-tests/smoldot-server/package.json @@ -0,0 +1,21 @@ +{ + "name": "smoldot-test-server", + "version": "0.0.1", + "description": "Light client that connects to Polkadot and Substrate-based blockchains", + "files": [ + "dist" + ], + "type": "module", + "scripts": { + "start": "node index.js" + }, + "dependencies": { + "@substrate/smoldot-light": "^0.6.17", + "websocket": "^1.0.34" + }, + "devDependencies": { + "@types/node": "^17.0.38", + "@types/websocket": "^1.0.5", + "typescript": "^4.7.2" + } +} diff --git a/end2end-tests/tests/common/balances.rs b/end2end-tests/tests/common/balances.rs index 781abf549e229cb28f20a18c387e60e4ad80ed46..7efc5109a94f5571e9ea2a13a14a79cca09bf46a 100644 --- a/end2end-tests/tests/common/balances.rs +++ b/end2end-tests/tests/common/balances.rs @@ -48,6 +48,7 @@ pub async fn set_balance( pub async fn transfer( api: &Api, client: &Client, + create_block: bool, from: AccountKeyring, amount: u64, to: AccountKeyring, @@ -55,15 +56,25 @@ pub async fn transfer( let from = PairSigner::new(from.pair()); let to = to.to_account_id(); - let _events = create_block_with_extrinsic( - client, + if create_block { + let _events = create_block_with_extrinsic( + client, + api.tx() + .balances() + .transfer(to.clone().into(), amount) + .create_signed(&from, ()) + .await?, + ) + .await?; + } else { api.tx() .balances() .transfer(to.clone().into(), amount) - .create_signed(&from, ()) - .await?, - ) - .await?; + .sign_and_submit_then_watch(&from) + .await? + .wait_for_in_block() + .await?; + } Ok(()) } diff --git a/end2end-tests/tests/common/mod.rs b/end2end-tests/tests/common/mod.rs index 078abd747617b2e2c04a6392e796d5e5ccd75507..51809a8f7f65ff46ac1654b66f95a8bdbc2bc263 100644 --- a/end2end-tests/tests/common/mod.rs +++ b/end2end-tests/tests/common/mod.rs @@ -83,6 +83,105 @@ pub async fn spawn_node(maybe_genesis_conf_file: Option<PathBuf>) -> (Api, Clien (api, client, process) } +pub async fn spawn_full_node_and_smoldot_node() -> (Api, Client, Process, Process) { + let duniter_binary_path = std::env::var("DUNITER_BINARY_PATH") + .unwrap_or_else(|_| "../target/debug/duniter".to_owned()); + + // Generate raw chain spec + let output = Command::new(&duniter_binary_path) + .args(["build-spec", "--chain=local", "--raw"]) + .env("DUNITER_BABE_EPOCH_DURATION", "5") + .output() + .expect("Fail to generate chain spec"); + + if !output.status.success() { + panic!("Fail to generate chain spec: status != success"); + } + + // Load raw chain spec + let mut chain_spec_json: serde_json::Value = + serde_json::from_slice(&output.stdout).expect("Error parsing raw chain spec"); + + // Write chain spec for the full node + let mut file = std::fs::File::create("gdev-local-spec-for-full-node.json") + .expect("Fail to create chain spec file for full node"); + file.write_all(chain_spec_json.to_string().as_bytes()) + .expect("Fail to write chain spec file for full node"); + + // Spawn the full node + let FullNode { + process: full_node_process, + p2p_port, + ws_port: _, + } = spawn_full_node( + &[ + "--alice", + "--chain=gdev-local-spec-for-full-node.json", + "--node-key=0000000000000000000000000000000000000000000000000000000000000001", + ], + &duniter_binary_path, + None, + ); + + // Update the bootnodes in chain spec + let bootnode = format!( + "/ip4/127.0.0.1/tcp/{}/p2p/12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp", + p2p_port + ); + if let Some(chain_spec_obj) = chain_spec_json.as_object_mut() { + if let Some(boot_nodes) = chain_spec_obj.get_mut("bootNodes") { + *boot_nodes = serde_json::Value::Array(vec![serde_json::Value::String(bootnode)]); + } + } + + // Write chain spec for smoldot + let mut file = std::fs::File::create("gdev-local-spec-for-smoldot.json") + .expect("Fail to create chain spec file for smoldot"); + file.write_all(chain_spec_json.to_string().as_bytes()) + .expect("Fail to write chain spec file for smoldot"); + + // Spawn the light node (smoldot) + let ws_port = portpicker::pick_unused_port().expect("No ports free"); + let current_dir = + std::fs::canonicalize("smoldot-server").expect("fail to canonicalize current_dir"); + let log_file_path = format!("smoldot-{}.log", ws_port); + let log_file = std::fs::File::create(&log_file_path).expect("fail to create log file"); + let process = Process( + Command::new("npm") + .current_dir(current_dir) + .args(["run", "start"]) + .env("CHAIN_SPEC_FILEPATH", "../gdev-local-spec-for-smoldot.json") + .env("WS_PORT", ws_port.to_string()) + .stdout(log_file) + .spawn() + .expect("failed to spawn node"), + ); + + let timeout = + if let Ok(duration_string) = std::env::var("DUNITER_END2END_TESTS_SPAWN_NODE_TIMEOUT") { + duration_string.parse().unwrap_or(8) + } else { + 8 + }; + + wait_until_log_line( + "Finalized block runtime ready. Spec version:", + &log_file_path, + std::time::Duration::from_secs(timeout), + ); + + let client = ClientBuilder::new() + .set_url(format!("ws://127.0.0.1:{}", ws_port)) + .build() + .await + .expect("fail to connect to node"); + let api = client.clone().to_runtime_api::<Api>(); + + println!("***** NETWORK FULLY STARTED *****"); + + (api, client, process, full_node_process) +} + pub async fn create_empty_block(client: &Client) -> Result<()> { // Create an empty block let _: Value = client @@ -121,6 +220,103 @@ pub async fn create_block_with_extrinsic( .map_err(Into::into) } +#[derive(Debug, serde::Deserialize)] +#[serde(tag = "event")] +#[serde(rename_all = "camelCase")] +enum SmoldotChainHeadFollowEvent { + #[serde(rename_all = "camelCase")] + Initialized { + finalized_block_hash: String, + }, + #[serde(rename_all = "camelCase")] + NewBlock { + block_hash: String, + }, + Stop, + #[serde(other)] + Unknown, +} + +#[derive(Debug, Clone, serde::Deserialize)] +#[serde(tag = "event")] +pub enum SmoldotChainHeadCallEvent { + #[serde(rename = "done")] + Done { output: String }, + #[serde(rename = "inaccessible")] + Inaccessible { error: String }, + #[serde(rename = "error")] + Error { error: String }, + #[serde(rename = "disjoint")] + Disjoint {}, +} + +pub async fn dry_run_extrinsic( + block_hash: &sp_core::H256, + client: &Client, + extrinsic: subxt::UncheckedExtrinsic<DefaultConfig, DefaultExtra<DefaultConfig>>, +) -> Result<bool> { + // Get a followSubscriptionId + let sub: subxt::rpc::Subscription<SmoldotChainHeadFollowEvent> = client + .rpc() + .client + .subscribe( + "chainHead_unstable_follow", + rpc_params![true], + "chainHead_unstable_unfollow", + ) + .await?; + let follow_subscription_id: Value = sub + .get_subscription_id() + .expect("chainHead_unstable_follow must return a subscription id") + .into(); + + println!( + "TMP DEBUG: follow_subscription_id={:?}", + &follow_subscription_id + ); + + use parity_scale_codec::Encode as _; + let block_hash_bytes: sp_core::Bytes = block_hash.encode().into(); + let extrinsic_bytes: sp_core::Bytes = extrinsic.encode().into(); + let mut sub: subxt::rpc::Subscription<SmoldotChainHeadCallEvent> = client + .rpc() + .client + .subscribe( + "chainHead_unstable_call", + rpc_params![ + follow_subscription_id, + block_hash_bytes, // Block hash + "BlockBuilder_apply_extrinsic", + extrinsic_bytes, // SCALE encoded extrinsic + Value::Null // Network config + ], + "chainHead_unstable_call_unwatch", + ) + .await?; + + loop { + if let Some(event) = sub.next().await { + println!("TMP DEBUG: event={:?}", &event); + } else { + panic!("subscription closed"); + } + } + + todo!() + /*println!("TMP DEBUG: result={:?}", &result); + + Ok(result + .as_str() + .ok_or("chainHead_unstable_call must return an opaque string")? + == "1")*/ +} + +pub fn encode_to_json<T: parity_scale_codec::Encode>(t: &T) -> Value { + use parity_scale_codec::Encode as _; + let bytes: sp_core::Bytes = t.encode().into(); + serde_json::to_value(bytes).unwrap() +} + fn spawn_full_node( args: &[&str], duniter_binary_path: &str, diff --git a/end2end-tests/tests/cucumber_tests.rs b/end2end-tests/tests/cucumber_tests.rs index ffe54030a61315e4f916611d0b95c6003594f379..98ae9b725a90e9018f668cc9af8d3c7fa2c0c91d 100644 --- a/end2end-tests/tests/cucumber_tests.rs +++ b/end2end-tests/tests/cucumber_tests.rs @@ -151,7 +151,7 @@ async fn transfer( if is_ud { common::balances::transfer_ud(world.api(), world.client(), from, amount, to).await } else { - common::balances::transfer(world.api(), world.client(), from, amount, to).await + common::balances::transfer(world.api(), world.client(), true, from, amount, to).await } } diff --git a/end2end-tests/tests/smoldot_tests.rs b/end2end-tests/tests/smoldot_tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..83d30d21b9aa6647e02e485e574be668bcf16484 --- /dev/null +++ b/end2end-tests/tests/smoldot_tests.rs @@ -0,0 +1,63 @@ +// Copyright 2021 Axiom-Team +// +// This file is part of Substrate-Libre-Currency. +// +// Substrate-Libre-Currency is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, version 3 of the License. +// +// Substrate-Libre-Currency is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>. + +mod common; + +use common::*; +use sp_keyring::AccountKeyring; + +#[tokio::test(flavor = "current_thread")] +//#[ignore] +async fn smoldot_test1() { + let (api, client, _process, _full_node_process) = spawn_full_node_and_smoldot_node().await; + + /*let mut blocks = client.rpc().subscribe_blocks().await.unwrap(); + + loop { + let header = blocks.next().await.unwrap().unwrap(); + println!("header.number: {}", header.number); + if header.number > 20 { + break; + } + } + + common::balances::transfer(&api, &client, false, AccountKeyring::Alice, 500, AccountKeyring::Bob) + .await + .expect("fail to submit extrinsic balances.transfer");*/ + + // get last finalized hash + /*let block_hash = client + .rpc() + .finalized_head() + .await + .expect("fail to get last finalized hash"); + + let from = subxt::PairSigner::new(AccountKeyring::Alice.pair()); + let _ = common::dry_run_extrinsic( + &block_hash, + &client, + api.tx() + .balances() + .transfer(AccountKeyring::Bob.to_account_id().into(), 5_000) + .create_signed(&from, ()) + .await + .expect("fail to generate extrinsic"), + ) + .await + .expect("fail to dry run extrinsic");*/ + + std::thread::sleep(std::time::Duration::from_secs(20_000)); +} diff --git a/node/src/chain_spec/gdev.rs b/node/src/chain_spec/gdev.rs index 87611694e0c9a4bc6990e87064664a1f4495164f..61c4335ce1fd96ef2ae50a9994dd6fa3f38b19ef 100644 --- a/node/src/chain_spec/gdev.rs +++ b/node/src/chain_spec/gdev.rs @@ -279,6 +279,9 @@ fn gen_genesis_for_local_chain( ) }) .collect::<BTreeMap<IdtyName, AccountId>>(); + let initial_balances = (0..initial_identities_len) + .map(|i| (get_account_id_from_seed::<sr25519::Public>(NAMES[i]), 1000)) + .collect::<Vec<_>>(); gdev_runtime::GenesisConfig { system: SystemConfig { @@ -340,7 +343,7 @@ fn gen_genesis_for_local_chain( .collect(), }, balances: BalancesConfig { - balances: Default::default(), + balances: initial_balances, }, babe: BabeConfig { authorities: Vec::with_capacity(0), @@ -421,7 +424,7 @@ fn gen_genesis_for_local_chain( universal_dividend: UniversalDividendConfig { first_reeval: 100, first_ud: 1_000, - initial_monetary_mass: 0, + initial_monetary_mass: initial_identities_len as u64 * 1_000, }, treasury: Default::default(), } diff --git a/scripts/run_smoldot_tests.sh b/scripts/run_smoldot_tests.sh new file mode 100755 index 0000000000000000000000000000000000000000..f5725e2ec553a31526ba0840fb4660265242b061 --- /dev/null +++ b/scripts/run_smoldot_tests.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# This script is meant to be run on Unix/Linux based systems +set -e + +echo "*** Run smoldot compatibility tests ***" + +cargo clean -p duniter +cargo build +cd end2end-tests/smoldot-server +npm i +cd ../.. +cargo smoldot --nocapture