Negligible Psychological Misery

    postchain-client

    0.10.5 • Public • Published

    Postchain client

    Example:

    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.newTransaction([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.newTransaction(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};
        }
    }

    GTX architecture

    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 database
    • tx_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 call
    • signers, all signers of this GTX. The signatures from these signers are already verified by the GTX framework when the backend function is called.

    Keywords

    none

    Install

    npm i postchain-client

    DownloadsWeekly Downloads

    190

    Version

    0.10.5

    License

    BSD-2-Clause-FreeBSD

    Unpacked Size

    2.47 MB

    Total Files

    45

    Last publish

    Collaborators

    • killerstorm
    • chromaway-kalle