UNPKG

apla-blockchain-tools

Version:

Module contains a number of tools to work with Apla Blockchain

276 lines (237 loc) 8.65 kB
"use strict"; // MIT License // // Copyright (c) 2016-2018 AplaProject // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. /* * Package: apla-blockchain-tools * Author: Anton Zuev * mail: a.zuev@apla.io * Company: apla.io */ let fetch = require("node-fetch"); let keyring = require("../keyring"); let formData = require('form-data'); let Contract = require("./contract"); let {setTimeoutPromise} = require("../lib/utils"); let {getError, getTransactionError} = require("./errors"); // Default: how many times script tries to get result of the transaction const TX_STATUS_MAX_TRIES = 20; // Default: how many ms to wait before next try to get tx result const WAIT_BEFORE_NEXT_TRY = 1000; /** * Class for interacting with the Apla Netowrk */ class Session { /** * * @param apiUrl {string} - url to connect to * @param networkId {string} - id of the network * @param options {object} * @param options.privateKey {string} - private key * @param options.maxSum {number | string} - not used for now * @param options.payOver {number | string} - not used for now * @param options.txStatusMaxTries {number} - how many times client will try to get the result * @param options.waitBeforeNextTry {number} - how many ms client has to wait before next try to get the result of tx */ constructor(apiUrl, networkId, options = {}) { this.apiUrl = apiUrl; this.networkId = Number.parseInt(networkId); this.keys = { private: options.privateKey }; if (this.keys.private) { this.keys.public = keyring.generatePublicKey(this.keys.private, true); } this.consts = { maxSum: options.maxSum || 1000 * 1000, payOver: options.payOver || 0, txStatusMaxTries: options.txStatusMaxTries || TX_STATUS_MAX_TRIES, waitBeforeNextTry: options.waitBeforeNextTry || WAIT_BEFORE_NEXT_TRY } } /** * generates new private key and corresponding public key * @public */ generateKeys() { let seed = keyring.generateSeed(); this.keys = keyring.generateKeyPair(seed); // trim (remove 04 from the beginning) this.keys.public = this.keys.public.slice(2); } /** * login to the system * @public * @param options * @param options.ecosystem {number | string} - ecosystem to login to * @param options.roleId {number | string} - login to login to */ async login(options = {}) { this.ecosystemId = options.ecosystem || 1; this.roleId = options.roleId || ""; let resp = await fetch(`${this.apiUrl}/getuid`); let {uid, token} = await resp.json(); let form = new formData(); form.append("pubkey", this.keys.public); form.append("signature", keyring.sign(`LOGIN${this.networkId}${uid}`, this.keys.private)); form.append('ecosystem', this.ecosystemId); form.append('role_id', this.roleId); resp = await fetch(`${this.apiUrl}/login`, { method: "POST", body: form, headers: { "content-type": form.getHeaders()['content-type'], authorization: `Bearer ${token}` } }); let res = await resp.json(); let err = getError(res); if(err){ throw err; } this.token = res.token; this.keyId = res.key_id; this.address = res.address; } /** * Send signed tx * @private * @param tx * @param hash * @return {{hashes: *}} */ async sendTx(tx, hash) { let form = new formData(); let txBuffer = Buffer.from(tx); form.append(hash, txBuffer, { filename: hash }); let response = await fetch(`${this.apiUrl}/sendTx`, { method: "POST", body: form, headers: { authorization: `Bearer ${this.token}`, "content-type": form.getHeaders()['content-type'] } } ); return await response.json(); } /** * getting information about the contract * @private * @param contractName * @throws {ContractNotFoundError} if there is no such contract * @return {Promise.<*>} */ async getContractSchema(contractName) { let response = await fetch(`${this.apiUrl}/contract/${contractName}`, { method: "GET", headers: { authorization: `Bearer ${this.token}` } } ); let json = await response.json(); let error = getError(json); if(error){ throw error; } return json; } /** * Waiting for the sent transaction to be executed * @param hash (for now supports only one hash at a time) * @return {object} * */ async waitTxStatus(hash){ let counter = 0; while(counter < this.consts.txStatusMaxTries){ let form = new formData(); let obj = { hashes: [hash] }; form.append("data", JSON.stringify(obj)); let response = await fetch(`${this.apiUrl}/txstatus`, { method: "POST", body: form, headers: { authorization: `Bearer ${this.token}`, "content-type": form.getHeaders()['content-type'] } }); let {results} = await response.json(); let txRes = results[hash]; let err = getTransactionError(txRes); if(err) throw err; if(txRes.blockid){ return txRes; } await setTimeoutPromise(this.consts.waitBeforeNextTry); counter++; } throw new Error(`tx #${hash}: tx wasn't included in the block. Tried ${counter} times`); } /** * Call the contract by name * @public * @param name {string} - name of the contract * @param params {object} - name->value mapping * @param options * @param options.maxSum {string | number } - max cost of transaction * @param options.payOver {string | number } - (fuelRate + options.payOver) * fuelVolume * @return {object} */ async callContract(name, params, options = { maxSum: this.consts.maxSum, payOver: this.consts.payOver }) { let contractSchema = await this.getContractSchema(name); options.keyId = this.keyId; options.ecosystemId = this.ecosystemId; options.networkId = this.networkId; let contract = new Contract(contractSchema, params, options); let {hash, data} = contract.sign(this.keys.private); let {hashes} = await this.sendTx(data, hash); return await this.waitTxStatus(hash); } /** * returns list of blocks of size `count` from block with id = `blockId` * @public * @param blockId {number | string} * @param count {number} * @return {Array} */ async getDetailedBlocks(blockId, count) { return await fetch(`${this.apiUrl}/detailed_blocks?block_id=${blockId}&count=${count}`); } /** * returns id of the last block for the node * @public * @return {number} */ async getMaxBlockId() { let response = await fetch(`${this.apiUrl}/maxblockid`); return (await response.json())['max_block_id'] } } module.exports = Session;