From 45b95588a3f568a04c1f029f349fcd7ed250dd97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Moreau?= <cem.moreau@gmail.com> Date: Sun, 10 Jun 2018 18:30:45 +0200 Subject: [PATCH] [enh] dividends are now stored in a specific loki collection --- app/lib/blockchain/DuniterBlockchain.ts | 44 +++- app/lib/common-libs/errors.ts | 3 + app/lib/computation/QuickSync.ts | 12 +- app/lib/dal/fileDAL.ts | 86 +++++-- app/lib/dal/indexDAL/abstract/DividendDAO.ts | 47 ++++ app/lib/dal/indexDAL/abstract/IIndexDAO.ts | 2 - app/lib/dal/indexDAL/abstract/SIndexDAO.ts | 23 +- app/lib/dal/indexDAL/loki/LokiBIndex.ts | 4 +- app/lib/dal/indexDAL/loki/LokiBlockchain.ts | 12 +- app/lib/dal/indexDAL/loki/LokiCIndex.ts | 4 +- .../indexDAL/loki/LokiCollectionManager.ts | 2 +- app/lib/dal/indexDAL/loki/LokiDividend.ts | 243 ++++++++++++++++++ app/lib/dal/indexDAL/loki/LokiIIndex.ts | 6 - app/lib/dal/indexDAL/loki/LokiIndex.ts | 20 +- .../dal/indexDAL/loki/LokiProtocolIndex.ts | 23 ++ .../indexDAL/loki/LokiPubkeySharingIndex.ts | 3 +- app/lib/dal/indexDAL/loki/LokiSIndex.ts | 58 +++-- app/lib/dal/indexDAL/loki/LokiTransactions.ts | 3 +- app/lib/dto/BlockDTO.ts | 2 +- app/lib/dto/TransactionDTO.ts | 4 +- app/lib/indexer.ts | 109 +++++--- app/lib/other_constants.ts | 1 + app/lib/rules/global_rules.ts | 4 +- .../bma/lib/controllers/transactions.ts | 16 +- test/dal/sources-dal.ts | 8 +- test/fast/dal/basic-loki.ts | 3 +- test/fast/protocol/protocol-brg106-number.ts | 10 +- test/integration/branches/branches_revert2.ts | 43 +++- 28 files changed, 627 insertions(+), 168 deletions(-) create mode 100644 app/lib/dal/indexDAL/abstract/DividendDAO.ts create mode 100644 app/lib/dal/indexDAL/loki/LokiDividend.ts create mode 100644 app/lib/dal/indexDAL/loki/LokiProtocolIndex.ts diff --git a/app/lib/blockchain/DuniterBlockchain.ts b/app/lib/blockchain/DuniterBlockchain.ts index 9b2c004ab..a12d9cbae 100644 --- a/app/lib/blockchain/DuniterBlockchain.ts +++ b/app/lib/blockchain/DuniterBlockchain.ts @@ -11,7 +11,16 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -import {FullIindexEntry, IindexEntry, IndexEntry, Indexer, MindexEntry, SindexEntry} from "../indexer" +import { + BasedAmount, + FullIindexEntry, + IindexEntry, + IndexEntry, + Indexer, + MindexEntry, + SimpleSindexEntryForWallet, SimpleTxEntryForWallet, SimpleUdEntryForWallet, + SindexEntry +} from "../indexer" import {ConfDTO} from "../dto/ConfDTO" import {BlockDTO} from "../dto/BlockDTO" import {DBHead} from "../db/DBHead" @@ -28,6 +37,8 @@ import {DataErrors} from "../common-libs/errors" import {NewLogger} from "../logger" import {DBTx} from "../db/DBTx" import {Underscore} from "../common-libs/underscore" +import {DividendEntry, UDSource} from "../dal/indexDAL/abstract/DividendDAO" +import {OtherConstants} from "../other_constants" export class DuniterBlockchain { @@ -224,7 +235,7 @@ export class DuniterBlockchain { await this.updateMembers(block, dal); // Update the wallets' blances - await this.updateWallets(indexes.sindex, dal) + await this.updateWallets(indexes.sindex, indexes.dividends, dal) if (trim) { const TAIL = await dal.bindexDAL.tail(); @@ -318,6 +329,7 @@ export class DuniterBlockchain { let ms = MembershipDTO.fromInline(inlineMS) const idty = await dal.getWrittenIdtyByPubkeyForWotbID(ms.issuer); dal.wotb.setEnabled(true, idty.wotb_id); + await dal.dividendDAL.setMember(true, ms.issuer) } // Revoked for (const inlineRevocation of block.revoked) { @@ -328,22 +340,27 @@ export class DuniterBlockchain { for (const excluded of block.excluded) { const idty = await dal.getWrittenIdtyByPubkeyForWotbID(excluded); dal.wotb.setEnabled(false, idty.wotb_id); + await dal.dividendDAL.setMember(false, excluded) } } - static async updateWallets(sindex:SindexEntry[], aDal:any, reverse = false) { - const differentConditions = Underscore.uniq(sindex.map((entry) => entry.conditions)) + static async updateWallets(sindex:SimpleTxEntryForWallet[], dividends:SimpleUdEntryForWallet[], aDal:any, reverse = false) { + const differentConditions = Underscore.uniq(sindex.map((entry) => entry.conditions).concat(dividends.map(d => d.conditions))) for (const conditions of differentConditions) { - const creates = Underscore.filter(sindex, (entry:SindexEntry) => entry.conditions === conditions && entry.op === CommonConstants.IDX_CREATE) - const updates = Underscore.filter(sindex, (entry:SindexEntry) => entry.conditions === conditions && entry.op === CommonConstants.IDX_UPDATE) - const positives = creates.reduce((sum:number, src:SindexEntry) => sum + src.amount * Math.pow(10, src.base), 0) - const negatives = updates.reduce((sum:number, src:SindexEntry) => sum + src.amount * Math.pow(10, src.base), 0) + const udsOfKey: BasedAmount[] = dividends.filter(d => d.conditions === conditions).map(d => ({ amount: d.amount, base: d.base })) + const creates: BasedAmount[] = sindex.filter(entry => entry.conditions === conditions && entry.op === CommonConstants.IDX_CREATE) + const updates: BasedAmount[] = sindex.filter(entry => entry.conditions === conditions && entry.op === CommonConstants.IDX_UPDATE) + const positives = creates.concat(udsOfKey).reduce((sum, src) => sum + src.amount * Math.pow(10, src.base), 0) + const negatives = updates.reduce((sum, src) => sum + src.amount * Math.pow(10, src.base), 0) const wallet = await aDal.getWallet(conditions) let variation = positives - negatives if (reverse) { // To do the opposite operations, for a reverted block variation *= -1 } + if (OtherConstants.TRACE_BALANCES) { + NewLogger().trace('Balance of %s: %s (%s %s %s)', wallet.conditions, wallet.balance + variation, wallet.balance, variation < 0 ? '-' : '+', Math.abs(variation)) + } wallet.balance += variation await aDal.saveWallet(wallet) } @@ -377,21 +394,25 @@ export class DuniterBlockchain { // Get the money movements to revert in the balance const REVERSE_BALANCE = true - const sindexOfBlock = await dal.sindexDAL.getWrittenOn(blockstamp) + const sindexOfBlock = await dal.sindexDAL.getWrittenOnTxs(blockstamp) await dal.bindexDAL.removeBlock(blockstamp); await dal.mindexDAL.removeBlock(blockstamp); await dal.iindexDAL.removeBlock(blockstamp); await dal.cindexDAL.removeBlock(blockstamp); await dal.sindexDAL.removeBlock(blockstamp); + const { createdUDsDestroyedByRevert, consumedUDsRecoveredByRevert } = await dal.dividendDAL.revertUDs(number) // Then: normal updates const previousBlock = await dal.getFullBlockOf(number - 1) // Set the block as SIDE block (equivalent to removal from main branch) await dal.blockDAL.setSideBlock(number, previousBlock); + // Update the dividends in our wallet + await this.updateWallets([], createdUDsDestroyedByRevert, dal, REVERSE_BALANCE) + await this.updateWallets([], consumedUDsRecoveredByRevert, dal) // Revert the balances variations for this block - await this.updateWallets(sindexOfBlock, dal, REVERSE_BALANCE) + await this.updateWallets(sindexOfBlock, [], dal, REVERSE_BALANCE) // Restore block's transaction as incoming transactions await this.undoDeleteTransactions(block, dal) @@ -407,6 +428,7 @@ export class DuniterBlockchain { if (entry.member === true && entry.op === CommonConstants.IDX_UPDATE) { const idty = await dal.getWrittenIdtyByPubkeyForWotbID(entry.pub); dal.wotb.setEnabled(false, idty.wotb_id); + await dal.dividendDAL.setMember(false, entry.pub) } } const newcomers = await dal.iindexDAL.getWrittenOn(blockstamp); @@ -417,6 +439,7 @@ export class DuniterBlockchain { // Does not matter which one it really was, we pop the last X identities NewLogger().trace('removeNode') dal.wotb.removeNode(); + await dal.dividendDAL.deleteMember(entry.pub) } } const excluded = await dal.iindexDAL.getWrittenOn(blockstamp); @@ -426,6 +449,7 @@ export class DuniterBlockchain { if (entry.member === false && entry.op === CommonConstants.IDX_UPDATE) { const idty = await dal.getWrittenIdtyByPubkeyForWotbID(entry.pub); dal.wotb.setEnabled(true, idty.wotb_id); + await dal.dividendDAL.setMember(true, entry.pub) } } } diff --git a/app/lib/common-libs/errors.ts b/app/lib/common-libs/errors.ts index 2af726625..74d0c31b3 100644 --- a/app/lib/common-libs/errors.ts +++ b/app/lib/common-libs/errors.ts @@ -1,5 +1,8 @@ export enum DataErrors { + LOKI_DIVIDEND_GET_WRITTEN_ON_SHOULD_NOT_BE_USED, + LOKI_DIVIDEND_REMOVE_BLOCK_SHOULD_NOT_BE_USED, + NEGATIVE_BALANCE, BLOCK_WASNT_COMMITTED, CANNOT_ARCHIVE_CHUNK_WRONG_SIZE, CORRUPTED_DATABASE, diff --git a/app/lib/computation/QuickSync.ts b/app/lib/computation/QuickSync.ts index 4993cbbe4..b7f4cd5f6 100644 --- a/app/lib/computation/QuickSync.ts +++ b/app/lib/computation/QuickSync.ts @@ -166,9 +166,9 @@ export class QuickSynchronizer { sync_nextExpiring = sync_expires.reduce((max, value) => max ? Math.min(max, value) : value, 9007199254740991); // Far far away date // Fills in correctly the SINDEX - await Promise.all(Underscore.where(sync_sindex.concat(local_sindex), { op: 'UPDATE' }).map(async (entry: any) => { + await Promise.all(Underscore.where(sync_sindex.concat(local_sindex), { op: 'UPDATE' }).map(async entry => { if (!entry.conditions) { - const src = (await this.dal.sindexDAL.getSource(entry.identifier, entry.pos)) as FullSindexEntry + const src = (await this.dal.getSource(entry.identifier, entry.pos, entry.srcType === 'D')) as FullSindexEntry entry.conditions = src.conditions; } })) @@ -185,8 +185,10 @@ export class QuickSynchronizer { sync_mindex = local_mindex sync_sindex = local_sindex - sync_sindex = sync_sindex.concat(await Indexer.ruleIndexGenDividend(HEAD, local_iindex, this.dal)); - sync_sindex = sync_sindex.concat(await Indexer.ruleIndexGarbageSmallAccounts(HEAD, sync_sindex, sync_memoryDAL)); + // Dividends and account garbaging + const dividends = await Indexer.ruleIndexGenDividend(HEAD, local_iindex, this.dal) + sync_sindex = sync_sindex.concat(await Indexer.ruleIndexGarbageSmallAccounts(HEAD, sync_sindex, dividends, sync_memoryDAL)); + if (nextExpiringChanged) { sync_cindex = sync_cindex.concat(await Indexer.ruleIndexGenCertificationExpiry(HEAD, this.dal)); sync_mindex = sync_mindex.concat(await Indexer.ruleIndexGenMembershipExpiry(HEAD, this.dal)); @@ -195,7 +197,7 @@ export class QuickSynchronizer { sync_mindex = sync_mindex.concat(await Indexer.ruleIndexGenImplicitRevocation(HEAD, this.dal)); } // Update balances with UD + local garbagings - await DuniterBlockchain.updateWallets(sync_sindex, sync_memoryDAL) + await DuniterBlockchain.updateWallets(sync_sindex, dividends, sync_memoryDAL) // Flush the INDEX again (needs to be done *before* the update of wotb links because of block#0) await this.dal.flushIndexes({ diff --git a/app/lib/dal/fileDAL.ts b/app/lib/dal/fileDAL.ts index 12a07818d..7b0586869 100644 --- a/app/lib/dal/fileDAL.ts +++ b/app/lib/dal/fileDAL.ts @@ -25,10 +25,11 @@ import { FullCindexEntry, FullIindexEntry, FullMindexEntry, - FullSindexEntry, IindexEntry, IndexEntry, MindexEntry, + SimpleTxInput, + SimpleUdEntryForWallet, SindexEntry } from "../indexer" import {TransactionDTO} from "../dto/TransactionDTO" @@ -75,6 +76,9 @@ import {Underscore} from "../common-libs/underscore" import {DBPeer} from "../db/DBPeer" import {MonitorFlushedIndex} from "../debug/MonitorFlushedIndex" import {cliprogram} from "../common-libs/programOptions" +import {DividendDAO, UDSource} from "./indexDAL/abstract/DividendDAO" +import {LokiDividend} from "./indexDAL/loki/LokiDividend" +import {HttpSource, HttpUD} from "../../modules/bma/lib/dtos" const readline = require('readline') const indexer = require('../indexer').Indexer @@ -126,6 +130,7 @@ export class FileDAL { iindexDAL:IIndexDAO sindexDAL:SIndexDAO cindexDAL:CIndexDAO + dividendDAL:DividendDAO newDals:{ [k:string]: Initiable } loadConfHook: (conf:ConfDTO) => Promise<void> @@ -156,6 +161,7 @@ export class FileDAL { this.iindexDAL = new LokiIIndex(this.loki.getLokiInstance()) this.sindexDAL = new LokiSIndex(this.loki.getLokiInstance()) this.cindexDAL = new LokiCIndex(this.loki.getLokiInstance()) + this.dividendDAL = new LokiDividend(this.loki.getLokiInstance()) this.newDals = { 'powDAL': this.powDAL, @@ -174,6 +180,7 @@ export class FileDAL { 'iindexDAL': this.iindexDAL, 'sindexDAL': this.sindexDAL, 'cindexDAL': this.cindexDAL, + 'dividendDAL': this.dividendDAL, 'blockchainArchiveDAL': this.blockchainArchiveDAL, } } @@ -191,6 +198,7 @@ export class FileDAL { this.iindexDAL, this.sindexDAL, this.cindexDAL, + this.dividendDAL, this.blockchainArchiveDAL, ] for (const indexDAL of dals) { @@ -396,8 +404,36 @@ export class FileDAL { return this.cindexDAL.getValidLinksTo(to) } - getAvailableSourcesByPubkey(pubkey:string) { - return this.sindexDAL.getAvailableForPubkey(pubkey) + async getAvailableSourcesByPubkey(pubkey:string): Promise<HttpSource[]> { + const txAvailable = await this.sindexDAL.getAvailableForPubkey(pubkey) + const sources: UDSource[] = await this.dividendDAL.getUDSources(pubkey) + return sources.map(d => { + return { + type: 'D', + noffset: d.pos, + identifier: pubkey, + amount: d.amount, + base: d.base, + conditions: 'SIG(' + pubkey + ')' + } + }).concat(txAvailable.map(s => { + return { + type: 'T', + noffset: s.pos, + identifier: s.identifier, + amount: s.amount, + base: s.base, + conditions: s.conditions + } + })) + } + + async findByIdentifierPosAmountBase(identifier: string, pos: number, amount: number, base: number, isDividend: boolean): Promise<SimpleTxInput[]> { + if (isDividend) { + return this.dividendDAL.findUdSourceByIdentifierPosAmountBase(identifier, pos, amount, base) + } else { + return this.sindexDAL.findTxSourceByIdentifierPosAmountBase(identifier, pos, amount, base) + } } async getGlobalIdentityByHashForExistence(hash:string): Promise<boolean> { @@ -810,8 +846,12 @@ export class FileDAL { return this.cindexDAL.existsNonReplayableLink(from, to) } - getSource(identifier:string, pos:number): Promise<FullSindexEntry | null> { - return this.sindexDAL.getSource(identifier, pos) + async getSource(identifier:string, pos:number, isDividend: boolean): Promise<SimpleTxInput | null> { + if (isDividend) { + return this.dividendDAL.getUDSource(identifier, pos) + } else { + return this.sindexDAL.getTxSource(identifier, pos) + } } async isMember(pubkey:string):Promise<boolean> { @@ -973,8 +1013,8 @@ export class FileDAL { let iindex = indexer.iindex(index); let sindex = indexer.sindex(index); let cindex = indexer.cindex(index); - sindex = sindex.concat(await indexer.ruleIndexGenDividend(HEAD, iindex, this)); - sindex = sindex.concat(await indexer.ruleIndexGarbageSmallAccounts(HEAD, sindex, this)); + const dividends = await indexer.ruleIndexGenDividend(HEAD, iindex, this) // Requires that newcomers are already in DividendDAO + sindex = sindex.concat(await indexer.ruleIndexGarbageSmallAccounts(HEAD, sindex, dividends, this)); cindex = cindex.concat(await indexer.ruleIndexGenCertificationExpiry(HEAD, this)); mindex = mindex.concat(await indexer.ruleIndexGenMembershipExpiry(HEAD, this)); iindex = iindex.concat(await indexer.ruleIndexGenExclusionByMembership(HEAD, mindex, this)); @@ -982,7 +1022,7 @@ export class FileDAL { mindex = mindex.concat(await indexer.ruleIndexGenImplicitRevocation(HEAD, this)); await indexer.ruleIndexCorrectMembershipExpiryDate(HEAD, mindex, this); await indexer.ruleIndexCorrectCertificationExpiryDate(HEAD, cindex, this); - return { HEAD, mindex, iindex, sindex, cindex }; + return { HEAD, mindex, iindex, sindex, cindex, dividends }; } async updateWotbLinks(cindex:CindexEntry[]) { @@ -990,7 +1030,7 @@ export class FileDAL { const from = await this.getWrittenIdtyByPubkeyForWotbID(entry.issuer); const to = await this.getWrittenIdtyByPubkeyForWotbID(entry.receiver); if (entry.op == CommonConstants.IDX_CREATE) { - NewLogger().trace('addLink %s -> %s', from.wotb_id, to.wotb_id) + // NewLogger().trace('addLink %s -> %s', from.wotb_id, to.wotb_id) this.wotb.addLink(from.wotb_id, to.wotb_id); } else { // Update = removal @@ -1108,13 +1148,19 @@ export class FileDAL { return history; } - async getUDHistory(pubkey:string) { - const sources = await this.sindexDAL.getUDSources(pubkey) + async getUDHistory(pubkey:string): Promise<{ history: HttpUD[] }> { + const sources: UDSource[] = await this.dividendDAL.getUDSources(pubkey) return { - history: sources.map((src:SindexEntry) => Underscore.extend({ - block_number: src.pos, - time: src.written_time - }, src)) + history: (await Promise.all<HttpUD>(sources.map(async (src) => { + const block = await this.getBlockWeHaveItForSure(src.pos) + return { + block_number: src.pos, + time: block.medianTime, + consumed: src.consumed, + amount: src.amount, + base: src.base + } + }))) } } @@ -1281,7 +1327,15 @@ export class FileDAL { async flushIndexes(indexes: IndexBatch) { await this.mindexDAL.insertBatch(indexes.mindex) await this.iindexDAL.insertBatch(indexes.iindex) - await this.sindexDAL.insertBatch(indexes.sindex) + await this.sindexDAL.insertBatch(indexes.sindex.filter(s => s.srcType === 'T')) // We don't store dividends in SINDEX await this.cindexDAL.insertBatch(indexes.cindex) + await this.dividendDAL.consume(indexes.sindex.filter(s => s.srcType === 'D')) + } + + async updateDividend(blockNumber: number, dividend: number|null, unitbase: number, local_iindex: IindexEntry[]): Promise<SimpleUdEntryForWallet[]> { + if (dividend) { + return this.dividendDAL.produceDividend(blockNumber, dividend, unitbase, local_iindex) + } + return [] } } diff --git a/app/lib/dal/indexDAL/abstract/DividendDAO.ts b/app/lib/dal/indexDAL/abstract/DividendDAO.ts new file mode 100644 index 000000000..bc07529a0 --- /dev/null +++ b/app/lib/dal/indexDAL/abstract/DividendDAO.ts @@ -0,0 +1,47 @@ +import {GenericDAO} from "./GenericDAO" +import { + IindexEntry, + SimpleSindexEntryForWallet, + SimpleTxInput, + SimpleUdEntryForWallet, + SindexEntry +} from "../../../indexer" + +export interface DividendEntry { + pub: string + member: boolean + availables: number[] + consumed: number[] + consumedUDs: { dividendNumber: number, dividend: { amount: number, base: number }Â }[] + dividends: { amount: number, base: number }[] +} + +export interface UDSource { + consumed: boolean + pos: number + amount: number + base: number +} + +export interface DividendDAO extends GenericDAO<DividendEntry> { + + setMember(member: boolean, pub: string): Promise<void> + + produceDividend(blockNumber: number, dividend: number, unitbase: number, local_iindex: IindexEntry[]): Promise<SimpleUdEntryForWallet[]> + + getUDSources(pub: string): Promise<UDSource[]> + + findUdSourceByIdentifierPosAmountBase(identifier: string, pos: number, amount: number, base: number): Promise<SimpleTxInput[]> + + getUDSource(identifier: string, pos: number): Promise<SimpleTxInput|null> + + createMember(pub: string): Promise<void> + + consume(filter: SindexEntry[]): Promise<void> + + deleteMember(pub: string): Promise<void> + + getWrittenOnUDs(number: number): Promise<SimpleUdEntryForWallet[]> + + revertUDs(number: number): Promise<{ createdUDsDestroyedByRevert: SimpleUdEntryForWallet[], consumedUDsRecoveredByRevert: SimpleUdEntryForWallet[] }> +} diff --git a/app/lib/dal/indexDAL/abstract/IIndexDAO.ts b/app/lib/dal/indexDAL/abstract/IIndexDAO.ts index ac4e52b45..698f580c1 100644 --- a/app/lib/dal/indexDAL/abstract/IIndexDAO.ts +++ b/app/lib/dal/indexDAL/abstract/IIndexDAO.ts @@ -26,8 +26,6 @@ export interface IIndexDAO extends ReduceableDAO<IindexEntry> { getFullFromHash(hash:string): Promise<FullIindexEntry> - getMembersPubkeys(): Promise<{ pub:string }[]> - getToBeKickedPubkeys(): Promise<string[]> findAllByWrittenOn(): Promise<IindexEntry[]> diff --git a/app/lib/dal/indexDAL/abstract/SIndexDAO.ts b/app/lib/dal/indexDAL/abstract/SIndexDAO.ts index 8f96f4ba5..be5aa1703 100644 --- a/app/lib/dal/indexDAL/abstract/SIndexDAO.ts +++ b/app/lib/dal/indexDAL/abstract/SIndexDAO.ts @@ -1,23 +1,24 @@ -import {FullSindexEntry, SindexEntry} from "../../../indexer" +import {FullSindexEntry, SimpleTxEntryForWallet, SimpleTxInput, SindexEntry} from "../../../indexer" import {ReduceableDAO} from "./ReduceableDAO" -export interface SIndexDAO extends ReduceableDAO<SindexEntry> { +export interface UDSource { + consumed: boolean + pos: number + amount: number + base: number +} - findByIdentifierPosAmountBase(identifier: string, pos: number, amount: number, base: number): Promise<SindexEntry[]> +export interface SIndexDAO extends ReduceableDAO<SindexEntry> { - getSource(identifier:string, pos:number): Promise<FullSindexEntry|null> + findTxSourceByIdentifierPosAmountBase(identifier: string, pos: number, amount: number, base: number): Promise<SimpleTxInput[]> - getUDSources(pubkey:string): Promise<FullSindexEntry[]> + getTxSource(identifier:string, pos:number): Promise<FullSindexEntry|null> - getAvailableForPubkey(pubkey:string): Promise<{ amount:number, base:number }[]> + getAvailableForPubkey(pubkey:string): Promise<{ amount:number, base:number, conditions: string, identifier: string, pos: number }[]> getAvailableForConditions(conditionsStr:string): Promise<SindexEntry[]> trimConsumedSource(belowNumber:number): Promise<void> - //--------------------- - //- TESTING FUNCTIONS - - //--------------------- - - + getWrittenOnTxs(blockstamp: string): Promise<SimpleTxEntryForWallet[]> } diff --git a/app/lib/dal/indexDAL/loki/LokiBIndex.ts b/app/lib/dal/indexDAL/loki/LokiBIndex.ts index 255743c2d..c466e46f7 100644 --- a/app/lib/dal/indexDAL/loki/LokiBIndex.ts +++ b/app/lib/dal/indexDAL/loki/LokiBIndex.ts @@ -1,13 +1,13 @@ -import {LokiIndex} from "./LokiIndex" import {DBHead} from "../../../db/DBHead" import {BIndexDAO} from "../abstract/BIndexDAO" import {NewLogger} from "../../../logger" import {getDurationInMicroSeconds, getMicrosecondsTime} from "../../../../ProcessCpuProfiler" import {MonitorLokiExecutionTime} from "../../../debug/MonitorLokiExecutionTime" +import {LokiProtocolIndex} from "./LokiProtocolIndex" const logger = NewLogger() -export class LokiBIndex extends LokiIndex<DBHead> implements BIndexDAO { +export class LokiBIndex extends LokiProtocolIndex<DBHead> implements BIndexDAO { private HEAD:DBHead|null = null diff --git a/app/lib/dal/indexDAL/loki/LokiBlockchain.ts b/app/lib/dal/indexDAL/loki/LokiBlockchain.ts index 4a1536355..1c125c194 100644 --- a/app/lib/dal/indexDAL/loki/LokiBlockchain.ts +++ b/app/lib/dal/indexDAL/loki/LokiBlockchain.ts @@ -1,9 +1,9 @@ -import {LokiIndex} from "./LokiIndex" import {BlockchainDAO} from "../abstract/BlockchainDAO" import {DBBlock} from "../../../db/DBBlock" import {MonitorLokiExecutionTime} from "../../../debug/MonitorLokiExecutionTime" +import {LokiProtocolIndex} from "./LokiProtocolIndex" -export class LokiBlockchain extends LokiIndex<DBBlock> implements BlockchainDAO { +export class LokiBlockchain extends LokiProtocolIndex<DBBlock> implements BlockchainDAO { private current:DBBlock|null = null @@ -54,8 +54,12 @@ export class LokiBlockchain extends LokiIndex<DBBlock> implements BlockchainDAO return this.insertBatch(blocks) } - async insert(record: DBBlock): Promise<void> { - return super.insert(record); + async insertBatch(records: DBBlock[]): Promise<void> { + const lastInBatch = records[records.length - 1] + if (!this.current || this.current.number < lastInBatch.number) { + this.current = lastInBatch + } + return super.insertBatch(records) } async removeBlock(blockstamp: string): Promise<void> { diff --git a/app/lib/dal/indexDAL/loki/LokiCIndex.ts b/app/lib/dal/indexDAL/loki/LokiCIndex.ts index 81cc750b2..218ef22ee 100644 --- a/app/lib/dal/indexDAL/loki/LokiCIndex.ts +++ b/app/lib/dal/indexDAL/loki/LokiCIndex.ts @@ -1,10 +1,10 @@ import {CIndexDAO} from "../abstract/CIndexDAO" -import {LokiIndex} from "./LokiIndex" import {CindexEntry, FullCindexEntry, Indexer} from "../../../indexer" import {CommonConstants} from "../../../common-libs/constants" import {MonitorLokiExecutionTime} from "../../../debug/MonitorLokiExecutionTime" +import {LokiProtocolIndex} from "./LokiProtocolIndex" -export class LokiCIndex extends LokiIndex<CindexEntry> implements CIndexDAO { +export class LokiCIndex extends LokiProtocolIndex<CindexEntry> implements CIndexDAO { constructor(loki:any) { super(loki, 'cindex', ['issuer', 'receiver']) diff --git a/app/lib/dal/indexDAL/loki/LokiCollectionManager.ts b/app/lib/dal/indexDAL/loki/LokiCollectionManager.ts index f922c0fcf..0d6322360 100644 --- a/app/lib/dal/indexDAL/loki/LokiCollectionManager.ts +++ b/app/lib/dal/indexDAL/loki/LokiCollectionManager.ts @@ -12,7 +12,7 @@ export abstract class LokiCollectionManager<T> { public constructor( protected loki:any, - protected collectionName:'iindex'|'mindex'|'cindex'|'sindex'|'bindex'|'blockchain'|'txs'|'wallet'|'peer', + protected collectionName:'iindex'|'mindex'|'cindex'|'sindex'|'bindex'|'blockchain'|'txs'|'wallet'|'peer'|'dividend', protected indices: (keyof T)[]) { this.collectionIsInitialized = new Promise<void>(res => this.resolveCollection = res) } diff --git a/app/lib/dal/indexDAL/loki/LokiDividend.ts b/app/lib/dal/indexDAL/loki/LokiDividend.ts new file mode 100644 index 000000000..36d4d42f0 --- /dev/null +++ b/app/lib/dal/indexDAL/loki/LokiDividend.ts @@ -0,0 +1,243 @@ +import {LokiIndex} from "./LokiIndex" +import {DividendDAO, DividendEntry, UDSource} from "../abstract/DividendDAO" +import { + IindexEntry, + SimpleSindexEntryForWallet, + SimpleTxEntryForWallet, + SimpleTxInput, + SimpleUdEntryForWallet, + SindexEntry +} from "../../../indexer" +import {DataErrors} from "../../../common-libs/errors" + +export class LokiDividend extends LokiIndex<DividendEntry> implements DividendDAO { + + // private lokiDividend: + + constructor(loki:any) { + super(loki, 'dividend', ['pub']) + } + + async createMember(pub: string): Promise<void> { + const existing = this.collection.find({ pub })[0] + if (!existing) { + await this.insert({ pub, member: true, availables: [], dividends: [], consumed: [], consumedUDs: [] }) + } else { + await this.setMember(true, pub) + } + } + + async setMember(member: boolean, pub: string) { + await this.collection + .chain() + .find({ pub }) + .update(r => { + r.member = member + }) + } + + async deleteMember(pub: string): Promise<void> { + this.collection + .chain() + .find({ pub }) + .remove() + } + + async produceDividend(blockNumber: number, dividend: number, unitbase: number, local_iindex: IindexEntry[]): Promise<SimpleUdEntryForWallet[]> { + const dividends: SimpleUdEntryForWallet[] = [] + // Then produce the UD + this.collection + .chain() + .find({ member: true }) + .update(r => { + r.availables.push(blockNumber) + r.dividends.push({ amount: dividend, base: unitbase }) + dividends.push({ + srcType: 'D', + amount: dividend, + base: unitbase, + conditions: 'SIG(' + r.pub + ')', + op: 'CREATE', + identifier: r.pub, + pos: blockNumber + }) + }) + return dividends + } + + async consume(filter: SindexEntry[]): Promise<void> { + for (const dividendToConsume of filter) { + this.collection + .chain() + .find({ + pub: dividendToConsume.identifier + }) + .update(m => { + const index = m.availables.indexOf(dividendToConsume.pos) + + // We add it to the consumption history + m.consumed.push(dividendToConsume.writtenOn) + m.consumedUDs.push({ + dividendNumber: dividendToConsume.pos, + dividend: m.dividends[index] + }) + + // We remove it from available dividends + m.availables.splice(index, 1) + m.dividends.splice(index, 1) + }) + } + } + + async getUDSources(pub: string): Promise<UDSource[]> { + const member = this.collection + .chain() + .find({ pub }) + .data()[0] + if (!member) { + return [] + } + return member.availables.map(pos => this.toUDSource(member, pos) as UDSource) + } + + async findUdSourceByIdentifierPosAmountBase(identifier: string, pos: number, amount: number, base: number): Promise<SimpleTxInput[]> { + const member = this.collection.find({ pub: identifier })[0] + let src: UDSource|null = null + if (member) { + const udSrc = this.toUDSource(member, pos) + if (udSrc && udSrc.amount === amount && udSrc.base === base) { + src = udSrc + } + } + return [{ + written_time: 0, + conditions: 'SIG(' + identifier + ')', + consumed: !src, + amount, + base + }] + } + + private toUDSource(entry: DividendEntry, pos: number): UDSource|null { + const index = entry.availables.indexOf(pos) + if (index === -1) { + return null + } + const src = entry.dividends[index] + return { + consumed: false, + pos, + amount: src.amount, + base: src.base, + } + } + + async getUDSource(identifier: string, pos: number): Promise<SimpleTxInput|null> { + const member = this.collection.find({ pub: identifier })[0] + let src: UDSource|null = null + if (member) { + src = this.toUDSource(member, pos) + } + if (!src) { + return null + } + return { + written_time: 0, + conditions: 'SIG(' + identifier + ')', + consumed: !src, + amount: src.amount, + base: src.base + } + } + + async getWrittenOn(blockstamp: string): Promise<DividendEntry[]> { + throw Error(DataErrors[DataErrors.LOKI_DIVIDEND_GET_WRITTEN_ON_SHOULD_NOT_BE_USED]) + } + + async getWrittenOnUDs(number: number): Promise<SimpleUdEntryForWallet[]> { + const res: SimpleUdEntryForWallet[] = [] + this.collection + .chain() + .find({ availables: { $contains: number } }) + .data() + .map(m => { + const s = this.toUDSource(m, number) as UDSource + res.push({ + srcType: 'D', + op: 'CREATE', + conditions: 'SIG(' + m.pub + ')', + amount: s.amount, + base: s.base, + identifier: m.pub, + pos: s.pos + }) + }) + return res + } + + async removeBlock(blockstamp: string): Promise<void> { + throw Error(DataErrors[DataErrors.LOKI_DIVIDEND_REMOVE_BLOCK_SHOULD_NOT_BE_USED]) + } + + /** + * Remove UD data produced in a block, either UD production or UD consumption. + * @param {number} number Block number to revert the created UDs. + * @returns {Promise<{createdUDsDestroyedByRevert: SimpleUdEntryForWallet[]}>} + */ + async revertUDs(number: number): Promise<{ + createdUDsDestroyedByRevert: SimpleUdEntryForWallet[] + consumedUDsRecoveredByRevert: SimpleUdEntryForWallet[] + }> { + const createdUDsDestroyedByRevert: SimpleUdEntryForWallet[] = [] + const consumedUDsRecoveredByRevert: SimpleUdEntryForWallet[] = [] + // Remove produced dividends at this block + this.collection + .chain() + .find({ availables: { $contains: number }}) + .update(m => { + const index = m.availables.indexOf(number) + const src = m.dividends[index] + createdUDsDestroyedByRevert.push({ + conditions: 'SIG(' + m.pub + ')', + pos: number, + identifier: m.pub, + amount: src.amount, + base: src.base, + srcType: 'D', + op: 'CREATE' + }) + m.availables.splice(index, 1) + m.dividends.splice(index, 1) + }) + // Unconsumed dividends consumed at this block + this.collection + .chain() + .find({ consumed: { $contains: number }}) + .update(m => { + const index = m.consumed.indexOf(number) + + const src = m.consumedUDs[index].dividend + consumedUDsRecoveredByRevert.push({ + conditions: 'SIG(' + m.pub + ')', + pos: m.consumedUDs[index].dividendNumber, + identifier: m.pub, + amount: src.amount, + base: src.base, + srcType: 'D', + op: 'CREATE' + }) + + // We put it back as available + m.availables.push(m.consumedUDs[index].dividendNumber) + m.dividends.push(m.consumedUDs[index].dividend) + + // We remove it from consumed + m.consumed.splice(index, 1) + m.consumedUDs.splice(index, 1) + }) + return { + createdUDsDestroyedByRevert, + consumedUDsRecoveredByRevert, + } + } +} diff --git a/app/lib/dal/indexDAL/loki/LokiIIndex.ts b/app/lib/dal/indexDAL/loki/LokiIIndex.ts index d0fc9cfdc..e0effa5d8 100644 --- a/app/lib/dal/indexDAL/loki/LokiIIndex.ts +++ b/app/lib/dal/indexDAL/loki/LokiIIndex.ts @@ -2,7 +2,6 @@ import {FullIindexEntry, IindexEntry, Indexer} from "../../../indexer" import {IIndexDAO} from "../abstract/IIndexDAO" import {LokiPubkeySharingIndex} from "./LokiPubkeySharingIndex" import {OldIindexEntry} from "../../../db/OldIindexEntry" -import {MonitorLokiExecutionTime} from "../../../debug/MonitorLokiExecutionTime" export class LokiIIndex extends LokiPubkeySharingIndex<IindexEntry> implements IIndexDAO { @@ -129,11 +128,6 @@ export class LokiIIndex extends LokiPubkeySharingIndex<IindexEntry> implements I ) as Promise<FullIindexEntry|null> } - @MonitorLokiExecutionTime() - async getMembersPubkeys(): Promise<{ pub: string }[]> { - return (await this.getMembers()).map(m => ({ pub: m.pubkey })) - } - async getToBeKickedPubkeys(): Promise<string[]> { return this.collection // Those who are still marked member somewhere diff --git a/app/lib/dal/indexDAL/loki/LokiIndex.ts b/app/lib/dal/indexDAL/loki/LokiIndex.ts index e4551fb26..877cdc511 100644 --- a/app/lib/dal/indexDAL/loki/LokiIndex.ts +++ b/app/lib/dal/indexDAL/loki/LokiIndex.ts @@ -2,12 +2,7 @@ import {GenericDAO} from "../abstract/GenericDAO" import {LokiCollectionManager} from "./LokiCollectionManager" import {MonitorLokiExecutionTime} from "../../../debug/MonitorLokiExecutionTime" -export interface IndexData { - written_on: string - writtenOn: number -} - -export abstract class LokiIndex<T extends IndexData> extends LokiCollectionManager<T> implements GenericDAO<T> { +export abstract class LokiIndex<T> extends LokiCollectionManager<T> implements GenericDAO<T> { cleanCache(): void { } @@ -35,15 +30,6 @@ export abstract class LokiIndex<T extends IndexData> extends LokiCollectionManag records.map(r => this.insert(r)) } - @MonitorLokiExecutionTime(true) - async getWrittenOn(blockstamp: string): Promise<T[]> { - const criterion:any = { writtenOn: parseInt(blockstamp) } - return this.collection.find(criterion) - } - - @MonitorLokiExecutionTime(true) - async removeBlock(blockstamp: string): Promise<void> { - const data = await this.getWrittenOn(blockstamp) - data.map(d => this.collection.remove(d)) - } + abstract getWrittenOn(blockstamp: string): Promise<T[]> + abstract removeBlock(blockstamp: string): Promise<void> } diff --git a/app/lib/dal/indexDAL/loki/LokiProtocolIndex.ts b/app/lib/dal/indexDAL/loki/LokiProtocolIndex.ts new file mode 100644 index 000000000..90848ed99 --- /dev/null +++ b/app/lib/dal/indexDAL/loki/LokiProtocolIndex.ts @@ -0,0 +1,23 @@ +import {GenericDAO} from "../abstract/GenericDAO" +import {MonitorLokiExecutionTime} from "../../../debug/MonitorLokiExecutionTime" +import {LokiIndex} from "./LokiIndex" + +export interface IndexData { + written_on: string + writtenOn: number +} + +export abstract class LokiProtocolIndex<T extends IndexData> extends LokiIndex<T> implements GenericDAO<T> { + + @MonitorLokiExecutionTime(true) + async getWrittenOn(blockstamp: string): Promise<T[]> { + const criterion:any = { writtenOn: parseInt(blockstamp) } + return this.collection.find(criterion) + } + + @MonitorLokiExecutionTime(true) + async removeBlock(blockstamp: string): Promise<void> { + const data = await this.getWrittenOn(blockstamp) + data.map(d => this.collection.remove(d)) + } +} diff --git a/app/lib/dal/indexDAL/loki/LokiPubkeySharingIndex.ts b/app/lib/dal/indexDAL/loki/LokiPubkeySharingIndex.ts index e44ee62f2..8b0d43274 100644 --- a/app/lib/dal/indexDAL/loki/LokiPubkeySharingIndex.ts +++ b/app/lib/dal/indexDAL/loki/LokiPubkeySharingIndex.ts @@ -1,8 +1,9 @@ import {Indexer} from "../../../indexer" import {LokiIndex} from "./LokiIndex" import {MonitorLokiExecutionTime} from "../../../debug/MonitorLokiExecutionTime" +import {LokiProtocolIndex} from "./LokiProtocolIndex" -export class LokiPubkeySharingIndex<T extends { written_on:string, writtenOn:number, pub:string }> extends LokiIndex<T> { +export class LokiPubkeySharingIndex<T extends { written_on:string, writtenOn:number, pub:string }> extends LokiProtocolIndex<T> { @MonitorLokiExecutionTime(true) async trimRecords(belowNumber: number): Promise<void> { diff --git a/app/lib/dal/indexDAL/loki/LokiSIndex.ts b/app/lib/dal/indexDAL/loki/LokiSIndex.ts index 2dbbfb249..180437f29 100644 --- a/app/lib/dal/indexDAL/loki/LokiSIndex.ts +++ b/app/lib/dal/indexDAL/loki/LokiSIndex.ts @@ -1,16 +1,27 @@ import {LokiIndex} from "./LokiIndex" -import {FullSindexEntry, Indexer, SindexEntry} from "../../../indexer" +import { + FullSindexEntry, + Indexer, + SimpleSindexEntryForWallet, + SimpleTxEntryForWallet, + SindexEntry +} from "../../../indexer" import {SIndexDAO} from "../abstract/SIndexDAO" import {Underscore} from "../../../common-libs/underscore" import {MonitorLokiExecutionTime} from "../../../debug/MonitorLokiExecutionTime" +import {LokiProtocolIndex} from "./LokiProtocolIndex" +import {LokiDividend} from "./LokiDividend" -export class LokiSIndex extends LokiIndex<SindexEntry> implements SIndexDAO { +export class LokiSIndex extends LokiProtocolIndex<SindexEntry> implements SIndexDAO { + + private lokiDividend: LokiDividend constructor(loki:any) { super(loki, 'sindex', ['identifier', 'conditions', 'writtenOn']) + this.lokiDividend = new LokiDividend(loki) } - async findByIdentifierPosAmountBase(identifier: string, pos: number, amount: number, base: number): Promise<SindexEntry[]> { + async findTxSourceByIdentifierPosAmountBase(identifier: string, pos: number, amount: number, base: number): Promise<SindexEntry[]> { return this.collection .chain() .find({ identifier, pos, amount, base }) @@ -36,7 +47,7 @@ export class LokiSIndex extends LokiIndex<SindexEntry> implements SIndexDAO { return Underscore.sortBy(sources, (row:SindexEntry) => row.type == 'D' ? 0 : 1) } - async getAvailableForPubkey(pubkey: string): Promise<{ amount: number; base: number }[]> { + async getAvailableForPubkey(pubkey: string): Promise<{ amount: number; base: number, conditions: string, identifier: string, pos: number }[]> { return this.collection .chain() .find({ conditions: { $regex: 'SIG\\(' + pubkey + '\\)' } }) @@ -49,11 +60,11 @@ export class LokiSIndex extends LokiIndex<SindexEntry> implements SIndexDAO { }) } - async getSource(identifier: string, pos: number): Promise<FullSindexEntry | null> { + async getTxSource(identifier: string, pos: number): Promise<FullSindexEntry | null> { const reducables = this.collection .chain() .find({ identifier, pos }) - .simplesort('writtenOn') + .compoundsort([['writtenOn', false], ['op', false]]) .data() .map(src => { src.type = src.tx ? 'T' : 'D' @@ -65,24 +76,6 @@ export class LokiSIndex extends LokiIndex<SindexEntry> implements SIndexDAO { return Indexer.DUP_HELPERS.reduce(reducables) } - async getUDSources(pubkey: string): Promise<FullSindexEntry[]> { - const reducables = this.collection - .chain() - .find({ - $and: [ - { tx: null }, - { conditions: 'SIG(' + pubkey + ')' }, - ] - }) - .simplesort('writtenOn') - .data() - .map(src => { - src.type = src.tx ? 'T' : 'D' - return src - }) - return Indexer.DUP_HELPERS.reduceBy(reducables, ['identifier', 'pos']) - } - @MonitorLokiExecutionTime(true) async trimConsumedSource(belowNumber: number): Promise<void> { const consumed = this.collection @@ -108,5 +101,20 @@ export class LokiSIndex extends LokiIndex<SindexEntry> implements SIndexDAO { return this.trimConsumedSource(belowNumber) } - + async getWrittenOnTxs(blockstamp: string): Promise<SimpleTxEntryForWallet[]> { + const entries = (await this.getWrittenOn(blockstamp)) + const res: SimpleTxEntryForWallet[] = [] + entries.forEach(s => { + res.push({ + srcType: 'T', + op: s.op, + conditions: s.conditions, + amount: s.amount, + base: s.base, + identifier: s.identifier, + pos: s.pos + }) + }) + return res + } } diff --git a/app/lib/dal/indexDAL/loki/LokiTransactions.ts b/app/lib/dal/indexDAL/loki/LokiTransactions.ts index 76f87a2a0..7db59faf4 100644 --- a/app/lib/dal/indexDAL/loki/LokiTransactions.ts +++ b/app/lib/dal/indexDAL/loki/LokiTransactions.ts @@ -18,10 +18,11 @@ import {SandBox} from "../../sqliteDAL/SandBox" import {TransactionDTO} from "../../../dto/TransactionDTO" import {DBTx} from "../../../db/DBTx" import {Underscore} from "../../../common-libs/underscore" +import {LokiProtocolIndex} from "./LokiProtocolIndex" const constants = require('../../../constants') -export class LokiTransactions extends LokiIndex<DBTx> implements TxsDAO { +export class LokiTransactions extends LokiProtocolIndex<DBTx> implements TxsDAO { constructor(loki: any) { super(loki, 'txs', []) diff --git a/app/lib/dto/BlockDTO.ts b/app/lib/dto/BlockDTO.ts index dba24f303..7ee505e4e 100644 --- a/app/lib/dto/BlockDTO.ts +++ b/app/lib/dto/BlockDTO.ts @@ -32,7 +32,7 @@ export class BlockDTO implements Cloneable { previousHash: string issuer: string previousIssuer: string - dividend: number + dividend: number|null time: number powMin: number unitbase: number diff --git a/app/lib/dto/TransactionDTO.ts b/app/lib/dto/TransactionDTO.ts index b443b1a80..3aefab836 100644 --- a/app/lib/dto/TransactionDTO.ts +++ b/app/lib/dto/TransactionDTO.ts @@ -23,7 +23,7 @@ export class InputDTO implements BaseDTO { constructor( public amount: number, public base: number, - public type: string, + public type: 'T'|'D', public identifier: string, public pos: number, public raw: string @@ -134,7 +134,7 @@ export class TransactionDTO implements Cloneable { return new InputDTO( parseInt(amount), parseInt(base), - type, + type as 'T'|'D', identifier, parseInt(pos), input diff --git a/app/lib/indexer.ts b/app/lib/indexer.ts index 6e5616d20..4220688dc 100644 --- a/app/lib/indexer.ts +++ b/app/lib/indexer.ts @@ -28,6 +28,7 @@ import {DBBlock} from "./db/DBBlock" import {DBWallet} from "./db/DBWallet" import {Tristamp} from "./common/Tristamp" import {Underscore} from "./common-libs/underscore" +import {DataErrors} from "./common-libs/errors" const constants = CommonConstants @@ -146,6 +147,7 @@ export interface FullCindexEntry { } export interface SindexEntry extends IndexEntry { + srcType: 'T'|'D' tx: string | null, identifier: string, pos: number, @@ -179,6 +181,37 @@ export interface FullSindexEntry { consumed: boolean } +export interface SimpleTxInput { + conditions: string + consumed: boolean + written_time: number + amount: number + base: number +} + +export interface BasedAmount { + amount: number + base: number +} + +export interface SimpleSindexEntryForWallet { + op: string + srcType: 'T'|'D' + conditions: string + amount: number + base: number + identifier: string + pos: number +} + +export interface SimpleTxEntryForWallet extends SimpleSindexEntryForWallet { + srcType: 'T' +} + +export interface SimpleUdEntryForWallet extends SimpleSindexEntryForWallet { + srcType: 'D' +} + export interface Ranger { (n:number, m:number): Promise<DBHead[]> } @@ -461,6 +494,7 @@ export class Indexer { index.push({ index: constants.S_INDEX, op: constants.IDX_UPDATE, + srcType: input.type, tx: txHash, identifier: input.identifier, pos: input.pos, @@ -485,6 +519,7 @@ export class Indexer { index.push({ index: constants.S_INDEX, op: constants.IDX_CREATE, + srcType: 'T', tx: txHash, identifier: txHash, pos: i++, @@ -537,7 +572,7 @@ export class Indexer { HEAD.powMin = block.powMin HEAD.unitBase = block.unitbase HEAD.membersCount = block.membersCount - HEAD.dividend = block.dividend + HEAD.dividend = block.dividend ||Â 0 HEAD.new_dividend = null const HEAD_1 = await head(1); @@ -972,18 +1007,19 @@ export class Indexer { } })) - const getInputLocalFirstOrFallbackGlobally = async (sindex:SindexEntry[], ENTRY:SindexEntry) => { - let source = Underscore.filter(sindex, src => + const getInputLocalFirstOrFallbackGlobally = async (sindex:SindexEntry[], ENTRY:SindexEntry): Promise<SimpleTxInput> => { + let source: SimpleTxInput|null = Underscore.filter(sindex, src => src.identifier == ENTRY.identifier && src.pos == ENTRY.pos && src.conditions !== '' && src.op === constants.IDX_CREATE)[0]; if (!source) { - const reducable = await dal.sindexDAL.findByIdentifierPosAmountBase( + const reducable = await dal.findByIdentifierPosAmountBase( ENTRY.identifier, ENTRY.pos, ENTRY.amount, - ENTRY.base + ENTRY.base, + ENTRY.srcType === 'D' ); source = reduce(reducable) } @@ -994,7 +1030,7 @@ export class Indexer { await Promise.all(Underscore.where(sindex, { op: constants.IDX_UPDATE }).map(async (ENTRY: SindexEntry) => { const source = await getInputLocalFirstOrFallbackGlobally(sindex, ENTRY) ENTRY.conditions = source.conditions; // We valuate the input conditions, so we can map these records to a same account - ENTRY.available = source.consumed === false; + ENTRY.available = !source.consumed })) // BR_G47 @@ -1640,34 +1676,22 @@ export class Indexer { } // BR_G91 - static async ruleIndexGenDividend(HEAD: DBHead, local_iindex: IindexEntry[], dal: FileDAL) { - const dividends = []; + static async ruleIndexGenDividend(HEAD: DBHead, local_iindex: IindexEntry[], dal: FileDAL): Promise<SimpleUdEntryForWallet[]> { + // Create the newcomers first, as they will produce a dividend too + for (const newcomer of local_iindex) { + await dal.dividendDAL.createMember(newcomer.pub) + } if (HEAD.new_dividend) { - const members = (await dal.iindexDAL.getMembersPubkeys()).concat(local_iindex.filter(i => i.member)) - for (const MEMBER of members) { - dividends.push({ - index: constants.S_INDEX, - op: 'CREATE', - tx: null, - identifier: MEMBER.pub, - pos: HEAD.number, - written_on: [HEAD.number, HEAD.hash].join('-'), - writtenOn: HEAD.number, - written_time: HEAD.medianTime, - amount: HEAD.dividend, - base: HEAD.unitBase, - locktime: null, - conditions: 'SIG(' + MEMBER.pub + ')', - consumed: false - }); - } + return dal.updateDividend(HEAD.number, HEAD.new_dividend, HEAD.unitBase, local_iindex) } - return dividends; + return [] } // BR_G106 - static async ruleIndexGarbageSmallAccounts(HEAD: DBHead, sindex: SindexEntry[], dal:AccountsGarbagingDAL) { - const garbages = []; + static async ruleIndexGarbageSmallAccounts(HEAD: DBHead, transactions: SindexEntry[], dividends: SimpleUdEntryForWallet[], dal:AccountsGarbagingDAL) { + let sindex: SimpleSindexEntryForWallet[] = transactions + sindex = sindex.concat(dividends) + const garbages: SindexEntry[] = []; const accounts = Object.keys(sindex.reduce((acc: { [k:string]: boolean }, src) => { acc[src.conditions] = true; return acc; @@ -1677,7 +1701,7 @@ export class Indexer { return map; }, {}); for (const account of accounts) { - const localAccountEntries = Underscore.filter(sindex, (src:SindexEntry) => src.conditions == account); + const localAccountEntries = Underscore.filter(sindex, src => src.conditions == account) const wallet = await wallets[account]; const balance = wallet.balance const variations = localAccountEntries.reduce((sum:number, src:SindexEntry) => { @@ -1687,15 +1711,19 @@ export class Indexer { return sum - src.amount * Math.pow(10, src.base); } }, 0) - // console.log('Balance of %s = %s (%s)', account, balance, variations > 0 ? '+' + variations : variations) - if (balance + variations < constants.ACCOUNT_MINIMUM_CURRENT_BASED_AMOUNT * Math.pow(10, HEAD.unitBase)) { + if (balance + variations < 0) { + throw Error(DataErrors[DataErrors.NEGATIVE_BALANCE]) + } + else if (balance + variations < constants.ACCOUNT_MINIMUM_CURRENT_BASED_AMOUNT * Math.pow(10, HEAD.unitBase)) { const globalAccountEntries = await dal.sindexDAL.getAvailableForConditions(account) for (const src of localAccountEntries.concat(globalAccountEntries)) { - const sourceBeingConsumed = Underscore.filter(sindex, (entry:SindexEntry) => entry.op === 'UPDATE' && entry.identifier == src.identifier && entry.pos == src.pos).length > 0; + const sourceBeingConsumed = Underscore.filter(sindex, entry => entry.op === 'UPDATE' && entry.identifier == src.identifier && entry.pos == src.pos).length > 0; if (!sourceBeingConsumed) { garbages.push({ + index: 'SINDEX', op: 'UPDATE', - tx: src.tx, + srcType: 'T', + tx: null, identifier: src.identifier, pos: src.pos, amount: src.amount, @@ -1704,7 +1732,14 @@ export class Indexer { writtenOn: HEAD.number, written_time: HEAD.medianTime, conditions: src.conditions, - consumed: true // It is now consumed + consumed: true, // It is now consumed + + // TODO: make these fields not required using good types + created_on: '', + locktime: 0, + unlock: null, + txObj: {} as TransactionDTO, + age: 0, }); } } @@ -1929,7 +1964,7 @@ function blockstamp(aNumber: number, aHash: string) { function reduce<T>(records: T[]): T { return records.reduce((obj:T, record) => { - const keys = Underscore.keys(record) + const keys = Object.keys(record) as (keyof T)[] for (const k of keys) { if (record[k] !== undefined && record[k] !== null) { obj[k] = record[k]; @@ -2087,7 +2122,7 @@ async function checkCertificationIsValid (block: BlockDTO, cert: CindexEntry, fi } } -function txSourceUnlock(ENTRY:SindexEntry, source:SindexEntry, HEAD: DBHead) { +function txSourceUnlock(ENTRY:SindexEntry, source:{ conditions: string, written_time: number}, HEAD: DBHead) { const tx = ENTRY.txObj; const unlockParams:string[] = TransactionDTO.unlock2params(ENTRY.unlock || '') const unlocksMetadata:UnlockMetadata = {} diff --git a/app/lib/other_constants.ts b/app/lib/other_constants.ts index 2e46465ee..2b0139b50 100644 --- a/app/lib/other_constants.ts +++ b/app/lib/other_constants.ts @@ -24,4 +24,5 @@ export const OtherConstants = { ENABLE_LOKI_MONITORING: false, ENABLE_SQL_MONITORING: false, + TRACE_BALANCES: false } \ No newline at end of file diff --git a/app/lib/rules/global_rules.ts b/app/lib/rules/global_rules.ts index 417b91c35..57721cfb1 100644 --- a/app/lib/rules/global_rules.ts +++ b/app/lib/rules/global_rules.ts @@ -21,7 +21,7 @@ import {rawer, txunlock} from "../common-libs/index" import {CommonConstants} from "../common-libs/constants" import {IdentityDTO} from "../dto/IdentityDTO" import {hashf} from "../common" -import {Indexer} from "../indexer" +import {Indexer, SimpleTxInput} from "../indexer" import {DBTx} from "../db/DBTx" import {Tristamp} from "../common/Tristamp" @@ -109,7 +109,7 @@ export const GLOBAL_RULES_FUNCTIONS = { } for (let k = 0, len2 = inputs.length; k < len2; k++) { let src = inputs[k]; - let dbSrc = await dal.getSource(src.identifier, src.pos); + let dbSrc: SimpleTxInput|null = await dal.getSource(src.identifier, src.pos, src.type === 'D'); logger.debug('Source %s:%s:%s:%s = %s', src.amount, src.base, src.identifier, src.pos, dbSrc && dbSrc.consumed); if (!dbSrc) { // For chained transactions which are checked on sandbox submission, we accept them if there is already diff --git a/app/modules/bma/lib/controllers/transactions.ts b/app/modules/bma/lib/controllers/transactions.ts index e921f4d88..a56834b50 100644 --- a/app/modules/bma/lib/controllers/transactions.ts +++ b/app/modules/bma/lib/controllers/transactions.ts @@ -16,7 +16,7 @@ import {ParametersService} from "../parameters"; import {Source} from "../entity/source"; import {BMAConstants} from "../constants"; import {TransactionDTO} from "../../../../lib/dto/TransactionDTO"; -import {HttpSources, HttpTransaction, HttpTxHistory, HttpTxOfHistory, HttpTxPending} from "../dtos"; +import {HttpSource, HttpSources, HttpTransaction, HttpTxHistory, HttpTxOfHistory, HttpTxPending} from "../dtos"; import {DBTx} from "../../../../lib/db/DBTx" import {Underscore} from "../../../../lib/common-libs/underscore" @@ -45,15 +45,11 @@ export class TransactionBinding extends AbstractController { async getSources(req:any): Promise<HttpSources> { const pubkey = await ParametersService.getPubkeyP(req); const sources = await this.server.dal.getAvailableSourcesByPubkey(pubkey); - const result:any = { - "currency": this.conf.currency, - "pubkey": pubkey, - "sources": [] - }; - sources.forEach(function (src:any) { - result.sources.push(new Source(src).json()); - }); - return result; + return { + currency: this.conf.currency, + pubkey, + sources + } } async getByHash(req:any): Promise<HttpTransaction> { diff --git a/test/dal/sources-dal.ts b/test/dal/sources-dal.ts index 58a45b06a..3288f5031 100644 --- a/test/dal/sources-dal.ts +++ b/test/dal/sources-dal.ts @@ -39,11 +39,9 @@ describe("Source DAL", function(){ sourcesOfDEF.should.have.length(1); const sourcesOfABC = await dal.sindexDAL.getAvailableForPubkey('ABC'); sourcesOfABC.should.have.length(1); - const source1 = await dal.sindexDAL.getSource('SOURCE_1', 4) as any + const source1 = await dal.sindexDAL.getTxSource('SOURCE_1', 4) as any source1.should.have.property('consumed').equal(true); - const udSources = await dal.sindexDAL.getUDSources('ABC'); - udSources.should.have.length(2); - udSources[0].should.have.property('consumed').equal(false); - udSources[1].should.have.property('consumed').equal(true); + const source2 = await dal.sindexDAL.getTxSource('SOURCE_2', 4) as any + source2.should.have.property('consumed').equal(false); }) }) diff --git a/test/fast/dal/basic-loki.ts b/test/fast/dal/basic-loki.ts index d95f0b1a3..2f26a3b84 100644 --- a/test/fast/dal/basic-loki.ts +++ b/test/fast/dal/basic-loki.ts @@ -13,6 +13,7 @@ import * as assert from "assert" import {LokiIndex} from "../../../app/lib/dal/indexDAL/loki/LokiIndex" +import {LokiProtocolIndex} from "../../../app/lib/dal/indexDAL/loki/LokiProtocolIndex" const loki = require('lokijs') @@ -24,7 +25,7 @@ interface TestEntity { let lokiIndex:LokiIndex<TestEntity> -class TheIndex extends LokiIndex<TestEntity> { +class TheIndex extends LokiProtocolIndex<TestEntity> { } describe("Basic LokiJS database", () => { diff --git a/test/fast/protocol/protocol-brg106-number.ts b/test/fast/protocol/protocol-brg106-number.ts index 0af3d3f9c..cf3675c14 100644 --- a/test/fast/protocol/protocol-brg106-number.ts +++ b/test/fast/protocol/protocol-brg106-number.ts @@ -62,33 +62,33 @@ describe("Protocol BR_G106 - Garbaging", function(){ // C sends 100 to E --> C keeps 0 { op: 'UPDATE', conditions: 'pubkeyC', amount: 100, base: 0, identifier: 'I8', pos: 0 }, { op: 'CREATE', conditions: 'pubkeyE', amount: 100, base: 0, identifier: 'I9', pos: 0 } - ] as any, dal as any); + ] as any, [], dal as any); cleaning.should.have.length(5); cleaning[0].should.have.property('identifier').equal('I3'); cleaning[0].should.have.property('amount').equal(3); cleaning[0].should.have.property('base').equal(0); - cleaning[0].should.have.property('tx').equal(undefined); + cleaning[0].should.have.property('tx').equal(null); cleaning[0].should.have.property('consumed').equal(true); cleaning[0].should.have.property('op').equal('UPDATE'); cleaning[1].should.have.property('identifier').equal('I7'); cleaning[1].should.have.property('amount').equal(5); cleaning[1].should.have.property('base').equal(0); - cleaning[1].should.have.property('tx').equal(undefined); + cleaning[1].should.have.property('tx').equal(null); cleaning[1].should.have.property('consumed').equal(true); cleaning[1].should.have.property('op').equal('UPDATE'); cleaning[2].should.have.property('identifier').equal('I5'); cleaning[2].should.have.property('amount').equal(9); cleaning[2].should.have.property('base').equal(0); - cleaning[2].should.have.property('tx').equal(undefined); + cleaning[2].should.have.property('tx').equal(null); cleaning[2].should.have.property('consumed').equal(true); cleaning[2].should.have.property('op').equal('UPDATE'); cleaning[3].should.have.property('identifier').equal('I10'); cleaning[3].should.have.property('amount').equal(22); cleaning[3].should.have.property('base').equal(0); - cleaning[3].should.have.property('tx').equal('B2'); + cleaning[3].should.have.property('tx').equal(null); cleaning[3].should.have.property('consumed').equal(true); cleaning[3].should.have.property('op').equal('UPDATE'); diff --git a/test/integration/branches/branches_revert2.ts b/test/integration/branches/branches_revert2.ts index 34d59d316..f00cf81a5 100644 --- a/test/integration/branches/branches_revert2.ts +++ b/test/integration/branches/branches_revert2.ts @@ -102,7 +102,7 @@ describe("Revert two blocks", function() { }); }); - it('/tx/sources/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd should have only UD', function() { + it('/tx/sources/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd should have nothing left because of garbaging', function() { return expectAnswer(rp('http://127.0.0.1:7712/tx/sources/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'), (body:string) => { let res = JSON.parse(body); res.sources.should.have.length(0) @@ -132,7 +132,7 @@ describe("Revert two blocks", function() { }); }) - describe("after revert", () => { + describe("after revert of transaction", () => { before(async () => { await s1.revert(); @@ -185,10 +185,49 @@ describe("Revert two blocks", function() { }); }) + describe("after revert of UD", () => { + + before(async () => { + await s1.revert(); + }) + + it('/block/0 should exist', function() { + return expectJSON(rp('http://127.0.0.1:7712/blockchain/block/0', { json: true }), { + number: 0 + }); + }); + + it('/block/2 should NOT exist', function() { + return expectHttpCode(404, rp('http://127.0.0.1:7712/blockchain/block/2', { json: true })); + }); + + it('/tx/sources/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd should have nothing', function() { + return expectAnswer(rp('http://127.0.0.1:7712/tx/sources/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'), (body:string) => { + let res = JSON.parse(body); + res.sources.should.have.length(0) + }); + }); + + it('/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo should have nothing', function() { + return expectAnswer(rp('http://127.0.0.1:7712/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo'), (body:string) => { + let res = JSON.parse(body); + res.sources.should.have.length(0) + }); + }); + + it('/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV should have nothing', function() { + return expectAnswer(rp('http://127.0.0.1:7712/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV'), (body:string) => { + let res = JSON.parse(body); + res.sources.should.have.length(0) + }); + }); + }) + describe("commit again (but send less, to check that the account is not cleaned this time)", () => { before(async () => { await s1.dal.txsDAL.removeAll() + await s1.resolveExistingBlock(1) // UD block await cat.sendMoney(19, toc); await s1.dal.blockDAL.removeBlock('DELETE FROM block WHERE fork AND number = 3') await s1.commit({ time: now + 1 }); -- GitLab