All files / src eos.js

54.95% Statements 50/91
41.67% Branches 20/48
40% Functions 8/20
54.44% Lines 49/90

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229  9x 9x 9x 9x         19x 19x 19x           25x 21x 21x 17x     4x                                         20x 19x   1x 1x           17x                     19x       19x   19x 19x     19x 19x 18x   18x 18x   1x 1x       18x 18x 18x 18x 24x 24x 23x 19x 16x 16x   19x 19x   5x 1x 1x   4x   23x 1x 1x                                                                     32x                                                                                                                                       9x                                
/* Private */
const { Serialize, RpcError } = require('eosjs');
const ecc = require('eosjs-ecc');
const { BLOCKS_BEHIND_REF_BLOCK, BLOCKS_TO_CHECK, CHECK_INTERVAL, GET_BLOCK_ATTEMPTS, TRANSACTION_ENCODING, TRANSACTION_EXPIRY_IN_SECONDS } = require('./constants');
const { mapError } = require('./errors');
// NOTE: More than a simple wrapper for eos.rpc.get_info
// NOTE: Saves state from get_info, which can be used by other methods
// NOTE: For example, newaccount will have different field names, depending on the server_version_string
async function getInfo() {
  const info = await this.eos.rpc.get_info({});
  this.chainInfo = info;
  return info;
}
 
/* Public */
 
async function hasTransaction(block, transactionId) {
  Eif (block.transactions) {
    const result = block.transactions.find(transaction => transaction.trx.id === transactionId);
    if (result !== undefined) {
      return true;
    }
  }
  return false;
}
 
async function getChainId() {
  const { chain_id: chainId = null } = await getInfo.bind(this)();
  return chainId;
}
 
function formatErrorStringIfRpcError(error) {
  let errString = '';
  if (error instanceof RpcError) {
    errString = JSON.stringify(error.json);
  } else {
    errString = JSON.stringify(error);
  }
  return errString;
}
 
async function sendTransaction(func, confirm, awaitTransactionOptions) {
  let transaction;
 
  if (confirm === true) {
    transaction = await awaitTransaction.bind(this)(func, awaitTransactionOptions);
  } else {
    try {
      transaction = await func();
    } catch (error) {
      const errString = mapError(error);
      throw new Error(`Send Transaction Failure: ${errString}`);
    }
  }
  return transaction;
}
 
// NOTE: Use this to await for transactions to be added to a block
// NOTE: Useful, when committing sequential transactions with inter-dependencies
// NOTE: This does NOT confirm that the transaction is irreversible, aka finalized
// NOTE: blocksToCheck = the number of blocks to check, after committing the transaction, before giving up
// NOTE: checkInterval = the time between block checks in MS
// NOTE: getBlockAttempts = the number of failed attempts at retrieving a particular block, before giving up
 
function awaitTransaction(func, options = {}) {
  const { blocksToCheck = BLOCKS_TO_CHECK, checkInterval = CHECK_INTERVAL, getBlockAttempts = GET_BLOCK_ATTEMPTS } = options;
  let startingBlockNumToCheck;
  let blockNumToCheck;
 
  return new Promise(async (resolve, reject) => {
    // check the current head block num...
    const preCommitInfo = await getInfo.bind(this)();
    const preCommitHeadBlockNum = preCommitInfo.head_block_num;
    // make the transaction...
    let transaction;
    try {
      transaction = await func();
      const { processed } = transaction || {};
      // starting block number should be the block number in the transaction reciept. If block number not in transaction, use preCommitHeadBlockNum
      const { block_num = preCommitHeadBlockNum } = processed || {};
      startingBlockNumToCheck = block_num - 1;
    } catch (error) {
      const errString = mapError(error);
      return reject(new Error(`Await Transaction Failure: ${errString}`));
    }
    // keep checking for the transaction in future blocks...
    let blockToCheck;
    let getBlockAttempt = 1;
    let blockHasTransaction = false;
    blockNumToCheck = startingBlockNumToCheck;
    const intConfirm = setInterval(async () => {
      try {
        blockToCheck = await this.eos.rpc.get_block(blockNumToCheck);
        blockHasTransaction = await hasTransaction(blockToCheck, transaction.transaction_id);
        if (blockHasTransaction) {
          clearInterval(intConfirm);
          resolve(transaction);
        }
        getBlockAttempt = 1;
        blockNumToCheck += 1;
      } catch (error) {
        if (getBlockAttempt >= getBlockAttempts) {
          clearInterval(intConfirm);
          return reject(new Error(`Await Transaction Failure: Failure to find a block, after ${getBlockAttempt} attempts to check block ${blockNumToCheck}.`));
        }
        getBlockAttempt += 1;
      }
      if (blockNumToCheck > startingBlockNumToCheck + blocksToCheck) {
        clearInterval(intConfirm);
        return reject(new Error(`Await Transaction Timeout: Waited for ${blocksToCheck} blocks ~(${(checkInterval / 1000) * blocksToCheck} seconds) starting with block num: ${startingBlockNumToCheck}. This does not mean the transaction failed just that the transaction wasn't found in a block before timeout`));
      }
    }, checkInterval);
  });
}
 
async function getAllTableRows(params, key_field = 'id', json = true) {
  let results = [];
  const lowerBound = 0;
  // const upperBound = -1;
  const limit = -1;
  const parameters = {
    ...params,
    json,
    lower_bound: params.lower_bound || lowerBound,
    scope: params.scope || params.code,
    limit: params.limit || limit
  };
  results = await this.eos.rpc.get_table_rows(parameters);
  return results.rows;
}
 
// check if the publickey belongs to the account provided
async function checkPubKeytoAccount(account, publicKey) {
  const keyaccounts = await this.eos.rpc.history_get_key_accounts(publicKey);
  const accounts = await keyaccounts.account_names;
 
  if (accounts.includes(account)) {
    return true;
  }
  return false;
}
 
// NOTE: setting the broadcast parameter to false allows us to receive signed transactions, without submitting them
function transact(actions, broadcast = true, blocksBehind = BLOCKS_BEHIND_REF_BLOCK, expireSeconds = TRANSACTION_EXPIRY_IN_SECONDS) {
  return this.eos.transact({
    actions
  }, {
    blocksBehind,
    broadcast,
    expireSeconds
  });
}
 
function serializeTransaction(transaction, transactionOptions = {}) {
  const { blocksBehind = BLOCKS_BEHIND_REF_BLOCK, expireSeconds = TRANSACTION_EXPIRY_IN_SECONDS, broadcast = false, sign = false } = transactionOptions;
 
  const options = {
    blocksBehind,
    expireSeconds,
    broadcast,
    sign
  };
 
  return this.eos.transact(transaction, options);
}
 
function deserializeTransaction(serializedTransaction) {
  return this.eos.deserializeTransaction(serializedTransaction);
}
 
async function createSignBuffer(serializedTransaction) {
  const chainId = await getChainId.bind(this)();
 
  return Buffer.concat([
    Buffer.from(chainId, 'hex'),
    Buffer.from(serializedTransaction),
    Buffer.from(new Uint8Array(32))
  ]);
}
 
function signSerializedTransactionBuffer(signBuffer, privateKey, encoding = TRANSACTION_ENCODING) {
  return ecc.sign(signBuffer, privateKey).toString();
}
 
function isValidPublicKey(publicKey) {
  return ecc.isValidPublic(publicKey);
}
 
function recoverPublicKeyFromSignature(signBuffer, transaction, encoding = TRANSACTION_ENCODING) {
  return ecc.recover(signBuffer, transaction);
}
 
async function signRawTransaction(transaction, transactionOptions = {}, privateKey, additionalSignatures = []) {
  const serializedTrx = await serializeTransaction.bind(this)(transaction, transactionOptions);
  const { serializeTransaction } = serializedTrx;
  const signBuf = await createSignBuffer.bind(this)(serializeTransaction);
  const signature = await signSerializedTransactionBuffer(signBuf, privateKey);
  const signedTrx = {};
  signedTrx.signatures = [];
  signedTrx.signatures.push(signature);
 
  signedTrx.serializedTransaction = serializedTrx.serializedTransaction;
  if (additionalSignatures.length > 0) {
    signedTrx.signatures.concat(additionalSignatures);
  }
  return signedTrx;
}
 
function pushSignedTransaction(signedTransaction) {
  return this.eos.pushSignedTransaction(signedTransaction);
}
 
module.exports = {
  checkPubKeytoAccount,
  createSignBuffer,
  getAllTableRows,
  hasTransaction,
  hexToUint8Array: Serialize.hexToUint8Array,
  isValidPublicKey,
  pushSignedTransaction,
  recoverPublicKeyFromSignature,
  sendTransaction,
  serializeTransaction,
  deserializeTransaction,
  signRawTransaction,
  signSerializedTransactionBuffer,
  transact
};