Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: chain sync projection primitives #520

Merged
merged 16 commits into from Nov 18, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 1 addition & 2 deletions .eslintignore
@@ -1,5 +1,4 @@
*.d.ts
node_modules
dist
packages/golden-test-generator
.yarn
.yarn
3 changes: 2 additions & 1 deletion README.md
Expand Up @@ -23,6 +23,7 @@ A suite of TypeScript packages suitable for both Node.js and browser-based devel
- [@cardano-sdk/key-management](./packages/key-management)
- [@cardano-sdk/web-extension](./packages/web-extension)
- [@cardano-sdk/wallet](./packages/wallet)
- [@cardano-sdk/projection](./packages/projection)
- [@cardano-sdk/util-rxjs](./packages/util-rxjs)
- [@cardano-sdk/util](./packages/util)
- [@cardano-sdk/util-dev](./packages/util-dev)
Expand All @@ -35,7 +36,7 @@ Packages are distributed as both CommonJS and ESM modules.

- Node.js >=14.20.1
- using with `type="module"` requires `--experimental-specifier-resolution=node` flag
- Browser via bundlers (see [example webpack config](./packages/web-extension/e2e/webpack.config.js))
- Browser via bundlers (see [example webpack config](./packages/e2e/test/web-extension/webpack.config.js))

### Testing

Expand Down
2 changes: 1 addition & 1 deletion packages/cardano-services/docker-compose.yml
Expand Up @@ -28,7 +28,7 @@ services:
max-file: "10"

cardano-node-ogmios:
image: cardanosolutions/cardano-node-ogmios:${OGMIOS_VERSION:-v5.5.5}_${CARDANO_NODE_VERSION:-1.35.3}-${NETWORK:-mainnet}
image: cardanosolutions/cardano-node-ogmios:${OGMIOS_VERSION:-v5.5.7}_${CARDANO_NODE_VERSION:-1.35.3}-${NETWORK:-mainnet}
healthcheck:
retries: 200
logging:
Expand Down
2 changes: 1 addition & 1 deletion packages/cardano-services/package.json
Expand Up @@ -61,7 +61,7 @@
"tscNoEmit": "shx echo typescript --noEmit command not implemented yet"
},
"devDependencies": {
"@cardano-ogmios/client": "5.5.5",
"@cardano-ogmios/client": "5.5.7",
"@cardano-sdk/cardano-services-client": "^0.6.0",
"@cardano-sdk/util-dev": "^0.5.0",
"@types/amqplib": "^0.8.2",
Expand Down
Expand Up @@ -148,7 +148,7 @@ export class DbSyncChainHistoryProvider extends DbSyncProvider() implements Chai
});
}

public async blocksByHashes({ ids }: BlocksByIdsArgs): Promise<Cardano.Block[]> {
public async blocksByHashes({ ids }: BlocksByIdsArgs): Promise<Cardano.ExtendedBlockInfo[]> {
if (ids.length > this.#paginationPageSizeLimit) {
throw new ProviderError(
ProviderFailure.BadRequest,
Expand Down
Expand Up @@ -204,7 +204,11 @@ export const mapTxAlonzo = (
}
});

export const mapBlock = (blockModel: BlockModel, blockOutputModel: BlockOutputModel, tip: TipModel): Cardano.Block => ({
export const mapBlock = (
blockModel: BlockModel,
blockOutputModel: BlockOutputModel,
tip: TipModel
): Cardano.ExtendedBlockInfo => ({
confirmations: tip.block_no - blockModel.block_no,
date: new Date(blockModel.time),
epoch: blockModel.epoch_no,
Expand Down
Expand Up @@ -139,7 +139,7 @@ describe('chain history mappers', () => {
};
test('map BlockModel to Cardano.Block', () => {
const result = mappers.mapBlock(blockModel, blockOutputModel, tipModel);
expect(result).toEqual<Cardano.Block>({
expect(result).toEqual<Cardano.ExtendedBlockInfo>({
confirmations: 100,
date: new Date(datetime),
epoch: 12,
Expand All @@ -165,7 +165,7 @@ describe('chain history mappers', () => {
blockOutputModel,
tipModel
);
expect(result).toEqual<Cardano.Block>({
expect(result).toEqual<Cardano.ExtendedBlockInfo>({
confirmations: 100,
date: new Date(datetime),
epoch: 12,
Expand Down
4 changes: 2 additions & 2 deletions packages/core/package.json
Expand Up @@ -50,6 +50,7 @@
"prepack": "yarn build"
},
"devDependencies": {
"@cardano-ogmios/schema": "5.5.7",
"@types/lodash": "^4.14.182",
"eslint": "^7.32.0",
"jest": "^28.1.3",
Expand All @@ -59,8 +60,7 @@
"typescript": "^4.7.4"
},
"dependencies": {
"@cardano-ogmios/client": "5.5.5",
"@cardano-ogmios/schema": "5.5.5",
"@cardano-ogmios/client": "5.5.7",
"@cardano-sdk/util": "^0.6.0",
"@dcspark/cardano-multiplatform-lib-browser": "^3.1.0",
"@dcspark/cardano-multiplatform-lib-nodejs": "^3.1.0",
Expand Down
15 changes: 15 additions & 0 deletions packages/core/src/CML/address.ts
@@ -0,0 +1,15 @@
import { Address, Ed25519KeyHash, NetworkId, RewardAccount } from '../Cardano';
import { CML } from './CML';
import { parseCmlAddress } from './parseCmlAddress';
import { usingAutoFree } from '@cardano-sdk/util';

export const addressNetworkId = (address: RewardAccount | Address): NetworkId =>
usingAutoFree((scope) => parseCmlAddress(scope, address.toString())!.network_id());

export const createRewardAccount = (stakeKeyHash: Ed25519KeyHash, networkId: NetworkId) =>
usingAutoFree((scope) => {
const keyHash = scope.manage(CML.Ed25519KeyHash.from_hex(stakeKeyHash.toString()));
const stakeCredential = scope.manage(CML.StakeCredential.from_keyhash(keyHash));
const rewardAccount = scope.manage(CML.RewardAddress.new(networkId, stakeCredential));
return RewardAccount(scope.manage(rewardAccount.to_address()).to_bech32());
});
14 changes: 6 additions & 8 deletions packages/core/src/CML/cmlToCore/certificate.ts
Expand Up @@ -14,7 +14,7 @@ import {
StakeDelegationCertificate,
VrfVkHex
} from '../../Cardano/types';
import { Hash32ByteBase16 } from '../../Cardano/util/primitives';
import { Hash28ByteBase16, Hash32ByteBase16 } from '../../Cardano/util/primitives';
import { NetworkId } from '../../Cardano/NetworkId';
import { NotImplementedError, SerializationError, SerializationFailure } from '../../errors';
import { usingAutoFree } from '@cardano-sdk/util';
Expand Down Expand Up @@ -132,14 +132,12 @@ const poolRetirement = (certificate: CML.PoolRetirement): PoolRetirementCertific
poolId: PoolId(scope.manage(certificate.pool_keyhash()).to_bech32('pool'))
}));

const genesisKeyDelegaation = (certificate: CML.GenesisKeyDelegation): GenesisKeyDelegationCertificate =>
const genesisKeyDelegation = (certificate: CML.GenesisKeyDelegation): GenesisKeyDelegationCertificate =>
usingAutoFree((scope) => ({
__typename: CertificateType.GenesisKeyDelegation,
genesisDelegateHash: Hash32ByteBase16(
Buffer.from(scope.manage(certificate.genesis_delegate_hash()).to_bytes()).toString()
),
genesisHash: Hash32ByteBase16(Buffer.from(scope.manage(certificate.genesishash()).to_bytes()).toString()),
vrfKeyHash: Hash32ByteBase16(Buffer.from(scope.manage(certificate.vrf_keyhash()).to_bytes()).toString())
genesisDelegateHash: Hash28ByteBase16(scope.manage(certificate.genesis_delegate_hash()).to_hex()),
genesisHash: Hash28ByteBase16(scope.manage(certificate.genesishash()).to_hex()),
vrfKeyHash: Hash32ByteBase16(scope.manage(certificate.vrf_keyhash()).to_hex())
}));

export const createCertificate = (cmlCertificate: CML.Certificate): Certificate =>
Expand All @@ -156,7 +154,7 @@ export const createCertificate = (cmlCertificate: CML.Certificate): Certificate
case CML.CertificateKind.PoolRetirement:
return poolRetirement(scope.manage(cmlCertificate.as_pool_retirement()!));
case CML.CertificateKind.GenesisKeyDelegation:
return genesisKeyDelegaation(scope.manage(cmlCertificate.as_genesis_key_delegation()!));
return genesisKeyDelegation(scope.manage(cmlCertificate.as_genesis_key_delegation()!));
case CML.CertificateKind.MoveInstantaneousRewardsCert:
throw new NotImplementedError('MIR certificate conversion'); // TODO: support this certificate type
default:
Expand Down
21 changes: 21 additions & 0 deletions packages/core/src/CML/coreToCml/certificate.ts
Expand Up @@ -7,6 +7,9 @@ import {
DNSRecordSRV,
Ed25519KeyHash,
Ed25519KeyHashes,
GenesisDelegateHash,
GenesisHash,
GenesisKeyDelegation,
Ipv4,
MultiHostName,
PoolMetadata,
Expand Down Expand Up @@ -182,6 +185,22 @@ export const stakeDelegation = (
)
);

const genesisKeyDelegation = (
scope: ManagedFreeableScope,
{ genesisDelegateHash, genesisHash, vrfKeyHash }: Cardano.GenesisKeyDelegationCertificate
) =>
scope.manage(
Certificate.new_genesis_key_delegation(
scope.manage(
GenesisKeyDelegation.new(
scope.manage(GenesisHash.from_hex(genesisHash.toString())),
scope.manage(GenesisDelegateHash.from_hex(genesisDelegateHash.toString())),
scope.manage(VRFKeyHash.from_hex(vrfKeyHash.toString()))
)
)
)
);

export const create = (scope: ManagedFreeableScope, certificate: Cardano.Certificate) => {
switch (certificate.__typename) {
case Cardano.CertificateType.PoolRegistration:
Expand All @@ -194,6 +213,8 @@ export const create = (scope: ManagedFreeableScope, certificate: Cardano.Certifi
return stakeKeyDeregistration(scope, certificate.stakeKeyHash);
case Cardano.CertificateType.StakeKeyRegistration:
return stakeKeyRegistration(scope, certificate.stakeKeyHash);
case Cardano.CertificateType.GenesisKeyDelegation:
return genesisKeyDelegation(scope, certificate);
default:
throw new NotImplementedError(`certificate.create ${certificate.__typename}`);
}
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/CML/index.ts
Expand Up @@ -3,3 +3,4 @@ export * as cmlUtil from './util';
export * as cmlToCore from './cmlToCore';
export * as coreToCml from './coreToCml';
export * from './parseCmlAddress';
export * from './address';
4 changes: 2 additions & 2 deletions packages/core/src/Cardano/types/Asset.ts
Expand Up @@ -32,8 +32,8 @@ export const AssetId = (value: string): AssetId => {
/**
* Hex-encoded policy id
*/
export type PolicyId = Hash28ByteBase16<'PolicyId'>;
export const PolicyId = (value: string): PolicyId => Hash28ByteBase16(value);
export type PolicyId = OpaqueString<'PolicyId'>;
export const PolicyId = (value: string): PolicyId => Hash28ByteBase16(value) as unknown as PolicyId;

/**
* Fingerprint of a native asset for human comparison
Expand Down
23 changes: 14 additions & 9 deletions packages/core/src/Cardano/types/Block.ts
@@ -1,9 +1,10 @@
import { CML } from '../..';
import { Ed25519PublicKey } from '.';
import { Ed25519PublicKey } from './Key';
import { Hash28ByteBase16, Hash32ByteBase16, OpaqueString, typedBech32 } from '../util/primitives';
import { InvalidStringError } from '../../errors';
import { Lovelace } from './Value';
import { PoolId } from './StakePool/primitives';
import { TxAlonzo } from './Transaction';

/**
* The block size in bytes
Expand All @@ -28,7 +29,7 @@ export type Slot = number;
/**
* block hash as hex string
*/
export type BlockId = Hash32ByteBase16<'BlockId'>;
export type BlockId = OpaqueString<'BlockId'>;

export interface PartialBlockHeader {
blockNo: BlockNo;
Expand All @@ -43,7 +44,7 @@ export type Tip = PartialBlockHeader;
* @param {string} value block hash as hex string
* @throws InvalidStringError
*/
export const BlockId = (value: string): BlockId => Hash32ByteBase16<'BlockId'>(value);
export const BlockId = (value: string): BlockId => Hash32ByteBase16(value) as unknown as BlockId;

/**
* 32 byte ed25519 verification key as bech32 string.
Expand All @@ -61,7 +62,7 @@ export const GenesisDelegate = (value: string): GenesisDelegate => {
if (/ShelleyGenesis-[\da-f]{16}/.test(value)) {
return value as unknown as GenesisDelegate;
}
return Hash28ByteBase16(value);
return Hash28ByteBase16(value) as unknown as GenesisDelegate;
};

export type SlotLeader = PoolId | GenesisDelegate;
Expand Down Expand Up @@ -89,7 +90,7 @@ export const VrfVkBech32FromBase64 = (value: string) =>
/** Minimal Block type meant as a base for the more complete version `Block` */
// TODO: optionals (except previousBlock) are there because they are not calculated for Byron yet.
// Remove them once calculation is done and remove the Required<BlockMinimal> from interface Block
export interface BlockMinimal {
export interface BlockInfo {
header: PartialBlockHeader;
/** Byron blocks fee not calculated yet */
fees?: Lovelace;
Expand All @@ -106,14 +107,18 @@ export interface BlockMinimal {
issuerVk?: Ed25519PublicKey;
}

export interface Block
extends Required<Omit<BlockMinimal, 'issuerVk' | 'previousBlock'>>,
Pick<BlockMinimal, 'previousBlock'> {
export interface Block extends BlockInfo {
body: TxAlonzo[];
}

export interface ExtendedBlockInfo
extends Required<Omit<BlockInfo, 'issuerVk' | 'previousBlock'>>,
Pick<BlockInfo, 'previousBlock'> {
/**
* In case of blocks produced by BFT nodes, the SlotLeader the issuerVk hash
* For blocks produced by stake pools, it is the Bech32 encoded value of issuerVk hash
*/
slotLeader: SlotLeader;
slotLeader: SlotLeader; // TODO: move to CompactBlockInfo and make nullable
date: Date;
epoch: EpochNo;
epochSlot: number;
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/Cardano/types/Certificate.ts
@@ -1,6 +1,6 @@
import { Ed25519KeyHash } from './Key';
import { EpochNo } from './Block';
import { Hash32ByteBase16 } from '../util/primitives';
import { Hash28ByteBase16, Hash32ByteBase16 } from '../util/primitives';
import { Lovelace } from './Value';
import { PoolId, PoolParameters } from './StakePool';
import { RewardAccount } from './RewardAccount';
Expand Down Expand Up @@ -51,8 +51,8 @@ export interface MirCertificate {

export interface GenesisKeyDelegationCertificate {
__typename: CertificateType.GenesisKeyDelegation;
genesisHash: Hash32ByteBase16;
genesisDelegateHash: Hash32ByteBase16;
genesisHash: Hash28ByteBase16;
genesisDelegateHash: Hash28ByteBase16;
vrfKeyHash: Hash32ByteBase16;
}

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/Cardano/types/Key.ts
Expand Up @@ -45,7 +45,7 @@ Ed25519PrivateKey.fromHexBlob = (value: HexBlob) => castHexBlob<Ed25519PrivateKe
* 28 byte ED25519 key hash as hex string
*/
export type Ed25519KeyHash = OpaqueString<'Ed25519KeyHash'>;
export const Ed25519KeyHash = (value: string): Ed25519KeyHash => Hash28ByteBase16(value);
export const Ed25519KeyHash = (value: string): Ed25519KeyHash => Hash28ByteBase16(value) as unknown as Ed25519KeyHash;
Ed25519KeyHash.fromRewardAccount = (rewardAccount: RewardAccount): Ed25519KeyHash =>
usingAutoFree((scope) => {
const bech32 = scope.manage(CML.Address.from_bech32(rewardAccount.toString()));
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/Cardano/types/StakePool/primitives.ts
Expand Up @@ -24,13 +24,13 @@ PoolId.fromKeyHash = (value: Ed25519KeyHash): PoolId =>
/**
* pool operator verification key hash as hex string
*/
export type PoolIdHex = Hash28ByteBase16<'PoolIdHex'>;
export type PoolIdHex = OpaqueString<'PoolIdHex'>;

/**
* @param {string} value operator verification key hash as hex string
* @throws InvalidStringError
*/
export const PoolIdHex = (value: string): PoolIdHex => Hash28ByteBase16(value);
export const PoolIdHex = (value: string): PoolIdHex => Hash28ByteBase16(value) as unknown as PoolIdHex;

/**
* 32 byte VRF verification key as hex string
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/Cardano/types/Transaction.ts
Expand Up @@ -13,13 +13,13 @@ import { RewardAccount } from './RewardAccount';
/**
* transaction hash as hex string
*/
export type TransactionId = Hash32ByteBase16<'TransactionId'>;
export type TransactionId = OpaqueString<'TransactionId'>;

/**
* @param {string} value transaction hash as hex string
* @throws InvalidStringError
*/
export const TransactionId = (value: string): TransactionId => Hash32ByteBase16<'TransactionId'>(value);
export const TransactionId = (value: string): TransactionId => Hash32ByteBase16(value) as unknown as TransactionId;
TransactionId.fromHexBlob = (value: HexBlob) => Hash32ByteBase16.fromHexBlob<TransactionId>(value);

/**
Expand Down
10 changes: 4 additions & 6 deletions packages/core/src/Cardano/util/primitives.ts
Expand Up @@ -126,24 +126,22 @@ export const castHexBlob = <T>(target: HexBlob, expectedLength?: number) => {
/**
* 32 byte hash as hex string
*/
export type Hash32ByteBase16<T extends string = 'Hash32ByteBase16'> = OpaqueString<T>;
export type Hash32ByteBase16 = OpaqueString<'Hash32ByteBase16'>;

/**
* @param {string} value 32 byte hash as hex string
* @throws InvalidStringError
*/
export const Hash32ByteBase16 = <T extends string = 'Hash32ByteBase16'>(value: string): Hash32ByteBase16<T> =>
typedHex<Hash32ByteBase16<T>>(value, 64);
export const Hash32ByteBase16 = (value: string): Hash32ByteBase16 => typedHex<Hash32ByteBase16>(value, 64);
Hash32ByteBase16.fromHexBlob = <T>(value: HexBlob) => castHexBlob<T>(value, 64);

/**
* 28 byte hash as hex string
*/
export type Hash28ByteBase16<T extends string = 'Hash28ByteBase16'> = OpaqueString<T>;
export type Hash28ByteBase16 = OpaqueString<'Hash28ByteBase16'>;

/**
* @param {string} value 28 byte hash as hex string
* @throws InvalidStringError
*/
export const Hash28ByteBase16 = <T extends string = 'Hash28ByteBase16'>(value: string): Hash28ByteBase16<T> =>
typedHex<Hash32ByteBase16<T>>(value, 56);
export const Hash28ByteBase16 = (value: string): Hash28ByteBase16 => typedHex<Hash28ByteBase16>(value, 56);