diff --git a/README.md b/README.md index 75e217a5..c4144ab2 100644 --- a/README.md +++ b/README.md @@ -49,9 +49,9 @@ To run tests for all packages: ### Localhost testing -To test the packages against a local node, you can use the script at `scripts/localhost.sh`. +To test the packages against a local node, you can use the script at `scripts/run-dev.js`. -1. Verify that the line 3-7 of the script matches your current OS and architecture. +1. Verify that the line 3-7 of the bash script in `scripts/download.sh` matches your current OS and architecture. ```bash # Change the following variables as needed @@ -64,10 +64,17 @@ To test the packages against a local node, you can use the script at `scripts/lo 2. Run the script: ```bash - ./scripts/localhost.sh + node scripts/run-dev.js ``` - This script will download the latest version of the node and farmer for your OS and architecture, start the node, and farmer + This script will: + + - Download the latest version of the node and farmer for your OS and architecture (`scripts/download.sh`); + - Start the node, create and insert the keystore in the node (`scripts/run-node.sh`); + - Start the farmer (`scripts/run-farmer.sh`); + - Register the node as operator, wait and kill the node and farmer (inside `scripts/run-dev.js`); + - Start the node as an operator (`scripts/run-operator.sh`); + - Restart the farmer (`scripts/run-farmer.sh`). 3. Run the tests: diff --git a/scripts/localhost.sh b/scripts/download.sh similarity index 74% rename from scripts/localhost.sh rename to scripts/download.sh index 646942a9..8f9deabc 100644 --- a/scripts/localhost.sh +++ b/scripts/download.sh @@ -8,17 +8,21 @@ ARCHITECTURE="aarch64" # aarch64 | x86_64-skylake | x86_64-v2 # GitHub repository REPO="subspace/subspace" +TAG="latest" # "tags/gemini-3h-2024-may-06" # Tag of the release to download or "latest" for the latest release # Directories DOWNLOAD_DIR="downloads" EXECUTABLE_DIR="executables" +# Delete the executable directory +rm -r "$EXECUTABLE_DIR" + # Create directories if they do not exist mkdir -p "$DOWNLOAD_DIR" mkdir -p "$EXECUTABLE_DIR" # Get the latest release data -RELEASE_DATA=$(curl -s "https://api.github.com/repos/$REPO/releases/latest") +RELEASE_DATA=$(curl -s "https://api.github.com/repos/$REPO/releases/$TAG") # Extract the download URLs for the selected os and architecture node and farmer assets NODE_URL=$(echo $RELEASE_DATA | jq -r '.assets[] | select(.name | contains("subspace-node-'$OS'-'$ARCHITECTURE'")) | .browser_download_url') @@ -50,18 +54,4 @@ chmod +x "$EXECUTABLE_DIR/farmer" # Delete the downloads directory rmdir "$DOWNLOAD_DIR" -echo "Downloaded and unzipped the latest node and farmer assets." - -# Run node in the background -echo "Running node in the background..." -./executables/node run --dev --tmp --base-path executables/node-temp --farmer --name "localhost-node" --rpc-rate-limit 1000 --rpc-max-connections 10000 & - -# Wait for 10 seconds before starting farmer -echo "Waiting for 10 seconds before starting farmer..." -sleep 10 - -# Run farmer -echo "Running farmer in the background..." -./executables/farmer farm --reward-address 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY path=executables/farmer-temp,size=1GiB & - -echo "Both node and farmer are running in parallel." \ No newline at end of file +echo "Downloaded and unzipped the latest node and farmer assets." \ No newline at end of file diff --git a/scripts/run-dev.js b/scripts/run-dev.js new file mode 100644 index 00000000..28f61a8b --- /dev/null +++ b/scripts/run-dev.js @@ -0,0 +1,164 @@ +const { spawn } = require('node:child_process') +const { readdir, readFile } = require('node:fs/promises') +const { ApiPromise, Keyring, WsProvider } = require('@polkadot/api') +const { u8aToHex } = require('@polkadot/util') +const { createType } = require('@polkadot/types') + +let runner = { + download: null, + node: null, + farmer: null, +} +let operatorRegistered = false + +function downloadNodeAndFarmer() { + console.log('First lets download the node and farmer') + runner.download = spawn('bash', ['scripts/download.sh'], { detached: true }) + runner.download.stdout.on('data', (data) => { + const message = data.toString() + console.log('\x1b[33m', 'Download: ', '\x1b[0m', message) + }) + runner.download.stderr.on('data', (data) => { + const message = data.toString() + if (message.includes('%') || message.includes('--:--') || message.includes('Time Current')) + console.log('\x1b[33m', 'Download: ', '\x1b[0m', message) + else console.error('\x1b[31m', 'Download error: ', '\x1b[0m', message) + }) + runner.download.on('close', (code) => { + console.log(`First script exited with code ${code}`) + if (code === 0) runSimpleNode() + else console.error('\x1b[31m', 'Download script failed with code: ', '\x1b[0m', code) + }) +} + +function runSimpleNode() { + console.log('Now lets start a simple node.') + runner.node = spawn('bash', ['scripts/run-node.sh'], { detached: true }) + runner.node.stdout.on('data', (data) => { + const message = data.toString() + console.log('\x1b[36m', 'Node: ', '\x1b[0m', message) + if (runner.farmer === null && message.includes('Idle (0 peers)')) runFarmer() + }) + runner.node.stderr.on('data', (data) => + console.error('\x1b[31m', 'Node error: ', '\x1b[0m', data.toString()), + ) + runner.node.on('close', (code) => + console.log('\x1b[31m', 'Node exited with code: ', '\x1b[0m', code), + ) +} + +function runFarmer() { + console.log('Now lets start the farmer.') + runner.farmer = spawn('bash', ['scripts/run-farmer.sh'], { detached: true }) + runner.farmer.stdout.on('data', (data) => { + const message = data.toString() + console.log('\x1b[35m', 'Farmer: ', '\x1b[0m', message) + if (!operatorRegistered && message.includes('Successfully signed reward hash')) { + operatorRegistered = true + registerOperator() + } + }) + runner.farmer.stderr.on('data', (data) => + console.error('\x1b[31m', 'Farmer error: ', '\x1b[0m', data.toString()), + ) + runner.farmer.on('close', (code) => + console.log('\x1b[31m', 'Farmer exited with code: ', '\x1b[0m', code), + ) +} + +function runOperatorNode() { + console.log('Now lets start a operator node.') + runner.operator = spawn('bash', ['scripts/run-operator.sh']) + runner.operator.stdout.on('data', (data) => { + const message = data.toString() + console.log('\x1b[32m', 'Operator: ', '\x1b[0m', message) + if (runner.farmer === null && message.includes('Idle (0 peers)')) runFarmer() + }) + runner.operator.stderr.on('data', (data) => + console.error('\x1b[31m', 'Operator error: ', '\x1b[0m', data.toString()), + ) + runner.operator.on('close', (code) => + console.log('\x1b[31m', 'Operator exited with code: ', '\x1b[0m', code), + ) +} + +async function registerOperator() { + console.log('Now lets register the operator.') + + const keystorePath = 'executables/node-temp/domains/0/keystore/' + const files = await readdir(keystorePath) + if (files.length === 0) throw new Error('No files found in keystore directory.') + + // Read the contents of the first file in the directory and extract and clean the seed + const seedFile = await readFile(`${keystorePath}/${files[0]}`, 'utf-8') + const seed = seedFile.trim().replace(/"/g, '') + + // Create the provider and the API instance + const provider = new WsProvider('ws://127.0.0.1:9944/ws') + const api = await ApiPromise.create({ provider }) + + const AliceKeyring = new Keyring({ type: 'sr25519' }) + const OperatorKeyring = new Keyring({ type: 'sr25519' }) + + const Alice = AliceKeyring.addFromUri('//Alice') + const Operator = OperatorKeyring.addFromUri(seed) + + const signingKey = u8aToHex(Operator.publicKey) + const signature = Operator.sign(createType(api.registry, 'AccountId', Alice.address).toU8a()) + + const tx = await api.tx.domains.registerOperator( + '0', + '100000000000000000000', + { + signingKey, + minimumNominatorStake: '1000000000000000000', + nominationTax: '2', + }, + signature, + ) + + await new Promise((resolve, reject) => { + tx.signAndSend(Alice, ({ status }) => { + if (status.isInBlock) { + txHash = status.asInBlock.toHex() + console.log( + '\x1b[33m', + 'Registering operator: ', + '\x1b[0m', + 'Successful transaction with tx.hash ' + txHash, + ) + // Wait for 12 seconds before killing the node and farmer to make sure the operator is registered + setTimeout(() => { + console.log('\x1b[33m', 'Registering operator: ', '\x1b[0m', 'Killing node and farmer.') + + process.kill(-runner.node.pid) + process.kill(-runner.farmer.pid) + runner.node = null + runner.farmer = null + + console.log('\x1b[33m', 'Registering operator: ', '\x1b[0m', 'Node and farmer killed.') + + // Wait for 2 seconds before starting the operator node + setTimeout(() => { + runner.node = runOperatorNode() + }, 5000) + }, 12000) + + resolve() + } else if ( + status.isRetracted || + status.isFinalityTimeout || + status.isDropped || + status.isInvalid + ) { + console.log('\x1b[31m', 'Registering operator: ', '\x1b[0m', 'Transaction failed') + reject(new Error('Transaction failed')) + } else + console.log('\x1b[33m', 'Registering operator: ', '\x1b[0m', 'Status of tx: ' + status.type) + }) + }) + + await api.disconnect() +} + +downloadNodeAndFarmer() diff --git a/scripts/run-farmer.sh b/scripts/run-farmer.sh new file mode 100644 index 00000000..0798e27a --- /dev/null +++ b/scripts/run-farmer.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# Run farmer +echo "Running farmer..." +./executables/farmer farm path=executables/farmer-temp,size=2GB --reward-address 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY --node-rpc-url ws://127.0.0.1:9944 + +echo "Both node and farmer are running in parallel." \ No newline at end of file diff --git a/scripts/run-node.sh b/scripts/run-node.sh new file mode 100644 index 00000000..def0b335 --- /dev/null +++ b/scripts/run-node.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +# Set the base-path and domain-id variables +BASE_PATH="executables/node-temp" +DOMAIN_ID=0 + +# Color definitions +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +echo -e "${YELLOW}Create keystore...${NC}\n" + +# Instructions for setting variables +echo -e "Using base-path: ${GREEN}$BASE_PATH${NC}" +echo -e "Using domain-id: ${GREEN}$DOMAIN_ID${NC}\n" +echo -e "${YELLOW}You can change these variables at the top of the script.${NC}" + +# Create keystore +output=$(./executables/node domain key create --base-path "$BASE_PATH" --domain-id "$DOMAIN_ID") + +# # Log the result of the first command +echo "$output" + +# Extract the seed +seed=$(echo "$output" | grep "Seed:" | awk -F '"' '{print $2}') + +# Check if seed was extracted +if [ -z "$seed" ]; then + echo -e "${RED}Failed to extract seed from the output.${NC}" + exit 1 +fi + +# Insert keystore with the extracted seed +echo -e "\n${YELLOW}Insert keystore...${NC}" +./executables/node domain key insert --base-path "$BASE_PATH" --domain-id "$DOMAIN_ID" --keystore-suri "$seed" +# Run node +echo -e "${GREEN}Keystore created successfully!${NC}" + +# Run an node +echo "Running an node..." +./executables/node run --dev --farmer --timekeeper --base-path executables/node-temp --name "localhost-node" --rpc-rate-limit 1000 --rpc-max-connections 10000 --state-pruning archive-canonical --blocks-pruning 512 --rpc-cors all --force-synced --force-authoring \ No newline at end of file diff --git a/scripts/run-operator.sh b/scripts/run-operator.sh new file mode 100644 index 00000000..d90d3196 --- /dev/null +++ b/scripts/run-operator.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# Set the base-path and domain-id variables +BASE_PATH="executables/node-temp" +DOMAIN_ID=0 + +# Color definitions +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Instructions for setting variables +echo -e "Using base-path: ${GREEN}$BASE_PATH${NC}" +echo -e "Using domain-id: ${GREEN}$DOMAIN_ID${NC}\n" +echo -e "${YELLOW}You can change these variables at the top of the script.${NC}" + +# Run an operator +echo "Running an operator..." +./executables/node run --dev --farmer --timekeeper --base-path "$BASE_PATH" --name "localhost-operator" --rpc-rate-limit 1000 --rpc-max-connections 10000 --state-pruning archive-canonical --blocks-pruning 512 --rpc-cors all --force-synced --force-authoring -- --domain-id 0 --operator-id 2 --state-pruning archive-canonical --blocks-pruning 512 --rpc-cors all \ No newline at end of file