From 04f09e7c5fe56475a417ecb2a789bed51249f9d3 Mon Sep 17 00:00:00 2001 From: Sebastien Guillemot Date: Sat, 13 Aug 2022 09:38:34 +0900 Subject: [PATCH] Block minter indexing --- docs/bin/default.dot | 55 +- docs/bin/default.svg | 254 ++--- docs/bin/openapi.json | 92 +- docs/bin/schema.sql | 53 + docs/bin/sql-graph.svg | 909 +++++++++--------- .../indexer/Tasks/ByronBlockMinterTask.md | 33 + indexer/entity/src/block.rs | 2 + indexer/entity/src/block_minter.rs | 29 + indexer/entity/src/lib.rs | 1 + indexer/entity/src/prelude.rs | 5 + indexer/execution_plans/default.toml | 3 + indexer/migration/src/lib.rs | 2 + ...220811_000014_create_block_minter_table.rs | 49 + indexer/tasks/src/byron/byron_block_minter.rs | 58 ++ indexer/tasks/src/byron/mod.rs | 1 + .../app/controllers/AddressInUseController.ts | 2 +- .../app/controllers/BlockMinterController.ts | 41 + .../CredentialAddressesController.ts | 2 +- .../TransactionHistoryController.ts | 2 +- .../models/block/sqlBlockMinter.queries.ts | 34 + .../app/models/block/sqlBlockMinter.sql | 4 + .../server/app/services/BlockMinterService.ts | 24 + webserver/shared/errors.ts | 4 +- webserver/shared/models/BlockMinter.ts | 9 + webserver/shared/models/common.ts | 25 +- webserver/shared/routes.ts | 7 + 26 files changed, 1085 insertions(+), 615 deletions(-) create mode 100644 docs/docs/indexer/Tasks/ByronBlockMinterTask.md create mode 100644 indexer/entity/src/block_minter.rs create mode 100644 indexer/migration/src/m20220811_000014_create_block_minter_table.rs create mode 100644 indexer/tasks/src/byron/byron_block_minter.rs create mode 100644 webserver/server/app/controllers/BlockMinterController.ts create mode 100644 webserver/server/app/models/block/sqlBlockMinter.queries.ts create mode 100644 webserver/server/app/models/block/sqlBlockMinter.sql create mode 100644 webserver/server/app/services/BlockMinterService.ts create mode 100644 webserver/shared/models/BlockMinter.ts diff --git a/docs/bin/default.dot b/docs/bin/default.dot index c5c70261..c839d1ed 100644 --- a/docs/bin/default.dot +++ b/docs/bin/default.dot @@ -17,6 +17,7 @@ N3; N4; N5; N6; +N7; } @@ -24,7 +25,6 @@ subgraph cluster_Multiera { label="Multiera"; color="grey85"; -N7; N8; N9; N10; @@ -37,6 +37,7 @@ N16; N17; N18; N19; +N20; } @@ -47,38 +48,40 @@ N19; N4[label="ByronAddressTask"][shape="box"]; N5[label="ByronOutputTask"][shape="box"]; N6[label="ByronInputTask"][shape="box"]; - N7[label="MultieraBlockTask"][shape="box"]; - N8[label="MultieraTransactionTask"][shape="box"]; - N9[label="MultieraMetadataTask"][shape="box"]; - N10[label="MultieraAddressTask"][shape="box"]; - N11[label="MultieraOutputTask"][shape="box"]; - N12[label="MultieraReferenceInputTask"][shape="box"]; - N13[label="MultieraUsedInputTask"][shape="box"]; - N14[label="MultieraUnusedInputTask"][shape="box"]; - N15[label="MultieraStakeCredentialTask"][shape="box"]; - N16[label="MultieraAddressCredentialRelationTask"][shape="box"]; - N17[label="MultieraTxCredentialRelationTask"][shape="box"]; - N18[label="MultieraAssetMintTask"][shape="box"]; - N19[label="MultieraCip25EntryTask"][shape="box"]; + N7[label="ByronBlockMinterTask"][shape="box"]; + N8[label="MultieraBlockTask"][shape="box"]; + N9[label="MultieraTransactionTask"][shape="box"]; + N10[label="MultieraMetadataTask"][shape="box"]; + N11[label="MultieraAddressTask"][shape="box"]; + N12[label="MultieraOutputTask"][shape="box"]; + N13[label="MultieraReferenceInputTask"][shape="box"]; + N14[label="MultieraUsedInputTask"][shape="box"]; + N15[label="MultieraUnusedInputTask"][shape="box"]; + N16[label="MultieraStakeCredentialTask"][shape="box"]; + N17[label="MultieraAddressCredentialRelationTask"][shape="box"]; + N18[label="MultieraTxCredentialRelationTask"][shape="box"]; + N19[label="MultieraAssetMintTask"][shape="box"]; + N20[label="MultieraCip25EntryTask"][shape="box"]; N0 -> N1[label=""]; N2 -> N3[label=""]; N3 -> N4[label=""]; N4 -> N5[label=""]; N5 -> N6[label=""]; - N7 -> N8[label=""]; + N2 -> N7[label=""]; N8 -> N9[label=""]; - N8 -> N10[label=""]; - N10 -> N11[label=""]; + N9 -> N10[label=""]; + N9 -> N11[label=""]; N11 -> N12[label=""]; - N11 -> N13[label=""]; - N11 -> N14[label=""]; - N13 -> N15[label=""]; - N14 -> N15[label=""]; - N10 -> N16[label=""]; + N12 -> N13[label=""]; + N12 -> N14[label=""]; + N12 -> N15[label=""]; + N14 -> N16[label=""]; N15 -> N16[label=""]; - N10 -> N17[label=""]; - N15 -> N17[label=""]; - N8 -> N18[label=""]; + N11 -> N17[label=""]; + N16 -> N17[label=""]; + N11 -> N18[label=""]; + N16 -> N18[label=""]; N9 -> N19[label=""]; - N18 -> N19[label=""]; + N10 -> N20[label=""]; + N19 -> N20[label=""]; } diff --git a/docs/bin/default.svg b/docs/bin/default.svg index 29434372..eea95c6e 100644 --- a/docs/bin/default.svg +++ b/docs/bin/default.svg @@ -4,11 +4,11 @@ - + default - + cluster_Genesis @@ -16,13 +16,13 @@ cluster_Byron - -Byron + +Byron cluster_Multiera - -Multiera + +Multiera @@ -45,8 +45,8 @@ N2 - -ByronBlockTask + +ByronBlockTask @@ -57,8 +57,20 @@ N2->N3 - - + + + + + +N7 + +ByronBlockMinterTask + + + +N2->N7 + + @@ -96,179 +108,179 @@ - - -N7 - -MultieraBlockTask - N8 - -MultieraTransactionTask - - - -N7->N8 - - + +MultieraBlockTask N9 - -MultieraMetadataTask + +MultieraTransactionTask N8->N9 - - + + N10 - -MultieraAddressTask + +MultieraMetadataTask - + -N8->N10 - - +N9->N10 + + - - -N18 - -MultieraAssetMintTask + + +N11 + +MultieraAddressTask - - -N8->N18 - - + + +N9->N11 + + N19 - -MultieraCip25EntryTask + +MultieraAssetMintTask N9->N19 - - + + - - -N11 - -MultieraOutputTask + + +N20 + +MultieraCip25EntryTask - - -N10->N11 - - + + +N10->N20 + + - - -N16 - -MultieraAddressCredentialRelationTask + + +N12 + +MultieraOutputTask - - -N10->N16 - - + + +N11->N12 + + N17 - -MultieraTxCredentialRelationTask + +MultieraAddressCredentialRelationTask - - -N10->N17 - - + + +N11->N17 + + - - -N12 - -MultieraReferenceInputTask + + +N18 + +MultieraTxCredentialRelationTask - - -N11->N12 - - + + +N11->N18 + + N13 - -MultieraUsedInputTask + +MultieraReferenceInputTask - + -N11->N13 - - +N12->N13 + + N14 - -MultieraUnusedInputTask + +MultieraUsedInputTask - + -N11->N14 - - +N12->N14 + + N15 - -MultieraStakeCredentialTask + +MultieraUnusedInputTask - + -N13->N15 - - +N12->N15 + + + + + +N16 + +MultieraStakeCredentialTask - + -N14->N15 - - +N14->N16 + + - + N15->N16 - - + + - - -N15->N17 - - + + +N16->N17 + + - - -N18->N19 - - + + +N16->N18 + + + + + +N19->N20 + + diff --git a/docs/bin/openapi.json b/docs/bin/openapi.json index f46e5c5c..946cb299 100644 --- a/docs/bin/openapi.json +++ b/docs/bin/openapi.json @@ -55,19 +55,23 @@ ], "type": "object" }, + "BlockHash": { + "type": "string", + "example": "cf8c63a909d91776e27f7d05457e823a9dba606a7ab499ac435e7904ee70d7c8", + "description": "[0-9a-fA-F]{64}" + }, + "TxHash": { + "type": "string", + "example": "336d520af58ff440b2f20210ddb5ef5b2c035e0ec7ec258bae4b519a87fa1696", + "pattern": "[0-9a-fA-F]{64}" + }, "BlockTxPair": { "properties": { "tx": { - "type": "string", - "description": "tx hash", - "example": "336d520af58ff440b2f20210ddb5ef5b2c035e0ec7ec258bae4b519a87fa1696", - "pattern": "[0-9a-fA-F]{64}" + "$ref": "#/components/schemas/TxHash" }, "block": { - "type": "string", - "description": "block hash", - "example": "2548ad5d0d9d33d50ab43151f574474454017a733e307229fa509c4987ca9782", - "pattern": "[0-9a-fA-F]{64}" + "$ref": "#/components/schemas/BlockHash" } }, "required": [ @@ -207,6 +211,28 @@ ], "type": "object" }, + "BlockMinterResponse": { + "properties": { + "pubkey": { + "type": "string" + } + }, + "required": [ + "pubkey" + ], + "type": "object" + }, + "BlockMinterRequest": { + "properties": { + "hash": { + "$ref": "#/components/schemas/BlockHash" + } + }, + "required": [ + "hash" + ], + "type": "object" + }, "PageInfo": { "properties": { "pageInfo": { @@ -654,6 +680,56 @@ } } }, + "/block/minter": { + "post": { + "operationId": "BlockMinter", + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlockMinterResponse" + } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorShape" + } + } + } + }, + "409": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorShape" + } + } + } + } + }, + "description": "Get the latest block. Useful for checking synchronization process and pagination", + "security": [], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlockMinterRequest" + } + } + } + } + } + }, "/credential/address": { "post": { "operationId": "AddressesForCredential", diff --git a/docs/bin/schema.sql b/docs/bin/schema.sql index b0433e9b..0d09487d 100644 --- a/docs/bin/schema.sql +++ b/docs/bin/schema.sql @@ -86,6 +86,36 @@ CREATE TABLE public."Block" ( ); +-- +-- Name: BlockMinter; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public."BlockMinter" ( + id integer NOT NULL, + key bytea NOT NULL +); + + +-- +-- Name: BlockMinter_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public."BlockMinter_id_seq" + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: BlockMinter_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public."BlockMinter_id_seq" OWNED BY public."BlockMinter".id; + + -- -- Name: Block_id_seq; Type: SEQUENCE; Schema: public; Owner: - -- @@ -454,6 +484,13 @@ ALTER TABLE ONLY public."Address" ALTER COLUMN id SET DEFAULT nextval('public."A ALTER TABLE ONLY public."Block" ALTER COLUMN id SET DEFAULT nextval('public."Block_id_seq"'::regclass); +-- +-- Name: BlockMinter id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."BlockMinter" ALTER COLUMN id SET DEFAULT nextval('public."BlockMinter_id_seq"'::regclass); + + -- -- Name: Cip25Entry id; Type: DEFAULT; Schema: public; Owner: - -- @@ -540,6 +577,14 @@ ALTER TABLE ONLY public."Address" ADD CONSTRAINT "Address_pkey" PRIMARY KEY (id); +-- +-- Name: BlockMinter BlockMinter_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."BlockMinter" + ADD CONSTRAINT "BlockMinter_pkey" PRIMARY KEY (id); + + -- -- Name: Block Block_hash_key; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -892,6 +937,14 @@ ALTER TABLE ONLY public."AssetMint" ADD CONSTRAINT "fk-asset_mint-transaction_id" FOREIGN KEY (tx_id) REFERENCES public."Transaction"(id) ON DELETE CASCADE; +-- +-- Name: BlockMinter fk-block_minter-block_id; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."BlockMinter" + ADD CONSTRAINT "fk-block_minter-block_id" FOREIGN KEY (id) REFERENCES public."Block"(id) ON DELETE CASCADE; + + -- -- Name: Cip25Entry fk-cip25_entry-asset_id; Type: FK CONSTRAINT; Schema: public; Owner: - -- diff --git a/docs/bin/sql-graph.svg b/docs/bin/sql-graph.svg index 8d15fb3d..db31970f 100644 --- a/docs/bin/sql-graph.svg +++ b/docs/bin/sql-graph.svg @@ -4,575 +4,598 @@ - - + + g - + ['public.', 'Address'] - - - ['public.', 'Address'] - - - id - - bigint NOT NULL - - - payload - - bytea NOT NULL - - - first_tx - - bigint NOT NULL + + + ['public.', 'Address'] + + + id + + bigint NOT NULL + + + payload + + bytea NOT NULL + + + first_tx + + bigint NOT NULL - + ['public.', 'Transaction'] - - - ['public.', 'Transaction'] - - - id - - bigint NOT NULL - - - hash - - bytea NOT NULL - - - block_id - - integer NOT NULL - - - tx_index - - integer NOT NULL - - - payload - - bytea NOT NULL - - - is_valid - - boolean NOT NULL + + + ['public.', 'Transaction'] + + + id + + bigint NOT NULL + + + hash + + bytea NOT NULL + + + block_id + + integer NOT NULL + + + tx_index + + integer NOT NULL + + + payload + + bytea NOT NULL + + + is_valid + + boolean NOT NULL ['public.', 'Address']:first_tx_right->['public.', 'Transaction']:id - - + + ['public.', 'AddressCredentialRelation'] - - - ['public.', 'AddressCredentialRelation'] - - - address_id - - bigint NOT NULL - - - credential_id - - bigint NOT NULL - - - relation - - integer NOT NULL + + + ['public.', 'AddressCredentialRelation'] + + + address_id + + bigint NOT NULL + + + credential_id + + bigint NOT NULL + + + relation + + integer NOT NULL ['public.', 'AddressCredentialRelation']:address_id_right->['public.', 'Address']:id - - + + - + ['public.', 'StakeCredential'] - - - ['public.', 'StakeCredential'] - - - id - - bigint NOT NULL - - - credential - - bytea NOT NULL - - - first_tx - - bigint NOT NULL + + + ['public.', 'StakeCredential'] + + + id + + bigint NOT NULL + + + credential + + bytea NOT NULL + + + first_tx + + bigint NOT NULL ['public.', 'AddressCredentialRelation']:credential_id_right->['public.', 'StakeCredential']:id - - + + ['public.', 'AssetMint'] - - - ['public.', 'AssetMint'] - - - tx_id - - bigint NOT NULL - - - asset_id - - bigint NOT NULL - - - amount - - bigint NOT NULL + + + ['public.', 'AssetMint'] + + + tx_id + + bigint NOT NULL + + + asset_id + + bigint NOT NULL + + + amount + + bigint NOT NULL - + ['public.', 'NativeAsset'] - - - ['public.', 'NativeAsset'] - - - id - - bigint NOT NULL - - - policy_id - - bytea NOT NULL - - - asset_name - - bytea NOT NULL - - - cip14_fingerprint - - bytea NOT NULL - - - first_tx - - bigint NOT NULL + + + ['public.', 'NativeAsset'] + + + id + + bigint NOT NULL + + + policy_id + + bytea NOT NULL + + + asset_name + + bytea NOT NULL + + + cip14_fingerprint + + bytea NOT NULL + + + first_tx + + bigint NOT NULL ['public.', 'AssetMint']:asset_id_right->['public.', 'NativeAsset']:id - - + + ['public.', 'AssetMint']:tx_id_right->['public.', 'Transaction']:id - - + + ['public.', 'Block'] - - - ['public.', 'Block'] - - - id - - integer NOT NULL - - - era - - integer NOT NULL - - - hash - - bytea NOT NULL - - - height - - integer NOT NULL - - - epoch - - integer NOT NULL - - - slot - - integer NOT NULL + + + ['public.', 'Block'] + + + id + + integer NOT NULL + + + era + + integer NOT NULL + + + hash + + bytea NOT NULL + + + height + + integer NOT NULL + + + epoch + + integer NOT NULL + + + slot + + integer NOT NULL + + + +['public.', 'BlockMinter'] + + + ['public.', 'BlockMinter'] + + + id + + integer NOT NULL + + + key + + bytea NOT NULL + + + +['public.', 'BlockMinter']:id_right->['public.', 'Block']:id + + - + ['public.', 'Cip25Entry'] - - - ['public.', 'Cip25Entry'] - - - id - - bigint NOT NULL - - - metadata_id - - bigint NOT NULL - - - asset_id - - bigint NOT NULL - - - version - - text NOT NULL - - - payload - - bytea NOT NULL + + + ['public.', 'Cip25Entry'] + + + id + + bigint NOT NULL + + + metadata_id + + bigint NOT NULL + + + asset_id + + bigint NOT NULL + + + version + + text NOT NULL + + + payload + + bytea NOT NULL - + ['public.', 'Cip25Entry']:asset_id_right->['public.', 'NativeAsset']:id - - + + - + ['public.', 'TransactionMetadata'] - - - ['public.', 'TransactionMetadata'] - - - id - - bigint NOT NULL - - - tx_id - - bigint NOT NULL - - - label - - bytea NOT NULL - - - payload - - bytea NOT NULL + + + ['public.', 'TransactionMetadata'] + + + id + + bigint NOT NULL + + + tx_id + + bigint NOT NULL + + + label + + bytea NOT NULL + + + payload + + bytea NOT NULL - + ['public.', 'Cip25Entry']:metadata_id_right->['public.', 'TransactionMetadata']:id - - + + - + ['public.', 'NativeAsset']:first_tx_right->['public.', 'Transaction']:id - - + + - + ['public.', 'PlutusData'] - - - ['public.', 'PlutusData'] - - - id - - bigint NOT NULL - - - data - - bytea NOT NULL + + + ['public.', 'PlutusData'] + + + id + + bigint NOT NULL + + + data + + bytea NOT NULL - + ['public.', 'PlutusDataHash'] - - - ['public.', 'PlutusDataHash'] - - - id - - bigint NOT NULL - - - hash - - bytea NOT NULL - - - first_tx - - bigint NOT NULL + + + ['public.', 'PlutusDataHash'] + + + id + + bigint NOT NULL + + + hash + + bytea NOT NULL + + + first_tx + + bigint NOT NULL - + ['public.', 'PlutusData']:id_right->['public.', 'PlutusDataHash']:id - - + + - + ['public.', 'PlutusDataHash']:first_tx_right->['public.', 'Transaction']:id - - + + - + ['public.', 'StakeCredential']:first_tx_right->['public.', 'Transaction']:id - - + + - + ['public.', 'Transaction']:block_id_right->['public.', 'Block']:id - - + + - + ['public.', 'TransactionInput'] - - - ['public.', 'TransactionInput'] - - - id - - bigint NOT NULL - - - utxo_id - - bigint NOT NULL - - - tx_id - - bigint NOT NULL - - - address_id - - bigint NOT NULL - - - input_index - - integer NOT NULL + + + ['public.', 'TransactionInput'] + + + id + + bigint NOT NULL + + + utxo_id + + bigint NOT NULL + + + tx_id + + bigint NOT NULL + + + address_id + + bigint NOT NULL + + + input_index + + integer NOT NULL - + ['public.', 'TransactionInput']:address_id_right->['public.', 'Address']:id - - + + - + ['public.', 'TransactionInput']:tx_id_right->['public.', 'Transaction']:id - - + + - + ['public.', 'TransactionOutput'] - - - ['public.', 'TransactionOutput'] - - - id - - bigint NOT NULL - - - payload - - bytea NOT NULL - - - address_id - - bigint NOT NULL - - - tx_id - - bigint NOT NULL - - - output_index - - integer NOT NULL + + + ['public.', 'TransactionOutput'] + + + id + + bigint NOT NULL + + + payload + + bytea NOT NULL + + + address_id + + bigint NOT NULL + + + tx_id + + bigint NOT NULL + + + output_index + + integer NOT NULL - + ['public.', 'TransactionInput']:utxo_id_right->['public.', 'TransactionOutput']:id - - + + - + ['public.', 'TransactionMetadata']:tx_id_right->['public.', 'Transaction']:id - - + + - + ['public.', 'TransactionOutput']:address_id_right->['public.', 'Address']:id - - + + - + ['public.', 'TransactionOutput']:tx_id_right->['public.', 'Transaction']:id - - + + - + ['public.', 'TransactionReferenceInput'] - - - ['public.', 'TransactionReferenceInput'] - - - id - - bigint NOT NULL - - - utxo_id - - bigint NOT NULL - - - tx_id - - bigint NOT NULL - - - address_id - - bigint NOT NULL - - - input_index - - integer NOT NULL + + + ['public.', 'TransactionReferenceInput'] + + + id + + bigint NOT NULL + + + utxo_id + + bigint NOT NULL + + + tx_id + + bigint NOT NULL + + + address_id + + bigint NOT NULL + + + input_index + + integer NOT NULL - + ['public.', 'TransactionReferenceInput']:address_id_right->['public.', 'Address']:id - - + + - + ['public.', 'TransactionReferenceInput']:tx_id_right->['public.', 'Transaction']:id - - + + - + ['public.', 'TransactionReferenceInput']:utxo_id_right->['public.', 'TransactionOutput']:id - - + + - + ['public.', 'TxCredentialRelation'] - - - ['public.', 'TxCredentialRelation'] - - - credential_id - - bigint NOT NULL - - - tx_id - - bigint NOT NULL - - - relation - - integer NOT NULL + + + ['public.', 'TxCredentialRelation'] + + + credential_id + + bigint NOT NULL + + + tx_id + + bigint NOT NULL + + + relation + + integer NOT NULL - + ['public.', 'TxCredentialRelation']:credential_id_right->['public.', 'StakeCredential']:id - - + + - + ['public.', 'TxCredentialRelation']:tx_id_right->['public.', 'Transaction']:id - - + + - + ['public.seaql_migrations'] - - - ['public.seaql_migrations'] - - - version - - character varying NOT NULL - - - applied_at - - bigint NOT NULL + + + ['public.seaql_migrations'] + + + version + + character varying NOT NULL + + + applied_at + + bigint NOT NULL diff --git a/docs/docs/indexer/Tasks/ByronBlockMinterTask.md b/docs/docs/indexer/Tasks/ByronBlockMinterTask.md new file mode 100644 index 00000000..7ac00f8c --- /dev/null +++ b/docs/docs/indexer/Tasks/ByronBlockMinterTask.md @@ -0,0 +1,33 @@ +# ByronBlockMinterTask +Adds the minter of a block to the database + + +
+ Configuration + +```rust +#[derive(Debug, Clone, Copy, serde::Deserialize, serde::Serialize)] +pub struct ReadonlyConfig { + pub readonly: bool, +} + +``` +
+ + +## Era +` byron ` + +## Dependencies + + * [ByronBlockTask](./ByronBlockTask) + + +## Data accessed +#### Reads from + + * ` byron_block ` + + +## Full source +[source](https://github.com/dcSpark/carp/tree/main/indexer/tasks/src/byron/byron_block_minter.rs) diff --git a/indexer/entity/src/block.rs b/indexer/entity/src/block.rs index 410cd7cd..044517d4 100644 --- a/indexer/entity/src/block.rs +++ b/indexer/entity/src/block.rs @@ -17,6 +17,8 @@ pub struct Model { pub enum Relation { #[sea_orm(has_many = "super::transaction::Entity")] Transaction, + #[sea_orm(has_many = "super::block_minter::Entity")] + BlockMinter, } impl ActiveModelBehavior for ActiveModel {} diff --git a/indexer/entity/src/block_minter.rs b/indexer/entity/src/block_minter.rs new file mode 100644 index 00000000..11a4344a --- /dev/null +++ b/indexer/entity/src/block_minter.rs @@ -0,0 +1,29 @@ +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize, Serialize)] +#[sea_orm(table_name = "BlockMinter")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub key: Vec, +} + +#[derive(Copy, Clone, Debug, DeriveRelation, EnumIter)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::block::Entity", + from = "Column::Id", + to = "super::block::Column::Id" + )] + Block, +} + +// TODO: figure out why this isn't automatically handle by the macros above +impl Related for Entity { + fn to() -> RelationDef { + Relation::Block.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/indexer/entity/src/lib.rs b/indexer/entity/src/lib.rs index 8f31e9d7..41a6b016 100644 --- a/indexer/entity/src/lib.rs +++ b/indexer/entity/src/lib.rs @@ -10,6 +10,7 @@ pub mod transaction_reference_input; pub mod tx_credential; pub use sea_orm; pub mod asset_mint; +pub mod block_minter; pub mod cip25_entry; pub mod native_asset; pub mod plutus_data; diff --git a/indexer/entity/src/prelude.rs b/indexer/entity/src/prelude.rs index 55d38c8f..f23c6ea3 100644 --- a/indexer/entity/src/prelude.rs +++ b/indexer/entity/src/prelude.rs @@ -15,6 +15,11 @@ pub use super::block::{ ActiveModel as BlockActiveModel, Column as BlockColumn, Entity as Block, Model as BlockModel, PrimaryKey as BlockPrimaryKey, Relation as BlockRelation, }; +pub use super::block_minter::{ + ActiveModel as BlockMinterActiveModel, Column as BlockMinterColumn, Entity as BlockMinter, + Model as BlockMinterModel, PrimaryKey as BlockMinterPrimaryKey, + Relation as BlockMinterRelation, +}; pub use super::cip25_entry::{ ActiveModel as Cip25EntryActiveModel, Column as Cip25EntryColumn, Entity as Cip25Entry, Model as Cip25EntryModel, PrimaryKey as Cip25EntryPrimaryKey, Relation as Cip25EntryRelation, diff --git a/indexer/execution_plans/default.toml b/indexer/execution_plans/default.toml index 0ddd810b..ceb39093 100644 --- a/indexer/execution_plans/default.toml +++ b/indexer/execution_plans/default.toml @@ -24,6 +24,9 @@ readonly=false [ByronInputTask] +[ByronBlockMinterTask] +readonly=false + [MultieraBlockTask] readonly=false diff --git a/indexer/migration/src/lib.rs b/indexer/migration/src/lib.rs index 0896f6df..cca00614 100644 --- a/indexer/migration/src/lib.rs +++ b/indexer/migration/src/lib.rs @@ -15,6 +15,7 @@ mod m20220520_000010_create_cip25_entry_table; mod m20220528_000011_create_plutus_data_hash_table; mod m20220528_000012_create_plutus_data_table; mod m20220808_000013_create_transaction_reference_input_table; +mod m20220811_000014_create_block_minter_table; pub struct Migrator; @@ -37,6 +38,7 @@ impl MigratorTrait for Migrator { Box::new(m20220528_000011_create_plutus_data_hash_table::Migration), Box::new(m20220528_000012_create_plutus_data_table::Migration), Box::new(m20220808_000013_create_transaction_reference_input_table::Migration), + Box::new(m20220811_000014_create_block_minter_table::Migration), ] } } diff --git a/indexer/migration/src/m20220811_000014_create_block_minter_table.rs b/indexer/migration/src/m20220811_000014_create_block_minter_table.rs new file mode 100644 index 00000000..829f849e --- /dev/null +++ b/indexer/migration/src/m20220811_000014_create_block_minter_table.rs @@ -0,0 +1,49 @@ +use sea_schema::migration::prelude::*; + +use entity::{ + block_minter::*, + prelude::{Block, BlockColumn}, +}; + +pub struct Migration; + +impl MigrationName for Migration { + fn name(&self) -> &str { + "m20220811_000014_create_block_minter_table" + } +} + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + Table::create() + .table(Entity) + .if_not_exists() + .col( + ColumnDef::new(Column::Id) + .integer() + .not_null() + .auto_increment() + .primary_key(), + ) + .foreign_key( + ForeignKey::create() + .name("fk-block_minter-block_id") + .from(Entity, Column::Id) + .to(Block, BlockColumn::Id) + .on_delete(ForeignKeyAction::Cascade), + ) + .col(ColumnDef::new(Column::Key).binary().not_null()) + .to_owned(), + ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(Entity).to_owned()) + .await + } +} diff --git a/indexer/tasks/src/byron/byron_block_minter.rs b/indexer/tasks/src/byron/byron_block_minter.rs new file mode 100644 index 00000000..4492241e --- /dev/null +++ b/indexer/tasks/src/byron/byron_block_minter.rs @@ -0,0 +1,58 @@ +use crate::config::ReadonlyConfig::ReadonlyConfig; +use crate::dsl::task_macro::*; +use entity::{block::EraValue, sea_orm::Set}; +use hex::ToHex; +use pallas::ledger::primitives::{byron, Fragment}; + +use super::byron_block::ByronBlockTask; + +carp_task! { + name ByronBlockMinterTask; + configuration ReadonlyConfig; + doc "Adds the minter of a block to the database"; + era byron; + dependencies [ByronBlockTask]; + read [byron_block]; + write []; + should_add_task |_block, _properties| { + true + }; + execute |previous_data, task| handle_block( + task.db_tx, + task.block, + &previous_data.byron_block.as_ref().unwrap(), + task.config.readonly + ); + merge_result |previous_data, _result| { + }; +} + +async fn handle_block( + db_tx: &DatabaseTransaction, + block: BlockInfo<'_, MultiEraBlock<'_>>, + database_block: &BlockModel, + readonly: bool, +) -> Result { + if readonly { + let entry = BlockMinter::find() + .filter(BlockMinterColumn::Id.eq(database_block.id)) + .one(db_tx) + .await?; + return Ok(match entry { + None => { + panic!( + "Block not found in database: {}", + hex::encode(block.1.hash()) + ); + } + Some(block_minter) => block_minter, + }); + } + + let block_minter = BlockMinterActiveModel { + id: Set(database_block.id), + key: Set(block.1.as_byron().unwrap().header.consensus_data.1.to_vec()), + }; + + Ok(block_minter.insert(db_tx).await?) +} diff --git a/indexer/tasks/src/byron/mod.rs b/indexer/tasks/src/byron/mod.rs index 7c8d0fcf..e166e31b 100644 --- a/indexer/tasks/src/byron/mod.rs +++ b/indexer/tasks/src/byron/mod.rs @@ -1,5 +1,6 @@ pub mod byron_address; pub mod byron_block; +pub mod byron_block_minter; pub mod byron_executor; pub mod byron_inputs; pub mod byron_outputs; diff --git a/webserver/server/app/controllers/AddressInUseController.ts b/webserver/server/app/controllers/AddressInUseController.ts index 880f21d9..112df564 100644 --- a/webserver/server/app/controllers/AddressInUseController.ts +++ b/webserver/server/app/controllers/AddressInUseController.ts @@ -78,7 +78,7 @@ export class AddressInUseController extends Controller { ]); if (until == null) { return genErrorMessage(Errors.BlockHashNotFound, { - untilBlock: requestBody.untilBlock, + block: requestBody.untilBlock, }); } if (requestBody.after != null && pageStart == null) { diff --git a/webserver/server/app/controllers/BlockMinterController.ts b/webserver/server/app/controllers/BlockMinterController.ts new file mode 100644 index 00000000..a8c38054 --- /dev/null +++ b/webserver/server/app/controllers/BlockMinterController.ts @@ -0,0 +1,41 @@ +import { Body, Controller, TsoaResponse, Res, Post, Route, SuccessResponse } from 'tsoa'; +import { StatusCodes } from 'http-status-codes'; +import pool from '../services/PgPoolSingleton'; +import type { ErrorShape } from '../../../shared/errors'; +import { genErrorMessage } from '../../../shared/errors'; +import { Errors } from '../../../shared/errors'; +import type { EndpointTypes } from '../../../shared/routes'; +import { Routes } from '../../../shared/routes'; +import { getBlockMinter } from '../services/BlockMinterService'; + +const route = Routes.blockMinter; + +@Route('block/minter') +export class BlockMinterController extends Controller { + /** + * Get the latest block. Useful for checking synchronization process and pagination + */ + @SuccessResponse(`${StatusCodes.OK}`) + @Post() + public async blockMinter( + @Body() + requestBody: EndpointTypes[typeof route]['input'], + @Res() + errorResponse: TsoaResponse + ): Promise { + const minter = await getBlockMinter({ + dbTx: pool, + ...requestBody, + }); + if (minter == null) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return errorResponse( + StatusCodes.CONFLICT, + genErrorMessage(Errors.BlockHashNotFound, { + block: requestBody.hash, + }) + ); + } + return minter; + } +} diff --git a/webserver/server/app/controllers/CredentialAddressesController.ts b/webserver/server/app/controllers/CredentialAddressesController.ts index 47c97f5b..2970c30a 100644 --- a/webserver/server/app/controllers/CredentialAddressesController.ts +++ b/webserver/server/app/controllers/CredentialAddressesController.ts @@ -103,7 +103,7 @@ export class CredentialAddressesController extends Controller { ]); if (until == null) { return genErrorMessage(Errors.BlockHashNotFound, { - untilBlock: requestBody.untilBlock, + block: requestBody.untilBlock, }); } diff --git a/webserver/server/app/controllers/TransactionHistoryController.ts b/webserver/server/app/controllers/TransactionHistoryController.ts index 36609e73..bfca9994 100644 --- a/webserver/server/app/controllers/TransactionHistoryController.ts +++ b/webserver/server/app/controllers/TransactionHistoryController.ts @@ -77,7 +77,7 @@ export class TransactionHistoryController extends Controller { ]); if (until == null) { return genErrorMessage(Errors.BlockHashNotFound, { - untilBlock: requestBody.untilBlock, + block: requestBody.untilBlock, }); } if (requestBody.after != null && pageStart == null) { diff --git a/webserver/server/app/models/block/sqlBlockMinter.queries.ts b/webserver/server/app/models/block/sqlBlockMinter.queries.ts new file mode 100644 index 00000000..2b637846 --- /dev/null +++ b/webserver/server/app/models/block/sqlBlockMinter.queries.ts @@ -0,0 +1,34 @@ +/** Types generated for queries found in "app/models/block/sqlBlockMinter.sql" */ +import { PreparedQuery } from '@pgtyped/query'; + +export type BufferArray = (Buffer)[]; + +/** 'SqlBlockMinter' parameters type */ +export interface ISqlBlockMinterParams { + addresses: BufferArray | null | void; +} + +/** 'SqlBlockMinter' return type */ +export interface ISqlBlockMinterResult { + key: Buffer; +} + +/** 'SqlBlockMinter' query type */ +export interface ISqlBlockMinterQuery { + params: ISqlBlockMinterParams; + result: ISqlBlockMinterResult; +} + +const sqlBlockMinterIR: any = {"name":"sqlBlockMinter","params":[{"name":"addresses","required":false,"transform":{"type":"scalar"},"codeRefs":{"used":[{"a":136,"b":144,"line":4,"col":27}]}}],"usedParamSet":{"addresses":true},"statement":{"body":"SELECT key FROM \"Block\"\nINNER JOIN \"BlockMinter\" ON \"BlockMinter\".id = \"Block\".id\nWHERE \"Block\".hash = ANY (:addresses)","loc":{"a":27,"b":145,"line":2,"col":0}}}; + +/** + * Query generated from SQL: + * ``` + * SELECT key FROM "Block" + * INNER JOIN "BlockMinter" ON "BlockMinter".id = "Block".id + * WHERE "Block".hash = ANY (:addresses) + * ``` + */ +export const sqlBlockMinter = new PreparedQuery(sqlBlockMinterIR); + + diff --git a/webserver/server/app/models/block/sqlBlockMinter.sql b/webserver/server/app/models/block/sqlBlockMinter.sql new file mode 100644 index 00000000..c98bc918 --- /dev/null +++ b/webserver/server/app/models/block/sqlBlockMinter.sql @@ -0,0 +1,4 @@ +/* @name sqlBlockMinter */ +SELECT key FROM "Block" +INNER JOIN "BlockMinter" ON "BlockMinter".id = "Block".id +WHERE "Block".hash = ANY (:addresses); \ No newline at end of file diff --git a/webserver/server/app/services/BlockMinterService.ts b/webserver/server/app/services/BlockMinterService.ts new file mode 100644 index 00000000..62dea488 --- /dev/null +++ b/webserver/server/app/services/BlockMinterService.ts @@ -0,0 +1,24 @@ +import type { Pool } from 'pg'; +import type { BlockMinterRequest, BlockMinterResponse } from '../../../shared/models/BlockMinter'; +// import { sqlBlockMinter } from '../models/block/sqlBlockMinter.queries'; + +export async function getBlockMinter( + request: BlockMinterRequest & { dbTx: Pool } +): Promise { + return undefined; + // const bestBlock = await sqlBlockMinter.run( + // { + // offset: request.offset.toString(), + // }, + // request.dbTx + // ); + // return { + // block: bestBlock.map(block => ({ + // era: block.era, + // hash: block.hash.toString('hex'), + // height: block.height, + // epoch: block.epoch, + // slot: block.slot, + // }))[0], + // }; +} diff --git a/webserver/shared/errors.ts b/webserver/shared/errors.ts index 9f9cd702..09cb6ef5 100644 --- a/webserver/shared/errors.ts +++ b/webserver/shared/errors.ts @@ -45,8 +45,8 @@ export const Errors = { BlockHashNotFound: { code: ErrorCodes.BlockHashNotFound, prefix: "Block hash not found.", - detailsGen: (details: { untilBlock: string }) => - `Searched block hash: ${details.untilBlock}`, + detailsGen: (details: { block: string }) => + `Searched block hash: ${details.block}`, }, PageStartNotFound: { code: ErrorCodes.PageStartNotFound, diff --git a/webserver/shared/models/BlockMinter.ts b/webserver/shared/models/BlockMinter.ts new file mode 100644 index 00000000..b636367f --- /dev/null +++ b/webserver/shared/models/BlockMinter.ts @@ -0,0 +1,9 @@ +import { BlockHash } from "./common"; + +export type BlockMinterRequest = { + hash: BlockHash; +}; +export type BlockMinterResponse = { + // TODO: example and pattern + pubkey: string; +}; diff --git a/webserver/shared/models/common.ts b/webserver/shared/models/common.ts index 5d313009..4e0e378c 100644 --- a/webserver/shared/models/common.ts +++ b/webserver/shared/models/common.ts @@ -37,19 +37,20 @@ export enum RelationFilterType { NO_FILTER = 0xff, } +/** + * [0-9a-fA-F]{64} + * @example "cf8c63a909d91776e27f7d05457e823a9dba606a7ab499ac435e7904ee70d7c8" + */ +export type BlockHash = string; +/** + * @pattern [0-9a-fA-F]{64} + * @example "336d520af58ff440b2f20210ddb5ef5b2c035e0ec7ec258bae4b519a87fa1696" + */ +export type TxHash = string; + export type BlockTxPair = { - /** - * block hash - * @pattern [0-9a-fA-F]{64} - * @example "2548ad5d0d9d33d50ab43151f574474454017a733e307229fa509c4987ca9782" - */ - block: string; - /** - * tx hash - * @pattern [0-9a-fA-F]{64} - * @example "336d520af58ff440b2f20210ddb5ef5b2c035e0ec7ec258bae4b519a87fa1696" - */ - tx: string; + block: BlockHash; + tx: TxHash; }; export type AfterBlockPagination = { /** diff --git a/webserver/shared/routes.ts b/webserver/shared/routes.ts index 0cb593fd..8e3d6b3d 100644 --- a/webserver/shared/routes.ts +++ b/webserver/shared/routes.ts @@ -3,6 +3,7 @@ import type { AddressUsedResponse, } from "./models/AddressUsed"; import { BlockLatestRequest, BlockLatestResponse } from "./models/BlockLatest"; +import { BlockMinterRequest, BlockMinterResponse } from "./models/BlockMinter"; import type { CredentialAddressRequest, CredentialAddressResponse, @@ -23,6 +24,7 @@ export enum Routes { addressUsed = "address/used", credentialAddress = "credential/address", blockLatest = "block/latest", + blockMinter = "block/minter", metadataNft = "metadata/nft", } @@ -47,6 +49,11 @@ export type EndpointTypes = { input: BlockLatestRequest; response: BlockLatestResponse; }; + [Routes.blockMinter]: { + name: typeof Routes.blockMinter; + input: BlockMinterRequest; + response: BlockMinterResponse; + }; [Routes.metadataNft]: { name: typeof Routes.metadataNft; input: PolicyIdAssetMapType;