diff --git a/migrations-shelley/1574938181811_add-accounts-table.js b/migrations-shelley/1574938181811_add-accounts-table.js index a5ecf5f..9f764aa 100644 --- a/migrations-shelley/1574938181811_add-accounts-table.js +++ b/migrations-shelley/1574938181811_add-accounts-table.js @@ -5,15 +5,16 @@ CREATE TABLE accounts ( epoch integer, slot integer, tx_ordinal integer, - block_num integer, - operation_id text UNIQUE, + block_num bigint, + operation_id text, operation_type integer, account text, - value integer, - balance integer, + value bigint, + balance bigint, spending_counter integer, PRIMARY KEY (operation_id, account) ); +CREATE INDEX ON accounts (operation_id); ` exports.up = (pgm) => { diff --git a/src/blockchain/common/index.js b/src/blockchain/common/index.js index 31e2cab..b5d9eaf 100644 --- a/src/blockchain/common/index.js +++ b/src/blockchain/common/index.js @@ -3,7 +3,7 @@ export { Block } from './block' export type { EpochIdType, SlotIdType } from './block' export type { - AccountInput, TxInputType, TxType, UtxoInput, + AccountInputType, TxInputType, TxType, UtxoInputType, } from './tx' export { TX_STATUS } from './tx' export { default as utils } from './utils' diff --git a/src/blockchain/common/tx.js b/src/blockchain/common/tx.js index 1b929ba..7816bda 100644 --- a/src/blockchain/common/tx.js +++ b/src/blockchain/common/tx.js @@ -7,19 +7,19 @@ * features are added this will have to change. */ -export type UtxoInput = { +export type UtxoInputType = { type: 'utxo', txId: string, idx: number, } -export type AccountInput = { +export type AccountInputType = { type: 'account', account_id: string, value: number, } -export type TxInputType = UtxoInput | AccountInput +export type TxInputType = UtxoInputType | AccountInputType export type TxOutputType = { // derived from address, here for convenience diff --git a/src/blockchain/shelley/block.js b/src/blockchain/shelley/block.js index c105d5f..24293da 100644 --- a/src/blockchain/shelley/block.js +++ b/src/blockchain/shelley/block.js @@ -115,6 +115,8 @@ export default class ShelleyBlock implements Block { txOrdinal: index, blockNum: chainLength, blockHash, + epoch: epochId, + slot: slotId, } const fragment = fragments.get(index) if (fragment.is_transaction()) console.log(`#${index} = TRANSACTION`) diff --git a/src/entities/postgres-storage/db-shelley.js b/src/entities/postgres-storage/db-shelley.js index d1bba4e..ae8a232 100644 --- a/src/entities/postgres-storage/db-shelley.js +++ b/src/entities/postgres-storage/db-shelley.js @@ -3,7 +3,11 @@ import _ from 'lodash' import { CERT_TYPE } from '../../blockchain/shelley/certificate' -import type { ShelleyTxType as TxType } from '../../blockchain/shelley/tx' +import type { + ShelleyTxType as TxType, +} from '../../blockchain/shelley/tx' + +import type { AccountInputType, Block } from '../../blockchain/common' import type { Database } from '../../interfaces' import DB from './database' @@ -12,6 +16,7 @@ import Q from './db-queries' const DELEGATION_CERTIFICATES_TBL = 'delegation_certificates' const ACCOUNTS_TBL = 'accounts' +const ACCOUNT_INP_TYPE = 'account' const ACCOUNT_OP_TYPE = { REGULAR_TX: 0, @@ -53,41 +58,96 @@ class DBShelley extends DB implements Database { await this.getConn().query(sql) } + async getAccountDbData(accountInputs: Array): Promise<{ + }> { + const accountIds = _.map(accountInputs, 'account_id') + const query = Q.sql.select() + .from(Q.sql.select() + .from(ACCOUNTS_TBL) + .where('account in ?', accountIds) + .order('account') + .order('spending_counter', false) + .distinct('account'), 't') + .order('spending_counter', false) + .toString() + const dbRes = await this.getConn().query(query) + let result = {} + for (const row of dbRes.rows) { + result = { + ...result, + ...{ + [row.account]: { + balance: parseInt(row.balance, 10), + counter: parseInt(row.spending_counter, 10), + }, + }, + } + } + return result + } + async storeAccountsChanges(tx: TxType): Promise { - const accountInputs = tx.inputs.filter(inp => inp.type === 'account') - const accountOutputs = tx.outputs.filter(out => out.type === 'account') - if (_.isEmpty([...accountInputs, ...accountOutputs])) { + const accountInputs = tx.inputs.filter(inp => inp.type === ACCOUNT_INP_TYPE) + const accountOutputs = tx.outputs.filter(out => out.type === ACCOUNT_INP_TYPE) + const allAccountIdsAndValues = [ + ...accountInputs, + ...(accountOutputs).map(inp => ({ + account_id: inp.address, + value: inp.value, + })), + ] + if (_.isEmpty(allAccountIdsAndValues)) { return } - const accountsBalance = new Map() - accountInputs.forEach((account, counter) => { + const accountStoredData = await this.getAccountDbData(allAccountIdsAndValues) + + const accountChanges = {} + accountInputs.forEach(account => { const { account_id, value } = account - const currentBalance = accountsBalance.get(account_id) - if (currentBalance !== undefined) { - accountsBalance.set(account_id, currentBalance - value) + const currentChange = accountChanges[account_id] + if (currentChange !== undefined) { + accountChanges[account_id] = { + value: currentChange - value, + counter: accountChanges[account_id] + 1, + } } else { - accountsBalance.set(account_id, 0 - value) + accountChanges[account_id] = { value: 0 - value, counter: 1 } } }) - accountOutputs.forEach((account, counter) => { + accountOutputs.forEach(account => { const { address, value } = account - const currentBalance = accountsBalance.get(address) - if (currentBalance !== undefined) { - accountsBalance.set(address, currentBalance + value) + const currentChange = accountChanges[address] + if (currentChange !== undefined) { + accountChanges[address] = { + value: currentChange + value, + counter: accountChanges[address].counter, + } } else { - accountsBalance.set(address, value) + accountChanges[address] = { value, counter: 0 } } }) const accountsData = [] - for (const [account, balance] of accountsBalance) { + for (const [account, data] of _.toPairs(accountChanges)) { + let previousBalance = 0 + let previousCounter = 0 + if (accountStoredData[account] !== undefined) { + previousBalance = accountStoredData[account].balance + previousCounter = accountStoredData[account].counter + } accountsData.push({ - account, - balance: Math.ceil(balance / 100000), + epoch: tx.epoch, + slot: tx.slot, + tx_ordinal: tx.txOrdinal, + block_num: tx.blockNum, + operation_id: tx.id, operation_type: ACCOUNT_OP_TYPE.REGULAR_TX, - operation_id: `${account}:${tx.id}`, + account, + value: data.value, + balance: previousBalance + data.value, + spending_counter: previousCounter + data.counter, }) } const conn = this.getConn() @@ -95,6 +155,7 @@ class DBShelley extends DB implements Database { .into(ACCOUNTS_TBL) .setFieldsRows(accountsData) .toString() + this.logger.debug('storeAccountsChanges', sql) await conn.query(sql) } diff --git a/src/entities/raw-data-parsers/shelley-data-parser.js b/src/entities/raw-data-parsers/shelley-data-parser.js index 20a57c0..b88c671 100644 --- a/src/entities/raw-data-parsers/shelley-data-parser.js +++ b/src/entities/raw-data-parsers/shelley-data-parser.js @@ -33,6 +33,8 @@ class ShelleyDataParser implements RawDataParser { parseTx(data: Buffer, extraData: { blockHash: ?string, blockNum: ?number, + epoch: ?number, + slot: ?number, txOrdinal: ?number, txTime: Date, }): TxType {