blocase-client v0.0.2
Blocase Javascript Client
Check out example.js for the full usage of the client lib.
Getting started
Blocase is a distributed NoSQL document database powered by the blockchain technology.
This guide assumes you have an existing basic knowledge of Web API, database and digital signature.
Download
Blocase supports Darwin, Linux and Windows with amd64.
MacOS
wget https://s3.us-east-2.amazonaws.com/blocase/darwin/blocase-v0.0.1-darwin
Linux
wget https://s3.us-east-2.amazonaws.com/blocase/linux/blocase-v0.0.1-linux
Windows
wget https://s3.us-east-2.amazonaws.com/blocase/windows/blocase-v0.0.1-win64.exe
Blocase In 10 Minutes
System prerequisites:
- Node.js
- JS libraries:
{
"dependencies": {
"ethereumjs-wallet": "^v0.6.2",
"ethereumjs-util": "^v6.0.0",
"bip39": "^2.5.0",
"axios": "^0.18.0"
}
}
Start Blocase server
$ ./blocase server
____ __ __ ___ __ ____ ____
( _ \( ) / \ / __) / _\ / ___)( __)
) _ (/ (_/\( O )( (__ / \\___ \ ) _)
(____/\____/ \__/ \___)\_/\_/(____/(____)
Community Edition v0.0.1
INFO[2019-01-01T01:26:04Z] configurations: maxtime=2000 maxtx=256 path=data port=6899
INFO[2019-01-01T01:26:04Z] db file exists.
INFO[2019-01-01T01:26:04Z] opening existing collections...
INFO[2019-01-01T01:26:04Z] awaiting signal...
INFO[2019-01-01T01:26:04Z] begin to monitor transactions every 2000 milliseconds...
By default, Blocase creates a data
directory within the working dir to store the blockchain and DB collections; the time interval to generate a block is 2 seconds; the max number of transactions (about documents) is 256; it listens on port 6899 for web API calls.
Create account
"use strict"
const ethUtil = require("ethereumjs-util")
const ethHDKey = require('ethereumjs-wallet/hdkey')
const axios = require('axios')
// The master seed is a mnemonic code or mnemonic sentence -- a group of easy to remember words -- for the generation of deterministic wallets.
const HDKey = ethHDKey.fromMasterSeed("guess tortoise flavor sorry brand ten faculty assist green reopen best gaze")
const wallet = HDKey.getWallet()
axios.post('http://localhost:6899/account', {
"dateOfBirth": "2018-10-01",
"firstName": "Hooper",
"lastName": "Vincent",
"company": "MITROC",
"email": "hoopervincent@mitroc.com",
"phone": "+1 (849) 503-2756",
"address": "699 Canton Court, Mulino, South Dakota, 9647",
"publicKey": wallet.getPublicKey().toString("hex")
}).then(function (response) {
console.log(response.data);
}).catch(function (error) {
console.log(error);
});
/account
response
{
"message": "account created",
"address": "0x730C5da9d5A7B39AD3B2e2274525B5eb2A9fa28D"
}
Generate challenge and get JWT
Blocase web API needs authentication/authorization to access. JSON Web Token is used for this purpose.
const getChallenge = async () => {
try {
return axios.get('http://localhost:6899/jwt/challenge/0x730C5da9d5A7B39AD3B2e2274525B5eb2A9fa28D')
} catch (error) {
console.error(error)
}
}
const getJWT = async () => {
const challengeResponse = await getChallenge()
var challengeHash = ethUtil.keccak(challengeResponse.data.challenge, 256)
var sig = ethUtil.ecsign(challengeHash, Buffer.from(wallet.getPrivateKey(), 'hex'))
axios.post('http://localhost:6899/jwt', {
"address": "0x730C5da9d5A7B39AD3B2e2274525B5eb2A9fa28D",
"signature": sig.r.toString("hex") + sig.s.toString("hex")
}).then(response => {
if (response.data.token) {
console.log(
`Token: ${response.data.token}`
)
}
}).catch(error => {
console.log(error)
})
}
getJWT()
/jwt/challenge
response:
{
"message": "challenge word created",
"challenge": "f6HlouQByP9VKJCfFMLwFUbJh45YCevmuoadflSQNfZGqrahkdrJ2j5dVDALckjd"
}
/jwt
response:
{
"message": "JWT issued",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYWRtaW4iLCJhZGRyZXNzIjoiMHg3MzBDNWRhOWQ1QTdCMzlBRDNCMmUyMjc0NTI1QjVlYjJBOWZhMjhEIiwiYXVkIjoiYmxvY2FzZSB1c2VyIiwiZXhwIjoxNTQ2MzEzMDgyLCJpYXQiOjE1NDYzMTI0ODIsImlzcyI6ImJsb2Nhc2UifQ.EG0pM1dNOU3V4W2cKwePflWzNMooTG3saOtGBb_2rCE"
}
Get account
From this step on, we have to use the token from /jwt
to access the web APIs. The token expires in 10 minutes by default.
axios.request({
url: 'http://localhost:6899/account/0x730C5da9d5A7B39AD3B2e2274525B5eb2A9fa28D',
method: 'get',
timeout: 1000,
headers: {'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYWRtaW4iLCJhZGRyZXNzIjoiMHg3MzBDNWRhOWQ1QTdCMzlBRDNCMmUyMjc0NTI1QjVlYjJBOWZhMjhEIiwiYXVkIjoiYmxvY2FzZSB1c2VyIiwiZXhwIjoxNTQ2MzEzNDEzLCJpYXQiOjE1NDYzMTI4MTMsImlzcyI6ImJsb2Nhc2UifQ.ST3iT_TiIp0gKrge1NIw7kgtDK_Tic8JXhDWrYuC0Yo'}
}).then(response => {
if (response.data) {
console.log(
`Account: ${JSON.stringify(response.data)}`
)
}
}).catch(error => {
console.log(error)
})
/account
response:
{
"address": "699 Canton Court, Mulino, South Dakota, 9647",
"company": "MITROC",
"dateOfBirth": "2018-10-01",
"email": "hoopervincent@mitroc.com",
"firstName": "Hooper",
"lastName": "Vincent",
"phone": "+1 (849) 503-2756"
}
Create collection
/collection
creates the data schema of the collections. Valid data typtes are: text
, number
, datetime
(RFC3339 format), boolean
, geopoint
. It also supports data encryption on fields.
axios.request({
url: 'http://localhost:6899/collection',
method: 'post',
timeout: 1000,
headers: {'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYWRtaW4iLCJhZGRyZXNzIjoiMHg3MzBDNWRhOWQ1QTdCMzlBRDNCMmUyMjc0NTI1QjVlYjJBOWZhMjhEIiwiYXVkIjoiYmxvY2FzZSB1c2VyIiwiZXhwIjoxNTQ2MzE0NDMwLCJpYXQiOjE1NDYzMTM4MzAsImlzcyI6ImJsb2Nhc2UifQ.Gu2VwItrrBuB64foM8PSGjJlFmxEjwVM3pCujStkL44'},
data: {
"collection": "new",
"fields": {
"id": {"type": "text"},
"guid": {"type": "text"},
"age": {"type": "number", "encrypted": true},
"registered": {"type": "datetime"},
"isActive": {"type": "boolean"},
"gender": {"type": "text"},
"name": {"type": "text","encrypted": true},
"location": {"type": "geopoint"},
"tags": {"type": "text"}
}
}
}).then(response => {
if (response.data) {
console.log(
`Response: ${JSON.stringify(response.data)}`
)
}
}).catch(error => {
console.log(error)
})
/collection
response:
{
"message": "collection new created"
}
Put a JSON document to the collection
const document = {
"id": "5bf1d3fdf6fd4a5c4638f64e",
"guid": "f51b68c5-f274-4ce1-984f-b4fb4d618ff3",
"isActive": false,
"age": 28,
"name": "Carly Compton",
"gender": "male",
"registered": "2015-09-18T12:59:51Z",
"location": {
"lon": 46.564666,
"lat": 53.15213
},
"tags": [
"incididunt",
"dolore"
],
"friends": [
{
"id": 0,
"name": "Jimenez Byers"
},
{
"id": 1,
"name": "Gabriela Mayer"
}
],
"notExist": "haha"
}
var docHash = ethUtil.keccak(JSON.stringify(document), 256)
var sig = ethUtil.ecsign(docHash, Buffer.from(wallet.getPrivateKey(), 'hex'))
axios.request({
url: 'http://localhost:6899/document/new',
method: 'post',
timeout: 1000,
headers: {'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYWRtaW4iLCJhZGRyZXNzIjoiMHg3MzBDNWRhOWQ1QTdCMzlBRDNCMmUyMjc0NTI1QjVlYjJBOWZhMjhEIiwiYXVkIjoiYmxvY2FzZSB1c2VyIiwiZXhwIjoxNTQ2MzE1MjAyLCJpYXQiOjE1NDYzMTQ2MDIsImlzcyI6ImJsb2Nhc2UifQ.zqFbNycKJfH-LJhfaYVyGcszMbmA6-aZ5l9yyXsCws8'},
data: {
"rawDocument": JSON.stringify(document),
"signature": sig.r.toString("hex") + sig.s.toString("hex")
}
}).then(response => {
if (response.data) {
console.log(
`Response: ${JSON.stringify(response.data)}`
)
}
}).catch(error => {
console.log(error)
})
/document/{collection}
response:
{
"status": "ok",
"fieldErrors": null,
"isValidSignature": true,
"transactionID": "82a9c23396dcf047f01cca6923541350a1b90fd37274c63a881a19ba1e97e0da"
}
Query documents
Blocase is shipped with a complete query DSL. The following example is to find all the documents containing Carly
in field name
.
axios.request({
url: 'http://localhost:6899/search/new',
method: 'post',
timeout: 1000,
headers: {'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYWRtaW4iLCJhZGRyZXNzIjoiMHg3MzBDNWRhOWQ1QTdCMzlBRDNCMmUyMjc0NTI1QjVlYjJBOWZhMjhEIiwiYXVkIjoiYmxvY2FzZSB1c2VyIiwiZXhwIjoxNTQ2MzE1OTk1LCJpYXQiOjE1NDYzMTUzOTUsImlzcyI6ImJsb2Nhc2UifQ.Mc6354XI4VSNi8le-v4Fce5M3PYTf0sbqvDA7jNJ-AI'},
data: {
"size": 10,
"from": 0,
"query": {
"match": "Carly",
"field": "name"
}
}
}).then(response => {
if (response.data) {
console.log(
`Response: ${JSON.stringify(response.data)}`
)
}
}).catch(error => {
console.log(error)
})
/search/{collection}
response:
{
"collection": "new",
"status": {
"total": 1,
"failed": 0,
"successful": 1
},
"total_hits": 2,
"hits": [{
"_id": "82a9c23396dcf047f01cca6923541350a1b90fd37274c63a881a19ba1e97e0da",
"_blockId": "33145e8dfc65b7a6a48f597e13574a32da939942c04f7c5b167b27417e13cb46",
"_source": {
"age": 28,
"friends": [{
"id": 0,
"name": "Jimenez Byers"
}, {
"id": 1,
"name": "Gabriela Mayer"
}],
"gender": "male",
"guid": "f51b68c5-f274-4ce1-984f-b4fb4d618ff3",
"id": "5bf1d3fdf6fd4a5c4638f64e",
"isActive": false,
"location": {
"lat": 53.15213,
"lon": 46.564666
},
"name": "Carly Compton",
"notExist": "haha",
"registered": "2015-09-18T12:59:51Z",
"tags": ["incididunt", "dolore"]
},
"_timestamp": "2018-12-31T22:51:00.118-05:00",
"_signature": "6b2064ddf73f7b96559ecae424b3b657d1daf62078305e92af991c22e04808d476e8161ec7be58324b662042965a9935de8fb697eb3df7afad5d1885f129f666"
}, {
"_id": "5aa8894ed1babae4938aa7ea34b5ee082bf0851e48174815c6d7be0a294afc30",
"_blockId": "aada5dc20d7de880dc6463dc93033019dfe5a17064b608c3f16d76ef54b8e4dd",
"_source": {
"age": 28,
"friends": [{
"id": 0,
"name": "Jimenez Byers"
}, {
"id": 1,
"name": "Gabriela Mayer"
}],
"gender": "male",
"guid": "f51b68c5-f274-4ce1-984f-b4fb4d618ff3",
"id": "5bf1d3fdf6fd4a5c4638f64e",
"isActive": false,
"location": {
"lat": 53.15213,
"lon": 46.564666
},
"name": "Carly Compton",
"notExist": "haha",
"registered": "2015-09-18T12:59:51Z",
"tags": ["incididunt", "dolore"]
},
"_timestamp": "2018-12-31T22:50:33.988-05:00",
"_signature": "6b2064ddf73f7b96559ecae424b3b657d1daf62078305e92af991c22e04808d476e8161ec7be58324b662042965a9935de8fb697eb3df7afad5d1885f129f666"
}]
}
The response includes the following system fields: _id
(transaction ID), _blockId
, _source
, _timestamp
and _signature
.
Verify a document
Blocase maintains a Merkle tree for each block. The client is able to use the tree to verify if a document is included in a block and also its integrity in a trustless environment.
axios.request({
url: 'http://localhost:6899/verification/33145e8dfc65b7a6a48f597e13574a32da939942c04f7c5b167b27417e13cb46/82a9c23396dcf047f01cca6923541350a1b90fd37274c63a881a19ba1e97e0da',
method: 'get',
timeout: 1000,
headers: {'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYWRtaW4iLCJhZGRyZXNzIjoiMHg3MzBDNWRhOWQ1QTdCMzlBRDNCMmUyMjc0NTI1QjVlYjJBOWZhMjhEIiwiYXVkIjoiYmxvY2FzZSB1c2VyIiwiZXhwIjoxNTQ2MzE2Nzc5LCJpYXQiOjE1NDYzMTYxNzksImlzcyI6ImJsb2Nhc2UifQ.AvARyrxY8bL1M6wzDMNdQ3Yg80nWN92ae2i_x6Rd2LM'}
}).then(response => {
if (response.data) {
var verificationPath = response.data.verificationPath
var txId = "82a9c23396dcf047f01cca6923541350a1b90fd37274c63a881a19ba1e97e0da" // also document id
var keys = Object.keys(verificationPath)
var hashData = Buffer.from(txId, "hex")
for (let i = keys.length - 1; i > 0; i--) {
var secondHash = Buffer.from(verificationPath[keys[i]], "hex")
if (keys[i]%2 == 0) { // right child
var prevHashes = Buffer.concat([hashData, secondHash])
hashData = ethUtil.keccak(prevHashes, 256)
} else {
var prevHashes = Buffer.concat([secondHash, hashData])
hashData = ethUtil.keccak(prevHashes, 256)
}
}
console.log("Verified document successfully: " + hashData.equals(Buffer.from(verificationPath["0"], "hex")));
}
}).catch(error => {
console.log(error)
})
/verification/{blockID}/{transactionID}
response:
{
"status": "ok",
"verificationPath": {
"0": "967d211a165de72e5c8e25e780eec1c573dd44b574cf60a40a62b8253f65de73",
"1": "99e1953a3eaf7c6779b18bcf6dc939d0b258b9d95280dcc6f5b6fe7a99a5bf2e",
"6": "768b72fad6299c1b12f1df73f377eb2dbb91abd8dd55d491181c215124269fce",
"12": "1dae7d2eaffe5596768b318b783e6d40f330e202b285baef61f54e2552a887c5",
"24": "aea3e287b898d8ed3d7a8d7f1242e3fa7837110d63c41895442b6e44b7961353",
"48": "51a3cee16a7d5b82371b3305f8dc1bd022b8133b3cd2310b97f28e859ed47f38",
"95": "3d32bd18441ea9dc17f1a23e8d3264d8a176b22d707c2fc7bd375d0f70822a17",
"193": "8a1feef1f5afedd8f425009d3c0d35d47825ace89207f5682f0d826debb2ab28",
"390": "9f8c158640e09d7309bb4a449ac5739a57523ddca06b39090f105cc16d82a8a1"
}
}
The verificationPath
is a sorted a map with tree index and hash value. The tree index is a level-by-level traversal order.
Get block information
axios.request({
url: 'http://localhost:6899/block/33145e8dfc65b7a6a48f597e13574a32da939942c04f7c5b167b27417e13cb46',
method: 'get',
timeout: 1000,
headers: {'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYWRtaW4iLCJhZGRyZXNzIjoiMHg3MzBDNWRhOWQ1QTdCMzlBRDNCMmUyMjc0NTI1QjVlYjJBOWZhMjhEIiwiYXVkIjoiYmxvY2FzZSB1c2VyIiwiZXhwIjoxNTQ2MzE3NDQ2LCJpYXQiOjE1NDYzMTY4NDYsImlzcyI6ImJsb2Nhc2UifQ.Zc-JZMckCcOS-lJsM8SXHAfiua4iufh57eaJCZwTGDc'}
}).then(response => {
if (response.data) {
console.log(
`Response: ${JSON.stringify(response.data)}`
)
}
}).catch(error => {
console.log(error)
})
/block/{blockID}
response:
{
"blockId": "33145e8dfc65b7a6a48f597e13574a32da939942c04f7c5b167b27417e13cb46",
"lastBlockId": "aada5dc20d7de880dc6463dc93033019dfe5a17064b608c3f16d76ef54b8e4dd",
"blockHeight": 5,
"totalTransactions": 256
}
Get blockchain information
axios.request({
url: 'http://localhost:6899/info',
method: 'get',
timeout: 1000,
headers: {'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYWRtaW4iLCJhZGRyZXNzIjoiMHg3MzBDNWRhOWQ1QTdCMzlBRDNCMmUyMjc0NTI1QjVlYjJBOWZhMjhEIiwiYXVkIjoiYmxvY2FzZSB1c2VyIiwiZXhwIjoxNTQ2MzE3NDQ2LCJpYXQiOjE1NDYzMTY4NDYsImlzcyI6ImJsb2Nhc2UifQ.Zc-JZMckCcOS-lJsM8SXHAfiua4iufh57eaJCZwTGDc'}
}).then(response => {
if (response.data) {
console.log(
`Response: ${JSON.stringify(response.data)}`
)
}
}).catch(error => {
console.log(error)
})
/info
response:
{
"newestBlockId": "33145e8dfc65b7a6a48f597e13574a32da939942c04f7c5b167b27417e13cb46",
"lastHeight": 5,
"totalTransactions": 462
}