UNPKG

factom

Version:

Library to build applications on the Factom blockchain

336 lines (314 loc) 12.9 kB
const { Transaction } = require('./transaction'), { keyToPublicEcAddress, rcdHashToPublicFctAddress } = require('./addresses'), { ADMIN_ID_TO_CODE, ADMIN_BLOCKS_CHAIN_ID, ENTRY_CREDIT_BLOCKS_CHAIN_ID, FACTOID_BLOCKS_CHAIN_ID } = require('./constant'); /** * Class representing a Directory block. * @property {string} keyMR - Key Merkel Root. * @property {number} height - Height. * @property {string} previousBlockKeyMR - Key Merkel Root of the previous Directory block. * @property {number} timestamp - UNIX timestamp (seconds). * @property {string} fullHash - Full hash of the block. Only available when the block is queried by height. * @property {string} previousFullHash - Full hash of the previous Directory block. Only available when the block is queried by height. * @property {string} bodyKeyMR - Key Merkle Root of the block body. Only available when the block is queried by height. * @property {string} adminBlockRef - Reference to the admin block. * @property {string} entryCreditBlockRef - Reference to the entry credit block. * @property {string} factoidBlockRef - Reference to the factoid block. * @property {{chainId: string, keyMR: string}[]} entryBlockRefs - References to the entry blocks. */ class DirectoryBlock { /** * @hideconstructor */ constructor(block, keyMR) { let entries = []; if (block.dblock) { const dblock = block.dblock; this.keyMR = dblock.keymr; this.height = dblock.header.dbheight; this.previousBlockKeyMR = dblock.header.prevkeymr; this.timestamp = dblock.header.timestamp * 60; entries = dblock.dbentries; // Extra fields this.fullHash = dblock.dbhash; this.previousFullHash = dblock.header.prevfullhash; this.bodyKeyMR = dblock.header.bodymr; } else { this.keyMR = keyMR; this.height = block.header.sequencenumber; this.previousBlockKeyMR = block.header.prevblockkeymr; this.timestamp = block.header.timestamp; entries = block.entryblocklist; } this.entryBlockRefs = []; for (const entry of entries) { switch (entry.chainid) { case ADMIN_BLOCKS_CHAIN_ID: this.adminBlockRef = entry.keymr; break; case ENTRY_CREDIT_BLOCKS_CHAIN_ID: this.entryCreditBlockRef = entry.keymr; break; case FACTOID_BLOCKS_CHAIN_ID: this.factoidBlockRef = entry.keymr; break; default: this.entryBlockRefs.push({ chainId: entry.chainid, keyMR: entry.keymr }); } } Object.freeze(this); } } /** * Class representing an Admin block. * @property {string} backReferenceHash - Back reference hash. * @property {string} lookupHash - Lookup hash. * @property {number} directoryBlockHeight - Directory block height. * @property {string} previousBackReferenceHash - Back reference hash of previous Admin block. * @property {number} headerExpansionSize - Header expansion size. * @property {string} headerExpansionArea - Header expansion area. * @property {number} bodySize - Size of the body. * @property {Object} entries - Admin entries. Each entry has its own type (can be identified either by its adminId (number) or its adminCode (string)). */ class AdminBlock { /** * @hideconstructor */ constructor(block) { const ab = block.ablock; this.backReferenceHash = ab.backreferencehash; this.lookupHash = ab.lookuphash; const header = ab.header; this.directoryBlockHeight = header.dbheight; this.previousBackReferenceHash = header.prevbackrefhash; this.headerExpansionSize = header.headerexpansionsize; this.headerExpansionArea = header.headerexpansionarea; this.bodySize = header.bodysize; this.entries = ab.abentries.map(transformAdminBlockEntry); Object.freeze(this); } /** * Return all the admin entries for given types. * @param {...number|string} types - A sequence of either numbers representing an adminId or strings representing an adminCode. * @returns {Object} - Admin entries. */ getEntriesOfTypes(...types) { const set = new Set(types); return this.entries.filter(e => set.has(e.adminId) || set.has(e.adminCode)); } } // https://github.com/FactomProject/factomd/tree/3871e26d562c3ed920a1a8fc0e61e50d49f1cf9b/common/adminBlock function transformAdminBlockEntry(entry) { const base = { adminId: entry.adminidtype, adminCode: ADMIN_ID_TO_CODE.get(entry.adminidtype) }; let data = {}; switch (base.adminId) { case 1: data.identityChainId = entry.identityadminchainid; data.previousDirectoryBlockSignature = { publicKey: entry.prevdbsig.pub, signature: entry.prevdbsig.sig }; break; case 2: case 3: data.identityChainId = entry.identitychainid; data.matryoshkaHash = entry.mhash; break; case 4: data.amount = entry.amount; break; case 5: case 6: case 7: data.identityChainId = entry.identitychainid; data.directoryBlockHeight = entry.dbheight; break; case 8: data.identityChainId = entry.identitychainid; data.keyPriority = entry.keypriority; data.publicKey = entry.publickey; data.directoryBlockHeight = entry.dbheight; break; case 9: data.identityChainId = entry.identitychainid; data.keyPriority = entry.keypriority; data.keyType = entry.keytype; data.ecdsaPublicKey = entry.ecdsapublickey; break; case 11: data.outputs = entry.Outputs.map(o => ({ address: o.useraddress, rcdHash: o.address, amount: o.amount })); break; case 12: data.descriptorHeight = entry.descriptor_height; data.descriptorIndex = entry.DescriptorIndex; break; case 13: data.identityChainId = entry.IdentityChainID; data.rcdHash = entry.FactoidAddress; data.factoidAddress = rcdHashToPublicFctAddress(entry.FactoidAddress); break; case 14: data.identityChainId = entry.IdentityChainID; data.efficiency = entry.Efficiency / 100; break; } return Object.assign(base, data); } /** * Class representing an Entry block. * @property {string} keyMR - Key Mertle Root. * @property {string} previousBlockKeyMR - Key Mertle Root of the previous Entry block. * @property {number} directoryBlockHeight - Directory block height. * @property {number} timestamp - UNIX timestamp (seconds). * @property {string} chainId - Chain ID. * @property {number} sequenceNumber - Sequence number of this block relative to that sub chain. * @property {{ entryHash: string, timestamp: number }[]} entryRefs - References to entries with their UNIX timestamps. */ class EntryBlock { /** * @hideconstructor */ constructor(block, keyMR) { this.keyMR = keyMR; const header = block.header; this.directoryBlockHeight = header.dbheight; this.timestamp = header.timestamp; this.previousBlockKeyMR = header.prevkeymr; this.chainId = header.chainid; this.sequenceNumber = header.blocksequencenumber; this.entryRefs = block.entrylist.map(e => ({ entryHash: e.entryhash, timestamp: e.timestamp })); Object.freeze(this); } } /** * Class representing a Factoid block. * @property {string} keyMR - Key Mertle Root. * @property {string} bodyMR - Merkle Root of the body. * @property {string} previousBlockKeyMR - Key Merkle Root of the previous Factoid block. * @property {string} ledgerKeyMR - Ledger Key Merkle Root. * @property {string} previousLedgerKeyMR - Ledger Key Merkle Root of the previous Factoid block. * @property {number} entryCreditRate - Entry credit rate. * @property {number} directoryBlockHeight - Directory block height. * @property {Transaction[]} transactions - Array of Factoid transactions part of this block. */ class FactoidBlock { /** * @hideconstructor */ constructor(block) { const fb = block.fblock; this.keyMR = fb.keymr; this.bodyMR = fb.bodymr; this.previousBlockKeyMR = fb.prevkeymr; this.ledgerKeyMR = fb.ledgerkeymr; this.previousLedgerKeyMR = fb.prevledgerkeymr; this.entryCreditRate = fb.exchrate; this.directoryBlockHeight = fb.dbheight; this.transactions = fb.transactions.map( t => new Transaction(t, { factoidBlockKeyMR: this.keyMR, directoryBlockHeight: this.directoryBlockHeight // directoryBlockKeyMR is not available }) ); } /** * Get coinbase transaction of the block. * @returns {Transaction} - Coinbase transaction of the block. */ getCoinbaseTransaction() { return this.transactions[0]; } } /** * Class representing an Entry Credit block. * @property {string} headerHash - Hash of the header. * @property {string} fullHash - Full hash. * @property {string} headerExpansionArea - Header expansion area. * @property {string} bodyHash - Hash of the body. * @property {string} previousHeaderHash - Hash of the previous Entry Credit block header. * @property {string} previousFullHash - Full hash of the previous Entry Credit block. * @property {number} directoryBlockHeight - Directory block height. * @property {number} bodySize - Size of the body. * @property {number} objectCount - Object count. * @property {number[]} minuteIndexes - Delimitation of the commits for each minute. Use method getCommitsForMinute rather than using this attribute directly. * @property {{version: number, millis: number, entryHash: string, credits: number, ecPublicKey: string, signature: string}[]} commits - Array of commits. */ class EntryCreditBlock { /** * @hideconstructor */ constructor(block) { const ecb = block.ecblock; this.headerHash = ecb.headerhash; this.fullHash = ecb.fullhash; const header = ecb.header; this.headerExpansionArea = header.headerexpansionarea; this.bodyHash = header.bodyhash; this.previousHeaderHash = header.prevheaderhash; this.previousFullHash = header.prevfullhash; this.directoryBlockHeight = header.dbheight; this.bodySize = header.bodysize; this.objectCount = header.objectcount; this.minuteIndexes = [0]; this.commits = []; for (const entry of ecb.body.entries) { if (entry.hasOwnProperty('number')) { this.minuteIndexes.push(this.commits.length); } else if (entry.hasOwnProperty('serverindexnumber')) { // serverindexnumber is a legacy field in old blocks continue; } else if (entry.hasOwnProperty('numec')) { // M1 blocks used to contain EC purchases // But those should be found in Factoid blocks anyway () continue; } else { this.commits.push({ version: entry.version, millis: parseInt(entry.millitime, 16), entryHash: entry.entryhash, credits: entry.credits, ecPublicKey: keyToPublicEcAddress(entry.ecpubkey), signature: entry.sig }); } } Object.freeze(this); } /** * Get all the commits for a given minute. * @param {number} minute - Minute (between 1 and 10 included) * @returns {{version: number, millis: number, entryHash: string, credits: number, ecPublicKey: string, signature: string}[]} - Commits. */ getCommitsForMinute(minute) { if (minute < 1 || minute >= this.minuteIndexes.length) { throw new RangeError(`Minute out of range [1, ${this.minuteIndexes.length - 1}]`); } return this.commits.slice(this.minuteIndexes[minute - 1], this.minuteIndexes[minute]); } } module.exports = { DirectoryBlock, EntryBlock, AdminBlock, FactoidBlock, EntryCreditBlock };