Skip to content

Commit

Permalink
feat: add accountCoins to account-controller
Browse files Browse the repository at this point in the history
  • Loading branch information
Juan Cruz committed Apr 7, 2021
1 parent 48c02da commit bff3480
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ import { withNetworkValidation } from '../controllers/controllers-helper';
import { BlockService } from '../services/block-service';
import { CardanoService } from '../services/cardano-services';
import { NetworkService } from '../services/network-service';
import { mapToAccountBalanceResponse } from '../utils/data-mapper';
import { mapToAccountBalanceResponse, mapToAccountCoinsResponse } from '../utils/data-mapper';
import { ErrorFactory } from '../utils/errors';

export interface AccountController {
accountBalance(
request: FastifyRequest<unknown, unknown, unknown, unknown, Components.Schemas.AccountBalanceRequest>
): Promise<Components.Schemas.AccountBalanceResponse | Components.Schemas.Error>;
accountCoins(
request: FastifyRequest<unknown, unknown, unknown, unknown, Components.Schemas.AccountCoinsRequest>
): Promise<Components.Schemas.AccountCoinsResponse | Components.Schemas.Error>;
}

const configure = (
Expand Down Expand Up @@ -41,6 +44,26 @@ const configure = (
},
request.log,
networkService
),
accountCoins: async request =>
withNetworkValidation(
request.body.network_identifier,
request,
async () => {
const logger = request.log;
const accountCoinsRequest = request.body;
const accountAddress = accountCoinsRequest.account_identifier.address;
logger.debug({ accountBalanceRequest: request.body }, '[accountCoins] Request received');
if (cardanoService.getEraAddressType(accountAddress) === null)
throw ErrorFactory.invalidAddressError(accountAddress);
logger.info('[accountCoins] Looking for latest block');
const blockUtxos = await blockService.findCoinsDataByAddress(logger, accountAddress);
const toReturn = mapToAccountCoinsResponse(blockUtxos);
logger.debug(toReturn, '[accountCoins] About to return ');
return toReturn;
},
request.log,
networkService
)
});

Expand Down
33 changes: 17 additions & 16 deletions cardano-rosetta-server/src/server/db/queries/blockchain-queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ LEFT JOIN tx_in ON
tx_out.index::smallint = tx_in.tx_out_index::smallint
LEFT JOIN tx as tx_in_tx ON
tx_in_tx.id = tx_in.tx_in_id AND
tx_in_tx.block_id <= (select id from block where hash = $2)
tx_in_tx.block_id <= (select id from block where hash = $2)
JOIN tx AS tx_out_tx ON
tx_out_tx.id = tx_out.tx_id AND
tx_out_tx.block_id <= (select id from block where hash = $2)
Expand Down Expand Up @@ -283,21 +283,22 @@ SELECT
ma_tx_out.policy, ma_tx_out.name
`;

const findBalanceByAddressAndBlock = `SELECT (SELECT COALESCE(SUM(r.amount),0)
FROM reward r
JOIN stake_address ON
stake_address.id = r.addr_id
JOIN block ON
block.id = r.block_id
WHERE stake_address.view = $1
AND block.id <= (SELECT id FROM block WHERE hash = $2))-
(SELECT COALESCE(SUM(w.amount),0)
FROM withdrawal w
JOIN tx ON tx.id = w.tx_id AND
tx.block_id <= (SELECT id FROM block WHERE hash = $2)
JOIN stake_address ON stake_address.id = w.addr_id
WHERE stake_address.view = $1)
AS balance
const findBalanceByAddressAndBlock = `
SELECT (SELECT COALESCE(SUM(r.amount),0)
FROM reward r
JOIN stake_address ON
stake_address.id = r.addr_id
JOIN block ON
block.id = r.block_id
WHERE stake_address.view = $1
AND block.id <= (SELECT id FROM block WHERE hash = $2))-
(SELECT COALESCE(SUM(w.amount),0)
FROM withdrawal w
JOIN tx ON tx.id = w.tx_id AND
tx.block_id <= (SELECT id FROM block WHERE hash = $2)
JOIN stake_address ON stake_address.id = w.addr_id
WHERE stake_address.view = $1)
AS balance
`;

const Queries = {
Expand Down
3 changes: 3 additions & 0 deletions cardano-rosetta-server/src/server/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ export interface Network {
export interface BlockUtxos {
block: Block;
utxos: Utxo[];
}

export interface BlockUtxosMultiAssets extends BlockUtxos {
maBalances: MaBalance[];
}

Expand Down
34 changes: 32 additions & 2 deletions cardano-rosetta-server/src/server/services/block-service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { Logger } from 'fastify';
import { Block, BlockUtxos, BalanceAtBlock, GenesisBlock, Transaction, PopulatedTransaction } from '../models';
import {
Block,
BlockUtxos,
BlockUtxosMultiAssets,
BalanceAtBlock,
GenesisBlock,
Transaction,
PopulatedTransaction
} from '../models';
import { ErrorFactory } from '../utils/errors';
import { BlockchainRepository } from '../db/blockchain-repository';
import { CardanoService } from './cardano-services';
Expand Down Expand Up @@ -74,7 +82,15 @@ export interface BlockService {
address: string,
number?: number,
hash?: string
): Promise<BlockUtxos | BalanceAtBlock>;
): Promise<BlockUtxosMultiAssets | BalanceAtBlock>;

/**
* Returns the coins for a given address.
*
* @param logger
* @param address
*/
findCoinsDataByAddress(logger: Logger, address: string): Promise<BlockUtxos>;
}

const configure = (repository: BlockchainRepository, cardanoService: CardanoService): BlockService => ({
Expand Down Expand Up @@ -171,6 +187,20 @@ const configure = (repository: BlockchainRepository, cardanoService: CardanoServ
maBalances
};
},
async findCoinsDataByAddress(logger, address) {
const block = await this.findBlock(logger);
if (block === null) {
logger.error('[findCoinsDataByAddress] Block not found');
throw ErrorFactory.blockNotFoundError();
}
logger.info(`[findCoinsDataByAddress] Looking for utxos for address ${address}`);
const utxoDetails = await repository.findUtxoByAddressAndBlock(logger, address, block.hash);
logger.debug(utxoDetails, `[findCoinsByAddress] Found ${utxoDetails.length} coin details for address ${address}`);
return {
block,
utxos: utxoDetails
};
},
findTransaction(logger, transactionHash, blockNumber, blockHash) {
return repository.findTransactionByHashAndBlock(logger, transactionHash, blockNumber, blockHash);
}
Expand Down
84 changes: 46 additions & 38 deletions cardano-rosetta-server/src/server/utils/data-mapper.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
/* eslint-disable camelcase */

import cbor from 'cbor';
import { BalanceAtBlock, Block, BlockUtxos, Network, PopulatedTransaction, TokenBundle, Utxo } from '../models';
import {
BalanceAtBlock,
Block,
BlockUtxos,
BlockUtxosMultiAssets,
Network,
PopulatedTransaction,
TokenBundle,
Utxo
} from '../models';
import { NetworkStatus } from '../services/network-service';
import {
ADA,
Expand Down Expand Up @@ -290,42 +299,34 @@ const updateMetadataCoin = (
return updatedCoin;
};

const mapToAddressBalanceAndCoins = (
blockBalanceData: BlockUtxos
): { adaBalance: Components.Schemas.Amount; adaCoins: Map<string, Components.Schemas.Coin> } => {
const mappedUtxos = blockBalanceData.utxos.reduce(
({ adaBalance, adaCoins }, current, index) => {
const previousValue = blockBalanceData.utxos[index - 1];
// This function accumulates ADA value. As there might be several, one for each multi-asset, we need to
// avoid counting them twice
const coinId = `${current.transactionHash}:${current.index}`;
if (!previousValue || !areEqualUtxos(previousValue, current)) {
adaBalance += BigInt(current.value);
adaCoins.set(coinId, {
coin_identifier: {
identifier: coinId
},
amount: mapAmount(current.value)
});
}
if (current.policy && current.name !== undefined && current.quantity) {
// MultiAsset
const relatedCoin = adaCoins.get(coinId);
if (relatedCoin) {
const updatedCoin = updateMetadataCoin(relatedCoin, current.policy, current.quantity, current.name);
adaCoins.set(coinId, updatedCoin);
}
export const mapToAccountCoinsResponse = (blockCoinsData: BlockUtxos): Components.Schemas.AccountCoinsResponse => {
const mappedUtxos = blockCoinsData.utxos.reduce((adaCoins, current, index) => {
const previousValue = blockCoinsData.utxos[index - 1];
const coinId = `${current.transactionHash}:${current.index}`;
if (!previousValue || !areEqualUtxos(previousValue, current)) {
adaCoins.set(coinId, {
coin_identifier: {
identifier: coinId
},
amount: mapAmount(current.value)
});
}
if (current.policy && current.name !== undefined && current.quantity) {
// MultiAsset
const relatedCoin = adaCoins.get(coinId);
if (relatedCoin) {
const updatedCoin = updateMetadataCoin(relatedCoin, current.policy, current.quantity, current.name);
adaCoins.set(coinId, updatedCoin);
}
return { adaBalance, adaCoins };
},
{
adaBalance: BigInt(0),
adaCoins: new Map<string, Components.Schemas.Coin>()
}
);
return adaCoins;
}, new Map<string, Components.Schemas.Coin>());
return {
adaBalance: mapAmount(mappedUtxos.adaBalance.toString()),
adaCoins: mappedUtxos.adaCoins
block_identifier: {
index: blockCoinsData.block.number,
hash: blockCoinsData.block.hash
},
coins: [...mappedUtxos.values()]
};
};

Expand All @@ -335,7 +336,7 @@ const mapToAddressBalanceAndCoins = (
* @param accountAddress
*/
export const mapToAccountBalanceResponse = (
blockBalanceData: BlockUtxos | BalanceAtBlock
blockBalanceData: BlockUtxosMultiAssets | BalanceAtBlock
): Components.Schemas.AccountBalanceResponse => {
if (isBlockUtxos(blockBalanceData)) {
const maBalances =
Expand All @@ -344,15 +345,22 @@ export const mapToAccountBalanceResponse = (
mapAmount(maBalance.value, maBalance.name, MULTI_ASSET_DECIMALS, { policyId: maBalance.policy })
)
: [];
const { adaBalance, adaCoins } = mapToAddressBalanceAndCoins(blockBalanceData);
const totalBalance = [adaBalance, ...maBalances];
const adaBalance = blockBalanceData.utxos.reduce((totalAmount, current, index) => {
const previousValue = blockBalanceData.utxos[index - 1];
// This function accumulates ADA value. As there might be several, one for each multi-asset, we need to
// avoid counting them twice
if (!previousValue || !areEqualUtxos(previousValue, current)) {
totalAmount += BigInt(current.value);
}
return totalAmount;
}, BigInt(0));
const totalBalance = [mapAmount(adaBalance.toString()), ...maBalances];
return {
block_identifier: {
index: blockBalanceData.block.number,
hash: blockBalanceData.block.hash
},
balances: totalBalance.length === 0 ? [mapAmount('0')] : totalBalance
// coins: [...adaCoins.values()]
};
}
return {
Expand Down

0 comments on commit bff3480

Please sign in to comment.