github.com/kaleido-io/firefly@v0.0.0-20210622132723-8b4b6aacb971/kat/src/lib/utils.ts (about)

     1  // Copyright © 2021 Kaleido, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  import { encode, decode } from 'bs58';
    16  import crypto from 'crypto';
    17  import axios, { AxiosRequestConfig } from 'axios';
    18  import { databaseCollectionName, indexes } from './interfaces';
    19  import { parseDN } from 'ldapjs';
    20  import { Logger } from './logging';
    21  
    22  export const constants = {
    23    DATA_DIRECTORY: process.env.DATA_DIRECTORY || '/data',
    24    LOG_LEVEL: process.env.LOG_LEVEL || 'info',
    25    CONFIG_FILE_NAME: 'config.json',
    26    SETTINGS_FILE_NAME: 'settings.json',
    27    IPFS_TIMEOUT_MS: 15000,
    28    DEFAULT_PAGINATION_LIMIT: 100,
    29    EVENT_STREAM_WEBSOCKET_RECONNECTION_DELAY_SECONDS: 5,
    30    DOC_EXCHANGE_ASSET_FOLDER_NAME: 'assets',
    31    EVENT_STREAM_PING_TIMEOUT_SECONDS: 60,
    32    ASSET_INSTANCE_TRADE_TIMEOUT_SECONDS: 15,
    33    TRADE_AUTHORIZATION_TIMEOUT_SECONDS: 10,
    34    DOCUMENT_EXCHANGE_TRANSFER_TIMEOUT_SECONDS: 15,
    35    SUBSCRIBE_RETRY_INTERVAL: 5 * 1000,
    36    APP2APP_BATCH_SIZE: parseInt(<string>process.env.APP2APP_BATCH_SIZE || "100"),
    37    APP2APP_BATCH_TIMEOUT: parseInt(<string>process.env.APP2APP_BATCH_TIMEOUT || "250"),
    38    APP2APP_READ_AHEAD: parseInt(<string>process.env.APP2APP_READ_AHEAD || "50"),
    39    REST_API_CALL_MAX_ATTEMPTS: parseInt(<string>process.env.REST_API_CALL_MAX_ATTEMPTS || "5"),
    40    REST_API_CALL_RETRY_DELAY_MS: parseInt(<string>process.env.REST_API_CALL_MAX_ATTEMPTS || "500"),
    41    BATCH_ADD_TIMEOUT_MILLIS: parseInt(<string>process.env.BATCH_ADD_TIMEOUT_MILLIS || '30000'),
    42    BATCH_TIMEOUT_OVERALL_MILLIS: parseInt(<string>process.env.BATCH_TIMEOUT_OVERALL_MILLIS || '2500'),
    43    BATCH_TIMEOUT_ARRIVAL_MILLIS: parseInt(<string>process.env.BATCH_TIMEOUT_ARRIVAL_MILLIS || '250'),
    44    BATCH_MAX_RECORDS: parseInt(<string>process.env.BATCH_MAX_RECORDS || '1000'),
    45    BATCH_RETRY_INITIAL_DELAY_MILLIS: parseInt(<string>process.env.BATCH_RETRY_INITIAL_DELAY_MILLIS || '100'),
    46    BATCH_RETRY_MAX_DELAY_MILLIS: parseInt(<string>process.env.BATCH_RETRY_MAX_DELAY_MILLIS || '10000'),
    47    BATCH_RETRY_MULTIPLIER: parseFloat(<string>process.env.BATCH_RETRY_MULTIPLIER || '2.0'),
    48  };
    49  
    50  const log = new Logger('utis.ts');
    51  
    52  export const databaseCollectionIndexes: { [name in databaseCollectionName]: indexes } = {
    53    members: [{ fields: ['address'], unique: true }],
    54    'asset-definitions': [{ fields: ['assetDefinitionID'], unique: true }],
    55    'payment-definitions': [{ fields: ['paymentDefinitionID'], unique: true }],
    56    'payment-instances': [{ fields: ['paymentInstanceID'], unique: true }],
    57    'batches': [
    58      { fields: ['batchID'], unique: true }, // Primary key
    59      { fields: ['type', 'author', 'completed', 'created'] }, // Search index for startup processing, and other queries
    60      { fields: ['batchHash'] } // To retrieve a batch by its hash, in response to a blockchain event
    61    ],
    62    'state': [{ fields: ['key'], unique: true }],
    63  };
    64  
    65  const ETHEREUM_ACCOUNT_REGEXP = /^0x[a-fA-F0-9]{40}$/;
    66  
    67  const isValidX500Name = (name: string) => {
    68    try {
    69      parseDN(name);
    70    } catch (e) {
    71      return false;
    72    }
    73    return true;
    74  };
    75  
    76  export const isAuthorValid = (author: string, protocol: string) => {
    77    switch (protocol) {
    78      case 'corda':
    79        return isValidX500Name(author);
    80      case 'ethereum':
    81        return ETHEREUM_ACCOUNT_REGEXP.test(author);
    82    }
    83  }
    84  
    85  export const requestKeys = {
    86    ASSET_AUTHOR: 'author',
    87    ASSET_DEFINITION_ID: 'assetDefinitionID',
    88    ASSET_DESCRIPTION: 'description',
    89    ASSET_CONTENT: 'content',
    90    ASSET_IS_CONTENT_PRIVATE: 'isContentPrivate'
    91  };
    92  
    93  export const contractEventSignaturesCorda = {
    94    ASSET_DEFINITION_CREATED: 'io.kaleido.kat.states.AssetDefinitionCreated',
    95    MEMBER_REGISTERED: 'io.kaleido.kat.states.MemberRegistered',
    96    DESCRIBED_PAYMENT_DEFINITION_CREATED: 'io.kaleido.kat.states.DescribedPaymentDefinitionCreated',
    97    PAYMENT_DEFINITION_CREATED: 'io.kaleido.kat.states.PaymentDefinitionCreated',
    98    DESCRIBED_ASSET_INSTANCE_CREATED: 'io.kaleido.kat.states.DescribedAssetInstanceCreated',
    99    ASSET_INSTANCE_BATCH_CREATED: 'io.kaleido.kat.states.AssetInstanceBatchCreated',
   100    ASSET_INSTANCE_CREATED: 'io.kaleido.kat.states.AssetInstanceCreated',
   101    DESCRIBED_PAYMENT_INSTANCE_CREATED: 'io.kaleido.kat.states.DescribedPaymentInstanceCreated',
   102    PAYMENT_INSTANCE_CREATED: 'io.kaleido.kat.states.PaymentInstanceCreated',
   103    ASSET_PROPERTY_SET: 'io.kaleido.kat.states.AssetInstancePropertySet'
   104  }
   105  
   106  export const contractEventSignatures = {
   107    ASSET_DEFINITION_CREATED: 'AssetDefinitionCreated(bytes32,address,uint256)',
   108    MEMBER_REGISTERED: 'MemberRegistered(address,string,string,string,string,uint256)',
   109    DESCRIBED_PAYMENT_DEFINITION_CREATED: 'DescribedPaymentDefinitionCreated(bytes32,address,string,bytes32,uint256)',
   110    PAYMENT_DEFINITION_CREATED: 'PaymentDefinitionCreated(bytes32,address,string,uint256)',
   111    DESCRIBED_ASSET_INSTANCE_CREATED: 'DescribedAssetInstanceCreated(bytes32,bytes32,address,bytes32,bytes32,uint256)',
   112    ASSET_INSTANCE_BATCH_CREATED: 'AssetInstanceBatchCreated(bytes32,address,uint256)',
   113    ASSET_INSTANCE_CREATED: 'AssetInstanceCreated(bytes32,bytes32,address,bytes32,uint256)',
   114    DESCRIBED_PAYMENT_INSTANCE_CREATED: 'DescribedPaymentInstanceCreated(bytes32,bytes32,address,address,uint256,bytes32,uint256)',
   115    PAYMENT_INSTANCE_CREATED: 'PaymentInstanceCreated(bytes32,bytes32,address,address,uint256,uint256)',
   116    ASSET_PROPERTY_SET: 'AssetInstancePropertySet(bytes32,bytes32,address,string,string,uint256)'
   117  };
   118  
   119  export const getSha256 = (value: string) => crypto.createHash('sha256').update(value).digest('hex');
   120  
   121  export const ipfsHashToSha256 = (hash: string) => '0x' + decode(hash).slice(2).toString('hex');
   122  
   123  export const sha256ToIPFSHash = (short: string) => encode(Buffer.from('1220' + short.slice(2), 'hex'));
   124  
   125  export const getTimestamp = () => {
   126    return Math.round(new Date().getTime() / 1000);
   127  };
   128  
   129  export const streamToString = (stream: NodeJS.ReadableStream): Promise<string> => {
   130    const chunks: Buffer[] = [];
   131    return new Promise((resolve, reject) => {
   132      stream.on('data', chunk => chunks.push(chunk));
   133      stream.on('error', reject);
   134      stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
   135    })
   136  }
   137  
   138  export const getUnstructuredFilePathInDocExchange = (assetInstanceID: string) => {
   139    return `${constants.DOC_EXCHANGE_ASSET_FOLDER_NAME}/${assetInstanceID}`;
   140  };
   141  
   142  export const uuidToHex = (uuid: string) => {
   143    return '0x' + Buffer.from(uuid.replace(/-/g, '')).toString('hex');
   144  };
   145  
   146  export const hexToUuid = (hex: string) => {
   147    const decodedTransferID = Buffer.from(hex.substr(2), 'hex').toString('utf-8');
   148    return decodedTransferID.substr(0, 8) + '-' + decodedTransferID.substr(8, 4) + '-' +
   149      decodedTransferID.substr(12, 4) + '-' + decodedTransferID.substr(16, 4) + '-' +
   150      decodedTransferID.substr(20, 12);
   151  };
   152  
   153  export const axiosWithRetry = async (config: AxiosRequestConfig) => {
   154    let attempts = 0;
   155    let currentError;
   156    while (attempts < constants.REST_API_CALL_MAX_ATTEMPTS) {
   157      try {
   158        return await axios(config);
   159      } catch (err) {
   160        const data = err.response?.data;
   161        log.error(`${config.method} ${config.url} attempt ${attempts} [${err.response?.status}]`, (data && !data.on) ? data : err.stack)
   162        if (err.response?.status === 404) {
   163          throw err;
   164        } else {
   165          currentError = err;
   166          attempts++;
   167          await new Promise(resolve => setTimeout(resolve, constants.REST_API_CALL_RETRY_DELAY_MS));
   168        }
   169      }
   170    }
   171    throw currentError;
   172  };
   173  
   174  export function getLogger(label: string) {
   175    return new Logger(label);
   176  }