Client library for accessing a Postchain node through REST.
Homepage Repository npm Download
npm install postchain-client@1.14.0
let crypto = require('crypto');
let secp256k1 = require('secp256k1');
// Create some dummy keys
let signerPrivKeyA = Buffer.alloc(32, 'a');
let signerPubKeyA = secp256k1.publicKeyCreate(signerPrivKeyA);
let signerPrivKeyB = Buffer.alloc(32, 'b');
let signerPubKeyB = secp256k1.publicKeyCreate(signerPrivKeyB);
// The lower-level client that can be used for any
// postchain client messages. It only handles binary data.
let restClient = require('postchain-client').restClient;
// The higher-level client that is used for generic transactions, GTX.
// This utilizes the GTX format described below to pass function calls
// from the client to the postchain backend implementation.
let gtxClient = require('postchain-client').gtxClient;
// Create an instance of the rest client and configure it for a specific
// base url. You may set an optional pool size for the connection pool.
// Default pool size is 10. Applications that do hundreds of requests
// per second may consider setting this a bit higher, for example 100.
// It *may* speed things up a bit.
let rest = restClient.createRestClient(`http://localhost:7741`, 5);
// Each blockchain has a blockchainRID, that identifies the blockchain
// we want to work with. This blockchainRID must match the blockchain RID
// encoded into the first block of the blockchain. How the blockchainRID
// is constructed is up to the creator of the blockchain. In this example
// we use the linux command:
// echo "A blockchain example"| sha256sum
let blockchainRID = Buffer.from('7d565d92fd15bd1cdac2dc276cbcbc5581349d05a9e94ba919e1155ef4daf8f9', 'hex')
// Create an instance of the higher-level gtx client. It will
// use the rest client instance and it will allow calls to functions
// fun1 and fun2. The backend implementation in Postchain must
// provide those functions.
let gtx = gtxClient.createClient(rest, blockchainRID, ['fun1', 'fun2']);
// Start a new request. A request instance is created.
// The public keys are the keys that must sign the request
// before sending it to postchain. Can be empty.
let req = gtx.newRequest([signerPubKeyA, signerPubKeyB]);
// call fun1 with three arguments: a string, an array and a Buffer
req.fun1('arg1', ['arg2', [1, 2]], Buffer.from('hello'));
// call the same function with only one argument
req.fun1('arg1');
// call fun2
req.fun2(1, 2);
// Signing can be done either through the sign() function ...
// The public key is optional. If omitted it will be calculated
// from the private key. It is recommended to include the public
// key for performance reasons.
req.sign(signerPrivKeyA, signerPubKeyA);
// ... or by the addSignature() function
let bufferToSign = req.getBufferToSign();
// Sign the buffer externally
let signatureFromB = askUserBToSign(bufferToSign);
// and add the signature to the request
req.addSignature(signerPubKeyB, signatureFromB);
// Finally send the request and supply an error callback
req.send((error) => {
if (error) {
console.log(error);
}
});
// Now we can query Postchain. The backend must have a method
// query method named "findStuff" (readOnlyConn, queryObject) that can
// understand the query object and typically perform a search using
// the database connection readOnlyConn. The backend query function
// can return any serializable result object you chose
let queryObject = {type: "findStuff", text: 'arg1'};
let resultHandler = (error, result) => {
if (error) {
console.error(error);
return;
}
if (result.hits == 0) {
// Poll every 2 seconds
setTimeout(gtx.query(queryObject, resultHandler), 2000);
}
console.log(JSON.stringify(result));
}
gtx.query(queryObject, resultHandler);
// This will make a request with a single operation
// and a single signature.
req = gtx.newRequest(blockchainRID, [signerPubKeyA]);
req.fun1('arg1');
req.sign(signerPrivKeyA);
req.send((error) => {
if (!error) {
done();
}
});
function sha256(buffer) {
return crypto.createHash('sha256').update(buffer).digest()
}
// This is to demonstrate that you can use external signing
// mechanisms.
function askUserBToSign(buffer) {
// The signed digest is a double sha-256
var digest = sha256(sha256(buffer));
return secp256k1.sign(digest, signerPrivKeyB).signature
}
A very simple backend for the above client might look like this:
module.exports.createSchema = async function (conn) {
console.log("Creating schema in backend");
await conn.query("CREATE TABLE IF NOT EXISTS example " +
"(id SERIAL PRIMARY KEY, stuff TEXT NOT NULL)");
}
// Example backend implementation that doesn't do anything but log the function calls
module.exports.backendFunctions = {
fun1: async function (conn, tx_iid, call_index, signers, stringArg, arrayArg, bufferArg) {
console.log("fun1 called in backend");
},
fun2: async function (conn, tx_iid, call_index, signers, intArg1, intArg2) {
console.log("fun2 called in backend");
},
}
module.exports.backendQueries = {
findStuff: async function (readOnlyConn, queryObject) {
console.log("Search for " + queryObject.text);
if (queryObject.text === 'giveMeHits') {
return {hits: 4};
}
return {hits: 0};
}
}
Generic transactions were developed to make it easier to make user implementations of Postchain. The user doesn't have to invent a binary format for it's transactions. With GTX you specify a set of functions that you will call from the client, and the GTX client will serialize the function calls, sign them and send to Postchain.
User
|
| req.fun1('arg1', 'arg2');
| req.fun2('arg1'); req.sign(privKeyA); req.send(err => {})
v
GtxClient
|
| <Buffer with serialized message>
v
RestClient
|
| POST http://localhost:7741/tx {tx: 'hex encoded message'}
v
RestApi
|
| <Buffer with serialized message>
v
Postchain
|
| backend.fun1(conn, tx_iid, 0, [pubKeyA], 'arg1', 'arg2');
| backend.fun2(conn, tx_iid, 1, [pubKeyA], 'arg1');
v
Backend
The first four arguments to backend.fun1 are
conn
is a database connection that the backend function can use to query/update the databasetx_iid
is the primary key of this postchain transaction.call_index
, 0 in this example. It's the index within the GTX of the current callsigners
, all signers of this GTX. The signatures from these signers are already verified by
the GTX framework when the backend function is called.