Skip to content

Commit 4f4af9d

Browse files
authored
Merge pull request #846 from input-output-hk/feat/address-projection
feat: address projection
2 parents c991d26 + 59ff6b1 commit 4f4af9d

27 files changed

+817
-38
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { AddressEntity } from '@cardano-sdk/projection-typeorm';
2+
import { MigrationInterface, QueryRunner } from 'typeorm';
3+
4+
export class AddressTableMigrations1690955710125 implements MigrationInterface {
5+
static entity = AddressEntity;
6+
7+
public async up(queryRunner: QueryRunner): Promise<void> {
8+
await queryRunner.query(
9+
"CREATE TYPE \"public\".\"address_type_enum\" AS ENUM('0', '1', '2', '3', '4', '5', '6', '7', '8', '14', '15')"
10+
);
11+
await queryRunner.query(
12+
'CREATE TABLE "address" ("address" character varying NOT NULL, "type" "public"."address_type_enum" NOT NULL, "payment_credential_hash" character(56), "stake_credential_hash" character(56), CONSTRAINT "PK_address_address" PRIMARY KEY ("address"))'
13+
);
14+
await queryRunner.query(
15+
'CREATE INDEX "IDX_address_payment_credential_hash" ON "address" ("payment_credential_hash") '
16+
);
17+
await queryRunner.query('CREATE INDEX "IDX_address_stake_credential_hash" ON "address" ("stake_credential_hash") ');
18+
}
19+
20+
public async down(queryRunner: QueryRunner): Promise<void> {
21+
await queryRunner.query('DROP INDEX "public"."IDX_address_stake_credential_hash"');
22+
await queryRunner.query('DROP INDEX "public"."IDX_address_payment_credential_hash"');
23+
await queryRunner.query('DROP TABLE "address"');
24+
await queryRunner.query('DROP TYPE "public"."address_type_enum"');
25+
}
26+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { MigrationInterface, QueryRunner } from 'typeorm';
2+
import { StakeKeyRegistrationEntity } from '@cardano-sdk/projection-typeorm';
3+
4+
export class StakeKeyRegistrationsTableMigrations1690964880195 implements MigrationInterface {
5+
static entity = StakeKeyRegistrationEntity;
6+
7+
public async up(queryRunner: QueryRunner): Promise<void> {
8+
await queryRunner.query(
9+
'CREATE TABLE "stake_key_registration" ("id" bigint NOT NULL, "stake_key_hash" character(56) NOT NULL, "block_slot" integer NOT NULL, CONSTRAINT "PK_stake_key_registration_id" PRIMARY KEY ("id"))'
10+
);
11+
await queryRunner.query(
12+
'CREATE INDEX "IDX_stake_key_registration_stake_key_hash" ON "stake_key_registration" ("stake_key_hash") '
13+
);
14+
await queryRunner.query(
15+
'ALTER TABLE "stake_key_registration" ADD CONSTRAINT "FK_stake_key_registration_block_slot" FOREIGN KEY ("block_slot") REFERENCES "block"("slot") ON DELETE CASCADE ON UPDATE NO ACTION'
16+
);
17+
18+
await queryRunner.query('ALTER TABLE "address" ADD "registration_id" bigint');
19+
await queryRunner.query(
20+
'ALTER TABLE "address" ADD CONSTRAINT "FK_address_registration_id" FOREIGN KEY ("registration_id") REFERENCES "stake_key_registration"("id") ON DELETE CASCADE ON UPDATE NO ACTION'
21+
);
22+
}
23+
24+
public async down(queryRunner: QueryRunner): Promise<void> {
25+
await queryRunner.query('ALTER TABLE "address" DROP CONSTRAINT "FK_address_registration_id"');
26+
await queryRunner.query('ALTER TABLE "address" DROP COLUMN "registration_id"');
27+
28+
await queryRunner.query(
29+
'ALTER TABLE "stake_key_registration" DROP CONSTRAINT "FK_stake_key_registration_block_slot"'
30+
);
31+
await queryRunner.query('DROP INDEX "public"."IDX_stake_key_registration_stake_key_hash"');
32+
await queryRunner.query('DROP TABLE "stake_key_registration"');
33+
}
34+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { MigrationInterface, QueryRunner } from 'typeorm';
2+
import { TokensEntity } from '@cardano-sdk/projection-typeorm';
3+
4+
export class TokensQuantityNumericMigrations1691042603934 implements MigrationInterface {
5+
static entity = TokensEntity;
6+
7+
public async up(queryRunner: QueryRunner): Promise<void> {
8+
await queryRunner.query('ALTER TABLE "tokens" ALTER COLUMN "quantity" TYPE numeric(20,0) USING quantity::numeric');
9+
}
10+
11+
public async down(queryRunner: QueryRunner): Promise<void> {
12+
await queryRunner.query('ALTER TABLE "tokens" ALTER COLUMN "quantity" TYPE bigint USING quantity::bigint');
13+
}
14+
}

packages/cardano-services/src/Projection/migrations/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { AddressTableMigrations1690955710125 } from './1690955710125-address-table';
12
import { AssetTableMigration1682519108365 } from './1682519108365-asset-table';
23
import { BlockDataTableMigration1682519108359 } from './1682519108359-block-data-table';
34
import { BlockTableMigration1682519108358 } from './1682519108358-block-table';
@@ -11,7 +12,9 @@ import { PoolMetadataTableMigration1682519108363 } from './1682519108363-pool-me
1112
import { PoolMetricsMigrations1685011799580 } from './1685011799580-stake-pool-metrics-table';
1213
import { PoolRegistrationTableMigration1682519108360 } from './1682519108360-pool-registration-table';
1314
import { PoolRetirementTableMigration1682519108361 } from './1682519108361-pool-retirement-table';
15+
import { StakeKeyRegistrationsTableMigrations1690964880195 } from './1690964880195-stake-key-registrations-table';
1416
import { StakePoolTableMigration1682519108362 } from './1682519108362-stake-pool-table';
17+
import { TokensQuantityNumericMigrations1691042603934 } from './1691042603934-tokens-quantity-numeric';
1518
import { TokensTableMigration1682519108368 } from './1682519108368-tokens-table';
1619

1720
type ProjectionMigration = Function & {
@@ -33,5 +36,8 @@ export const migrations: ProjectionMigration[] = [
3336
PoolMetricsMigrations1685011799580,
3437
HandleTableMigration1686138943349,
3538
CostPledgeNumericMigration1689091319930,
36-
NftMetadataTableMigration1690269355640
39+
NftMetadataTableMigration1690269355640,
40+
AddressTableMigrations1690955710125,
41+
StakeKeyRegistrationsTableMigrations1690964880195,
42+
TokensQuantityNumericMigrations1691042603934
3743
];

packages/cardano-services/src/Projection/prepareTypeormProjection.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
AddressEntity,
23
AssetEntity,
34
BlockDataEntity,
45
BlockEntity,
@@ -10,14 +11,17 @@ import {
1011
PoolMetadataEntity,
1112
PoolRegistrationEntity,
1213
PoolRetirementEntity,
14+
StakeKeyRegistrationEntity,
1315
StakePoolEntity,
1416
TokensEntity,
1517
TypeormStabilityWindowBuffer,
1618
createStorePoolMetricsUpdateJob,
19+
storeAddresses,
1720
storeAssets,
1821
storeBlock,
1922
storeHandles,
2023
storeNftMetadata,
24+
storeStakeKeyRegistrations,
2125
storeStakePoolMetadataJob,
2226
storeStakePools,
2327
storeUtxo
@@ -38,7 +42,8 @@ export enum ProjectionName {
3842
StakePool = 'stake-pool',
3943
StakePoolMetadataJob = 'stake-pool-metadata-job',
4044
StakePoolMetricsJob = 'stake-pool-metrics-job',
41-
UTXO = 'utxo'
45+
UTXO = 'utxo',
46+
Address = 'address'
4247
}
4348

4449
export interface ProjectionOptions {
@@ -65,11 +70,13 @@ const createMapperOperators = (
6570
return {
6671
filterMint,
6772
filterUtxo,
73+
withAddresses: Mapper.withAddresses(),
6874
withCIP67: Mapper.withCIP67(),
6975
withCertificates: Mapper.withCertificates(),
7076
withHandles,
7177
withMint: Mapper.withMint(),
7278
withNftMetadata: Mapper.withNftMetadata({ logger }),
79+
withStakeKeyRegistrations: Mapper.withStakeKeyRegistrations(),
7380
withStakePools: Mapper.withStakePools(),
7481
withUtxo: Mapper.withUtxo()
7582
};
@@ -79,11 +86,13 @@ type MapperName = keyof MapperOperators;
7986
type MapperOperator = MapperOperators[MapperName];
8087

8188
export const storeOperators = {
89+
storeAddresses: storeAddresses(),
8290
storeAssets: storeAssets(),
8391
storeBlock: storeBlock(),
8492
storeHandles: storeHandles(),
8593
storeNftMetadata: storeNftMetadata(),
8694
storePoolMetricsUpdateJob: createStorePoolMetricsUpdateJob(POOLS_METRICS_INTERVAL_DEFAULT)(),
95+
storeStakeKeyRegistrations: storeStakeKeyRegistrations(),
8796
storeStakePoolMetadataJob: storeStakePoolMetadataJob(),
8897
storeStakePools: storeStakePools(),
8998
storeUtxo: storeUtxo()
@@ -93,6 +102,7 @@ type StoreName = keyof StoreOperators;
93102
type StoreOperator = StoreOperators[StoreName];
94103

95104
const entities = {
105+
address: AddressEntity,
96106
asset: AssetEntity,
97107
block: BlockEntity,
98108
blockData: BlockDataEntity,
@@ -103,6 +113,7 @@ const entities = {
103113
poolMetadata: PoolMetadataEntity,
104114
poolRegistration: PoolRegistrationEntity,
105115
poolRetirement: PoolRetirementEntity,
116+
stakeKeyRegistration: StakeKeyRegistrationEntity,
106117
stakePool: StakePoolEntity,
107118
tokens: TokensEntity
108119
};
@@ -112,17 +123,20 @@ type EntityName = keyof Entities;
112123
type Entity = Entities[EntityName];
113124

114125
const storeEntities: Partial<Record<StoreName, EntityName[]>> = {
126+
storeAddresses: ['address'],
115127
storeAssets: ['asset'],
116128
storeBlock: ['block'],
117129
storeHandles: ['handle', 'asset', 'tokens', 'output'],
118130
storeNftMetadata: ['asset'],
119131
storePoolMetricsUpdateJob: ['stakePool', 'currentPoolMetrics', 'poolMetadata'],
132+
storeStakeKeyRegistrations: ['block', 'stakeKeyRegistration'],
120133
storeStakePoolMetadataJob: ['stakePool', 'currentPoolMetrics', 'poolMetadata'],
121134
storeStakePools: ['stakePool', 'currentPoolMetrics', 'poolMetadata'],
122135
storeUtxo: ['tokens', 'output']
123136
};
124137

125138
const entityInterDependencies: Partial<Record<EntityName, EntityName[]>> = {
139+
address: ['stakeKeyRegistration'],
126140
asset: ['block', 'nftMetadata'],
127141
blockData: ['block'],
128142
currentPoolMetrics: ['stakePool'],
@@ -131,6 +145,7 @@ const entityInterDependencies: Partial<Record<EntityName, EntityName[]>> = {
131145
poolMetadata: ['stakePool'],
132146
poolRegistration: ['block'],
133147
poolRetirement: ['block'],
148+
stakeKeyRegistration: ['block'],
134149
stakePool: ['block', 'poolRegistration', 'poolRetirement'],
135150
tokens: ['asset']
136151
};
@@ -158,22 +173,27 @@ export const getEntities = (entityNames: EntityName[]): Entity[] => {
158173
const mapperInterDependencies: Partial<Record<MapperName, MapperName[]>> = {
159174
filterMint: ['withMint'],
160175
filterUtxo: ['withUtxo'],
176+
withAddresses: ['withUtxo'],
161177
withCIP67: ['withUtxo'],
162178
withHandles: ['withMint', 'filterMint', 'withUtxo', 'filterUtxo'],
163179
withNftMetadata: ['withCIP67', 'withMint'],
180+
withStakeKeyRegistrations: ['withCertificates'],
164181
withStakePools: ['withCertificates']
165182
};
166183

167184
const storeMapperDependencies: Partial<Record<StoreName, MapperName[]>> = {
185+
storeAddresses: ['withAddresses'],
168186
storeAssets: ['withMint'],
169187
storeHandles: ['withHandles'],
170188
storeNftMetadata: ['withNftMetadata'],
189+
storeStakeKeyRegistrations: ['withStakeKeyRegistrations'],
171190
storeStakePoolMetadataJob: ['withStakePools'],
172191
storeStakePools: ['withStakePools'],
173192
storeUtxo: ['withUtxo']
174193
};
175194

176195
const storeInterDependencies: Partial<Record<StoreName, StoreName[]>> = {
196+
storeAddresses: ['storeStakeKeyRegistrations'],
177197
storeAssets: ['storeBlock'],
178198
storeHandles: ['storeUtxo'],
179199
storeNftMetadata: ['storeAssets'],
@@ -184,6 +204,7 @@ const storeInterDependencies: Partial<Record<StoreName, StoreName[]>> = {
184204
};
185205

186206
const projectionStoreDependencies: Record<ProjectionName, StoreName[]> = {
207+
address: ['storeAddresses'],
187208
handle: ['storeHandles', 'storeNftMetadata'],
188209
'stake-pool': ['storeStakePools'],
189210
'stake-pool-metadata-job': ['storeStakePoolMetadataJob'],
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { Cardano } from '@cardano-sdk/core';
2+
import { Column, Entity, Index, JoinColumn, ManyToOne, PrimaryColumn } from 'typeorm';
3+
import { Hash28ByteBase16 } from '@cardano-sdk/crypto';
4+
import { StakeKeyRegistrationEntity } from './StakeKeyRegistration.entity';
5+
6+
@Entity()
7+
export class AddressEntity {
8+
@PrimaryColumn()
9+
address?: Cardano.PaymentAddress;
10+
@Column({ enum: Cardano.AddressType, type: 'enum' })
11+
type?: Cardano.AddressType;
12+
@Column('char', { length: 56, nullable: true })
13+
@Index()
14+
/**
15+
* Applicable only for base/grouped, enterprise and pointer addresses
16+
*/
17+
paymentCredentialHash?: Hash28ByteBase16 | null;
18+
@Column('char', { length: 56, nullable: true })
19+
@Index()
20+
/**
21+
* Applicable only for base/grouped and pointer addresses
22+
*/
23+
stakeCredentialHash?: Hash28ByteBase16 | null;
24+
@ManyToOne(() => StakeKeyRegistrationEntity, { nullable: true, onDelete: 'CASCADE' })
25+
@JoinColumn()
26+
/**
27+
* Applicable only for pointer addresses
28+
*/
29+
registration?: StakeKeyRegistrationEntity | null;
30+
}

packages/projection-typeorm/src/entity/PoolRegistration.entity.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* eslint-disable brace-style */
2-
import { BigIntColumnOptions, ImaginaryCoinsColumnOptions, OnDeleteCascadeRelationOptions } from './util';
2+
import { BigIntColumnOptions, OnDeleteCascadeRelationOptions, UInt64ColumnOptions } from './util';
33
import { BlockEntity } from './Block.entity';
44
import { Cardano } from '@cardano-sdk/core';
55
import { Column, Entity, JoinColumn, ManyToOne, OneToOne, PrimaryColumn } from 'typeorm';
@@ -17,9 +17,9 @@ export class PoolRegistrationEntity {
1717
id?: bigint;
1818
@Column()
1919
rewardAccount?: Cardano.RewardAccount;
20-
@Column(ImaginaryCoinsColumnOptions)
20+
@Column(UInt64ColumnOptions)
2121
pledge?: bigint;
22-
@Column(ImaginaryCoinsColumnOptions)
22+
@Column(UInt64ColumnOptions)
2323
cost?: bigint;
2424
// Review: should we store this as 'double' instead?
2525
// Maybe both formats? If we'll need to do computations with this
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { BigIntColumnOptions, OnDeleteCascadeRelationOptions } from './util';
2+
import { BlockEntity } from './Block.entity';
3+
import { Column, Entity, Index, JoinColumn, ManyToOne, PrimaryColumn } from 'typeorm';
4+
import { Ed25519KeyHashHex } from '@cardano-sdk/crypto';
5+
6+
@Entity()
7+
export class StakeKeyRegistrationEntity {
8+
/**
9+
* Computed from certificate pointer.
10+
* Can be used to query by pointer using `Cardano.PointerToId` util.
11+
*/
12+
@PrimaryColumn(BigIntColumnOptions)
13+
id?: bigint;
14+
@Column('char', { length: 56 })
15+
@Index()
16+
stakeKeyHash?: Ed25519KeyHashHex;
17+
@ManyToOne(() => BlockEntity, OnDeleteCascadeRelationOptions)
18+
@JoinColumn()
19+
block?: BlockEntity;
20+
}

packages/projection-typeorm/src/entity/Tokens.entity.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { AssetEntity } from './Asset.entity';
2-
import { BigIntColumnOptions, OnDeleteCascadeRelationOptions } from './util';
32
import { Column, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
3+
import { OnDeleteCascadeRelationOptions, UInt64ColumnOptions } from './util';
44
import { OutputEntity } from './Output.entity';
55

66
@Entity()
@@ -13,6 +13,6 @@ export class TokensEntity {
1313
@JoinColumn()
1414
@ManyToOne(() => OutputEntity, (output) => output.tokens, OnDeleteCascadeRelationOptions)
1515
output?: OutputEntity;
16-
@Column(BigIntColumnOptions)
16+
@Column(UInt64ColumnOptions)
1717
quantity?: bigint;
1818
}

packages/projection-typeorm/src/entity/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,5 @@ export * from './Output.entity';
1111
export * from './CurrentPoolMetrics.entity';
1212
export * from './Handle.entity';
1313
export * from './NftMetadata.entity';
14+
export * from './Address.entity';
15+
export * from './StakeKeyRegistration.entity';

0 commit comments

Comments
 (0)