Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Simplify local boot and enable multi node #1751

Merged
merged 8 commits into from
Dec 18, 2018
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Added compatibility to Gaia/SDK version 0.28.0-rc2 @faboweb
- [\#1673](https://github.com/cosmos/voyager/issues/1673) Documentation and single command to run one or all tests with fallback for end to end test @sabau
- [\#1683](https://github.com/cosmos/voyager/issues/1683) Governance: block voting twice for the same option @sabau
- [\#1661](https://github.com/cosmos/voyager/issues/1661) Boot: multinode available for local-testnet @sabau
- [\#1748](https://github.com/cosmos/voyager/issues/1748) display governance parameters on tab @fedekunze
- [\#1660](https://github.com/cosmos/voyager/issues/1660) Add parameters and pool to store @fedekunze

Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,14 @@ $ yarn start local-testnet

Import the account with the 12 word seed phrase you wrote down earlier.

### Run several nodes

This command will build and run several nodes at once. All nodes will be validators:

```bash
$ yarn start local-testnet 5
```

## Flags

A list of all environment variables and their purpose:
Expand Down
9 changes: 5 additions & 4 deletions app/src/root.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ const { COSMOS_HOME, NODE_ENV } = process.env
if (COSMOS_HOME) {
module.exports = COSMOS_HOME
} else {
const home = require(`user-home`)
const { join } = require(`path`)
const { resolve, join } = require(`path`)
const networkName = require(`./network.js`).name
const appDir = resolve(`${__dirname}/../../`)
const buildTestnetPath = join(appDir, `builds`, `testnets`)

const { name } = require(`../../package.json`)
const DEV = NODE_ENV === `development`
const appName = name.toLowerCase()
const appDirName = `.${appName}${DEV ? `-dev` : ``}`
const appDirName = `${appName}${DEV ? `-dev` : ``}`

module.exports = join(home, appDirName, networkName)
module.exports = join(buildTestnetPath, networkName, appDirName)
}
90 changes: 9 additions & 81 deletions tasks/build/local/build.js
Original file line number Diff line number Diff line change
@@ -1,90 +1,18 @@
"use strict"

const fs = require(`fs-extra`)
const buildLocalNode = require(`./helper`).buildLocalNode
const { cli } = require(`@nodeguy/cli`)
const path = require(`path`)
const homeDir = require(`os`).homedir()
const appDir = path.resolve(__dirname + `/../../../`)

let {
initNode,
createKey,
initGenesis,
makeExec,
nodeBinary
} = require(`../../gaia.js`)
const appDir = path.resolve(`${__dirname}/../../`)
const buildTestnetPath = `${appDir}/builds/testnets`

const optionsSpecification = {
overwrite: [`overwrite ~/.gaiad-testnet/`, false],
password: [`custom password, default is 1234567890`, 1234567890]
password: [`custom password, default is 1234567890`, 1234567890],
numberNodes: [`number of validators in the network`, 1],
moniker: [`The prefix for each node in your network`, `local`],
chainId: [`Chain name you want to create`, `default-testnet`],
keyName: [`Main account to operate`, `username`]
}

cli(optionsSpecification, async options => {
try {
// remove existing config
if (options.overwrite) {
if (fs.existsSync(appDir + `/builds/testnets/local-testnet`)) {
await makeExec(`rm -r builds/testnets/local-testnet`)
}
if (fs.existsSync(homeDir + `/.gaiad-testnet`)) {
await makeExec(`rm -r ~/.gaiad-testnet`)
}
if (fs.existsSync(homeDir + `/.cosmos-voyager-dev/local-testnet`)) {
await makeExec(`rm -r ~/.cosmos-voyager-dev/local-testnet`)
}
}

const chainId = `local-testnet`
const moniker = `local`
const clientHome = `./builds/testnets/local-testnet/lcd`
const nodeHome = `${homeDir}/.gaiad-testnet`
const defaultAccountInfo = {
keyName: `local`,
password: options.password,
clientHomeDir: clientHome
}
await initNode(
chainId,
moniker,
`${homeDir}/.gaiad-testnet`,
options.password,
options.overwrite
)
const { address } = await createKey(defaultAccountInfo)
await initGenesis(defaultAccountInfo, address, nodeHome)
await moveFiles()
console.log(`\n 🎉 SUCCESS 🎉\n`)
console.log(
`To start Voyager with a local node please run:
yarn start local-testnet

Default account:
username: '${defaultAccountInfo.keyName}'
password: '${defaultAccountInfo.password}'
`
)
} catch (error) {
console.log(`Encountered an Error:`)
console.error(error.msg ? error : error.toString())
}
})

async function moveFiles() {
fs.ensureDirSync(`builds/testnets/local-testnet`)

await makeExec(
`cp ~/.gaiad-testnet/config/{genesis.json,config.toml} builds/testnets/local-testnet/`
)

await makeExec(
`sed -i.bak 's/seeds = ""/seeds = "localhost"/g' ./builds/testnets/local-testnet/config.toml`
)

await makeExec(
`sed -i.bak 's/index_all_tags = false/index_all_tags = true/g' ${homeDir}/.gaiad-testnet/config/config.toml`
)

await makeExec(
`${nodeBinary} version > ./builds/testnets/local-testnet/gaiaversion.txt`
)
}
cli(optionsSpecification, buildLocalNode(buildTestnetPath))
223 changes: 223 additions & 0 deletions tasks/build/local/helper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
"use strict"

const fs = require(`fs-extra`)
const { join } = require(`path`)
const {
startLocalNode,
getNodeId,
makeValidator,
initNode,
createKey,
initGenesis,
makeExec,
getKeys,
nodeBinary
} = require(`../../gaia`)

const buildLocalNode = buildTestnetPath => async ({
numberNodes,
...options
}) => {
try {
const { nodes, mainAccountSignInfo } = await buildNodes(
buildTestnetPath,
options,
numberNodes,
false
)
console.log(`\n 🎉 SUCCESS 🎉\n`)
console.log(
`To start Voyager with ${nodes.length - 1} local node${
nodes.length > 2 ? `s` : ``
} please run:
yarn start local-testnet

Default account:
username: '${mainAccountSignInfo.keyName}'
password: '${mainAccountSignInfo.password}'
`
)
} catch (error) {
console.log(`Encountered an Error:`)
console.error(error.msg ? error : error.toString())
}
}

// save the version of the currently used gaia into the newly created network config folder
const saveVersion = nodeHome => {
const versionPath = join(nodeHome, `config`)
let versionFilePath = join(versionPath, `gaiaversion.txt`) // nodeHome/config is used to copy created config files from
return makeExec(
`mkdir -p ${versionPath} && ${nodeBinary} version > ${versionFilePath}`
)
}

// nodes[0] is a placeholder just to be aligned with the enumeration used in gaia
// TODO: next PR refactor also gaia to simplify numeration
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

const startNodes = async (nodes, mainAccountSignInfo, chainId) => {
for (let i = 1; i < nodes.length; i++) {
// start secondary nodes and connect to node one
// wait until all nodes are showing blocks, so we know they are running
await startLocalNode(nodes[i].home, i, i > 1 ? nodes[1].id : ``)
// make our secondary nodes also to validators
i > 1 &&
(await makeValidator(
mainAccountSignInfo,
nodes[i].home,
nodes[i].cliHome,
nodes[i].moniker,
chainId
))
}
}

const buildNodes = async (
targetDir,
options = {
chainId: `default-testnet`,
password: `1234567890`,
overwrite: true,
moniker: `local`,
keyName: `main-account`
},
numberNodes = 1,
isTest = false
) => {
const cliHomePrefix = join(targetDir, `cli_home`)
const nodeHomePrefix = join(targetDir, `node_home`)

fs.removeSync(targetDir)
// fs.removeSync(`${os.home}/.cosmos-voyager-dev/${network}`)

// create address to delegate staking tokens to 2nd and 3rd validator
let mainAccountSignInfo = undefined
let genesis = undefined

const nodes = [{ id: `dummy` }]
for (let i = 1; i < numberNodes + 1; i++) {
// setup additional nodes
const home = `${nodeHomePrefix}_${i}`
const cliHome = `${cliHomePrefix}_${i}`
const moniker = `${options.moniker}_${i}`
nodes.push({
home,
cliHome,
moniker,
id: await setupLocalNode(home, options, moniker, isTest, genesis)
})
if (i === 1) {
await saveVersion(home)
mainAccountSignInfo = {
keyName: options.keyName,
password: options.password,
clientHomeDir: cliHome
}
let { address } = await createKey(mainAccountSignInfo)
genesis = await initGenesis(mainAccountSignInfo, address, home)
}
}

return { nodes, mainAccountSignInfo, genesis, cliHomePrefix }
}

// init a node and define it as a validator
async function setupLocalNode(
nodeHome,
options,
moniker,
isTest = false,
mainGenesis = undefined
) {
await initNode(
options.chainId,
moniker,
nodeHome,
options.password,
options.overwrite
)
mainGenesis &&
(await fs.writeJSON(join(nodeHome, `config`, `genesis.json`), mainGenesis))
await adjustConfig(nodeHome, isTest)

return await getNodeId(nodeHome)
}

// declare candidacy for node

function adjustConfig(nodeHome, isTest = false, strictAddressbook = false) {
const configPath = join(nodeHome, `config`, `config.toml`)
let configToml = fs.readFileSync(configPath, `utf8`)

const timeouts = [
`timeout_propose`,
`timeout_propose_delta`,
`timeout_prevote`,
`timeout_prevote_delta`,
`timeout_precommit`,
`timeout_precommit_delta`,
`timeout_commit`,
`flush_throttle_timeout`
]
const updatedConfigToml = configToml
.split(`\n`)
.map(line => {
let [key, value] = line.split(` = `)

if (!isTest) {
// TODO: this was happening on ./builds/testnets/local-testnet/config.toml
// but then the network was launched through the config ~/.gaiad-testnet/config/config.toml
// and it had seeds=""
// What was the goal of this replacement? is mentioned also in the readme
// if (key === `seeds`) return `${key} = "localhost"`
if (key === `index_all_tags`) return `${key} = true`
return line
}

if (key === `addr_book_strict`) {
return `${key} = ${strictAddressbook ? `true` : `false`}`
}

if (!timeouts.includes(key)) {
return line
}

// timeouts are in the format "100ms" or "5s"
value = value.replace(/"/g, ``)
if (value.trim().endsWith(`ms`)) {
value = parseInt(value.trim().substr(0, value.length - 2))
} else if (value.trim().endsWith(`s`)) {
value = parseInt(value.trim().substr(0, value.length - 1)) * 1000
}

return `${key} = "${value / 10}ms"`
})
.join(`\n`)

fs.writeFileSync(configPath, updatedConfigToml, `utf8`)
}

const setupAccounts = async (srcClientDir, dstClientDir, options) => {
// use the master account that holds funds from node 1
// to use it, we copy the key database from node one to our Voyager cli config folder
fs.copySync(srcClientDir, dstClientDir)

// this account is later used to send funds to, to test token sending
await createKey({
keyName: (options && options.keyName) || `testreceiver`,
password: (options && options.password) || `1234567890`,
clientHomeDir: dstClientDir
})

let accounts = await getKeys(dstClientDir)
console.log(`setup test accounts`, accounts)

return accounts
}

module.exports = {
buildLocalNode,
buildNodes,
saveVersion,
setupAccounts,
startNodes
}
Loading