UNPKG

@dashevo/rs-drive

Version:

Node.JS binding for Rust Drive

942 lines (857 loc) 25.4 kB
const { promisify } = require('util'); const cbor = require('cbor'); const { Document, ExtendedDocument, decodeProtocolEntity, Identity, DataContract, } = require('@dashevo/wasm-dpp'); // This file is crated when run `npm run build`. The actual source file that // exports those functions is ./src/lib.rs const { driveOpen, driveClose, driveCreateInitialStateStructure, driveFetchContract, driveCreateContract, driveUpdateContract, driveCreateDocument, driveUpdateDocument, driveDeleteDocument, driveQueryDocuments, driveProveDocumentsQuery, driveInsertIdentity, driveFetchIdentity, driveFetchIdentityBalance, driveFetchIdentityBalanceWithCosts, driveFetchIdentityBalanceIncludeDebtWithCosts, driveFetchProvedIdentity, driveFetchManyProvedIdentities, driveFetchIdentityWithCosts, driveAddToIdentityBalance, driveAddKeysToIdentity, driveDisableIdentityKeys, driveUpdateIdentityRevision, driveRemoveFromIdentityBalance, driveApplyFeesToIdentityBalance, driveFetchLatestWithdrawalTransactionIndex, abciInitChain, abciBlockBegin, abciBlockEnd, abciAfterFinalizeBlock, calculateStorageFeeDistributionAmountAndLeftovers, driveFetchIdentitiesByPublicKeyHashes, driveProveIdentitiesByPublicKeyHashes, driveAddToSystemCredits, } = require('neon-load-or-build')({ dir: __dirname, }); const GroveDB = require('./GroveDB'); const FeeResult = require('./FeeResult'); const { appendStackAsync, appendStack } = require('./appendStack'); // Convert the Drive methods from using callbacks to returning promises const driveCloseAsync = appendStackAsync(promisify(driveClose)); const driveCreateInitialStateStructureAsync = appendStackAsync( promisify(driveCreateInitialStateStructure), ); const driveFetchContractAsync = appendStackAsync(promisify(driveFetchContract)); const driveCreateContractAsync = appendStackAsync(promisify(driveCreateContract)); const driveUpdateContractAsync = appendStackAsync(promisify(driveUpdateContract)); const driveCreateDocumentAsync = appendStackAsync(promisify(driveCreateDocument)); const driveUpdateDocumentAsync = appendStackAsync(promisify(driveUpdateDocument)); const driveDeleteDocumentAsync = appendStackAsync(promisify(driveDeleteDocument)); const driveQueryDocumentsAsync = appendStackAsync(promisify(driveQueryDocuments)); const driveProveDocumentsQueryAsync = appendStackAsync(promisify(driveProveDocumentsQuery)); const driveFetchLatestWithdrawalTransactionIndexAsync = appendStackAsync( promisify(driveFetchLatestWithdrawalTransactionIndex), ); const driveInsertIdentityAsync = appendStackAsync(promisify(driveInsertIdentity)); const driveFetchIdentityAsync = appendStackAsync(promisify(driveFetchIdentity)); const driveFetchProvedIdentityAsync = appendStackAsync(promisify(driveFetchProvedIdentity)); const driveFetchIdentityBalanceAsync = appendStackAsync(promisify(driveFetchIdentityBalance)); const driveFetchIdentityBalanceWithCostsAsync = appendStackAsync( promisify(driveFetchIdentityBalanceWithCosts), ); const driveFetchIdentityBalanceIncludeDebtWithCostsAsync = appendStackAsync( promisify(driveFetchIdentityBalanceIncludeDebtWithCosts), ); const driveFetchManyProvedIdentitiesAsync = appendStackAsync( promisify(driveFetchManyProvedIdentities), ); const driveFetchIdentityWithCostsAsync = appendStackAsync(promisify(driveFetchIdentityWithCosts)); const driveAddToIdentityBalanceAsync = appendStackAsync(promisify(driveAddToIdentityBalance)); const driveAddToSystemCreditsAsync = appendStackAsync(promisify(driveAddToSystemCredits)); const driveFetchIdentitiesByPublicKeyHashesAsync = appendStackAsync( promisify(driveFetchIdentitiesByPublicKeyHashes), ); const driveProveIdentitiesByPublicKeyHashesAsync = appendStackAsync( promisify(driveProveIdentitiesByPublicKeyHashes), ); const driveAddKeysToIdentityAsync = appendStackAsync(promisify(driveAddKeysToIdentity)); const driveDisableIdentityKeysAsync = appendStackAsync(promisify(driveDisableIdentityKeys)); const driveUpdateIdentityRevisionAsync = appendStackAsync(promisify(driveUpdateIdentityRevision)); const driveRemoveFromIdentityBalanceAsync = appendStackAsync( promisify(driveRemoveFromIdentityBalance), ); const driveApplyFeesToIdentityBalanceAsync = appendStackAsync( promisify(driveApplyFeesToIdentityBalance), ); const abciInitChainAsync = appendStackAsync(promisify(abciInitChain)); const abciBlockBeginAsync = appendStackAsync(promisify(abciBlockBegin)); const abciBlockEndAsync = appendStackAsync(promisify(abciBlockEnd)); const abciAfterFinalizeBlockAsync = appendStackAsync(promisify(abciAfterFinalizeBlock)); const calculateStorageFeeDistributionAmountAndLeftoversWithStack = appendStack( calculateStorageFeeDistributionAmountAndLeftovers, ); // Wrapper class for the boxed `Drive` for idiomatic JavaScript usage class Drive { /** * @param {string} dbPath * @param {Object} config * @param {number} config.dataContractsGlobalCacheSize * @param {number} config.dataContractsBlockCacheSize */ constructor(dbPath, config) { this.drive = driveOpen(dbPath, config); this.groveDB = new GroveDB(this.drive); } /** * @returns {GroveDB} */ getGroveDB() { return this.groveDB; } /** * @returns {Promise<void>} */ async close() { return driveCloseAsync.call(this.drive); } /** * @param {boolean} [useTransaction=false] * * @returns {Promise<[number, number]>} */ async createInitialStateStructure(useTransaction = false) { return driveCreateInitialStateStructureAsync.call(this.drive, useTransaction); } /** * @param {Buffer|Identifier} id * @param {number} [epochIndex] * @param {boolean} [useTransaction=false] * * @returns {Promise<[DataContract|null, FeeResult]>} */ async fetchContract(id, epochIndex = undefined, useTransaction = false) { return driveFetchContractAsync.call( this.drive, id, epochIndex, useTransaction, ).then(([encodedDataContract, innerFeeResult]) => { let dataContract = encodedDataContract; if (encodedDataContract !== null) { const [protocolVersion, rawDataContract] = decodeProtocolEntity( encodedDataContract, ); rawDataContract.protocolVersion = protocolVersion; dataContract = new DataContract(rawDataContract); } const result = [dataContract]; if (innerFeeResult) { result.push(new FeeResult(innerFeeResult)); } return result; }); } /** * @param {DataContract} dataContract * @param {RawBlockInfo} blockInfo * @param {boolean} [useTransaction=false] * @param {boolean} [dryRun=false] * * @returns {Promise<FeeResult>} */ async createContract(dataContract, blockInfo, useTransaction = false, dryRun = false) { return driveCreateContractAsync.call( this.drive, dataContract.toBuffer(), blockInfo, !dryRun, useTransaction, ).then((innerFeeResult) => new FeeResult(innerFeeResult)); } /** * @param {DataContract} dataContract * @param {RawBlockInfo} blockInfo * @param {boolean} [useTransaction=false] * @param {boolean} [dryRun=false] * * @returns {Promise<FeeResult>} */ async updateContract(dataContract, blockInfo, useTransaction = false, dryRun = false) { return driveUpdateContractAsync.call( this.drive, dataContract.toBuffer(), blockInfo, !dryRun, useTransaction, ).then((innerFeeResult) => new FeeResult(innerFeeResult)); } /** * @param {Document} document * @param {RawBlockInfo} blockInfo * @param {boolean} [useTransaction=false] * @param {boolean} [dryRun=false] * * @returns {Promise<FeeResult>} */ async createDocument(document, blockInfo, useTransaction = false, dryRun = false) { return driveCreateDocumentAsync.call( this.drive, document.toBuffer(), document.getDataContractId(), document.getType(), document.getOwnerId(), true, blockInfo, !dryRun, useTransaction, ).then((innerFeeResult) => new FeeResult(innerFeeResult)); } /** * @param {Document} document * @param {RawBlockInfo} blockInfo * @param {boolean} [useTransaction=false] * @param {boolean} [dryRun=false] * * @returns {Promise<FeeResult>} */ async updateDocument(document, blockInfo, useTransaction = false, dryRun = false) { return driveUpdateDocumentAsync.call( this.drive, document.toBuffer(), document.getDataContractId(), document.getType(), document.getOwnerId(), blockInfo, !dryRun, useTransaction, ).then((innerFeeResult) => new FeeResult(innerFeeResult)); } /** * @param {Identifier} dataContractId * @param {string} documentType * @param {Identifier} documentId * @param {RawBlockInfo} blockInfo * @param {boolean} [useTransaction=false] * @param {boolean} [dryRun=false] * * @returns {Promise<FeeResult>} */ async deleteDocument( dataContractId, documentType, documentId, blockInfo, useTransaction = false, dryRun = false, ) { return driveDeleteDocumentAsync.call( this.drive, documentId, dataContractId, documentType, blockInfo, !dryRun, useTransaction, ).then((innerFeeResult) => new FeeResult(innerFeeResult)); } /** * * @param {DataContract} dataContract * @param {string} documentType * @param {number} [epochIndex] * @param [query] * @param [query.where] * @param [query.limit] * @param [query.startAt] * @param [query.startAfter] * @param [query.orderBy] * @param {boolean} [useTransaction=false] * @param {boolean} [extended=false] * * @returns {Promise<[Document[], number]>} */ async queryDocuments( dataContract, documentType, epochIndex = undefined, query = {}, useTransaction = false, extended = false, ) { const encodedQuery = await cbor.encodeAsync(query); const [encodedDocuments, , processingFee] = await driveQueryDocumentsAsync.call( this.drive, encodedQuery, dataContract.getId(), documentType, epochIndex, useTransaction, ); const documents = encodedDocuments.map((encodedDocument) => { const [protocolVersion, rawDocument] = decodeProtocolEntity(encodedDocument); rawDocument.$protocolVersion = protocolVersion; return extended ? new ExtendedDocument(rawDocument, dataContract) : new Document(rawDocument, dataContract, documentType); }); return [ documents, processingFee, ]; } /** * * @param {DataContract} dataContract * @param {string} documentType * @param [query] * @param [query.where] * @param [query.limit] * @param [query.startAt] * @param [query.startAfter] * @param [query.orderBy] * @param {boolean} [useTransaction=false] * * @returns {Promise<[Document[], number]>} */ async proveDocumentsQuery(dataContract, documentType, query = {}, useTransaction = false) { const encodedQuery = await cbor.encodeAsync(query); // eslint-disable-next-line no-return-await return await driveProveDocumentsQueryAsync.call( this.drive, encodedQuery, dataContract.getId(), documentType, useTransaction, ); } /** * @param {Identity} identity * @param {RawBlockInfo} blockInfo * @param {boolean} [useTransaction=false] * @param {boolean} [dryRun=false] * * @returns {Promise<FeeResult>} */ async insertIdentity(identity, blockInfo, useTransaction = false, dryRun = false) { return driveInsertIdentityAsync.call( this.drive, identity.toBuffer(), blockInfo, !dryRun, useTransaction, ).then((innerFeeResult) => new FeeResult(innerFeeResult)); } /** * @param {Buffer|Identifier} id * @param {boolean} [useTransaction=false] * * @returns {Promise<Identity|null>} */ async fetchIdentity(id, useTransaction = false) { return driveFetchIdentityAsync.call( this.drive, id, useTransaction, ).then((encodedIdentity) => { if (encodedIdentity === null) { return null; } const [protocolVersion, rawIdentity] = decodeProtocolEntity( encodedIdentity, ); rawIdentity.protocolVersion = protocolVersion; return new Identity(rawIdentity); }); } /** * @param {Buffer|Identifier} id * @param {boolean} [useTransaction=false] * * @returns {Promise<number|null>} */ async fetchIdentityBalance(id, useTransaction = false) { return driveFetchIdentityBalanceAsync.call( this.drive, id, useTransaction, ); } /** * @param {Buffer|Identifier} id * @param {RawBlockInfo} blockInfo * @param {boolean} [useTransaction=false] * @param {boolean} [dryRun=false] * * @returns {Promise<[number, FeeResult]>} */ async fetchIdentityBalanceWithCosts(id, blockInfo, useTransaction = false, dryRun = false) { return driveFetchIdentityBalanceWithCostsAsync.call( this.drive, id, blockInfo, !dryRun, useTransaction, ).then(([balance, innerFeeResult]) => [balance, new FeeResult(innerFeeResult)]); } /** * @param {Buffer|Identifier} id * @param {RawBlockInfo} blockInfo * @param {boolean} [useTransaction=false] * @param {boolean} [dryRun=false] * * @returns {Promise<[number|null, FeeResult]>} */ async fetchIdentityBalanceIncludeDebtWithCosts( id, blockInfo, useTransaction = false, dryRun = false, ) { return driveFetchIdentityBalanceIncludeDebtWithCostsAsync.call( this.drive, id, blockInfo, !dryRun, useTransaction, ).then(([balance, innerFeeResult]) => [balance, new FeeResult(innerFeeResult)]); } /** * @param {Identifier} id * @param {boolean} [useTransaction=false] * * @returns {Promise<Buffer|null>} */ async proveIdentity(id, useTransaction = false) { return driveFetchProvedIdentityAsync.call( this.drive, id, useTransaction, ); } /** * @param {Identifier[]} ids * @param {boolean} [useTransaction=false] * * @returns {Promise<Buffer|null>} */ async proveManyIdentities(ids, useTransaction = false) { return driveFetchManyProvedIdentitiesAsync.call( this.drive, ids.map((id) => id), useTransaction, ); } /** * @param {Buffer|Identifier} id * @param {number} epochIndex * @param {boolean} [useTransaction=false] * * @returns {Promise<[Identity|null, FeeResult]>} */ async fetchIdentityWithCosts(id, epochIndex, useTransaction = false) { return driveFetchIdentityWithCostsAsync.call( this.drive, id, epochIndex, useTransaction, ).then(([encodedIdentity, innerFeeResult]) => { let identity = encodedIdentity; if (encodedIdentity !== null) { const [protocolVersion, rawIdentity] = decodeProtocolEntity( encodedIdentity, ); rawIdentity.protocolVersion = protocolVersion; identity = new Identity(rawIdentity); } return [identity, new FeeResult(innerFeeResult)]; }); } /** * @param {Identifier} identityId * @param {number} amount * @param {RawBlockInfo} blockInfo * @param {boolean} [useTransaction=false] * @param {boolean} [dryRun=false] * * @returns {Promise<FeeResult>} */ async addToIdentityBalance( identityId, amount, blockInfo, useTransaction = false, dryRun = false, ) { return driveAddToIdentityBalanceAsync.call( this.drive, identityId, amount, blockInfo, !dryRun, useTransaction, ).then((innerFeeResult) => new FeeResult(innerFeeResult)); } /** * @param {Identifier} identityId * @param {number} amount * @param {RawBlockInfo} blockInfo * @param {boolean} [useTransaction=false] * @param {boolean} [dryRun=false] * * @returns {Promise<FeeResult>} */ async removeFromIdentityBalance( identityId, amount, blockInfo, useTransaction = false, dryRun = false, ) { return driveRemoveFromIdentityBalanceAsync.call( this.drive, identityId, amount, blockInfo, !dryRun, useTransaction, ).then((innerFeeResult) => new FeeResult(innerFeeResult)); } /** * @param {Identifier} identityId * @param {FeeResult} fees * @param {boolean} [useTransaction=false] * * @returns {Promise<FeeResult>} */ async applyFeesToIdentityBalance( identityId, fees, useTransaction = false, ) { return driveApplyFeesToIdentityBalanceAsync.call( this.drive, identityId, fees.inner, useTransaction, ).then((innerFeeResult) => new FeeResult(innerFeeResult)); } /** * @param {number} amount * @param {boolean} [useTransaction=false] * * @returns {Promise<void>} */ async addToSystemCredits( amount, useTransaction = false, ) { return driveAddToSystemCreditsAsync.call( this.drive, amount, useTransaction, ); } /** * @param {Buffer[]} hashes * @param {boolean} [useTransaction=false] * * @returns {Promise<Array<Identity|null>>} */ async fetchIdentitiesByPublicKeyHashes(hashes, useTransaction = false) { return driveFetchIdentitiesByPublicKeyHashesAsync.call( this.drive, hashes.map((h) => Buffer.from(h)), useTransaction, ).then((encodedIdentities) => ( encodedIdentities.map((encodedIdentity) => { const [protocolVersion, rawIdentity] = decodeProtocolEntity( encodedIdentity, ); rawIdentity.protocolVersion = protocolVersion; return new Identity(rawIdentity); }) )); } /** * @param {Buffer[]} hashes * @param {boolean} [useTransaction=false] * * @returns {Promise<Array<Identity|null>>} */ async proveIdentitiesByPublicKeyHashes(hashes, useTransaction = false) { return driveProveIdentitiesByPublicKeyHashesAsync.call( this.drive, hashes.map((h) => Buffer.from(h)), useTransaction, ); } /** * @param {Identifier} identityId * @param {IdentityPublicKey[]} keys * @param {RawBlockInfo} blockInfo * @param {boolean} [useTransaction=false] * @param {boolean} [dryRun=false] * * @returns {Promise<FeeResult>} */ async addKeysToIdentity( identityId, keys, blockInfo, useTransaction = false, dryRun = false, ) { return driveAddKeysToIdentityAsync.call( this.drive, identityId, keys, blockInfo, !dryRun, useTransaction, ).then((innerFeeResult) => new FeeResult(innerFeeResult)); } /** * @param {Identifier} identityId * @param {number[]} keyIds * @param {number} disableAt * @param {RawBlockInfo} blockInfo * @param {boolean} [useTransaction=false] * @param {boolean} [dryRun=false] * * @returns {Promise<FeeResult>} */ async disableIdentityKeys( identityId, keyIds, disableAt, blockInfo, useTransaction = false, dryRun = false, ) { return driveDisableIdentityKeysAsync.call( this.drive, identityId, keyIds, disableAt, blockInfo, !dryRun, useTransaction, ).then((innerFeeResult) => new FeeResult(innerFeeResult)); } /** * @param {Identifier} identityId * @param {number} revision * @param {RawBlockInfo} blockInfo * @param {boolean} [useTransaction=false] * @param {boolean} [dryRun=false] * * @returns {Promise<FeeResult>} */ async updateIdentityRevision( identityId, revision, blockInfo, useTransaction = false, dryRun = false, ) { return driveUpdateIdentityRevisionAsync.call( this.drive, identityId, revision, blockInfo, !dryRun, useTransaction, ).then((innerFeeResult) => new FeeResult(innerFeeResult)); } /** * Fetch the latest index of the withdrawal transaction in a queue * * @param {RawBlockInfo} blockInfo * @param {boolean} [useTransaction=false] * @param {boolean} [dryRun=false] * * @returns {Promise<number>} */ async fetchLatestWithdrawalTransactionIndex(blockInfo, useTransaction = false, dryRun = false) { return driveFetchLatestWithdrawalTransactionIndexAsync.call( this.drive, blockInfo, !dryRun, useTransaction, ); } /** * Get the ABCI interface * @returns {RSAbci} */ getAbci() { const { drive } = this; /** * @typedef RSAbci */ return { /** * ABCI init chain * * @param {InitChainRequest} request * @param {boolean} [useTransaction=false] * * @returns {Promise<InitChainResponse>} */ async initChain(request, useTransaction = false) { const requestBytes = cbor.encode(request); const responseBytes = await abciInitChainAsync.call( drive, requestBytes, useTransaction, ); return cbor.decode(responseBytes); }, /** * ABCI block begin * * @param {BlockBeginRequest} request * @param {boolean} [useTransaction=false] * * @returns {Promise<BlockBeginResponse>} */ async blockBegin(request, useTransaction = false) { const requestBytes = cbor.encode({ ...request, // cborium doesn't eat Buffers proposerProTxHash: Array.from(request.proposerProTxHash), validatorSetQuorumHash: Array.from(request.validatorSetQuorumHash), }); const responseBytes = await abciBlockBeginAsync.call( drive, requestBytes, useTransaction, ); return cbor.decode(responseBytes); }, /** * ABCI block end * * @param {BlockEndRequest} request * @param {boolean} [useTransaction=false] * * @returns {Promise<BlockEndResponse>} */ async blockEnd(request, useTransaction = false) { const responseBytes = await abciBlockEndAsync.call( drive, request, useTransaction, ); return cbor.decode(responseBytes); }, /** * ABCI after finalize block * * @param {AfterFinalizeBlockRequest} request * * @returns {Promise<AfterFinalizeBlockResponse>} */ async afterFinalizeBlock(request) { const requestBytes = cbor.encode({ ...request, // cborium doesn't eat Buffers updatedDataContractIds: request.updatedDataContractIds .map((identifier) => Array.from(identifier)), }); const responseBytes = await abciAfterFinalizeBlockAsync.call( drive, requestBytes, ); return cbor.decode(responseBytes); }, }; } } // eslint-disable-next-line max-len Drive.calculateStorageFeeDistributionAmountAndLeftovers = calculateStorageFeeDistributionAmountAndLeftoversWithStack; Drive.FeeResult = FeeResult; /** * @typedef RawBlockInfo * @property {number} height * @property {number} epoch * @property {number} timeMs */ /** * @typedef InitChainRequest * @property {number} genesisTimeMs * @property {SystemIdentityPublicKeys} systemIdentityPublicKeys */ /** * @typedef SystemIdentityPublicKeys * @property {RequiredIdentityPublicKeysSet} masternodeRewardSharesContractOwner * @property {RequiredIdentityPublicKeysSet} featureFlagsContractOwner * @property {RequiredIdentityPublicKeysSet} dpnsContractOwner * @property {RequiredIdentityPublicKeysSet} withdrawalsContractOwner * @property {RequiredIdentityPublicKeysSet} dashpayContractOwner */ /** * @typedef RequiredIdentityPublicKeysSet * @property {Buffer} master * @property {Buffer} high */ /** * @typedef InitChainResponse */ /** * @typedef BlockBeginRequest * @property {number} blockHeight * @property {number} blockTimeMs - timestamp in milliseconds * @property {number} [previousBlockTimeMs] - timestamp in milliseconds * @property {Buffer} proposerProTxHash * @property {Buffer} validatorSetQuorumHash * @property {number} lastSyncedCoreHeight * @property {number} coreChainLockedHeight, * @property {number} proposedAppVersion * @property {number} totalHpmns */ /** * @typedef BlockBeginResponse * @property {Buffer[]} unsignedWithdrawalTransactions * @property {EpochInfo} epochInfo */ /** * @typedef EpochInfo * @property {number} currentEpochIndex * @property {boolean} isEpochChange * @property {number} [previousEpochIndex] - Available only on epoch change */ /** * @typedef BlockEndRequest * @property {BlockFees} fees */ /** * @typedef BlockFees * @property {number} storageFee * @property {number} processingFee * @property {Object<string, number>} refundsPerEpoch */ /** * @typedef BlockEndResponse * @property {number} [proposersPaidCount] * @property {number} [paidEpochIndex] * @property {number} [refundedEpochsCount] */ /** * @typedef AfterFinalizeBlockRequest * @property {Identifier[]|Buffer[]} updatedDataContractIds */ /** * @typedef AfterFinalizeBlockResponse */ module.exports = Drive;