Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,18 @@ for you.
nix run .#config-update
```

## Get CBOR representation of an on chain transaction

Once we have a [running network](packages/cardano-services/README.md#production) synced at least up to the block
containing the transaction we are interested in, issue following command to get the CBOR representation of the
transaction.

```
yarn tx-cbor <txId>
```

This works regardless of the local ports configuration through environment variables.

## Attic

Previously supported features, no longer supported, but packed with a reference branch.
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
"test": "yarn workspaces foreach -v run test",
"test:build:verify": "yarn workspaces foreach -v run test:build:verify",
"test:e2e": "yarn workspaces foreach -v run test:e2e",
"test:debug": "DEBUG=true yarn workspaces foreach -v run test"
"test:debug": "DEBUG=true yarn workspaces foreach -v run test",
"tx-cbor": "tsx packages/cardano-services/scripts/tx-cbor.js"
},
"repository": {
"type": "git",
Expand Down
105 changes: 105 additions & 0 deletions packages/cardano-services/scripts/tx-cbor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { Pool } from 'pg';
import { readFileSync } from 'fs';
import { spawnSync } from 'child_process';
import WebSocket from 'ws';

interface Tx {
cbor: string;
id: string;
}

const txId = process.argv[2];

const normalizeError = (status: number | null, stderr: string) =>
[status || 1, stderr || 'Unknown error\n', ''] as const;

const inside = async () => {
const db = new Pool({
database: readFileSync(process.env.POSTGRES_DB_FILE_DB_SYNC!).toString(),
host: 'postgres',
password: readFileSync(process.env.POSTGRES_PASSWORD_FILE_DB_SYNC!).toString(),
user: readFileSync(process.env.POSTGRES_USER_FILE_DB_SYNC!).toString()
});

const query = `\
SELECT ENCODE(b2.hash, 'hex') AS id, b2.slot_no::INTEGER AS slot FROM tx
JOIN block b1 ON block_id = b1.id
JOIN block b2 ON b1.previous_id = b2.id
WHERE tx.hash = $1`;

const { rows } = await db.query(query, [Buffer.from(txId, 'hex')]);
const [prevBlock] = rows;

await db.end();

if (!prevBlock) return [1, `Unknown transaction id ${txId}\n`, ''] as const;

const cbor = await new Promise<string>((resolve) => {
const client = new WebSocket(process.env.OGMIOS_URL!);
let request = 0;

const rpc = (method: string, params: unknown) =>
client.send(JSON.stringify({ id: ++request, jsonrpc: '2.0', method, params }));

client.on('open', () => rpc('findIntersection', { points: [prevBlock] }));

client.on('message', (msg) => {
const { result } = JSON.parse(msg.toString()) as { result: { block: { transactions: Tx[] } } };
let tx: Tx | undefined;

if (
result &&
result.block &&
result.block.transactions &&
(tx = result.block.transactions.find((t) => t.id === txId))
) {
client.on('close', () => resolve(tx!.cbor));
client.close();
} else rpc('nextBlock', {});
});
});

return [0, '', `${cbor}\n`] as const;
};

const outside = async () => {
if (!txId) return [1, 'Missing input transaction id\n', ''] as const;

let { status, stderr, stdout } = spawnSync('docker', ['ps'], { encoding: 'utf-8' });

if (status || stderr) return normalizeError(status, stderr);

const container = [
'cardano-services-mainnet-provider-server-1',
'cardano-services-preprod-provider-server-1',
'cardano-services-preview-provider-server-1',
'cardano-services-sanchonet-provider-server-1',
'local-network-e2e-provider-server-1'
].find((name) => stdout.includes(name));

if (!container) return [1, "Can't find any valid container\n", ''] as const;

({ status, stderr, stdout } = spawnSync(
'docker',
['container', 'exec', '-i', container, 'bash', '-c', `cd /app ; INSIDE_THE_CONTAINER=true yarn tx-cbor ${txId}`],
{ encoding: 'utf-8' }
));

if (status || stderr) return normalizeError(status, stderr);

return [0, '', stdout] as const;
};

(process.env.INSIDE_THE_CONTAINER ? inside() : outside())
.then(([status, stderr, stdout]) => {
if (status) {
process.stderr.write(stderr);
// eslint-disable-next-line unicorn/no-process-exit
process.exit(status);
}

process.stdout.write(stdout);
})
.catch((error) => {
throw error;
});
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// cSpell:ignore descr

import * as Queries from './queries';
import {
AuthorizeCommitteeHotCertModel,
Expand Down Expand Up @@ -261,7 +263,10 @@ export class ChainHistoryBuilder {
if (result.rows.length === 0) return [];

const txOutIds = result.rows.flatMap((txOut) => BigInt(txOut.id));
const multiAssets = await this.queryMultiAssetsByTxOut(txOutIds);
// In case of collateralReturn requests (collateral = true) assets in the output can't be read as for regular outputs:
// db-sync stores assets from collateral outputs in collateral_tx_out.multi_assets_descr column rather than in
// ma_tx_out table like for regular outputs. To have a complete collateralReturn, given column should be read and parsed.
const multiAssets = collateral ? new Map() : await this.queryMultiAssetsByTxOut(txOutIds);
const referenceScripts = await this.queryReferenceScriptsByTxOut(result.rows);

return result.rows.map((txOut) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,12 +136,10 @@ export class DbSyncChainHistoryProvider extends DbSyncProvider() implements Chai

return txResults.rows.map((tx) => {
const txId = tx.id.toString('hex') as unknown as Cardano.TransactionId;
const txInputs = orderBy(inputs.filter((input) => input.txInputId === txId).map(mapTxIn), ['index']);
const txCollaterals = orderBy(collaterals.filter((col) => col.txInputId === txId).map(mapTxIn), ['index']);
const txInputs = inputs.filter((input) => input.txInputId === txId).map(mapTxIn);
const txCollaterals = collaterals.filter((col) => col.txInputId === txId).map(mapTxIn);
const txOutputs = orderBy(outputs.filter((output) => output.txId === txId).map(mapTxOut), ['index']);
const txCollateralOutputs = orderBy(collateralOutputs.filter((output) => output.txId === txId).map(mapTxOut), [
'index'
]);
const txCollateralOutputs = collateralOutputs.filter((output) => output.txId === txId).map(mapTxOut);
const inputSource: Cardano.InputSource = tx.valid_contract
? Cardano.InputSource.inputs
: Cardano.InputSource.collaterals;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
BlockOutputModel,
CertificateModel,
MultiAssetModel,
PoolRegisterCertModel,
ProtocolParametersUpdateModel,
RedeemerModel,
ScriptModel,
Expand Down Expand Up @@ -191,6 +192,17 @@ const mapDrepDelegation = ({
__typename: 'AlwaysAbstain'
};

const mapPoolParameters = (certModel: WithCertType<PoolRegisterCertModel>): Cardano.PoolParameters => ({
cost: BigInt(certModel.fixed_cost),
id: certModel.pool_id as unknown as Cardano.PoolId,
margin: Cardano.FractionUtils.toFraction(certModel.margin),
owners: [],
pledge: BigInt(certModel.pledge),
relays: [],
rewardAccount: certModel.reward_account as Cardano.RewardAccount,
vrf: certModel.vrf_key_hash.toString('hex') as Cardano.VrfVkHex
});

// eslint-disable-next-line complexity
export const mapCertificate = (
certModel: WithCertType<CertificateModel>
Expand All @@ -209,7 +221,7 @@ export const mapCertificate = (
__typename: Cardano.CertificateType.PoolRegistration,
cert_index: certModel.cert_index,
deposit: BigInt(certModel.deposit),
poolParameters: null as unknown as Cardano.PoolParameters
poolParameters: mapPoolParameters(certModel)
} as WithCertIndex<Cardano.HydratedPoolRegistrationCertificate>;

if (isMirCertModel(certModel)) {
Expand All @@ -231,25 +243,15 @@ export const mapCertificate = (
: Cardano.CertificateType.Unregistration,
cert_index: certModel.cert_index,
deposit: BigInt(certModel.deposit),
stakeCredential: {
hash: Cardano.RewardAccount.toHash(
Cardano.RewardAccount(certModel.address)
) as unknown as Crypto.Hash28ByteBase16,
type: Cardano.CredentialType.KeyHash
}
stakeCredential: Cardano.Address.fromBech32(certModel.address).asReward()!.getPaymentCredential()
} as WithCertIndex<Cardano.NewStakeAddressCertificate>;

if (isDelegationCertModel(certModel))
return {
__typename: Cardano.CertificateType.StakeDelegation,
cert_index: certModel.cert_index,
poolId: certModel.pool_id as unknown as Cardano.PoolId,
stakeCredential: {
hash: Cardano.RewardAccount.toHash(
Cardano.RewardAccount(certModel.address)
) as unknown as Crypto.Hash28ByteBase16,
type: Cardano.CredentialType.KeyHash
}
stakeCredential: Cardano.Address.fromBech32(certModel.address).asReward()!.getPaymentCredential()
} as WithCertIndex<Cardano.StakeDelegationCertificate>;

if (isDrepRegistrationCertModel(certModel))
Expand Down Expand Up @@ -292,12 +294,7 @@ export const mapCertificate = (
__typename: Cardano.CertificateType.VoteDelegation,
cert_index: certModel.cert_index,
dRep: mapDrepDelegation(certModel),
stakeCredential: {
hash: Cardano.RewardAccount.toHash(
Cardano.RewardAccount(certModel.address)
) as unknown as Crypto.Hash28ByteBase16,
type: Cardano.CredentialType.KeyHash
}
stakeCredential: Cardano.Address.fromBech32(certModel.address).asReward()!.getPaymentCredential()
};

if (isVoteRegistrationDelegationCertModel(certModel))
Expand All @@ -306,12 +303,7 @@ export const mapCertificate = (
cert_index: certModel.cert_index,
dRep: mapDrepDelegation(certModel),
deposit: BigInt(certModel.deposit),
stakeCredential: {
hash: Cardano.RewardAccount.toHash(
Cardano.RewardAccount(certModel.address)
) as unknown as Crypto.Hash28ByteBase16,
type: Cardano.CredentialType.KeyHash
}
stakeCredential: Cardano.Address.fromBech32(certModel.address).asReward()!.getPaymentCredential()
};

if (isStakeVoteDelegationCertModel(certModel))
Expand All @@ -320,12 +312,7 @@ export const mapCertificate = (
cert_index: certModel.cert_index,
dRep: mapDrepDelegation(certModel),
poolId: certModel.pool_id as unknown as Cardano.PoolId,
stakeCredential: {
hash: Cardano.RewardAccount.toHash(
Cardano.RewardAccount(certModel.address)
) as unknown as Crypto.Hash28ByteBase16,
type: Cardano.CredentialType.KeyHash
}
stakeCredential: Cardano.Address.fromBech32(certModel.address).asReward()!.getPaymentCredential()
};

if (isStakeRegistrationDelegationCertModel(certModel))
Expand All @@ -334,12 +321,7 @@ export const mapCertificate = (
cert_index: certModel.cert_index,
deposit: BigInt(certModel.deposit),
poolId: certModel.pool_id as unknown as Cardano.PoolId,
stakeCredential: {
hash: Cardano.RewardAccount.toHash(
Cardano.RewardAccount(certModel.address)
) as unknown as Crypto.Hash28ByteBase16,
type: Cardano.CredentialType.KeyHash
}
stakeCredential: Cardano.Address.fromBech32(certModel.address).asReward()!.getPaymentCredential()
};

if (isStakeVoteRegistrationDelegationCertModel(certModel))
Expand All @@ -349,12 +331,7 @@ export const mapCertificate = (
dRep: mapDrepDelegation(certModel),
deposit: BigInt(certModel.deposit),
poolId: certModel.pool_id as unknown as Cardano.PoolId,
stakeCredential: {
hash: Cardano.RewardAccount.toHash(
Cardano.RewardAccount(certModel.address)
) as unknown as Crypto.Hash28ByteBase16,
type: Cardano.CredentialType.KeyHash
}
stakeCredential: Cardano.Address.fromBech32(certModel.address).asReward()!.getPaymentCredential()
};

if (isAuthorizeCommitteeHotCertModel(certModel))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,15 +250,21 @@ export const findPoolRetireCertsTxIds = `
export const findPoolRegisterCertsByTxIds = `
SELECT
cert.cert_index AS cert_index,
pool."view" AS pool_id,
pool.view AS pool_id,
tx.hash AS tx_id,
CASE
WHEN cert.deposit IS NULL THEN '0'
ELSE cert.deposit
END AS deposit
END AS deposit,
stake_address.view AS reward_account,
pledge,
fixed_cost,
margin,
vrf_key_hash
FROM tx
JOIN pool_update AS cert ON cert.registered_tx_id = tx.id
JOIN pool_hash AS pool ON pool.id = cert.hash_id
JOIN stake_address ON stake_address.id = reward_addr_id
WHERE tx.id = ANY($1)
ORDER BY tx.id ASC`;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,11 @@ export interface PoolRetireCertModel extends CertificateModel {
export interface PoolRegisterCertModel extends CertificateModel {
pool_id: string;
deposit: string;
reward_account: string;
pledge: string;
fixed_cost: string;
margin: number;
vrf_key_hash: Buffer;
}

export interface MirCertModel extends CertificateModel {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const blockHash = '7a48b034645f51743550bbaf81f8a14771e58856e031eb63844738ca8ad72
const poolId = 'pool1zuevzm3xlrhmwjw87ec38mzs02tlkwec9wxpgafcaykmwg7efhh';
const datetime = '2022-05-10T19:22:43.620Z';
const vrfKey = 'vrf_vk19j362pkr4t9y0m3qxgmrv0365vd7c4ze03ny4jh84q8agjy4ep4s99zvg8';
const vrfKeyHash = '220ba9398e3e5fae23a83d0d5927649d577a5f69d6ef1d5253c259d9393ba294';
const genesisLeaderHash = 'eff1b5b26e65b791d6f236c7c0264012bd1696759d22bdb4dd0f6f56';
const transactionHash = 'cefd2fcf657e5e5d6c35975f4e052f427819391b153ebb16ad8aa107ba5a3819';
const sourceTransactionHash = 'cefd2fcf657e5e5d6c35975f4e052f427819391b153ebb16ad8aa107ba5a3812';
Expand Down Expand Up @@ -292,14 +293,28 @@ describe('chain history mappers', () => {
const result = mappers.mapCertificate({
...baseCertModel,
deposit: '500000000',
fixed_cost: '390000000',
margin: 0.15,
pledge: '420000000',
pool_id: poolId,
type: 'register'
reward_account: stakeAddress,
type: 'register',
vrf_key_hash: Buffer.from(vrfKeyHash, 'hex')
} as WithCertType<PoolRegisterCertModel>);
expect(result).toEqual<WithCertIndex<Cardano.HydratedPoolRegistrationCertificate>>({
__typename: Cardano.CertificateType.PoolRegistration,
cert_index: 0,
deposit: 500_000_000n,
poolParameters: null as unknown as Cardano.PoolParameters
poolParameters: {
cost: 390_000_000n,
id: poolId as Cardano.PoolId,
margin: { denominator: 20, numerator: 3 },
owners: [],
pledge: 420_000_000n,
relays: [],
rewardAccount: stakeAddress as Cardano.RewardAccount,
vrf: vrfKeyHash as Cardano.VrfVkHex
}
});
});
test('map MirCertModel to Cardano.MirCertificate', () => {
Expand Down
Loading