Skip to content

Commit

Permalink
Merge pull request #5 from jplomas/master
Browse files Browse the repository at this point in the history
adds WIP OTS command
  • Loading branch information
jplomas authored Jul 27, 2019
2 parents e4858ba + 357e85b commit 2efecb9
Show file tree
Hide file tree
Showing 5 changed files with 744 additions and 18 deletions.
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
},
"bugs": "https://github.com/theqrl/qrl-cli/issues",
"dependencies": {
"@grpc/proto-loader": "^0.5.1",
"@oclif/command": "^1",
"@oclif/config": "^1",
"@oclif/plugin-help": "^2",
Expand All @@ -17,10 +18,15 @@
"bech32": "^1.1.3",
"bignumber.js": "^9.0.0",
"crypto": "^1.0.1",
"crypto-js": "^3.1.9-1",
"grpc": "^1.22.2",
"grpc-kit": "^0.2.0",
"kleur": "^3.0.3",
"ora": "^3.4.0",
"qrcode-terminal": "^0.12.0",
"qrllib": "^1.0.4"
"qrllib": "^1.0.4",
"tmp": "^0.1.0",
"util": "^0.12.1"
},
"devDependencies": {
"@oclif/dev-cli": "^1",
Expand Down
168 changes: 168 additions & 0 deletions src/commands/ots.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/* eslint new-cap: 0 */
const {Command, flags} = require('@oclif/command')
const {red, white} = require('kleur')
const ora = require('ora')
const validateQrlAddress = require('@theqrl/validate-qrl-address')
const grpc = require('grpc')
const {createClient} = require('grpc-kit')
const tmp = require('tmp')
const fs = require('fs')
const util = require('util')
const CryptoJS = require('crypto-js')
const {QRLPROTO_SHA256} = require('../get-qrl-proto-shasum')
const protoLoader = require('@grpc/proto-loader')
const PROTO_PATH = `${__dirname}/../../src/qrlbase.proto`

const readFile = util.promisify(fs.readFile)
const writeFile = util.promisify(fs.writeFile)

const clientGetNodeInfo = client => {
return new Promise((resolve, reject) => {
client.getNodeInfo({}, (error, response) => {
if (error) {
reject(error)
}
resolve(response)
})
})
}

let qrlClient = null

async function checkProtoHash(file) {
return readFile(file).then(async contents => {
const protoFileWordArray = CryptoJS.lib.WordArray.create(contents)
const calculatedProtoHash = CryptoJS.SHA256(protoFileWordArray).toString(CryptoJS.enc.Hex)
let verified = false
QRLPROTO_SHA256.forEach(value => {
if (value.protoSha256 === calculatedProtoHash) {
verified = true
}
})
return verified
}).catch(error => {
throw new Error(error)
})
}

async function loadGrpcBaseProto(grpcEndpoint) {
return protoLoader.load(PROTO_PATH, {}).then(async packageDefinition => {
const packageObject = grpc.loadPackageDefinition(packageDefinition)
const client = await new packageObject.qrl.Base(grpcEndpoint, grpc.credentials.createInsecure())
const res = await clientGetNodeInfo(client)
const qrlProtoFilePath = tmp.fileSync({mode: '0644', prefix: 'qrl-', postfix: '.proto'}).name
await writeFile(qrlProtoFilePath, res.grpcProto).then(fsErr => {
if (fsErr) {
throw new Error('tmp filesystem error')
}
})
return qrlProtoFilePath
})
}

async function loadGrpcProto(protofile, endpoint) {
const options = {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
}
const packageDefinition = await protoLoader.load(protofile, options)
const grpcObject = await grpc.loadPackageDefinition(packageDefinition)
const grpcObjectString = JSON.stringify(util.inspect(grpcObject.qrl, {showHidden: true, depth: 4}))
const protoObjectWordArray = CryptoJS.lib.WordArray.create(grpcObjectString)
const calculatedObjectHash = CryptoJS.SHA256(protoObjectWordArray).toString(CryptoJS.enc.Hex)
let verified = false
QRLPROTO_SHA256.forEach(value => {
if (value.objectSha256 === calculatedObjectHash) {
verified = true
}
})
// If the grpc object shasum matches, establish the grpc connection.
if (verified) {
qrlClient = createClient({
protoPath: protofile,
packageName: 'qrl',
serviceName: 'PublicAPI',
options: {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
},
}, endpoint)
} else {
throw new Error('Unable to verify proto file')
}
}

class OTSKey extends Command {
async run() {
const {args, flags} = this.parse(OTSKey)
const address = args.address
if (!validateQrlAddress.hexString(address).result) {
this.log(`${red('⨉')} Unable to get a OTS: invalid QRL address`)
this.exit(1)
}
let grpcEndpoint = 'testnet-4.automated.theqrl.org:19009'
let network = 'Testnet'
if (flags.grpc) {
grpcEndpoint = flags.grpc
network = `Custom GRPC endpoint: [${flags.grpc}]`
}
if (flags.testnet) {
grpcEndpoint = 'testnet-4.automated.theqrl.org:19009'
network = 'Testnet'
}
if (flags.mainnet) {
grpcEndpoint = 'mainnet-4.automated.theqrl.org:19009'
network = 'Mainnet'
}
this.log(white().bgBlue(network))
const spinner = ora({text: 'Fetching OTS from API...'}).start()
const proto = await loadGrpcBaseProto(grpcEndpoint)
checkProtoHash(proto).then(async protoHash => {
if (!protoHash) {
this.log(`${red('⨉')} Unable to validate .proto file from node`)
this.exit(1)
}
// next load GRPC object and check hash of that too
await loadGrpcProto(proto, grpcEndpoint)
const request = {
address: Buffer.from(address.substring(1), 'hex'),
}
await qrlClient.GetOTS(request, async (error, response) => {
if (error) {
this.log(`${red('⨉')} Unable to read next unused OTS key`)
this.exit(1)
}
// console.log('response:', response)
spinner.succeed(`Next unused OTS key: ${response.next_unused_ots_index}`)
this.exit(0)
})
})
}
}

OTSKey.description = `Get a address's OTS state from the network
...
TODO
`

OTSKey.args = [
{
name: 'address',
description: 'address to return OTS state for',
required: true,
},
]

OTSKey.flags = {
testnet: flags.boolean({char: 't', default: false, description: 'queries testnet for the OTS state'}),
mainnet: flags.boolean({char: 'm', default: false, description: 'queries mainnet for the OTS state'}),
grpc: flags.string({char: 'g', required: false, description: 'advanced: grcp endpoint (for devnet/custom QRL network deployments)'}),
}

module.exports = {OTSKey}
15 changes: 15 additions & 0 deletions src/get-qrl-proto-shasum.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/* qrl.proto sha256 sum for each release of QRL Node */
const QRLPROTO_SHA256 = [
{
version: '1.1.10 python',
protoSha256: '00032d07d4b4637103db15b3d68ae019c14988e870475832af6eb5bd390e04f5',
objectSha256: 'a93598b15aea7d4d40656e3a824891a6c881cbcb3678c7bec0110f9614b8e271',
},
{
version: '1.1.11 python',
protoSha256: '00032d07d4b4637103db15b3d68ae019c14988e870475832af6eb5bd390e04f5',
objectSha256: 'a93598b15aea7d4d40656e3a824891a6c881cbcb3678c7bec0110f9614b8e271',
},
]

module.exports = {QRLPROTO_SHA256}
17 changes: 17 additions & 0 deletions src/qrlbase.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
syntax = "proto3";

package qrl;

service Base {
rpc GetNodeInfo (GetNodeInfoReq) returns (GetNodeInfoResp) {}
}

message GetNodeInfoReq
{
}

message GetNodeInfoResp
{
string version = 1;
string grpcProto = 2;
}
Loading

0 comments on commit 2efecb9

Please sign in to comment.