Skip to content

Commit

Permalink
Merge pull request #520 from input-output-hk/feat/generalized-block-h…
Browse files Browse the repository at this point in the history
…andler

feat: chain sync projection primitives
  • Loading branch information
rhyslbw committed Nov 18, 2022
2 parents 22794be + 6df9255 commit 91b0fc2
Show file tree
Hide file tree
Showing 95 changed files with 9,432 additions and 1,071 deletions.
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);

0 comments on commit 91b0fc2

Please sign in to comment.