From d5b1cadd113ef6ceee6ff4b7db8ebb57e87a401c Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Tue, 15 Oct 2024 17:26:38 +0300 Subject: [PATCH 01/22] Add database schema --- backend-rust/README.md | 2 +- .../migrations/0001_initialize.up.sql | 95 +++++++++++++++++++ 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/backend-rust/README.md b/backend-rust/README.md index a801a0ac..aeb9612b 100644 --- a/backend-rust/README.md +++ b/backend-rust/README.md @@ -61,7 +61,7 @@ cargo run --bin ccdscan-api ### GraphiQL IDE Starting the GraphQL API Service above will provide you an interface -(usually at 127.0.0.1:8000) to execute GraphQL queries. +(usually at 127.0.0.1:8000) to execute graphQL queries. An example is shown below: diff --git a/backend-rust/migrations/0001_initialize.up.sql b/backend-rust/migrations/0001_initialize.up.sql index 8212b7ac..0bec6633 100644 --- a/backend-rust/migrations/0001_initialize.up.sql +++ b/backend-rust/migrations/0001_initialize.up.sql @@ -169,6 +169,7 @@ CREATE TABLE transactions( BOOLEAN NOT NULL, -- Transaction details. Events if success is true. + -- TODO: It would be nice to have every event in a separate row in a table to easily query a specific event from a specific transaction. events JSONB, -- Transaction details. Reject reason if success is false. @@ -337,6 +338,100 @@ CREATE TABLE contracts( PRIMARY KEY (index, sub_index) ); +-- Every event that links or unlinks a contract to a module. +CREATE TABLE module_contract_link_events( + -- An index/id for this event (row number). + index + BIGINT + SERIAL + PRIMARY KEY + NOT NULL, + -- Event index of the event. + event_index + BIGINT + NOT NULL, + -- Transaction index including the event. + transaction_index + BIGINT + NOT NULL, + -- Module index that gets linked/unlinked. + module_index + BIGINT + NOT NULL, + -- Contract index that gets linked/unlinked. + contract_index + BIGINT + NOT NULL, + -- Contract subindex that gets linked/unlinked. + contract_sub_index + BIGINT + NOT NULL, + -- True if contract gets linked to the given module, false if contract gets unlinked to the given module. + is_linked + BOOLEAN + NOT NULL, + + -- TODO: link_action = int? source = int? +) + +-- Every successful event associated to a contract. +-- TODO: add index over the contract (index,subindex) +CREATE TABLE contract_events( + -- An index/id for this event (row number). + index + BIGINT + SERIAL + PRIMARY KEY + NOT NULL, + -- Transaction index including the event. + transaction_index + BIGINT + NOT NULL, + -- Event index of the event. + event_index + BIGINT + NOT NULL, + -- Contract index that event is associated with. + contract_index + BIGINT + NOT NULL, + -- Contract subindex that event is associated with. + contract_sub_index + BIGINT + NOT NULL, + + -- TODO: source = int? +) + +-- Every rejected event associated to a contract. +-- TODO: add index over the contract (index,subindex) +CREATE TABLE contract_reject_events( + -- An index/id for this event (row number). + index + BIGINT + SERIAL + PRIMARY KEY + NOT NULL, + -- Transaction index including the event. + transaction_index + BIGINT + NOT NULL, + -- Event index of the event. + event_index + BIGINT + NOT NULL, + -- Contract index that event is associated with. + contract_index + BIGINT + NOT NULL, + -- Contract subindex that event is associated with. + contract_sub_index + BIGINT + NOT NULL, + + -- TODO: source = int? +) + CREATE OR REPLACE FUNCTION block_added_notify_trigger_function() RETURNS trigger AS $trigger$ DECLARE rec blocks; From 7598e931874f9940cf161c454852d2dfcc9aff20 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Tue, 15 Oct 2024 17:41:45 +0300 Subject: [PATCH 02/22] Handle rejected events correctly --- backend-rust/src/indexer.rs | 63 ++++++++++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 14 deletions(-) diff --git a/backend-rust/src/indexer.rs b/backend-rust/src/indexer.rs index d69dc6c2..2ec503d8 100644 --- a/backend-rust/src/indexer.rs +++ b/backend-rust/src/indexer.rs @@ -870,13 +870,12 @@ struct PreparedBlockItem { /// Events of the block item. Is none for rejected block items. events: Option, /// Reject reason the block item. Is none for successful block items. - reject: Option, + reject: Option, /// All affected accounts for this transaction. Each entry is the `String` /// representation of an account address. affected_accounts: Vec, - // This is an option temporarily, until we are able to handle every type of event. /// Block item events prepared for inserting into the database. - prepared_event: Option, + prepared_event: PreparedEventData, } impl PreparedBlockItem { @@ -935,7 +934,7 @@ impl PreparedBlockItem { let affected_accounts = block_item.affected_addresses().into_iter().map(|a| a.to_string()).collect(); - let prepared_event = PreparedEvent::prepare(node_client, data, block_item).await?; + let prepared_event = PreparedEventData::prepare(node_client, data, block_item).await?; Ok(Self { block_item_index, @@ -1028,14 +1027,24 @@ impl PreparedBlockItem { .execute(tx.as_mut()) .await?; - if let Some(prepared_event) = &self.prepared_event { - prepared_event.save(tx, tx_idx).await?; - } + self.prepared_event.save(tx).await?; Ok(()) } } +/// Different types of block item events that can be prepared and the +/// status of the event (if the event was in a successful or rejected +/// transaction). +struct PreparedEventData { + /// The prepared event. Note: this is optional temporarily until we can + /// handle all events. + event: Option, + /// The status of the event (if the event belongs to a successful or + /// rejected transaction). + success: bool, +} + /// Different types of block item events that can be prepared. enum PreparedEvent { /// A new account got created. @@ -1049,12 +1058,12 @@ enum PreparedEvent { /// No changes in the database was caused by this event. NoOperation, } -impl PreparedEvent { +impl PreparedEventData { async fn prepare( node_client: &mut v2::Client, data: &BlockData, block_item: &BlockItemSummary, - ) -> anyhow::Result> { + ) -> anyhow::Result { let prepared_event = match &block_item.details { BlockItemSummaryDetails::AccountCreation(details) => { Some(PreparedEvent::AccountCreation(PreparedAccountCreation::prepare( @@ -1198,7 +1207,10 @@ impl PreparedEvent { None } }; - Ok(prepared_event) + Ok(PreparedEventData { + event: prepared_event, + success: block_item.is_success(), + }) } async fn save( @@ -1206,16 +1218,39 @@ impl PreparedEvent { tx: &mut sqlx::Transaction<'static, sqlx::Postgres>, tx_idx: i64, ) -> anyhow::Result<()> { - match self { - PreparedEvent::AccountCreation(event) => event.save(tx, tx_idx).await, + let Some(event) = &self.event else { + return Ok(()); + }; + + match event { + PreparedEvent::AccountCreation(event) => event.save(tx).await, + // TODO: need to handle `rejected` baker events properly. PreparedEvent::BakerEvents(events) => { for event in events { event.save(tx).await?; } Ok(()) } - PreparedEvent::ModuleDeployed(event) => event.save(tx, tx_idx).await, - PreparedEvent::ContractInitialized(event) => event.save(tx, tx_idx).await, + PreparedEvent::ModuleDeployed(event) => + // Only save the module into the `modules` table if the transaction was + // successful. + { + if self.success { + event.save(tx).await + } else { + Ok(()) + } + } + PreparedEvent::ContractInitialized(event) => + // Only save the contract into the `contracts` table if the transaction was + // successful. + { + if self.success { + event.save(tx).await + } else { + Ok(()) + } + } PreparedEvent::NoOperation => Ok(()), } } From 7b97eef3d12848f4720eb5f8b427956b7b2ad924 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Tue, 15 Oct 2024 17:58:18 +0300 Subject: [PATCH 03/22] Add contract event to database --- ...96052b3c664746400d987b23ee4a58560459d.json | 17 ++++++++++++++ .../migrations/0001_initialize.up.sql | 23 ++++++++----------- backend-rust/src/indexer.rs | 20 ++++++++++++++++ 3 files changed, 47 insertions(+), 13 deletions(-) create mode 100644 backend-rust/.sqlx/query-da0c0bc6c2a9fdf7ddefaa807d596052b3c664746400d987b23ee4a58560459d.json diff --git a/backend-rust/.sqlx/query-da0c0bc6c2a9fdf7ddefaa807d596052b3c664746400d987b23ee4a58560459d.json b/backend-rust/.sqlx/query-da0c0bc6c2a9fdf7ddefaa807d596052b3c664746400d987b23ee4a58560459d.json new file mode 100644 index 00000000..07e69b73 --- /dev/null +++ b/backend-rust/.sqlx/query-da0c0bc6c2a9fdf7ddefaa807d596052b3c664746400d987b23ee4a58560459d.json @@ -0,0 +1,17 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO contract_events (\n transaction_index,\n event_index,\n contract_index,\n contract_sub_index\n )\n VALUES (\n $1, $2, $3, $4\n )", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8", + "Int8", + "Int8", + "Int8" + ] + }, + "nullable": [] + }, + "hash": "da0c0bc6c2a9fdf7ddefaa807d596052b3c664746400d987b23ee4a58560459d" +} diff --git a/backend-rust/migrations/0001_initialize.up.sql b/backend-rust/migrations/0001_initialize.up.sql index 0bec6633..1c7c3bdf 100644 --- a/backend-rust/migrations/0001_initialize.up.sql +++ b/backend-rust/migrations/0001_initialize.up.sql @@ -342,8 +342,7 @@ CREATE TABLE contracts( CREATE TABLE module_contract_link_events( -- An index/id for this event (row number). index - BIGINT - SERIAL + BIGSERIAL PRIMARY KEY NOT NULL, -- Event index of the event. @@ -369,18 +368,17 @@ CREATE TABLE module_contract_link_events( -- True if contract gets linked to the given module, false if contract gets unlinked to the given module. is_linked BOOLEAN - NOT NULL, + NOT NULL -- TODO: link_action = int? source = int? -) +); -- Every successful event associated to a contract. -- TODO: add index over the contract (index,subindex) -CREATE TABLE contract_events( +CREATE TABLE contract_events ( -- An index/id for this event (row number). index - BIGINT - SERIAL + BIGSERIAL PRIMARY KEY NOT NULL, -- Transaction index including the event. @@ -398,18 +396,17 @@ CREATE TABLE contract_events( -- Contract subindex that event is associated with. contract_sub_index BIGINT - NOT NULL, + NOT NULL -- TODO: source = int? -) +); -- Every rejected event associated to a contract. -- TODO: add index over the contract (index,subindex) CREATE TABLE contract_reject_events( -- An index/id for this event (row number). index - BIGINT - SERIAL + BIGSERIAL PRIMARY KEY NOT NULL, -- Transaction index including the event. @@ -427,10 +424,10 @@ CREATE TABLE contract_reject_events( -- Contract subindex that event is associated with. contract_sub_index BIGINT - NOT NULL, + NOT NULL -- TODO: source = int? -) +); CREATE OR REPLACE FUNCTION block_added_notify_trigger_function() RETURNS trigger AS $trigger$ DECLARE diff --git a/backend-rust/src/indexer.rs b/backend-rust/src/indexer.rs index 2ec503d8..f4a06dd7 100644 --- a/backend-rust/src/indexer.rs +++ b/backend-rust/src/indexer.rs @@ -1670,6 +1670,26 @@ impl PreparedContractInitialized { ) .execute(tx.as_mut()) .await?; + + // TODO: fix event index. + sqlx::query!( + r#"INSERT INTO contract_events ( + transaction_index, + event_index, + contract_index, + contract_sub_index + ) + VALUES ( + $1, $2, $3, $4 + )"#, + self.tx_index, + 0i64, + self.index, + self.sub_index + ) + .execute(tx.as_mut()) + .await?; + Ok(()) } } From c3c20dd8dafc60462bd93640ff0b95157f5ce8b0 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Tue, 22 Oct 2024 16:44:22 +0300 Subject: [PATCH 04/22] Add contract event query --- ...ad689b899c452b483e013fa5c0fd98a1ea297.json | 33 +++++++ ...b8e579f0869c6e393cda16bc8848ec5258c07.json | 14 +++ ...cf61dbe52a9e65fa7bf3959d47c4d39fff8b3.json | 65 +++++++++++++ backend-rust/README.md | 2 +- .../migrations/0001_initialize.up.sql | 10 +- backend-rust/src/graphql_api.rs | 92 ++++++++++++++++++- backend-rust/src/indexer.rs | 33 +++++-- 7 files changed, 236 insertions(+), 13 deletions(-) create mode 100644 backend-rust/.sqlx/query-4b16a5f1e8c54901560903d367ead689b899c452b483e013fa5c0fd98a1ea297.json create mode 100644 backend-rust/.sqlx/query-fc6b58746391e0be522913ceaa1cf61dbe52a9e65fa7bf3959d47c4d39fff8b3.json diff --git a/backend-rust/.sqlx/query-4b16a5f1e8c54901560903d367ead689b899c452b483e013fa5c0fd98a1ea297.json b/backend-rust/.sqlx/query-4b16a5f1e8c54901560903d367ead689b899c452b483e013fa5c0fd98a1ea297.json new file mode 100644 index 00000000..10e2eb64 --- /dev/null +++ b/backend-rust/.sqlx/query-4b16a5f1e8c54901560903d367ead689b899c452b483e013fa5c0fd98a1ea297.json @@ -0,0 +1,33 @@ +{ + "db_name": "PostgreSQL", +<<<<<<<< HEAD:backend-rust/.sqlx/query-c2769b7e5c028ba895fefc53744b8e579f0869c6e393cda16bc8848ec5258c07.json + "query": "INSERT INTO contracts (\n index,\n sub_index,\n module_reference,\n name,\n amount,\n transaction_index\n ) VALUES ($1, $2, $3, $4, $5, $6)", +======== + "query": "INSERT INTO contracts (\n index,\n sub_index,\n module_reference,\n name,\n amount,\n init_block_height,\n init_transaction_index,\n version\n )\n VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8\n )", +>>>>>>>> 4e5d9a0b (Add contract event query):backend-rust/.sqlx/query-4b16a5f1e8c54901560903d367ead689b899c452b483e013fa5c0fd98a1ea297.json + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8", + "Int8", + "Bpchar", + "Varchar", + "Int8", +<<<<<<<< HEAD:backend-rust/.sqlx/query-c2769b7e5c028ba895fefc53744b8e579f0869c6e393cda16bc8848ec5258c07.json + "Int8" +======== + "Int8", + "Int8", + "Int4" +>>>>>>>> 4e5d9a0b (Add contract event query):backend-rust/.sqlx/query-4b16a5f1e8c54901560903d367ead689b899c452b483e013fa5c0fd98a1ea297.json + ] + }, + "nullable": [] + }, +<<<<<<<< HEAD:backend-rust/.sqlx/query-c2769b7e5c028ba895fefc53744b8e579f0869c6e393cda16bc8848ec5258c07.json + "hash": "c2769b7e5c028ba895fefc53744b8e579f0869c6e393cda16bc8848ec5258c07" +======== + "hash": "4b16a5f1e8c54901560903d367ead689b899c452b483e013fa5c0fd98a1ea297" +>>>>>>>> 4e5d9a0b (Add contract event query):backend-rust/.sqlx/query-4b16a5f1e8c54901560903d367ead689b899c452b483e013fa5c0fd98a1ea297.json +} diff --git a/backend-rust/.sqlx/query-c2769b7e5c028ba895fefc53744b8e579f0869c6e393cda16bc8848ec5258c07.json b/backend-rust/.sqlx/query-c2769b7e5c028ba895fefc53744b8e579f0869c6e393cda16bc8848ec5258c07.json index fdb6ccf1..10e2eb64 100644 --- a/backend-rust/.sqlx/query-c2769b7e5c028ba895fefc53744b8e579f0869c6e393cda16bc8848ec5258c07.json +++ b/backend-rust/.sqlx/query-c2769b7e5c028ba895fefc53744b8e579f0869c6e393cda16bc8848ec5258c07.json @@ -1,6 +1,10 @@ { "db_name": "PostgreSQL", +<<<<<<<< HEAD:backend-rust/.sqlx/query-c2769b7e5c028ba895fefc53744b8e579f0869c6e393cda16bc8848ec5258c07.json "query": "INSERT INTO contracts (\n index,\n sub_index,\n module_reference,\n name,\n amount,\n transaction_index\n ) VALUES ($1, $2, $3, $4, $5, $6)", +======== + "query": "INSERT INTO contracts (\n index,\n sub_index,\n module_reference,\n name,\n amount,\n init_block_height,\n init_transaction_index,\n version\n )\n VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8\n )", +>>>>>>>> 4e5d9a0b (Add contract event query):backend-rust/.sqlx/query-4b16a5f1e8c54901560903d367ead689b899c452b483e013fa5c0fd98a1ea297.json "describe": { "columns": [], "parameters": { @@ -10,10 +14,20 @@ "Bpchar", "Varchar", "Int8", +<<<<<<<< HEAD:backend-rust/.sqlx/query-c2769b7e5c028ba895fefc53744b8e579f0869c6e393cda16bc8848ec5258c07.json "Int8" +======== + "Int8", + "Int8", + "Int4" +>>>>>>>> 4e5d9a0b (Add contract event query):backend-rust/.sqlx/query-4b16a5f1e8c54901560903d367ead689b899c452b483e013fa5c0fd98a1ea297.json ] }, "nullable": [] }, +<<<<<<<< HEAD:backend-rust/.sqlx/query-c2769b7e5c028ba895fefc53744b8e579f0869c6e393cda16bc8848ec5258c07.json "hash": "c2769b7e5c028ba895fefc53744b8e579f0869c6e393cda16bc8848ec5258c07" +======== + "hash": "4b16a5f1e8c54901560903d367ead689b899c452b483e013fa5c0fd98a1ea297" +>>>>>>>> 4e5d9a0b (Add contract event query):backend-rust/.sqlx/query-4b16a5f1e8c54901560903d367ead689b899c452b483e013fa5c0fd98a1ea297.json } diff --git a/backend-rust/.sqlx/query-fc6b58746391e0be522913ceaa1cf61dbe52a9e65fa7bf3959d47c4d39fff8b3.json b/backend-rust/.sqlx/query-fc6b58746391e0be522913ceaa1cf61dbe52a9e65fa7bf3959d47c4d39fff8b3.json new file mode 100644 index 00000000..abc4fa29 --- /dev/null +++ b/backend-rust/.sqlx/query-fc6b58746391e0be522913ceaa1cf61dbe52a9e65fa7bf3959d47c4d39fff8b3.json @@ -0,0 +1,65 @@ +{ + "db_name": "PostgreSQL", + "query": "\nSELECT\n module_reference,\n name as contract_name,\n contracts.amount as amount,\n blocks.slot_time as block_slot_time,\n init_block_height as block_height,\n transactions.hash as transaction_hash,\n accounts.address as creator,\n version\nFROM contracts\nJOIN blocks ON init_block_height=blocks.height\nJOIN transactions ON init_block_height=transactions.block_height AND init_transaction_index=transactions.index\nJOIN accounts ON transactions.sender=accounts.index\nWHERE contracts.index=$1 AND contracts.sub_index=$2\n", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "module_reference", + "type_info": "Bpchar" + }, + { + "ordinal": 1, + "name": "contract_name", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "amount", + "type_info": "Int8" + }, + { + "ordinal": 3, + "name": "block_slot_time", + "type_info": "Timestamp" + }, + { + "ordinal": 4, + "name": "block_height", + "type_info": "Int8" + }, + { + "ordinal": 5, + "name": "transaction_hash", + "type_info": "Bpchar" + }, + { + "ordinal": 6, + "name": "creator", + "type_info": "Bpchar" + }, + { + "ordinal": 7, + "name": "version", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Int8", + "Int8" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + false, + false + ] + }, + "hash": "fc6b58746391e0be522913ceaa1cf61dbe52a9e65fa7bf3959d47c4d39fff8b3" +} diff --git a/backend-rust/README.md b/backend-rust/README.md index aeb9612b..a801a0ac 100644 --- a/backend-rust/README.md +++ b/backend-rust/README.md @@ -61,7 +61,7 @@ cargo run --bin ccdscan-api ### GraphiQL IDE Starting the GraphQL API Service above will provide you an interface -(usually at 127.0.0.1:8000) to execute graphQL queries. +(usually at 127.0.0.1:8000) to execute GraphQL queries. An example is shown below: diff --git a/backend-rust/migrations/0001_initialize.up.sql b/backend-rust/migrations/0001_initialize.up.sql index 1c7c3bdf..0115cb2b 100644 --- a/backend-rust/migrations/0001_initialize.up.sql +++ b/backend-rust/migrations/0001_initialize.up.sql @@ -333,6 +333,10 @@ CREATE TABLE contracts( BIGINT NOT NULL REFERENCES transactions, + -- The version of the smart contract. + version + INT + NOT NULL, -- Make the contract index and subindex the primary key. PRIMARY KEY (index, sub_index) @@ -342,7 +346,7 @@ CREATE TABLE contracts( CREATE TABLE module_contract_link_events( -- An index/id for this event (row number). index - BIGSERIAL + BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, -- Event index of the event. @@ -378,7 +382,7 @@ CREATE TABLE module_contract_link_events( CREATE TABLE contract_events ( -- An index/id for this event (row number). index - BIGSERIAL + BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, -- Transaction index including the event. @@ -406,7 +410,7 @@ CREATE TABLE contract_events ( CREATE TABLE contract_reject_events( -- An index/id for this event (row number). index - BIGSERIAL + BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, -- Transaction index including the event. diff --git a/backend-rust/src/graphql_api.rs b/backend-rust/src/graphql_api.rs index 0dc34da4..abfed318 100644 --- a/backend-rust/src/graphql_api.rs +++ b/backend-rust/src/graphql_api.rs @@ -285,6 +285,10 @@ enum ApiError { InvalidInt(#[from] std::num::TryFromIntError), #[error("Invalid integer: {0}")] InvalidIntString(#[from] std::num::ParseIntError), + #[error("Parse error: {0}")] + InvalidContractVersion(#[from] InvalidContractVersionError), + #[error("Parse error: {0}")] + UnsignedLongNotNegative(#[from] UnsignedLongNotNegativeError), #[error("Schema in database should be valid")] InvalidModuleSchema, } @@ -1336,14 +1340,70 @@ struct Contract { block_slot_time: DateTime, snapshot: ContractSnapshot, } + +// TODO #[ComplexObject] impl Contract { async fn contract_events( &self, + ctx: &Context<'_>, skip: i32, take: i32, ) -> ApiResult { - todo_api!() + // take some events from the `contract_events` table. + + // take sometimes initial event from `contracts` table. + + let pool = get_pool(ctx)?; + + let row = sqlx::query!( + r#" +SELECT + module_reference, + name as contract_name, + contracts.amount as amount, + blocks.slot_time as block_slot_time, + init_block_height as block_height, + transactions.hash as transaction_hash, + accounts.address as creator, + version +FROM contracts +JOIN blocks ON init_block_height=blocks.height +JOIN transactions ON init_block_height=transactions.block_height AND init_transaction_index=transactions.index +JOIN accounts ON transactions.sender=accounts.index +WHERE contracts.index=$1 AND contracts.sub_index=$2 +"#, +self.contract_address_index.0 as i64,self.contract_address_sub_index.0 as i64 + ).fetch_optional(pool).await? + .ok_or(ApiError::NotFound)?; + + Ok(ContractEventsCollectionSegment { + // TODO: add pagination info + page_info: CollectionSegmentInfo { + has_next_page: false, + has_previous_page: false, + }, + items: Some(vec![ContractEvent { + contract_address_index: self.contract_address_index, + contract_address_sub_index: self.contract_address_sub_index, + sender: row.creator.into(), + event: Event::ContractInitialized(ContractInitialized { + module_ref: row.module_reference, + contract_address: ContractAddress::from( + self.contract_address_index, + self.contract_address_sub_index, + ), + amount: row.amount, + // Check name or have to add `init_` to the name + init_name: row.contract_name, + version: row.version.try_into()?, + }), + block_height: row.block_height, + transaction_hash: row.transaction_hash, + block_slot_time: row.block_slot_time, + }]), + total_count: 1, + }) } async fn contract_reject_events( @@ -3506,6 +3566,16 @@ struct ContractAddress { as_string: String, } +impl ContractAddress { + fn from(index: ContractIndex, sub_index: ContractIndex) -> Self { + Self { + index, + sub_index, + as_string: format!("<{},{}>", index, sub_index), + } + } +} + impl From for ContractAddress { fn from(value: concordium_rust_sdk::types::ContractAddress) -> Self { Self { @@ -4504,6 +4574,26 @@ impl From for Contract } } +#[derive(Debug, thiserror::Error, Clone)] +#[error("Invalid contract version: {value}")] +pub struct InvalidContractVersionError { + value: i32, +} + +impl TryFrom for ContractVersion { + type Error = InvalidContractVersionError; + + fn try_from(value: i32) -> Result { + match value { + 0 => Ok(ContractVersion::V0), + 1 => Ok(ContractVersion::V1), + _ => Err(InvalidContractVersionError { + value, + }), + } + } +} + #[derive(SimpleObject, serde::Serialize, serde::Deserialize)] pub struct ContractModuleDeployed { module_ref: String, diff --git a/backend-rust/src/indexer.rs b/backend-rust/src/indexer.rs index f4a06dd7..5a739ba5 100644 --- a/backend-rust/src/indexer.rs +++ b/backend-rust/src/indexer.rs @@ -1630,6 +1630,9 @@ struct PreparedContractInitialized { module_reference: String, name: String, amount: i64, + height: i64, + tx_index: i64, + version: i32, } impl PreparedContractInitialized { @@ -1638,12 +1641,25 @@ impl PreparedContractInitialized { block_item: &BlockItemSummary, event: &ContractInitializedEvent, ) -> anyhow::Result { + let height = i64::try_from(data.finalized_block_info.height.height)?; + let tx_index = block_item.index.index.try_into()?; + + let index = i64::try_from(event.address.index)?; + let sub_index = i64::try_from(event.address.subindex)?; + let amount = i64::try_from(event.amount.micro_ccd)?; + let module_reference = event.origin_ref; + let name = event.init_name.to_string(); + let version = i32::try_from(event.contract_version as u32)?; + Ok(Self { - index: i64::try_from(event.address.index)?, - sub_index: i64::try_from(event.address.subindex)?, - amount: i64::try_from(event.amount.micro_ccd)?, - module_reference: event.origin_ref.into(), - name: event.init_name.to_string(), + index, + sub_index, + module_reference: module_reference.into(), + amount, + name, + height, + tx_index, + version, }) } @@ -1659,14 +1675,15 @@ impl PreparedContractInitialized { module_reference, name, amount, - transaction_index - ) VALUES ($1, $2, $3, $4, $5, $6)", + transaction_index, + version + ) VALUES ($1, $2, $3, $4, $5, $6, $7)", self.index, self.sub_index, self.module_reference, self.name, self.amount, - transaction_index, + self.version ) .execute(tx.as_mut()) .await?; From 620b0cdeee09a79953db16f2ce3752135fa0620d Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Tue, 22 Oct 2024 18:31:41 +0300 Subject: [PATCH 05/22] Insert data to contract_events table --- ...e822c45e6eaa327bc7b7d7125b058bd2daeb8.json | 16 ++++ ...96052b3c664746400d987b23ee4a58560459d.json | 17 ---- .../migrations/0001_initialize.up.sql | 4 - backend-rust/src/graphql_api.rs | 35 +++++--- backend-rust/src/indexer.rs | 89 +++++++++++++++++-- 5 files changed, 119 insertions(+), 42 deletions(-) create mode 100644 backend-rust/.sqlx/query-400990b63507f21d65c36b8f13fe822c45e6eaa327bc7b7d7125b058bd2daeb8.json delete mode 100644 backend-rust/.sqlx/query-da0c0bc6c2a9fdf7ddefaa807d596052b3c664746400d987b23ee4a58560459d.json diff --git a/backend-rust/.sqlx/query-400990b63507f21d65c36b8f13fe822c45e6eaa327bc7b7d7125b058bd2daeb8.json b/backend-rust/.sqlx/query-400990b63507f21d65c36b8f13fe822c45e6eaa327bc7b7d7125b058bd2daeb8.json new file mode 100644 index 00000000..a7d95674 --- /dev/null +++ b/backend-rust/.sqlx/query-400990b63507f21d65c36b8f13fe822c45e6eaa327bc7b7d7125b058bd2daeb8.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO contract_events (\n transaction_index,\n contract_index,\n contract_sub_index\n )\n VALUES (\n $1, $2, $3\n )", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8", + "Int8", + "Int8" + ] + }, + "nullable": [] + }, + "hash": "400990b63507f21d65c36b8f13fe822c45e6eaa327bc7b7d7125b058bd2daeb8" +} diff --git a/backend-rust/.sqlx/query-da0c0bc6c2a9fdf7ddefaa807d596052b3c664746400d987b23ee4a58560459d.json b/backend-rust/.sqlx/query-da0c0bc6c2a9fdf7ddefaa807d596052b3c664746400d987b23ee4a58560459d.json deleted file mode 100644 index 07e69b73..00000000 --- a/backend-rust/.sqlx/query-da0c0bc6c2a9fdf7ddefaa807d596052b3c664746400d987b23ee4a58560459d.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO contract_events (\n transaction_index,\n event_index,\n contract_index,\n contract_sub_index\n )\n VALUES (\n $1, $2, $3, $4\n )", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8", - "Int8", - "Int8", - "Int8" - ] - }, - "nullable": [] - }, - "hash": "da0c0bc6c2a9fdf7ddefaa807d596052b3c664746400d987b23ee4a58560459d" -} diff --git a/backend-rust/migrations/0001_initialize.up.sql b/backend-rust/migrations/0001_initialize.up.sql index 0115cb2b..f845d6f4 100644 --- a/backend-rust/migrations/0001_initialize.up.sql +++ b/backend-rust/migrations/0001_initialize.up.sql @@ -389,10 +389,6 @@ CREATE TABLE contract_events ( transaction_index BIGINT NOT NULL, - -- Event index of the event. - event_index - BIGINT - NOT NULL, -- Contract index that event is associated with. contract_index BIGINT diff --git a/backend-rust/src/graphql_api.rs b/backend-rust/src/graphql_api.rs index abfed318..5dbe7eaf 100644 --- a/backend-rust/src/graphql_api.rs +++ b/backend-rust/src/graphql_api.rs @@ -1356,7 +1356,13 @@ impl Contract { let pool = get_pool(ctx)?; - let row = sqlx::query!( + let include_initial_event = true; + + let mut contract_events = vec![]; + let mut total_events_count = 0; + + if include_initial_event { + let row = sqlx::query!( r#" SELECT module_reference, @@ -1377,13 +1383,7 @@ self.contract_address_index.0 as i64,self.contract_address_sub_index.0 as i64 ).fetch_optional(pool).await? .ok_or(ApiError::NotFound)?; - Ok(ContractEventsCollectionSegment { - // TODO: add pagination info - page_info: CollectionSegmentInfo { - has_next_page: false, - has_previous_page: false, - }, - items: Some(vec![ContractEvent { + let initial_event = ContractEvent { contract_address_index: self.contract_address_index, contract_address_sub_index: self.contract_address_sub_index, sender: row.creator.into(), @@ -1394,15 +1394,26 @@ self.contract_address_index.0 as i64,self.contract_address_sub_index.0 as i64 self.contract_address_sub_index, ), amount: row.amount, - // Check name or have to add `init_` to the name - init_name: row.contract_name, + init_name: format!("init_{:}", row.contract_name), version: row.version.try_into()?, }), block_height: row.block_height, transaction_hash: row.transaction_hash, block_slot_time: row.block_slot_time, - }]), - total_count: 1, + }; + + contract_events.push(initial_event); + total_events_count += 1; + } + + Ok(ContractEventsCollectionSegment { + // TODO: add pagination info + page_info: CollectionSegmentInfo { + has_next_page: false, + has_previous_page: false, + }, + items: Some(contract_events), + total_count: total_events_count, }) } diff --git a/backend-rust/src/indexer.rs b/backend-rust/src/indexer.rs index 5a739ba5..34d10dcb 100644 --- a/backend-rust/src/indexer.rs +++ b/backend-rust/src/indexer.rs @@ -15,7 +15,7 @@ use concordium_rust_sdk::{ types::{ self as sdk_types, queries::BlockInfo, AccountStakingInfo, AccountTransactionDetails, AccountTransactionEffects, BlockItemSummary, BlockItemSummaryDetails, - ContractInitializedEvent, PartsPerHundredThousands, RewardsOverview, + ContractInitializedEvent, ContractTraceElement, PartsPerHundredThousands, RewardsOverview, }, v2::{ self, BlockIdentifier, ChainParameters, FinalizedBlockInfo, QueryError, QueryResult, @@ -1055,6 +1055,8 @@ enum PreparedEvent { ModuleDeployed(PreparedModuleDeployed), /// Contract got initialized. ContractInitialized(PreparedContractInitialized), + /// Contract got updated. + ContractUpdate(Vec), /// No changes in the database was caused by this event. NoOperation, } @@ -1088,7 +1090,15 @@ impl PreparedEventData { )), AccountTransactionEffects::ContractUpdateIssued { effects, - } => None, + } => Some(PreparedEvent::ContractUpdate( + effects + .iter() + .enumerate() + .map(|(index, effect)| { + PreparedContractUpdate::prepare(data, block_item, effect) + }) + .collect::>>()?, + )), AccountTransactionEffects::AccountTransfer { amount, to, @@ -1251,6 +1261,14 @@ impl PreparedEventData { Ok(()) } } + PreparedEvent::ContractUpdate(events) => { + if self.success { + for event in events { + event.save(tx).await?; + } + } + Ok(()) + } PreparedEvent::NoOperation => Ok(()), } } @@ -1648,7 +1666,7 @@ impl PreparedContractInitialized { let sub_index = i64::try_from(event.address.subindex)?; let amount = i64::try_from(event.amount.micro_ccd)?; let module_reference = event.origin_ref; - let name = event.init_name.to_string(); + let name = event.init_name.to_string().replace("init_", ""); let version = i32::try_from(event.contract_version as u32)?; Ok(Self { @@ -1688,21 +1706,74 @@ impl PreparedContractInitialized { .execute(tx.as_mut()) .await?; - // TODO: fix event index. + Ok(()) + } +} + +struct PreparedContractUpdate { + tx_index: i64, + contract_index: i64, + contract_sub_index: i64, +} + +impl PreparedContractUpdate { + fn prepare( + data: &BlockData, + block_item: &BlockItemSummary, + event: &ContractTraceElement, + ) -> anyhow::Result { + let tx_index = block_item.index.index.try_into()?; + + let contract_address = match event { + ContractTraceElement::Updated { + data, + } => data.address, + ContractTraceElement::Transferred { + from, + amount, + to, + } => *from, + ContractTraceElement::Interrupted { + address, + events, + } => *address, + ContractTraceElement::Resumed { + address, + success, + } => *address, + ContractTraceElement::Upgraded { + address, + from, + to, + } => *address, + }; + + let index = i64::try_from(contract_address.index)?; + let sub_index = i64::try_from(contract_address.subindex)?; + + Ok(Self { + tx_index, + contract_index: index, + contract_sub_index: sub_index, + }) + } + + async fn save( + &self, + tx: &mut sqlx::Transaction<'static, sqlx::Postgres>, + ) -> anyhow::Result<()> { sqlx::query!( r#"INSERT INTO contract_events ( transaction_index, - event_index, contract_index, contract_sub_index ) VALUES ( - $1, $2, $3, $4 + $1, $2, $3 )"#, self.tx_index, - 0i64, - self.index, - self.sub_index + self.contract_index, + self.contract_sub_index ) .execute(tx.as_mut()) .await?; From caf0f4c4c4d0e62eefb02877c6350e82c92af45c Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Tue, 22 Oct 2024 19:17:24 +0300 Subject: [PATCH 06/22] Add more data to contract_events table --- ...e822c45e6eaa327bc7b7d7125b058bd2daeb8.json | 16 ------ ...01255c86bd973c330aa23a7633b567ad9a6f9.json | 54 +++++++++++++++++++ ...29cb898fc3a526fbdb26bc86fc312946e30b2.json | 18 +++++++ .../migrations/0001_initialize.up.sql | 8 +++ backend-rust/src/graphql_api.rs | 33 ++++++++++++ backend-rust/src/indexer.rs | 31 ++++++++--- 6 files changed, 136 insertions(+), 24 deletions(-) delete mode 100644 backend-rust/.sqlx/query-400990b63507f21d65c36b8f13fe822c45e6eaa327bc7b7d7125b058bd2daeb8.json create mode 100644 backend-rust/.sqlx/query-d2b36be1d8a6488c40883f3f46001255c86bd973c330aa23a7633b567ad9a6f9.json create mode 100644 backend-rust/.sqlx/query-fda54688c61b83b30236f240a7c29cb898fc3a526fbdb26bc86fc312946e30b2.json diff --git a/backend-rust/.sqlx/query-400990b63507f21d65c36b8f13fe822c45e6eaa327bc7b7d7125b058bd2daeb8.json b/backend-rust/.sqlx/query-400990b63507f21d65c36b8f13fe822c45e6eaa327bc7b7d7125b058bd2daeb8.json deleted file mode 100644 index a7d95674..00000000 --- a/backend-rust/.sqlx/query-400990b63507f21d65c36b8f13fe822c45e6eaa327bc7b7d7125b058bd2daeb8.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO contract_events (\n transaction_index,\n contract_index,\n contract_sub_index\n )\n VALUES (\n $1, $2, $3\n )", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8", - "Int8", - "Int8" - ] - }, - "nullable": [] - }, - "hash": "400990b63507f21d65c36b8f13fe822c45e6eaa327bc7b7d7125b058bd2daeb8" -} diff --git a/backend-rust/.sqlx/query-d2b36be1d8a6488c40883f3f46001255c86bd973c330aa23a7633b567ad9a6f9.json b/backend-rust/.sqlx/query-d2b36be1d8a6488c40883f3f46001255c86bd973c330aa23a7633b567ad9a6f9.json new file mode 100644 index 00000000..a9362c05 --- /dev/null +++ b/backend-rust/.sqlx/query-d2b36be1d8a6488c40883f3f46001255c86bd973c330aa23a7633b567ad9a6f9.json @@ -0,0 +1,54 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT * FROM (\n SELECT\n contract_events.index,\n contract_events.transaction_index,\n trace_element_index,\n contract_events.block_height AS event_block_height,\n transactions.hash as transaction_hash,\n blocks.slot_time as block_slot_time\n FROM contract_events\n JOIN transactions\n ON contract_events.block_height = transactions.block_height\n AND contract_events.transaction_index = transactions.index\n JOIN blocks ON contract_events.block_height = blocks.height\n WHERE contract_events.contract_index = $1\n AND contract_events.contract_sub_index <= $2\n LIMIT $3\n ) AS contract_data\n ORDER BY contract_data.index ASC\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "index", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "transaction_index", + "type_info": "Int8" + }, + { + "ordinal": 2, + "name": "trace_element_index", + "type_info": "Int8" + }, + { + "ordinal": 3, + "name": "event_block_height", + "type_info": "Int8" + }, + { + "ordinal": 4, + "name": "transaction_hash", + "type_info": "Bpchar" + }, + { + "ordinal": 5, + "name": "block_slot_time", + "type_info": "Timestamp" + } + ], + "parameters": { + "Left": [ + "Int8", + "Int8", + "Int8" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false + ] + }, + "hash": "d2b36be1d8a6488c40883f3f46001255c86bd973c330aa23a7633b567ad9a6f9" +} diff --git a/backend-rust/.sqlx/query-fda54688c61b83b30236f240a7c29cb898fc3a526fbdb26bc86fc312946e30b2.json b/backend-rust/.sqlx/query-fda54688c61b83b30236f240a7c29cb898fc3a526fbdb26bc86fc312946e30b2.json new file mode 100644 index 00000000..d4a4306b --- /dev/null +++ b/backend-rust/.sqlx/query-fda54688c61b83b30236f240a7c29cb898fc3a526fbdb26bc86fc312946e30b2.json @@ -0,0 +1,18 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO contract_events (\n transaction_index,\n trace_element_index,\n block_height,\n contract_index,\n contract_sub_index\n )\n VALUES (\n $1, $2, $3, $4, $5\n )", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8", + "Int8", + "Int8", + "Int8", + "Int8" + ] + }, + "nullable": [] + }, + "hash": "fda54688c61b83b30236f240a7c29cb898fc3a526fbdb26bc86fc312946e30b2" +} diff --git a/backend-rust/migrations/0001_initialize.up.sql b/backend-rust/migrations/0001_initialize.up.sql index f845d6f4..6bbc09fa 100644 --- a/backend-rust/migrations/0001_initialize.up.sql +++ b/backend-rust/migrations/0001_initialize.up.sql @@ -389,6 +389,14 @@ CREATE TABLE contract_events ( transaction_index BIGINT NOT NULL, + -- Trace element index of the event traces from above transaction. + trace_element_index + BIGINT + NOT NULL, + -- The absolute block height of the block that includes the event. + block_height + BIGINT + NOT NULL, -- Contract index that event is associated with. contract_index BIGINT diff --git a/backend-rust/src/graphql_api.rs b/backend-rust/src/graphql_api.rs index 5dbe7eaf..06c26fa1 100644 --- a/backend-rust/src/graphql_api.rs +++ b/backend-rust/src/graphql_api.rs @@ -1356,6 +1356,8 @@ impl Contract { let pool = get_pool(ctx)?; + // TODO: depending on `skip` and `take` values, we either include or not include + // the initial event. let include_initial_event = true; let mut contract_events = vec![]; @@ -1406,6 +1408,37 @@ self.contract_address_index.0 as i64,self.contract_address_sub_index.0 as i64 total_events_count += 1; } + // TODO: add `take` and `skip` to the query. + let limit = 3; + + // TODO: add events and extract/decode it based on the `trace_element_index` + let row_stream = sqlx::query!( + r#" + SELECT * FROM ( + SELECT + contract_events.index, + contract_events.transaction_index, + trace_element_index, + contract_events.block_height AS event_block_height, + transactions.hash as transaction_hash, + blocks.slot_time as block_slot_time + FROM contract_events + JOIN transactions + ON contract_events.block_height = transactions.block_height + AND contract_events.transaction_index = transactions.index + JOIN blocks ON contract_events.block_height = blocks.height + WHERE contract_events.contract_index = $1 + AND contract_events.contract_sub_index <= $2 + LIMIT $3 + ) AS contract_data + ORDER BY contract_data.index ASC + "#, + self.contract_address_index.0 as i64, + self.contract_address_sub_index.0 as i64, + limit + ) + .fetch(pool); + Ok(ContractEventsCollectionSegment { // TODO: add pagination info page_info: CollectionSegmentInfo { diff --git a/backend-rust/src/indexer.rs b/backend-rust/src/indexer.rs index 34d10dcb..573030b3 100644 --- a/backend-rust/src/indexer.rs +++ b/backend-rust/src/indexer.rs @@ -1094,8 +1094,13 @@ impl PreparedEventData { effects .iter() .enumerate() - .map(|(index, effect)| { - PreparedContractUpdate::prepare(data, block_item, effect) + .map(|(trace_element_index, effect)| { + PreparedContractUpdate::prepare( + data, + block_item, + effect, + trace_element_index, + ) }) .collect::>>()?, )), @@ -1711,9 +1716,11 @@ impl PreparedContractInitialized { } struct PreparedContractUpdate { - tx_index: i64, - contract_index: i64, - contract_sub_index: i64, + tx_index: i64, + trace_element_index: i64, + height: i64, + contract_index: i64, + contract_sub_index: i64, } impl PreparedContractUpdate { @@ -1721,9 +1728,8 @@ impl PreparedContractUpdate { data: &BlockData, block_item: &BlockItemSummary, event: &ContractTraceElement, + trace_element_index: usize, ) -> anyhow::Result { - let tx_index = block_item.index.index.try_into()?; - let contract_address = match event { ContractTraceElement::Updated { data, @@ -1748,11 +1754,16 @@ impl PreparedContractUpdate { } => *address, }; + let tx_index = block_item.index.index.try_into()?; + let trace_element_index = trace_element_index.try_into()?; + let height = i64::try_from(data.finalized_block_info.height.height)?; let index = i64::try_from(contract_address.index)?; let sub_index = i64::try_from(contract_address.subindex)?; Ok(Self { tx_index, + trace_element_index, + height, contract_index: index, contract_sub_index: sub_index, }) @@ -1765,13 +1776,17 @@ impl PreparedContractUpdate { sqlx::query!( r#"INSERT INTO contract_events ( transaction_index, + trace_element_index, + block_height, contract_index, contract_sub_index ) VALUES ( - $1, $2, $3 + $1, $2, $3, $4, $5 )"#, self.tx_index, + self.trace_element_index, + self.height, self.contract_index, self.contract_sub_index ) From e45d6a8497504c7610b264a5aac59317bff5f9b1 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Wed, 23 Oct 2024 14:14:53 +0300 Subject: [PATCH 07/22] Add contract events --- backend-rust/src/graphql_api.rs | 117 +++++++++++++++++++++----------- 1 file changed, 79 insertions(+), 38 deletions(-) diff --git a/backend-rust/src/graphql_api.rs b/backend-rust/src/graphql_api.rs index 06c26fa1..3864dba5 100644 --- a/backend-rust/src/graphql_api.rs +++ b/backend-rust/src/graphql_api.rs @@ -287,8 +287,6 @@ enum ApiError { InvalidIntString(#[from] std::num::ParseIntError), #[error("Parse error: {0}")] InvalidContractVersion(#[from] InvalidContractVersionError), - #[error("Parse error: {0}")] - UnsignedLongNotNegative(#[from] UnsignedLongNotNegativeError), #[error("Schema in database should be valid")] InvalidModuleSchema, } @@ -916,8 +914,14 @@ LIMIT 30", // WHERE slot_time > (LOCALTIMESTAMP - $1::interval) let mut connection = connection::Connection::new(true, true); while let Some(row) = row_stream.try_next().await? { - let contract_address_index = row.index.try_into()?; - let contract_address_sub_index = row.sub_index.try_into()?; + let contract_address_index = + row.index.try_into().map_err(|e: >::Error| { + ApiError::InternalError(e.to_string()) + })?; + let contract_address_sub_index = + row.sub_index.try_into().map_err(|e: >::Error| { + ApiError::InternalError(e.to_string()) + })?; let snapshot = ContractSnapshot { block_height: row.block_height, @@ -1356,13 +1360,81 @@ impl Contract { let pool = get_pool(ctx)?; + let mut contract_events = vec![]; + let mut total_events_count = 0; + + // TODO: add `take` and `skip` to the query. + let limit = 3; + + let mut row_stream = sqlx::query!( + r#" + SELECT * FROM ( + SELECT + contract_events.index, + contract_events.transaction_index, + trace_element_index, + contract_events.block_height AS event_block_height, + transactions.hash as transaction_hash, + transactions.events, + accounts.address as creator, + blocks.slot_time as block_slot_time, + blocks.height as block_height + FROM contract_events + JOIN transactions + ON contract_events.block_height = transactions.block_height + AND contract_events.transaction_index = transactions.index + JOIN accounts + ON transactions.sender = accounts.index + JOIN blocks ON contract_events.block_height = blocks.height + WHERE contract_events.contract_index = $1 + AND contract_events.contract_sub_index <= $2 + LIMIT $3 + ) AS contract_data + ORDER BY contract_data.index DESC + "#, + self.contract_address_index.0 as i64, + self.contract_address_sub_index.0 as i64, + limit + ) + .fetch(pool); + + while let Some(row) = row_stream.try_next().await? { + let Some(events) = row.events else { + return Err(ApiError::InternalError( + "Missing events in database".to_string(), + )); + }; + + let mut events: Vec = serde_json::from_value(events).map_err(|_| { + ApiError::InternalError("Failed to deserialize events from database".to_string()) + })?; + + if row.trace_element_index as usize >= events.len() { + return Err(ApiError::InternalError( + "Trace element index does not exist in events".to_string(), + )); + } + + let event = events.swap_remove(row.trace_element_index as usize); + + let contract_event = ContractEvent { + contract_address_index: self.contract_address_index, + contract_address_sub_index: self.contract_address_sub_index, + sender: row.creator.into(), + event, + block_height: row.block_height, + transaction_hash: row.transaction_hash, + block_slot_time: row.block_slot_time, + }; + + contract_events.push(contract_event); + total_events_count += 1; + } + // TODO: depending on `skip` and `take` values, we either include or not include // the initial event. let include_initial_event = true; - let mut contract_events = vec![]; - let mut total_events_count = 0; - if include_initial_event { let row = sqlx::query!( r#" @@ -1408,37 +1480,6 @@ self.contract_address_index.0 as i64,self.contract_address_sub_index.0 as i64 total_events_count += 1; } - // TODO: add `take` and `skip` to the query. - let limit = 3; - - // TODO: add events and extract/decode it based on the `trace_element_index` - let row_stream = sqlx::query!( - r#" - SELECT * FROM ( - SELECT - contract_events.index, - contract_events.transaction_index, - trace_element_index, - contract_events.block_height AS event_block_height, - transactions.hash as transaction_hash, - blocks.slot_time as block_slot_time - FROM contract_events - JOIN transactions - ON contract_events.block_height = transactions.block_height - AND contract_events.transaction_index = transactions.index - JOIN blocks ON contract_events.block_height = blocks.height - WHERE contract_events.contract_index = $1 - AND contract_events.contract_sub_index <= $2 - LIMIT $3 - ) AS contract_data - ORDER BY contract_data.index ASC - "#, - self.contract_address_index.0 as i64, - self.contract_address_sub_index.0 as i64, - limit - ) - .fetch(pool); - Ok(ContractEventsCollectionSegment { // TODO: add pagination info page_info: CollectionSegmentInfo { From f1c388b36c81ed6c4bcb4cc9e2910c6e7d1c760d Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Thu, 24 Oct 2024 11:57:42 +0300 Subject: [PATCH 08/22] Add event and message endpoints --- ...01255c86bd973c330aa23a7633b567ad9a6f9.json | 54 -- ...29cb898fc3a526fbdb26bc86fc312946e30b2.json | 18 - backend-rust/src/graphql_api.rs | 902 +++++++++--------- 3 files changed, 468 insertions(+), 506 deletions(-) delete mode 100644 backend-rust/.sqlx/query-d2b36be1d8a6488c40883f3f46001255c86bd973c330aa23a7633b567ad9a6f9.json delete mode 100644 backend-rust/.sqlx/query-fda54688c61b83b30236f240a7c29cb898fc3a526fbdb26bc86fc312946e30b2.json diff --git a/backend-rust/.sqlx/query-d2b36be1d8a6488c40883f3f46001255c86bd973c330aa23a7633b567ad9a6f9.json b/backend-rust/.sqlx/query-d2b36be1d8a6488c40883f3f46001255c86bd973c330aa23a7633b567ad9a6f9.json deleted file mode 100644 index a9362c05..00000000 --- a/backend-rust/.sqlx/query-d2b36be1d8a6488c40883f3f46001255c86bd973c330aa23a7633b567ad9a6f9.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT * FROM (\n SELECT\n contract_events.index,\n contract_events.transaction_index,\n trace_element_index,\n contract_events.block_height AS event_block_height,\n transactions.hash as transaction_hash,\n blocks.slot_time as block_slot_time\n FROM contract_events\n JOIN transactions\n ON contract_events.block_height = transactions.block_height\n AND contract_events.transaction_index = transactions.index\n JOIN blocks ON contract_events.block_height = blocks.height\n WHERE contract_events.contract_index = $1\n AND contract_events.contract_sub_index <= $2\n LIMIT $3\n ) AS contract_data\n ORDER BY contract_data.index ASC\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "index", - "type_info": "Int8" - }, - { - "ordinal": 1, - "name": "transaction_index", - "type_info": "Int8" - }, - { - "ordinal": 2, - "name": "trace_element_index", - "type_info": "Int8" - }, - { - "ordinal": 3, - "name": "event_block_height", - "type_info": "Int8" - }, - { - "ordinal": 4, - "name": "transaction_hash", - "type_info": "Bpchar" - }, - { - "ordinal": 5, - "name": "block_slot_time", - "type_info": "Timestamp" - } - ], - "parameters": { - "Left": [ - "Int8", - "Int8", - "Int8" - ] - }, - "nullable": [ - false, - false, - false, - false, - false, - false - ] - }, - "hash": "d2b36be1d8a6488c40883f3f46001255c86bd973c330aa23a7633b567ad9a6f9" -} diff --git a/backend-rust/.sqlx/query-fda54688c61b83b30236f240a7c29cb898fc3a526fbdb26bc86fc312946e30b2.json b/backend-rust/.sqlx/query-fda54688c61b83b30236f240a7c29cb898fc3a526fbdb26bc86fc312946e30b2.json deleted file mode 100644 index d4a4306b..00000000 --- a/backend-rust/.sqlx/query-fda54688c61b83b30236f240a7c29cb898fc3a526fbdb26bc86fc312946e30b2.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO contract_events (\n transaction_index,\n trace_element_index,\n block_height,\n contract_index,\n contract_sub_index\n )\n VALUES (\n $1, $2, $3, $4, $5\n )", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8", - "Int8", - "Int8", - "Int8", - "Int8" - ] - }, - "nullable": [] - }, - "hash": "fda54688c61b83b30236f240a7c29cb898fc3a526fbdb26bc86fc312946e30b2" -} diff --git a/backend-rust/src/graphql_api.rs b/backend-rust/src/graphql_api.rs index 3864dba5..de5db729 100644 --- a/backend-rust/src/graphql_api.rs +++ b/backend-rust/src/graphql_api.rs @@ -1400,15 +1400,16 @@ impl Contract { while let Some(row) = row_stream.try_next().await? { let Some(events) = row.events else { - return Err(ApiError::InternalError( - "Missing events in database".to_string(), - )); + return Err(ApiError::InternalError("Missing events in database".to_string())); }; let mut events: Vec = serde_json::from_value(events).map_err(|_| { ApiError::InternalError("Failed to deserialize events from database".to_string()) })?; + // Add check if not `initEvent`, `interrupt`, `upgrade`, `resume` ... return + // error. + if row.trace_element_index as usize >= events.len() { return Err(ApiError::InternalError( "Trace element index does not exist in events".to_string(), @@ -3735,214 +3736,229 @@ pub fn events_from_summary( ) -> anyhow::Result> { use concordium_rust_sdk::types::{AccountTransactionEffects, BlockItemSummaryDetails}; let events = match value { - BlockItemSummaryDetails::AccountTransaction(details) => { - match details.effects { - AccountTransactionEffects::None { - .. - } => { - anyhow::bail!("Transaction was rejected") - } - AccountTransactionEffects::ModuleDeployed { - module_ref, - } => { - vec![Event::ContractModuleDeployed(ContractModuleDeployed { - module_ref: module_ref.to_string(), - })] - } - AccountTransactionEffects::ContractInitialized { - data, - } => { - vec![Event::ContractInitialized(ContractInitialized { - module_ref: data.origin_ref.to_string(), - contract_address: data.address.into(), - amount: i64::try_from(data.amount.micro_ccd)?, - init_name: data.init_name.to_string(), - version: data.contract_version.into(), - })] - } - AccountTransactionEffects::ContractUpdateIssued { - effects, - } => { - use concordium_rust_sdk::types::ContractTraceElement; - effects - .into_iter() - .map(|effect| { - match effect { - ContractTraceElement::Updated { - data, - } => { - Ok(Event::ContractUpdated(ContractUpdated { - contract_address: data.address.into(), - instigator: data.instigator.into(), - amount: data.amount.micro_ccd().try_into()?, - message_as_hex: hex::encode(data.message.as_ref()), - receive_name: data.receive_name.to_string(), - version: data.contract_version.into(), - // TODO message: (), - })) - } - ContractTraceElement::Transferred { - from, - amount, - to, - } => Ok(Event::Transferred(Transferred { - amount: amount.micro_ccd().try_into()?, - from: Address::ContractAddress(from.into()), - to: to.into(), - })), - ContractTraceElement::Interrupted { - address, - events, - } => Ok(Event::ContractInterrupted(ContractInterrupted { - contract_address: address.into(), - })), - ContractTraceElement::Resumed { - address, - success, - } => Ok(Event::ContractResumed(ContractResumed { - contract_address: address.into(), - success, - })), - ContractTraceElement::Upgraded { - address, - from, - to, - } => Ok(Event::ContractUpgraded(ContractUpgraded { - contract_address: address.into(), - from: from.to_string(), - to: to.to_string(), - })), - } - }) - .collect::>>()? - } - AccountTransactionEffects::AccountTransfer { - amount, - to, - } => { - vec![Event::Transferred(Transferred { + BlockItemSummaryDetails::AccountTransaction(details) => match details.effects { + AccountTransactionEffects::None { + .. + } => { + anyhow::bail!("Transaction was rejected") + } + AccountTransactionEffects::ModuleDeployed { + module_ref, + } => { + vec![Event::ContractModuleDeployed(ContractModuleDeployed { + module_ref: module_ref.to_string(), + })] + } + AccountTransactionEffects::ContractInitialized { + data, + } => { + vec![Event::ContractInitialized(ContractInitialized { + module_ref: data.origin_ref.to_string(), + contract_address: data.address.into(), + amount: i64::try_from(data.amount.micro_ccd)?, + init_name: data.init_name.to_string(), + version: data.contract_version.into(), + })] + } + AccountTransactionEffects::ContractUpdateIssued { + effects, + } => { + use concordium_rust_sdk::types::ContractTraceElement; + effects + .into_iter() + .map(|effect| match effect { + ContractTraceElement::Updated { + data, + } => Ok(Event::ContractUpdated(ContractUpdated { + contract_address: data.address.into(), + instigator: data.instigator.into(), + amount: data.amount.micro_ccd().try_into()?, + receive_name: data.receive_name.to_string(), + version: data.contract_version.into(), + input_parameter: data.message.as_ref().to_vec(), + contrat_logs: data + .events + .iter() + .map(|e| e.as_ref().to_vec()) + .collect(), + })), + ContractTraceElement::Transferred { + from, + amount, + to, + } => Ok(Event::Transferred(Transferred { + amount: amount.micro_ccd().try_into()?, + from: Address::ContractAddress(from.into()), + to: to.into(), + })), + ContractTraceElement::Interrupted { + address, + events, + } => Ok(Event::ContractInterrupted(ContractInterrupted { + contract_address: address.into(), + })), + ContractTraceElement::Resumed { + address, + success, + } => Ok(Event::ContractResumed(ContractResumed { + contract_address: address.into(), + success, + })), + ContractTraceElement::Upgraded { + address, + from, + to, + } => Ok(Event::ContractUpgraded(ContractUpgraded { + contract_address: address.into(), + from: from.to_string(), + to: to.to_string(), + })), + }) + .collect::>>()? + } + AccountTransactionEffects::AccountTransfer { + amount, + to, + } => { + vec![Event::Transferred(Transferred { + amount: i64::try_from(amount.micro_ccd)?, + from: Address::AccountAddress(details.sender.into()), + to: to.into(), + })] + } + AccountTransactionEffects::AccountTransferWithMemo { + amount, + to, + memo, + } => { + vec![ + Event::Transferred(Transferred { amount: i64::try_from(amount.micro_ccd)?, from: Address::AccountAddress(details.sender.into()), to: to.into(), - })] - } - AccountTransactionEffects::AccountTransferWithMemo { - amount, - to, - memo, - } => { - vec![ - Event::Transferred(Transferred { - amount: i64::try_from(amount.micro_ccd)?, - from: Address::AccountAddress(details.sender.into()), - to: to.into(), - }), - Event::TransferMemo(memo.into()), - ] - } - AccountTransactionEffects::BakerAdded { - data, - } => { - vec![Event::BakerAdded(BakerAdded { - staked_amount: data.stake.micro_ccd.try_into()?, - restake_earnings: data.restake_earnings, - baker_id: data.keys_event.baker_id.id.index.try_into()?, - sign_key: serde_json::to_string(&data.keys_event.sign_key)?, - election_key: serde_json::to_string(&data.keys_event.election_key)?, - aggregation_key: serde_json::to_string(&data.keys_event.aggregation_key)?, - })] - } - AccountTransactionEffects::BakerRemoved { - baker_id, - } => { - vec![Event::BakerRemoved(BakerRemoved { - baker_id: baker_id.id.index.try_into()?, - })] - } - AccountTransactionEffects::BakerStakeUpdated { - data, - } => { - if let Some(data) = data { - if data.increased { - vec![Event::BakerStakeIncreased(BakerStakeIncreased { - baker_id: data.baker_id.id.index.try_into()?, - new_staked_amount: data.new_stake.micro_ccd.try_into()?, - })] - } else { - vec![Event::BakerStakeDecreased(BakerStakeDecreased { - baker_id: data.baker_id.id.index.try_into()?, - new_staked_amount: data.new_stake.micro_ccd.try_into()?, - })] - } + }), + Event::TransferMemo(memo.into()), + ] + } + AccountTransactionEffects::BakerAdded { + data, + } => { + vec![Event::BakerAdded(BakerAdded { + staked_amount: data.stake.micro_ccd.try_into()?, + restake_earnings: data.restake_earnings, + baker_id: data.keys_event.baker_id.id.index.try_into()?, + sign_key: serde_json::to_string(&data.keys_event.sign_key)?, + election_key: serde_json::to_string(&data.keys_event.election_key)?, + aggregation_key: serde_json::to_string(&data.keys_event.aggregation_key)?, + })] + } + AccountTransactionEffects::BakerRemoved { + baker_id, + } => { + vec![Event::BakerRemoved(BakerRemoved { + baker_id: baker_id.id.index.try_into()?, + })] + } + AccountTransactionEffects::BakerStakeUpdated { + data, + } => { + if let Some(data) = data { + if data.increased { + vec![Event::BakerStakeIncreased(BakerStakeIncreased { + baker_id: data.baker_id.id.index.try_into()?, + new_staked_amount: data.new_stake.micro_ccd.try_into()?, + })] } else { - Vec::new() + vec![Event::BakerStakeDecreased(BakerStakeDecreased { + baker_id: data.baker_id.id.index.try_into()?, + new_staked_amount: data.new_stake.micro_ccd.try_into()?, + })] } + } else { + Vec::new() } - AccountTransactionEffects::BakerRestakeEarningsUpdated { - baker_id, + } + AccountTransactionEffects::BakerRestakeEarningsUpdated { + baker_id, + restake_earnings, + } => { + vec![Event::BakerSetRestakeEarnings(BakerSetRestakeEarnings { + baker_id: baker_id.id.index.try_into()?, restake_earnings, - } => { - vec![Event::BakerSetRestakeEarnings(BakerSetRestakeEarnings { - baker_id: baker_id.id.index.try_into()?, - restake_earnings, - })] - } - AccountTransactionEffects::BakerKeysUpdated { - data, - } => { - vec![Event::BakerKeysUpdated(BakerKeysUpdated { - baker_id: data.baker_id.id.index.try_into()?, - sign_key: serde_json::to_string(&data.sign_key)?, - election_key: serde_json::to_string(&data.election_key)?, - aggregation_key: serde_json::to_string(&data.aggregation_key)?, - })] - } - AccountTransactionEffects::EncryptedAmountTransferred { - removed, - added, - } => { - vec![ - Event::EncryptedAmountsRemoved((*removed).try_into()?), - Event::NewEncryptedAmount((*added).try_into()?), - ] - } - AccountTransactionEffects::EncryptedAmountTransferredWithMemo { - removed, - added, - memo, - } => { - vec![ - Event::EncryptedAmountsRemoved((*removed).try_into()?), - Event::NewEncryptedAmount((*added).try_into()?), - Event::TransferMemo(memo.into()), - ] - } - AccountTransactionEffects::TransferredToEncrypted { - data, - } => { - vec![Event::EncryptedSelfAmountAdded(EncryptedSelfAmountAdded { - account_address: data.account.into(), - new_encrypted_amount: serde_json::to_string(&data.new_amount)?, - amount: data.amount.micro_ccd.try_into()?, - })] - } - AccountTransactionEffects::TransferredToPublic { - removed, - amount, - } => { - vec![ - Event::EncryptedAmountsRemoved((*removed).try_into()?), - Event::AmountAddedByDecryption(AmountAddedByDecryption { - amount: amount.micro_ccd().try_into()?, - account_address: details.sender.into(), - }), - ] - } - AccountTransactionEffects::TransferredWithSchedule { - to, - amount, - } => { - vec![Event::TransferredWithSchedule(TransferredWithSchedule { + })] + } + AccountTransactionEffects::BakerKeysUpdated { + data, + } => { + vec![Event::BakerKeysUpdated(BakerKeysUpdated { + baker_id: data.baker_id.id.index.try_into()?, + sign_key: serde_json::to_string(&data.sign_key)?, + election_key: serde_json::to_string(&data.election_key)?, + aggregation_key: serde_json::to_string(&data.aggregation_key)?, + })] + } + AccountTransactionEffects::EncryptedAmountTransferred { + removed, + added, + } => { + vec![ + Event::EncryptedAmountsRemoved((*removed).try_into()?), + Event::NewEncryptedAmount((*added).try_into()?), + ] + } + AccountTransactionEffects::EncryptedAmountTransferredWithMemo { + removed, + added, + memo, + } => { + vec![ + Event::EncryptedAmountsRemoved((*removed).try_into()?), + Event::NewEncryptedAmount((*added).try_into()?), + Event::TransferMemo(memo.into()), + ] + } + AccountTransactionEffects::TransferredToEncrypted { + data, + } => { + vec![Event::EncryptedSelfAmountAdded(EncryptedSelfAmountAdded { + account_address: data.account.into(), + new_encrypted_amount: serde_json::to_string(&data.new_amount)?, + amount: data.amount.micro_ccd.try_into()?, + })] + } + AccountTransactionEffects::TransferredToPublic { + removed, + amount, + } => { + vec![ + Event::EncryptedAmountsRemoved((*removed).try_into()?), + Event::AmountAddedByDecryption(AmountAddedByDecryption { + amount: amount.micro_ccd().try_into()?, + account_address: details.sender.into(), + }), + ] + } + AccountTransactionEffects::TransferredWithSchedule { + to, + amount, + } => { + vec![Event::TransferredWithSchedule(TransferredWithSchedule { + from_account_address: details.sender.into(), + to_account_address: to.into(), + total_amount: amount + .into_iter() + .map(|(_, amount)| amount.micro_ccd()) + .sum::() + .try_into()?, + })] + } + AccountTransactionEffects::TransferredWithScheduleAndMemo { + to, + amount, + memo, + } => { + vec![ + Event::TransferredWithSchedule(TransferredWithSchedule { from_account_address: details.sender.into(), to_account_address: to.into(), total_amount: amount @@ -3950,233 +3966,214 @@ pub fn events_from_summary( .map(|(_, amount)| amount.micro_ccd()) .sum::() .try_into()?, - })] - } - AccountTransactionEffects::TransferredWithScheduleAndMemo { - to, - amount, - memo, - } => { - vec![ - Event::TransferredWithSchedule(TransferredWithSchedule { - from_account_address: details.sender.into(), - to_account_address: to.into(), - total_amount: amount - .into_iter() - .map(|(_, amount)| amount.micro_ccd()) - .sum::() - .try_into()?, - }), - Event::TransferMemo(memo.into()), - ] - } - AccountTransactionEffects::CredentialKeysUpdated { - cred_id, - } => { - vec![Event::CredentialKeysUpdated(CredentialKeysUpdated { - cred_id: cred_id.to_string(), - })] - } - AccountTransactionEffects::CredentialsUpdated { - new_cred_ids, - removed_cred_ids, - new_threshold, - } => { - vec![Event::CredentialsUpdated(CredentialsUpdated { - account_address: details.sender.into(), - new_cred_ids: new_cred_ids - .into_iter() - .map(|cred| cred.to_string()) - .collect(), - removed_cred_ids: removed_cred_ids - .into_iter() - .map(|cred| cred.to_string()) - .collect(), - new_threshold: Byte(u8::from(new_threshold)), - })] - } - AccountTransactionEffects::DataRegistered { - data, - } => { - vec![Event::DataRegistered(DataRegistered { - data_as_hex: hex::encode(data.as_ref()), - decoded: DecodedText::from_bytes(data.as_ref()), - })] - } - AccountTransactionEffects::BakerConfigured { - data, - } => data - .into_iter() - .map(|baker_event| { - use concordium_rust_sdk::types::BakerEvent; - match baker_event { - BakerEvent::BakerAdded { - data, - } => Ok(Event::BakerAdded(BakerAdded { - staked_amount: data.stake.micro_ccd.try_into()?, - restake_earnings: data.restake_earnings, - baker_id: data.keys_event.baker_id.id.index.try_into()?, - sign_key: serde_json::to_string(&data.keys_event.sign_key)?, - election_key: serde_json::to_string( - &data.keys_event.election_key, - )?, - aggregation_key: serde_json::to_string( - &data.keys_event.aggregation_key, - )?, - })), - BakerEvent::BakerRemoved { - baker_id, - } => Ok(Event::BakerRemoved(BakerRemoved { - baker_id: baker_id.id.index.try_into()?, - })), - BakerEvent::BakerStakeIncreased { - baker_id, - new_stake, - } => Ok(Event::BakerStakeIncreased(BakerStakeIncreased { - baker_id: baker_id.id.index.try_into()?, - new_staked_amount: new_stake.micro_ccd.try_into()?, - })), - BakerEvent::BakerStakeDecreased { - baker_id, - new_stake, - } => Ok(Event::BakerStakeDecreased(BakerStakeDecreased { - baker_id: baker_id.id.index.try_into()?, - new_staked_amount: new_stake.micro_ccd.try_into()?, - })), - BakerEvent::BakerRestakeEarningsUpdated { - baker_id, - restake_earnings, - } => Ok(Event::BakerSetRestakeEarnings(BakerSetRestakeEarnings { + }), + Event::TransferMemo(memo.into()), + ] + } + AccountTransactionEffects::CredentialKeysUpdated { + cred_id, + } => { + vec![Event::CredentialKeysUpdated(CredentialKeysUpdated { + cred_id: cred_id.to_string(), + })] + } + AccountTransactionEffects::CredentialsUpdated { + new_cred_ids, + removed_cred_ids, + new_threshold, + } => { + vec![Event::CredentialsUpdated(CredentialsUpdated { + account_address: details.sender.into(), + new_cred_ids: new_cred_ids + .into_iter() + .map(|cred| cred.to_string()) + .collect(), + removed_cred_ids: removed_cred_ids + .into_iter() + .map(|cred| cred.to_string()) + .collect(), + new_threshold: Byte(u8::from(new_threshold)), + })] + } + AccountTransactionEffects::DataRegistered { + data, + } => { + vec![Event::DataRegistered(DataRegistered { + data_as_hex: hex::encode(data.as_ref()), + decoded: DecodedText::from_bytes(data.as_ref()), + })] + } + AccountTransactionEffects::BakerConfigured { + data, + } => data + .into_iter() + .map(|baker_event| { + use concordium_rust_sdk::types::BakerEvent; + match baker_event { + BakerEvent::BakerAdded { + data, + } => Ok(Event::BakerAdded(BakerAdded { + staked_amount: data.stake.micro_ccd.try_into()?, + restake_earnings: data.restake_earnings, + baker_id: data.keys_event.baker_id.id.index.try_into()?, + sign_key: serde_json::to_string(&data.keys_event.sign_key)?, + election_key: serde_json::to_string(&data.keys_event.election_key)?, + aggregation_key: serde_json::to_string( + &data.keys_event.aggregation_key, + )?, + })), + BakerEvent::BakerRemoved { + baker_id, + } => Ok(Event::BakerRemoved(BakerRemoved { + baker_id: baker_id.id.index.try_into()?, + })), + BakerEvent::BakerStakeIncreased { + baker_id, + new_stake, + } => Ok(Event::BakerStakeIncreased(BakerStakeIncreased { + baker_id: baker_id.id.index.try_into()?, + new_staked_amount: new_stake.micro_ccd.try_into()?, + })), + BakerEvent::BakerStakeDecreased { + baker_id, + new_stake, + } => Ok(Event::BakerStakeDecreased(BakerStakeDecreased { + baker_id: baker_id.id.index.try_into()?, + new_staked_amount: new_stake.micro_ccd.try_into()?, + })), + BakerEvent::BakerRestakeEarningsUpdated { + baker_id, + restake_earnings, + } => Ok(Event::BakerSetRestakeEarnings(BakerSetRestakeEarnings { + baker_id: baker_id.id.index.try_into()?, + restake_earnings, + })), + BakerEvent::BakerKeysUpdated { + data, + } => Ok(Event::BakerKeysUpdated(BakerKeysUpdated { + baker_id: data.baker_id.id.index.try_into()?, + sign_key: serde_json::to_string(&data.sign_key)?, + election_key: serde_json::to_string(&data.election_key)?, + aggregation_key: serde_json::to_string(&data.aggregation_key)?, + })), + BakerEvent::BakerSetOpenStatus { + baker_id, + open_status, + } => Ok(Event::BakerSetOpenStatus(BakerSetOpenStatus { + baker_id: baker_id.id.index.try_into()?, + account_address: details.sender.into(), + open_status: open_status.into(), + })), + BakerEvent::BakerSetMetadataURL { + baker_id, + metadata_url, + } => Ok(Event::BakerSetMetadataURL(BakerSetMetadataURL { + baker_id: baker_id.id.index.try_into()?, + account_address: details.sender.into(), + metadata_url: metadata_url.into(), + })), + BakerEvent::BakerSetTransactionFeeCommission { + baker_id, + transaction_fee_commission, + } => Ok(Event::BakerSetTransactionFeeCommission( + BakerSetTransactionFeeCommission { + baker_id: baker_id.id.index.try_into()?, + account_address: details.sender.into(), + transaction_fee_commission: transaction_fee_commission.into(), + }, + )), + BakerEvent::BakerSetBakingRewardCommission { + baker_id, + baking_reward_commission, + } => Ok(Event::BakerSetBakingRewardCommission( + BakerSetBakingRewardCommission { + baker_id: baker_id.id.index.try_into()?, + account_address: details.sender.into(), + baking_reward_commission: baking_reward_commission.into(), + }, + )), + BakerEvent::BakerSetFinalizationRewardCommission { + baker_id, + finalization_reward_commission, + } => Ok(Event::BakerSetFinalizationRewardCommission( + BakerSetFinalizationRewardCommission { baker_id: baker_id.id.index.try_into()?, - restake_earnings, - })), - BakerEvent::BakerKeysUpdated { - data, - } => Ok(Event::BakerKeysUpdated(BakerKeysUpdated { - baker_id: data.baker_id.id.index.try_into()?, - sign_key: serde_json::to_string(&data.sign_key)?, - election_key: serde_json::to_string(&data.election_key)?, - aggregation_key: serde_json::to_string(&data.aggregation_key)?, - })), - BakerEvent::BakerSetOpenStatus { - baker_id, - open_status, - } => Ok(Event::BakerSetOpenStatus(BakerSetOpenStatus { - baker_id: baker_id.id.index.try_into()?, account_address: details.sender.into(), - open_status: open_status.into(), - })), - BakerEvent::BakerSetMetadataURL { - baker_id, - metadata_url, - } => Ok(Event::BakerSetMetadataURL(BakerSetMetadataURL { - baker_id: baker_id.id.index.try_into()?, + finalization_reward_commission: finalization_reward_commission + .into(), + }, + )), + BakerEvent::DelegationRemoved { + delegator_id, + } => { + unimplemented!() + } + } + }) + .collect::>>()?, + AccountTransactionEffects::DelegationConfigured { + data, + } => { + use concordium_rust_sdk::types::DelegationEvent; + data.into_iter() + .map(|event| match event { + DelegationEvent::DelegationStakeIncreased { + delegator_id, + new_stake, + } => Ok(Event::DelegationStakeIncreased(DelegationStakeIncreased { + delegator_id: delegator_id.id.index.try_into()?, + account_address: details.sender.into(), + new_staked_amount: new_stake.micro_ccd().try_into()?, + })), + DelegationEvent::DelegationStakeDecreased { + delegator_id, + new_stake, + } => Ok(Event::DelegationStakeDecreased(DelegationStakeDecreased { + delegator_id: delegator_id.id.index.try_into()?, + account_address: details.sender.into(), + new_staked_amount: new_stake.micro_ccd().try_into()?, + })), + DelegationEvent::DelegationSetRestakeEarnings { + delegator_id, + restake_earnings, + } => { + Ok(Event::DelegationSetRestakeEarnings(DelegationSetRestakeEarnings { + delegator_id: delegator_id.id.index.try_into()?, account_address: details.sender.into(), - metadata_url: metadata_url.into(), - })), - BakerEvent::BakerSetTransactionFeeCommission { - baker_id, - transaction_fee_commission, - } => Ok(Event::BakerSetTransactionFeeCommission( - BakerSetTransactionFeeCommission { - baker_id: baker_id.id.index.try_into()?, - account_address: details.sender.into(), - transaction_fee_commission: transaction_fee_commission.into(), - }, - )), - BakerEvent::BakerSetBakingRewardCommission { - baker_id, - baking_reward_commission, - } => Ok(Event::BakerSetBakingRewardCommission( - BakerSetBakingRewardCommission { - baker_id: baker_id.id.index.try_into()?, - account_address: details.sender.into(), - baking_reward_commission: baking_reward_commission.into(), - }, - )), - BakerEvent::BakerSetFinalizationRewardCommission { - baker_id, - finalization_reward_commission, - } => Ok(Event::BakerSetFinalizationRewardCommission( - BakerSetFinalizationRewardCommission { - baker_id: baker_id.id.index.try_into()?, - account_address: details.sender.into(), - finalization_reward_commission: finalization_reward_commission - .into(), - }, - )), - BakerEvent::DelegationRemoved { - delegator_id, - } => { - unimplemented!() - } + restake_earnings, + })) } - }) - .collect::>>()?, - AccountTransactionEffects::DelegationConfigured { - data, - } => { - use concordium_rust_sdk::types::DelegationEvent; - data.into_iter() - .map(|event| match event { - DelegationEvent::DelegationStakeIncreased { - delegator_id, - new_stake, - } => Ok(Event::DelegationStakeIncreased(DelegationStakeIncreased { - delegator_id: delegator_id.id.index.try_into()?, - account_address: details.sender.into(), - new_staked_amount: new_stake.micro_ccd().try_into()?, - })), - DelegationEvent::DelegationStakeDecreased { - delegator_id, - new_stake, - } => Ok(Event::DelegationStakeDecreased(DelegationStakeDecreased { + DelegationEvent::DelegationSetDelegationTarget { + delegator_id, + delegation_target, + } => Ok(Event::DelegationSetDelegationTarget( + DelegationSetDelegationTarget { delegator_id: delegator_id.id.index.try_into()?, account_address: details.sender.into(), - new_staked_amount: new_stake.micro_ccd().try_into()?, - })), - DelegationEvent::DelegationSetRestakeEarnings { - delegator_id, - restake_earnings, - } => Ok(Event::DelegationSetRestakeEarnings( - DelegationSetRestakeEarnings { - delegator_id: delegator_id.id.index.try_into()?, - account_address: details.sender.into(), - restake_earnings, - }, - )), - DelegationEvent::DelegationSetDelegationTarget { - delegator_id, - delegation_target, - } => Ok(Event::DelegationSetDelegationTarget( - DelegationSetDelegationTarget { - delegator_id: delegator_id.id.index.try_into()?, - account_address: details.sender.into(), - delegation_target: delegation_target.try_into()?, - }, - )), - DelegationEvent::DelegationAdded { - delegator_id, - } => Ok(Event::DelegationAdded(DelegationAdded { - delegator_id: delegator_id.id.index.try_into()?, - account_address: details.sender.into(), - })), - DelegationEvent::DelegationRemoved { - delegator_id, - } => Ok(Event::DelegationRemoved(DelegationRemoved { - delegator_id: delegator_id.id.index.try_into()?, - account_address: details.sender.into(), - })), - DelegationEvent::BakerRemoved { - baker_id, - } => { - unimplemented!(); - } - }) - .collect::>>()? - } + delegation_target: delegation_target.try_into()?, + }, + )), + DelegationEvent::DelegationAdded { + delegator_id, + } => Ok(Event::DelegationAdded(DelegationAdded { + delegator_id: delegator_id.id.index.try_into()?, + account_address: details.sender.into(), + })), + DelegationEvent::DelegationRemoved { + delegator_id, + } => Ok(Event::DelegationRemoved(DelegationRemoved { + delegator_id: delegator_id.id.index.try_into()?, + account_address: details.sender.into(), + })), + DelegationEvent::BakerRemoved { + baker_id, + } => { + unimplemented!(); + } + }) + .collect::>>()? } - } + }, BlockItemSummaryDetails::AccountCreation(details) => { vec![Event::AccountCreated(AccountCreated { account_address: details.address.into(), @@ -4871,22 +4868,59 @@ pub struct ContractResumed { } #[derive(SimpleObject, serde::Serialize, serde::Deserialize)] +#[graphql(complex)] pub struct ContractUpdated { contract_address: ContractAddress, instigator: Address, amount: Amount, - message_as_hex: String, receive_name: String, version: ContractVersion, - // eventsAsHex("Returns the first _n_ elements from the list." first: Int "Returns the elements - // in the list that come after the specified cursor." after: String "Returns the last _n_ - // elements from the list." last: Int "Returns the elements in the list that come before the - // specified cursor." before: String): StringConnection events("Returns the first _n_ - // elements from the list." first: Int "Returns the elements in the list that come after the - // specified cursor." after: String "Returns the last _n_ elements from the list." last: Int - // "Returns the elements in the list that come before the specified cursor." before: String): - // StringConnection - // TODO message: String, + // All logged events by smart contracts during the transaction execution. + contrat_logs: Vec>, + input_parameter: Vec, +} + +#[ComplexObject] +impl ContractUpdated { + async fn message(&self) -> ApiResult { + // TODO: decode input-parameter/message with schema. + + Ok(hex::encode(self.input_parameter.clone())) + } + + async fn message_as_hex(&self) -> ApiResult { + Ok(hex::encode(self.input_parameter.clone())) + } + + async fn events_as_hex<'a>(&self) -> ApiResult> { + let mut connection = connection::Connection::new(true, true); + + self.contrat_logs.iter().enumerate().for_each(|(index, log)| { + connection + .edges + .push(connection::Edge::new(index.to_string(), hex::encode(log.clone()))); + }); + + // Nice-to-have: pagination info but not used at front-end currently. + + Ok(connection) + } + + async fn events<'a>(&self) -> ApiResult> { + let mut connection = connection::Connection::new(true, true); + + self.contrat_logs.iter().enumerate().for_each(|(index, log)| { + connection + .edges + .push(connection::Edge::new(index.to_string(), hex::encode(log.clone()))); + }); + + // TODO: decode event with schema. + + // Nice-to-have: pagination info but not used at front-end currently. + + Ok(connection) + } } #[derive(SimpleObject, serde::Serialize, serde::Deserialize)] From 0615c2867e762837204b73d57476e0738d716701 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Mon, 28 Oct 2024 15:25:59 +0700 Subject: [PATCH 09/22] Add log decoding --- ...e193f269144b06676993e428ca7976b58fe11.json | 19 +++++ ...94381f8780ac10c2d6a898e3b47cc4b09f289.json | 72 ++++++++++++++++++ ...a9516e7ce66b7c7b66172f12a22a7a624c96f.json | 35 +++++++++ backend-rust/src/graphql_api.rs | 75 +++++++++++++++---- 4 files changed, 186 insertions(+), 15 deletions(-) create mode 100644 backend-rust/.sqlx/query-6360a21563dcea7c919eb3c8e7fe193f269144b06676993e428ca7976b58fe11.json create mode 100644 backend-rust/.sqlx/query-c6af2642c0b9f9a6ff49e03123f94381f8780ac10c2d6a898e3b47cc4b09f289.json create mode 100644 backend-rust/.sqlx/query-e3ec36d7b5af0917cd7a68ce411a9516e7ce66b7c7b66172f12a22a7a624c96f.json diff --git a/backend-rust/.sqlx/query-6360a21563dcea7c919eb3c8e7fe193f269144b06676993e428ca7976b58fe11.json b/backend-rust/.sqlx/query-6360a21563dcea7c919eb3c8e7fe193f269144b06676993e428ca7976b58fe11.json new file mode 100644 index 00000000..2fd0cb85 --- /dev/null +++ b/backend-rust/.sqlx/query-6360a21563dcea7c919eb3c8e7fe193f269144b06676993e428ca7976b58fe11.json @@ -0,0 +1,19 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO contract_events (\n transaction_index,\n trace_element_index,\n contract_logs,\n block_height,\n contract_index,\n contract_sub_index\n )\n VALUES (\n $1, $2, $3, $4, $5, $6\n )", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8", + "Int8", + "Jsonb", + "Int8", + "Int8", + "Int8" + ] + }, + "nullable": [] + }, + "hash": "6360a21563dcea7c919eb3c8e7fe193f269144b06676993e428ca7976b58fe11" +} diff --git a/backend-rust/.sqlx/query-c6af2642c0b9f9a6ff49e03123f94381f8780ac10c2d6a898e3b47cc4b09f289.json b/backend-rust/.sqlx/query-c6af2642c0b9f9a6ff49e03123f94381f8780ac10c2d6a898e3b47cc4b09f289.json new file mode 100644 index 00000000..e37d44e5 --- /dev/null +++ b/backend-rust/.sqlx/query-c6af2642c0b9f9a6ff49e03123f94381f8780ac10c2d6a898e3b47cc4b09f289.json @@ -0,0 +1,72 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT * FROM (\n SELECT\n contract_events.index,\n contract_events.transaction_index,\n trace_element_index,\n contract_events.block_height AS event_block_height,\n transactions.hash as transaction_hash,\n transactions.events,\n accounts.address as creator,\n blocks.slot_time as block_slot_time,\n blocks.height as block_height\n FROM contract_events\n JOIN transactions\n ON contract_events.block_height = transactions.block_height\n AND contract_events.transaction_index = transactions.index\n JOIN accounts\n ON transactions.sender = accounts.index\n JOIN blocks ON contract_events.block_height = blocks.height\n WHERE contract_events.contract_index = $1\n AND contract_events.contract_sub_index <= $2\n LIMIT $3\n ) AS contract_data\n ORDER BY contract_data.index DESC\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "index", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "transaction_index", + "type_info": "Int8" + }, + { + "ordinal": 2, + "name": "trace_element_index", + "type_info": "Int8" + }, + { + "ordinal": 3, + "name": "event_block_height", + "type_info": "Int8" + }, + { + "ordinal": 4, + "name": "transaction_hash", + "type_info": "Bpchar" + }, + { + "ordinal": 5, + "name": "events", + "type_info": "Jsonb" + }, + { + "ordinal": 6, + "name": "creator", + "type_info": "Bpchar" + }, + { + "ordinal": 7, + "name": "block_slot_time", + "type_info": "Timestamp" + }, + { + "ordinal": 8, + "name": "block_height", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Int8", + "Int8", + "Int8" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + true, + false, + false, + false + ] + }, + "hash": "c6af2642c0b9f9a6ff49e03123f94381f8780ac10c2d6a898e3b47cc4b09f289" +} diff --git a/backend-rust/.sqlx/query-e3ec36d7b5af0917cd7a68ce411a9516e7ce66b7c7b66172f12a22a7a624c96f.json b/backend-rust/.sqlx/query-e3ec36d7b5af0917cd7a68ce411a9516e7ce66b7c7b66172f12a22a7a624c96f.json new file mode 100644 index 00000000..2b3fdbbd --- /dev/null +++ b/backend-rust/.sqlx/query-e3ec36d7b5af0917cd7a68ce411a9516e7ce66b7c7b66172f12a22a7a624c96f.json @@ -0,0 +1,35 @@ +{ + "db_name": "PostgreSQL", + "query": "\nSELECT\n contracts.module_reference as module_reference,\n name as contract_name,\n schema as display_schema\nFROM contracts\nJOIN smart_contract_modules ON smart_contract_modules.module_reference=contracts.module_reference\nWHERE index=$1 AND sub_index=$2\n", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "module_reference", + "type_info": "Bpchar" + }, + { + "ordinal": 1, + "name": "contract_name", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "display_schema", + "type_info": "Bytea" + } + ], + "parameters": { + "Left": [ + "Int8", + "Int8" + ] + }, + "nullable": [ + false, + false, + true + ] + }, + "hash": "e3ec36d7b5af0917cd7a68ce411a9516e7ce66b7c7b66172f12a22a7a624c96f" +} diff --git a/backend-rust/src/graphql_api.rs b/backend-rust/src/graphql_api.rs index de5db729..e4944225 100644 --- a/backend-rust/src/graphql_api.rs +++ b/backend-rust/src/graphql_api.rs @@ -28,12 +28,16 @@ use async_graphql::{ use async_graphql_axum::GraphQLSubscription; use chrono::Duration; use concordium_rust_sdk::{ - base::contracts_common::{from_bytes, schema::VersionedModuleSchema}, + base::contracts_common::{ + schema::{VersionedModuleSchema, VersionedSchemaError}, + Cursor, + }, id::types as sdk_types, types::AmountFraction, }; use futures::prelude::*; use prometheus_client::registry::Registry; +use serde_json::to_string; use sqlx::{postgres::types::PgInterval, PgPool}; use std::{error::Error, str::FromStr, sync::Arc}; use tokio::{net::TcpListener, sync::broadcast}; @@ -287,8 +291,8 @@ enum ApiError { InvalidIntString(#[from] std::num::ParseIntError), #[error("Parse error: {0}")] InvalidContractVersion(#[from] InvalidContractVersionError), - #[error("Schema in database should be valid")] - InvalidModuleSchema, + #[error("Schema in database should be a valid versioned module schema")] + InvalidVersionedModuleSchema(#[from] VersionedSchemaError), } impl From for ApiError { @@ -987,11 +991,11 @@ LIMIT 30", // WHERE slot_time > (LOCALTIMESTAMP - $1::interval) .await? .ok_or(ApiError::NotFound)?; - let display_schema = row.display_schema.as_ref().map_or(Ok(None), |s| { - from_bytes::(s) - .map(|opt_schema| Some(opt_schema.to_string())) - .map_err(|_| ApiError::InvalidModuleSchema) - })?; + let display_schema = row + .display_schema + .as_ref() + .map(|s| VersionedModuleSchema::new(s, &None).map(|schema| schema.to_string())) + .transpose()?; Ok(ModuleReferenceEvent { module_reference, @@ -4875,7 +4879,7 @@ pub struct ContractUpdated { amount: Amount, receive_name: String, version: ContractVersion, - // All logged events by smart contracts during the transaction execution. + // All logged events by the smart contract during the transaction execution. contrat_logs: Vec>, input_parameter: Vec, } @@ -4906,16 +4910,57 @@ impl ContractUpdated { Ok(connection) } - async fn events<'a>(&self) -> ApiResult> { + async fn events<'a>( + &self, + ctx: &Context<'a>, + ) -> ApiResult> { + let pool = get_pool(ctx)?; + + let row = sqlx::query!( + r#" +SELECT + contracts.module_reference as module_reference, + name as contract_name, + schema as display_schema +FROM contracts +JOIN smart_contract_modules ON smart_contract_modules.module_reference=contracts.module_reference +WHERE index=$1 AND sub_index=$2 +"#, + self.contract_address.index.0 as i64, + self.contract_address.sub_index.0 as i64 + ) + .fetch_optional(pool) + .await? + .ok_or(ApiError::NotFound)?; + + let opt_event_schema = row + .display_schema + .as_ref() + .and_then(|schema| VersionedModuleSchema::new(schema, &None).ok()) + .and_then(|versioned_schema| { + versioned_schema.get_event_schema(&row.contract_name).ok() + }); + let mut connection = connection::Connection::new(true, true); self.contrat_logs.iter().enumerate().for_each(|(index, log)| { - connection - .edges - .push(connection::Edge::new(index.to_string(), hex::encode(log.clone()))); - }); + let decoded_logs = if let Some(event_schema) = &opt_event_schema { + let mut cursor = Cursor::new(&log); + match event_schema.to_json(&mut cursor) { + Ok(v) => to_string(&v).map_err(|_| { + ApiError::InternalError("Failed to serialize event schema".into()) + }), + Err(e) => Err(ApiError::InternalError(e.display(true))), + } + } else { + // Note: Shall we use something better than empty string if no event schema is + // available for decoding. + Ok("".to_string()) + } + .unwrap(); - // TODO: decode event with schema. + connection.edges.push(connection::Edge::new(index.to_string(), decoded_logs)); + }); // Nice-to-have: pagination info but not used at front-end currently. From 6ff5c26d1e3625705b08e0a07e90ddf9e6c71012 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Tue, 29 Oct 2024 01:29:55 +0700 Subject: [PATCH 10/22] Add contract_logs decoding for init transactions --- backend-rust/src/graphql_api.rs | 234 +++++++++++++++++++++++--------- 1 file changed, 172 insertions(+), 62 deletions(-) diff --git a/backend-rust/src/graphql_api.rs b/backend-rust/src/graphql_api.rs index e4944225..6db085dd 100644 --- a/backend-rust/src/graphql_api.rs +++ b/backend-rust/src/graphql_api.rs @@ -293,6 +293,8 @@ enum ApiError { InvalidContractVersion(#[from] InvalidContractVersionError), #[error("Schema in database should be a valid versioned module schema")] InvalidVersionedModuleSchema(#[from] VersionedSchemaError), + #[error("Schema error: {0}")] + SchemaError(String), } impl From for ApiError { @@ -1444,43 +1446,56 @@ impl Contract { let row = sqlx::query!( r#" SELECT - module_reference, - name as contract_name, - contracts.amount as amount, - blocks.slot_time as block_slot_time, - init_block_height as block_height, - transactions.hash as transaction_hash, - accounts.address as creator, - version + module_reference, + name as contract_name, + contracts.amount as amount, + blocks.slot_time as block_slot_time, + init_block_height as block_height, + transactions.events, + transactions.hash as transaction_hash, + accounts.address as creator, + version FROM contracts JOIN blocks ON init_block_height=blocks.height JOIN transactions ON init_block_height=transactions.block_height AND init_transaction_index=transactions.index JOIN accounts ON transactions.sender=accounts.index WHERE contracts.index=$1 AND contracts.sub_index=$2 "#, -self.contract_address_index.0 as i64,self.contract_address_sub_index.0 as i64 - ).fetch_optional(pool).await? - .ok_or(ApiError::NotFound)?; + self.contract_address_index.0 as i64, + self.contract_address_sub_index.0 as i64 + ) + .fetch_optional(pool) + .await? + .ok_or(ApiError::NotFound)?; + + let Some(events) = row.events else { + return Err(ApiError::InternalError("Missing events in database".to_string())); + }; + + let mut events: Vec = serde_json::from_value(events).map_err(|_| { + ApiError::InternalError("Failed to deserialize events from database".to_string()) + })?; + + // Add check if not `initEvent` return error. + + if events.len() != 1 { + return Err(ApiError::InternalError( + "Contract init transaction expects exactly one event".to_string(), + )); + } + + // Contract init transactions have exactly one top-level event. + let event = events.swap_remove(0_usize); let initial_event = ContractEvent { contract_address_index: self.contract_address_index, contract_address_sub_index: self.contract_address_sub_index, sender: row.creator.into(), - event: Event::ContractInitialized(ContractInitialized { - module_ref: row.module_reference, - contract_address: ContractAddress::from( - self.contract_address_index, - self.contract_address_sub_index, - ), - amount: row.amount, - init_name: format!("init_{:}", row.contract_name), - version: row.version.try_into()?, - }), + event, block_height: row.block_height, transaction_hash: row.transaction_hash, block_slot_time: row.block_slot_time, }; - contract_events.push(initial_event); total_events_count += 1; } @@ -3656,16 +3671,6 @@ struct ContractAddress { as_string: String, } -impl ContractAddress { - fn from(index: ContractIndex, sub_index: ContractIndex) -> Self { - Self { - index, - sub_index, - as_string: format!("<{},{}>", index, sub_index), - } - } -} - impl From for ContractAddress { fn from(value: concordium_rust_sdk::types::ContractAddress) -> Self { Self { @@ -3762,6 +3767,11 @@ pub fn events_from_summary( amount: i64::try_from(data.amount.micro_ccd)?, init_name: data.init_name.to_string(), version: data.contract_version.into(), + // TODO: the send message/input parameter is missing and not exposed by the + // node and rust SDK currently, it should be added when available. + // + // input_parameter: data.message.as_ref().to_vec(), + contract_logs: data.events.iter().map(|e| e.as_ref().to_vec()).collect(), })] } AccountTransactionEffects::ContractUpdateIssued { @@ -3780,7 +3790,7 @@ pub fn events_from_summary( receive_name: data.receive_name.to_string(), version: data.contract_version.into(), input_parameter: data.message.as_ref().to_vec(), - contrat_logs: data + contract_logs: data .events .iter() .map(|e| e.as_ref().to_vec()) @@ -4628,20 +4638,114 @@ impl BakerStakeIncreased { } #[derive(SimpleObject, serde::Serialize, serde::Deserialize)] +#[graphql(complex)] pub struct ContractInitialized { module_ref: String, contract_address: ContractAddress, amount: Amount, init_name: String, version: ContractVersion, - // TODO: eventsAsHex("Returns the first _n_ elements from the list." first: Int "Returns the - // elements in the list that come after the specified cursor." after: String "Returns the last - // _n_ elements from the list." last: Int "Returns the elements in the list that come before - // the specified cursor." before: String): StringConnection TODO: events("Returns the first - // _n_ elements from the list." first: Int "Returns the elements in the list that come after - // the specified cursor." after: String "Returns the last _n_ elements from the list." last: - // Int "Returns the elements in the list that come before the specified cursor." before: - // String): StringConnection + // All logged events by the smart contract during the transaction execution. + contract_logs: Vec>, + // TODO: the send message/input parameter is missing and not exposed by the node and rust SDK + // currently, it should be added when available. + // + // input_parameter: Vec, +} + +#[ComplexObject] +impl ContractInitialized { + // TODO: the send message/input parameter is missing and not exposed by the node + // and rust SDK currently, it should be added when available. + // + // async fn message(&self) -> ApiResult { + // // TODO: decode input-parameter/message with schema. + + // Ok(hex::encode(self.input_parameter.clone())) + // } + + // TODO: the send message/input parameter is missing and not exposed by the node + // and rust SDK currently, it should be added when available. + // + // async fn message_as_hex(&self) -> ApiResult { + // Ok(hex::encode(self.input_parameter.clone())) } + + async fn events_as_hex<'a>(&self) -> ApiResult> { + let mut connection = connection::Connection::new(true, true); + + self.contract_logs.iter().enumerate().for_each(|(index, log)| { + connection.edges.push(connection::Edge::new(index.to_string(), hex::encode(log))); + }); + + // Nice-to-have: pagination info but not used at front-end currently. + + Ok(connection) + } + + async fn events<'a>( + &self, + ctx: &Context<'a>, + ) -> ApiResult> { + let pool = get_pool(ctx)?; + + let row = sqlx::query!( + r#" +SELECT + contracts.module_reference as module_reference, + name as contract_name, + schema as display_schema +FROM contracts +JOIN smart_contract_modules ON smart_contract_modules.module_reference=contracts.module_reference +WHERE index=$1 AND sub_index=$2 +"#, + self.contract_address.index.0 as i64, + self.contract_address.sub_index.0 as i64 + ) + .fetch_optional(pool) + .await? + .ok_or(ApiError::NotFound)?; + + let opt_event_schema = row + .display_schema + .as_ref() + .and_then(|schema| VersionedModuleSchema::new(schema, &None).ok()) + .and_then(|versioned_schema| { + versioned_schema.get_event_schema(&row.contract_name).ok() + }); + + let mut connection = connection::Connection::new(true, true); + + for (index, log) in self.contract_logs.iter().enumerate() { + // Note: Shall we use something better than empty string if no event schema is + // available for decoding. + let decoded_logs = + opt_event_schema.as_ref().map_or(Ok("".to_string()), |event_schema| { + let mut cursor = Cursor::new(&log); + event_schema + .to_json(&mut cursor) + .map_err(|e| { + ApiError::SchemaError(format!( + "Failed to deserialize event schema: {:?}", + e.display(true) + )) + }) + .and_then(|v| { + to_string(&v).map_err(|e| { + ApiError::SchemaError(format!( + "Failed to deserialize event schema into string: {:?}", + e + )) + }) + }) + })?; + + connection.edges.push(connection::Edge::new(index.to_string(), decoded_logs)); + } + + // Nice-to-have: pagination info but not used at front-end currently. + + Ok(connection) + } } #[derive(Enum, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] @@ -4880,7 +4984,7 @@ pub struct ContractUpdated { receive_name: String, version: ContractVersion, // All logged events by the smart contract during the transaction execution. - contrat_logs: Vec>, + contract_logs: Vec>, input_parameter: Vec, } @@ -4899,10 +5003,8 @@ impl ContractUpdated { async fn events_as_hex<'a>(&self) -> ApiResult> { let mut connection = connection::Connection::new(true, true); - self.contrat_logs.iter().enumerate().for_each(|(index, log)| { - connection - .edges - .push(connection::Edge::new(index.to_string(), hex::encode(log.clone()))); + self.contract_logs.iter().enumerate().for_each(|(index, log)| { + connection.edges.push(connection::Edge::new(index.to_string(), hex::encode(log))); }); // Nice-to-have: pagination info but not used at front-end currently. @@ -4943,24 +5045,32 @@ WHERE index=$1 AND sub_index=$2 let mut connection = connection::Connection::new(true, true); - self.contrat_logs.iter().enumerate().for_each(|(index, log)| { - let decoded_logs = if let Some(event_schema) = &opt_event_schema { - let mut cursor = Cursor::new(&log); - match event_schema.to_json(&mut cursor) { - Ok(v) => to_string(&v).map_err(|_| { - ApiError::InternalError("Failed to serialize event schema".into()) - }), - Err(e) => Err(ApiError::InternalError(e.display(true))), - } - } else { - // Note: Shall we use something better than empty string if no event schema is - // available for decoding. - Ok("".to_string()) - } - .unwrap(); + for (index, log) in self.contract_logs.iter().enumerate() { + // Note: Shall we use something better than empty string if no event schema is + // available for decoding. + let decoded_logs = + opt_event_schema.as_ref().map_or(Ok("".to_string()), |event_schema| { + let mut cursor = Cursor::new(&log); + event_schema + .to_json(&mut cursor) + .map_err(|e| { + ApiError::SchemaError(format!( + "Failed to deserialize event schema: {:?}", + e.display(true) + )) + }) + .and_then(|v| { + to_string(&v).map_err(|e| { + ApiError::SchemaError(format!( + "Failed to deserialize event schema into string: {:?}", + e + )) + }) + }) + })?; connection.edges.push(connection::Edge::new(index.to_string(), decoded_logs)); - }); + } // Nice-to-have: pagination info but not used at front-end currently. From ac8324ab67c8d45ff0ae79eccbba258106b277fc Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Tue, 29 Oct 2024 20:35:07 +0700 Subject: [PATCH 11/22] Adjust skip and take values --- backend-rust/src/graphql_api.rs | 107 +++++++++++++++++++------------- 1 file changed, 64 insertions(+), 43 deletions(-) diff --git a/backend-rust/src/graphql_api.rs b/backend-rust/src/graphql_api.rs index 6db085dd..2ee84b0b 100644 --- a/backend-rust/src/graphql_api.rs +++ b/backend-rust/src/graphql_api.rs @@ -60,6 +60,12 @@ pub struct ApiServiceConfig { account_connection_limit: u64, #[arg(long, env = "CCDSCAN_API_CONFIG_CONTRACT_CONNECTION_LIMIT", default_value = "100")] contract_connection_limit: u64, + #[arg( + long, + env = "CCDSCAN_API_CONFIG_CONTRACT_EVENTS_COLLECTION_LIMIT", + default_value = "100" + )] + contract_events_collection_limit: i64, #[arg( long, env = "CCDSCAN_API_CONFIG_TRANSACTION_EVENT_CONNECTION_LIMIT", @@ -1351,27 +1357,43 @@ struct Contract { snapshot: ContractSnapshot, } -// TODO +// TODOTODO #[ComplexObject] impl Contract { + // This function returns events from the `contract_events` table as well as + // one `init_transaction_event` from when the contract was initialized. The + // `skip` and `take` parameters are used to paginate the events. async fn contract_events( &self, ctx: &Context<'_>, skip: i32, take: i32, ) -> ApiResult { - // take some events from the `contract_events` table. - - // take sometimes initial event from `contracts` table. - + let config = get_config(ctx)?; let pool = get_pool(ctx)?; + // If `skip` is 0 and at least one event is taken, include the + // `init_transaction_event`. + let include_initial_event = skip == 0 && take > 0; + // Adjust the `take` and `skip` values considering if the + // `init_transaction_event` is requested to be included or not. + let take_without_initial_event = + std::cmp::max(0, take as i64 - include_initial_event as i64); + let skip_without_initial_event = std::cmp::max(0, skip as i64 - 1i64); + + // Limit the number of events to be fetched from the `contract_events` table. + let limit = std::cmp::min( + take_without_initial_event, + std::cmp::max( + 0, + config.contract_events_collection_limit - include_initial_event as i64, + ), + ); + let mut contract_events = vec![]; let mut total_events_count = 0; - // TODO: add `take` and `skip` to the query. - let limit = 3; - + // Get the events from the `contract_events` table. let mut row_stream = sqlx::query!( r#" SELECT * FROM ( @@ -1395,12 +1417,14 @@ impl Contract { WHERE contract_events.contract_index = $1 AND contract_events.contract_sub_index <= $2 LIMIT $3 + OFFSET $4 ) AS contract_data ORDER BY contract_data.index DESC "#, self.contract_address_index.0 as i64, self.contract_address_sub_index.0 as i64, - limit + limit, + skip_without_initial_event ) .fetch(pool); @@ -1438,10 +1462,7 @@ impl Contract { total_events_count += 1; } - // TODO: depending on `skip` and `take` values, we either include or not include - // the initial event. - let include_initial_event = true; - + // Get the `init_transaction_event`. if include_initial_event { let row = sqlx::query!( r#" @@ -3762,16 +3783,16 @@ pub fn events_from_summary( data, } => { vec![Event::ContractInitialized(ContractInitialized { - module_ref: data.origin_ref.to_string(), - contract_address: data.address.into(), - amount: i64::try_from(data.amount.micro_ccd)?, - init_name: data.init_name.to_string(), - version: data.contract_version.into(), + module_ref: data.origin_ref.to_string(), + contract_address: data.address.into(), + amount: i64::try_from(data.amount.micro_ccd)?, + init_name: data.init_name.to_string(), + version: data.contract_version.into(), // TODO: the send message/input parameter is missing and not exposed by the // node and rust SDK currently, it should be added when available. // // input_parameter: data.message.as_ref().to_vec(), - contract_logs: data.events.iter().map(|e| e.as_ref().to_vec()).collect(), + contract_logs_raw: data.events.iter().map(|e| e.as_ref().to_vec()).collect(), })] } AccountTransactionEffects::ContractUpdateIssued { @@ -3784,13 +3805,13 @@ pub fn events_from_summary( ContractTraceElement::Updated { data, } => Ok(Event::ContractUpdated(ContractUpdated { - contract_address: data.address.into(), - instigator: data.instigator.into(), - amount: data.amount.micro_ccd().try_into()?, - receive_name: data.receive_name.to_string(), - version: data.contract_version.into(), - input_parameter: data.message.as_ref().to_vec(), - contract_logs: data + contract_address: data.address.into(), + instigator: data.instigator.into(), + amount: data.amount.micro_ccd().try_into()?, + receive_name: data.receive_name.to_string(), + version: data.contract_version.into(), + input_parameter: data.message.as_ref().to_vec(), + contract_logs_raw: data .events .iter() .map(|e| e.as_ref().to_vec()) @@ -4640,13 +4661,13 @@ impl BakerStakeIncreased { #[derive(SimpleObject, serde::Serialize, serde::Deserialize)] #[graphql(complex)] pub struct ContractInitialized { - module_ref: String, - contract_address: ContractAddress, - amount: Amount, - init_name: String, - version: ContractVersion, + module_ref: String, + contract_address: ContractAddress, + amount: Amount, + init_name: String, + version: ContractVersion, // All logged events by the smart contract during the transaction execution. - contract_logs: Vec>, + contract_logs_raw: Vec>, // TODO: the send message/input parameter is missing and not exposed by the node and rust SDK // currently, it should be added when available. // @@ -4673,7 +4694,7 @@ impl ContractInitialized { async fn events_as_hex<'a>(&self) -> ApiResult> { let mut connection = connection::Connection::new(true, true); - self.contract_logs.iter().enumerate().for_each(|(index, log)| { + self.contract_logs_raw.iter().enumerate().for_each(|(index, log)| { connection.edges.push(connection::Edge::new(index.to_string(), hex::encode(log))); }); @@ -4715,7 +4736,7 @@ WHERE index=$1 AND sub_index=$2 let mut connection = connection::Connection::new(true, true); - for (index, log) in self.contract_logs.iter().enumerate() { + for (index, log) in self.contract_logs_raw.iter().enumerate() { // Note: Shall we use something better than empty string if no event schema is // available for decoding. let decoded_logs = @@ -4978,14 +4999,14 @@ pub struct ContractResumed { #[derive(SimpleObject, serde::Serialize, serde::Deserialize)] #[graphql(complex)] pub struct ContractUpdated { - contract_address: ContractAddress, - instigator: Address, - amount: Amount, - receive_name: String, - version: ContractVersion, + contract_address: ContractAddress, + instigator: Address, + amount: Amount, + receive_name: String, + version: ContractVersion, // All logged events by the smart contract during the transaction execution. - contract_logs: Vec>, - input_parameter: Vec, + contract_logs_raw: Vec>, + input_parameter: Vec, } #[ComplexObject] @@ -5003,7 +5024,7 @@ impl ContractUpdated { async fn events_as_hex<'a>(&self) -> ApiResult> { let mut connection = connection::Connection::new(true, true); - self.contract_logs.iter().enumerate().for_each(|(index, log)| { + self.contract_logs_raw.iter().enumerate().for_each(|(index, log)| { connection.edges.push(connection::Edge::new(index.to_string(), hex::encode(log))); }); @@ -5045,7 +5066,7 @@ WHERE index=$1 AND sub_index=$2 let mut connection = connection::Connection::new(true, true); - for (index, log) in self.contract_logs.iter().enumerate() { + for (index, log) in self.contract_logs_raw.iter().enumerate() { // Note: Shall we use something better than empty string if no event schema is // available for decoding. let decoded_logs = From 00ba8e1692623f32c5a86326047988debbb946e1 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Tue, 29 Oct 2024 21:16:37 +0700 Subject: [PATCH 12/22] Add pagination info --- backend-rust/src/graphql_api.rs | 45 ++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/backend-rust/src/graphql_api.rs b/backend-rust/src/graphql_api.rs index 2ee84b0b..47f7e775 100644 --- a/backend-rust/src/graphql_api.rs +++ b/backend-rust/src/graphql_api.rs @@ -1394,7 +1394,7 @@ impl Contract { let mut total_events_count = 0; // Get the events from the `contract_events` table. - let mut row_stream = sqlx::query!( + let row_stream = sqlx::query!( r#" SELECT * FROM ( SELECT @@ -1423,12 +1423,24 @@ impl Contract { "#, self.contract_address_index.0 as i64, self.contract_address_sub_index.0 as i64, - limit, + limit + 1, skip_without_initial_event ) .fetch(pool); - while let Some(row) = row_stream.try_next().await? { + // Collect the stream into a vector to inspect the number of rows. + let mut rows: Vec<_> = row_stream.take(limit as usize + 1).try_collect().await?; + + // Determine if there is a next page by checking if we got more than `limit` + // rows. + let has_next_page = rows.len() > limit as usize; + + // If there is a next page, remove the extra row used for pagination detection. + if has_next_page { + rows.pop(); + } + + for row in rows { let Some(events) = row.events else { return Err(ApiError::InternalError("Missing events in database".to_string())); }; @@ -1437,9 +1449,6 @@ impl Contract { ApiError::InternalError("Failed to deserialize events from database".to_string()) })?; - // Add check if not `initEvent`, `interrupt`, `upgrade`, `resume` ... return - // error. - if row.trace_element_index as usize >= events.len() { return Err(ApiError::InternalError( "Trace element index does not exist in events".to_string(), @@ -1448,6 +1457,20 @@ impl Contract { let event = events.swap_remove(row.trace_element_index as usize); + if let Event::ContractInterrupted(_) + | Event::ContractResumed(_) + | Event::ContractUpgraded(_) + | Event::ContractUpdated(_) = event + { + Ok(()) + } else { + Err(ApiError::InternalError( + "Not ContractInterrupted, ContractResumed, ContractUpgraded, or \ + ContractUpdated event" + .to_string(), + )) + }?; + let contract_event = ContractEvent { contract_address_index: self.contract_address_index, contract_address_sub_index: self.contract_address_sub_index, @@ -1508,6 +1531,11 @@ WHERE contracts.index=$1 AND contracts.sub_index=$2 // Contract init transactions have exactly one top-level event. let event = events.swap_remove(0_usize); + match event { + Event::ContractInitialized(_) => Ok(()), + _ => Err(ApiError::InternalError("Not ContractInitialized event".to_string())), + }?; + let initial_event = ContractEvent { contract_address_index: self.contract_address_index, contract_address_sub_index: self.contract_address_sub_index, @@ -1522,10 +1550,9 @@ WHERE contracts.index=$1 AND contracts.sub_index=$2 } Ok(ContractEventsCollectionSegment { - // TODO: add pagination info page_info: CollectionSegmentInfo { - has_next_page: false, - has_previous_page: false, + has_next_page, + has_previous_page: skip > 0, }, items: Some(contract_events), total_count: total_events_count, From f244ca552c467603b92a2fc9301bd8f89a2a6b91 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Wed, 30 Oct 2024 16:32:39 +0700 Subject: [PATCH 13/22] Add input parameter decoding --- backend-rust/src/graphql_api.rs | 109 ++++++++++++++++++++++++++------ 1 file changed, 90 insertions(+), 19 deletions(-) diff --git a/backend-rust/src/graphql_api.rs b/backend-rust/src/graphql_api.rs index 47f7e775..c9a1c45c 100644 --- a/backend-rust/src/graphql_api.rs +++ b/backend-rust/src/graphql_api.rs @@ -39,7 +39,7 @@ use futures::prelude::*; use prometheus_client::registry::Registry; use serde_json::to_string; use sqlx::{postgres::types::PgInterval, PgPool}; -use std::{error::Error, str::FromStr, sync::Arc}; +use std::{error::Error, mem, str::FromStr, sync::Arc}; use tokio::{net::TcpListener, sync::broadcast}; use tokio_util::sync::CancellationToken; use transaction_metrics::TransactionMetricsQuery; @@ -1457,18 +1457,19 @@ impl Contract { let event = events.swap_remove(row.trace_element_index as usize); - if let Event::ContractInterrupted(_) + if let Event::Transferred(_) + | Event::ContractInterrupted(_) | Event::ContractResumed(_) | Event::ContractUpgraded(_) | Event::ContractUpdated(_) = event { Ok(()) } else { - Err(ApiError::InternalError( - "Not ContractInterrupted, ContractResumed, ContractUpgraded, or \ - ContractUpdated event" - .to_string(), - )) + Err(ApiError::InternalError(format!( + "Not Transferred, ContractInterrupted, ContractResumed, ContractUpgraded, or \ + ContractUpdated event; Wrong event enum tag: {:?}", + mem::discriminant(&event) + ))) }?; let contract_event = ContractEvent { @@ -1520,8 +1521,6 @@ WHERE contracts.index=$1 AND contracts.sub_index=$2 ApiError::InternalError("Failed to deserialize events from database".to_string()) })?; - // Add check if not `initEvent` return error. - if events.len() != 1 { return Err(ApiError::InternalError( "Contract init transaction expects exactly one event".to_string(), @@ -1533,7 +1532,10 @@ WHERE contracts.index=$1 AND contracts.sub_index=$2 match event { Event::ContractInitialized(_) => Ok(()), - _ => Err(ApiError::InternalError("Not ContractInitialized event".to_string())), + _ => Err(ApiError::InternalError(format!( + "Not ContractInitialized event; Wrong event enum tag: {:?}", + mem::discriminant(&event) + ))), }?; let initial_event = ContractEvent { @@ -4718,7 +4720,7 @@ impl ContractInitialized { // async fn message_as_hex(&self) -> ApiResult { // Ok(hex::encode(self.input_parameter.clone())) } - async fn events_as_hex<'a>(&self) -> ApiResult> { + async fn events_as_hex(&self) -> ApiResult> { let mut connection = connection::Connection::new(true, true); self.contract_logs_raw.iter().enumerate().for_each(|(index, log)| { @@ -5038,17 +5040,85 @@ pub struct ContractUpdated { #[ComplexObject] impl ContractUpdated { - async fn message(&self) -> ApiResult { - // TODO: decode input-parameter/message with schema. - + async fn message_as_hex(&self) -> ApiResult { Ok(hex::encode(self.input_parameter.clone())) } - async fn message_as_hex(&self) -> ApiResult { - Ok(hex::encode(self.input_parameter.clone())) + async fn message<'a>(&self, ctx: &Context<'a>) -> ApiResult { + let pool = get_pool(ctx)?; + + let row = sqlx::query!( + r#" +SELECT + contracts.module_reference as module_reference, + name as contract_name, + schema as display_schema +FROM contracts +JOIN smart_contract_modules ON smart_contract_modules.module_reference=contracts.module_reference +WHERE index=$1 AND sub_index=$2 +"#, + self.contract_address.index.0 as i64, + self.contract_address.sub_index.0 as i64 + ) + .fetch_optional(pool) + .await? + .ok_or(ApiError::NotFound)?; + + let opt_init_schema = row + .display_schema + .as_ref() + .and_then(|schema| VersionedModuleSchema::new(schema, &None).ok()) + .and_then(|versioned_schema| { + versioned_schema.get_init_param_schema(&row.contract_name).ok() + }); + + let decoded_input_parameter = match opt_init_schema.as_ref() { + Some(init_schema) => { + let mut cursor = Cursor::new(&self.input_parameter); + match init_schema.to_json(&mut cursor) { + Ok(v) => { + to_string(&v).unwrap_or_else(|e| { + // We don't return an error here since the query is correctly formed and + // the CCDScan backend is working as + // expected. A wrong/missing schema is a + // mistake by the smart contract + // developer which in general cannot be fixed after the deployment of + // the contract. We display the error + // message (instead of the decoded value) in the block explorer + // to make it visible to the smart contract developer for debugging + // purposes here. + format!( + "Failed to deserialize input parameter with init schema into \ + string: {:?}", + e + ) + }) + } + Err(e) => { + // We don't return an error here since the query is correctly formed and the + // CCDScan backend is working as expected. + // A wrong/missing schema is a mistake by the smart contract + // developer which in general cannot be fixed after the deployment of the + // contract. We display the error message (instead + // of the decoded value) in the block explorer + // to make it visible to the smart contract developer for debugging purposes + // here. + format!( + "Failed to deserialize input parameter with init schema: {:?}", + e.display(true) + ) + } + } + } + // Note: Shall we use something better than empty string if no init schema is + // available for decoding. + None => "".to_string(), + }; + + Ok(decoded_input_parameter) } - async fn events_as_hex<'a>(&self) -> ApiResult> { + async fn events_as_hex(&self) -> ApiResult> { let mut connection = connection::Connection::new(true, true); self.contract_logs_raw.iter().enumerate().for_each(|(index, log)| { @@ -5103,14 +5173,15 @@ WHERE index=$1 AND sub_index=$2 .to_json(&mut cursor) .map_err(|e| { ApiError::SchemaError(format!( - "Failed to deserialize event schema: {:?}", + "Failed to deserialize contract log with event schema: {:?}", e.display(true) )) }) .and_then(|v| { to_string(&v).map_err(|e| { ApiError::SchemaError(format!( - "Failed to deserialize event schema into string: {:?}", + "Failed to deserialize contract log with event schema into \ + string: {:?}", e )) }) From f3274a3fed8600254032d6b9a6a090a8c7f4f1c3 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Wed, 30 Oct 2024 19:35:44 +0700 Subject: [PATCH 14/22] Reduce code duplication --- backend-rust/src/graphql_api.rs | 155 +++++++++++++------------------- 1 file changed, 63 insertions(+), 92 deletions(-) diff --git a/backend-rust/src/graphql_api.rs b/backend-rust/src/graphql_api.rs index c9a1c45c..3f41694d 100644 --- a/backend-rust/src/graphql_api.rs +++ b/backend-rust/src/graphql_api.rs @@ -29,7 +29,7 @@ use async_graphql_axum::GraphQLSubscription; use chrono::Duration; use concordium_rust_sdk::{ base::contracts_common::{ - schema::{VersionedModuleSchema, VersionedSchemaError}, + schema::{Type, VersionedModuleSchema, VersionedSchemaError}, Cursor, }, id::types as sdk_types, @@ -299,8 +299,6 @@ enum ApiError { InvalidContractVersion(#[from] InvalidContractVersionError), #[error("Schema in database should be a valid versioned module schema")] InvalidVersionedModuleSchema(#[from] VersionedSchemaError), - #[error("Schema error: {0}")] - SchemaError(String), } impl From for ApiError { @@ -3714,6 +3712,56 @@ impl SearchResult { } } +fn decode_value_with_schema( + opt_schema: Option, + schema_name: &str, + value: &Vec, + value_name: &str, +) -> String { + match opt_schema.as_ref() { + Some(schema) => { + let mut cursor = Cursor::new(&value); + match schema.to_json(&mut cursor) { + Ok(v) => { + to_string(&v).unwrap_or_else(|e| { + // We don't return an error here since the query is correctly formed and + // the CCDScan backend is working as expected. + // A wrong/missing schema is a mistake by the smart contract + // developer which in general cannot be fixed after the deployment of + // the contract. We display the error message (instead of the decoded + // value) in the block explorer to make the info visible to the smart + // contract developer for debugging purposes here. + format!( + "Failed to deserialize {} with {} schema into string: {:?}", + value_name, schema_name, e + ) + }) + } + Err(e) => { + // We don't return an error here since the query is correctly formed and + // the CCDScan backend is working as expected. + // A wrong/missing schema is a mistake by the smart contract + // developer which in general cannot be fixed after the deployment of + // the contract. We display the error message (instead of the decoded + // value) in the block explorer to make the info visible to the smart + // contract developer for debugging purposes here. + format!( + "Failed to deserialize {} with {} schema: {:?}", + value_name, + schema_name, + e.display(true) + ) + } + } + } + // Note: Shall we use something better than this string if no init schema is + // available for decoding. + None => { + format!("No embedded {} schema in smart contract available for decoding", schema_name,) + } + } +} + #[derive(SimpleObject, serde::Serialize, serde::Deserialize)] struct ContractAddress { index: ContractIndex, @@ -4766,30 +4814,10 @@ WHERE index=$1 AND sub_index=$2 let mut connection = connection::Connection::new(true, true); for (index, log) in self.contract_logs_raw.iter().enumerate() { - // Note: Shall we use something better than empty string if no event schema is - // available for decoding. - let decoded_logs = - opt_event_schema.as_ref().map_or(Ok("".to_string()), |event_schema| { - let mut cursor = Cursor::new(&log); - event_schema - .to_json(&mut cursor) - .map_err(|e| { - ApiError::SchemaError(format!( - "Failed to deserialize event schema: {:?}", - e.display(true) - )) - }) - .and_then(|v| { - to_string(&v).map_err(|e| { - ApiError::SchemaError(format!( - "Failed to deserialize event schema into string: {:?}", - e - )) - }) - }) - })?; + let decoded_log = + decode_value_with_schema(opt_event_schema.clone(), "event", log, "contract_log"); - connection.edges.push(connection::Edge::new(index.to_string(), decoded_logs)); + connection.edges.push(connection::Edge::new(index.to_string(), decoded_log)); } // Nice-to-have: pagination info but not used at front-end currently. @@ -5072,48 +5100,12 @@ WHERE index=$1 AND sub_index=$2 versioned_schema.get_init_param_schema(&row.contract_name).ok() }); - let decoded_input_parameter = match opt_init_schema.as_ref() { - Some(init_schema) => { - let mut cursor = Cursor::new(&self.input_parameter); - match init_schema.to_json(&mut cursor) { - Ok(v) => { - to_string(&v).unwrap_or_else(|e| { - // We don't return an error here since the query is correctly formed and - // the CCDScan backend is working as - // expected. A wrong/missing schema is a - // mistake by the smart contract - // developer which in general cannot be fixed after the deployment of - // the contract. We display the error - // message (instead of the decoded value) in the block explorer - // to make it visible to the smart contract developer for debugging - // purposes here. - format!( - "Failed to deserialize input parameter with init schema into \ - string: {:?}", - e - ) - }) - } - Err(e) => { - // We don't return an error here since the query is correctly formed and the - // CCDScan backend is working as expected. - // A wrong/missing schema is a mistake by the smart contract - // developer which in general cannot be fixed after the deployment of the - // contract. We display the error message (instead - // of the decoded value) in the block explorer - // to make it visible to the smart contract developer for debugging purposes - // here. - format!( - "Failed to deserialize input parameter with init schema: {:?}", - e.display(true) - ) - } - } - } - // Note: Shall we use something better than empty string if no init schema is - // available for decoding. - None => "".to_string(), - }; + let decoded_input_parameter = decode_value_with_schema( + opt_init_schema, + "init", + &self.input_parameter, + "init_parameter", + ); Ok(decoded_input_parameter) } @@ -5164,31 +5156,10 @@ WHERE index=$1 AND sub_index=$2 let mut connection = connection::Connection::new(true, true); for (index, log) in self.contract_logs_raw.iter().enumerate() { - // Note: Shall we use something better than empty string if no event schema is - // available for decoding. - let decoded_logs = - opt_event_schema.as_ref().map_or(Ok("".to_string()), |event_schema| { - let mut cursor = Cursor::new(&log); - event_schema - .to_json(&mut cursor) - .map_err(|e| { - ApiError::SchemaError(format!( - "Failed to deserialize contract log with event schema: {:?}", - e.display(true) - )) - }) - .and_then(|v| { - to_string(&v).map_err(|e| { - ApiError::SchemaError(format!( - "Failed to deserialize contract log with event schema into \ - string: {:?}", - e - )) - }) - }) - })?; + let decoded_log = + decode_value_with_schema(opt_event_schema.clone(), "event", log, "contract_log"); - connection.edges.push(connection::Edge::new(index.to_string(), decoded_logs)); + connection.edges.push(connection::Edge::new(index.to_string(), decoded_log)); } // Nice-to-have: pagination info but not used at front-end currently. From 6a52085837389bd07de55a7de012d1ff5e9920c2 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Wed, 30 Oct 2024 20:08:26 +0700 Subject: [PATCH 15/22] Update .sqlx --- ...c158eae1156386185ecd1cc218eb66c2ce9d4fe0f.json} | 5 +++-- ...52e19d545ee861d33c92496edba4008a2a70688d9.json} | 14 ++++++++++---- ...0a7c29cb898fc3a526fbdb26bc86fc312946e30b2.json} | 5 ++--- 3 files changed, 15 insertions(+), 9 deletions(-) rename backend-rust/.sqlx/{query-c6af2642c0b9f9a6ff49e03123f94381f8780ac10c2d6a898e3b47cc4b09f289.json => query-5100bc5f8caacc9d82428fec158eae1156386185ecd1cc218eb66c2ce9d4fe0f.json} (90%) rename backend-rust/.sqlx/{query-fc6b58746391e0be522913ceaa1cf61dbe52a9e65fa7bf3959d47c4d39fff8b3.json => query-cc1deb6662e0e4fd90a46cf52e19d545ee861d33c92496edba4008a2a70688d9.json} (58%) rename backend-rust/.sqlx/{query-6360a21563dcea7c919eb3c8e7fe193f269144b06676993e428ca7976b58fe11.json => query-fda54688c61b83b30236f240a7c29cb898fc3a526fbdb26bc86fc312946e30b2.json} (53%) diff --git a/backend-rust/.sqlx/query-c6af2642c0b9f9a6ff49e03123f94381f8780ac10c2d6a898e3b47cc4b09f289.json b/backend-rust/.sqlx/query-5100bc5f8caacc9d82428fec158eae1156386185ecd1cc218eb66c2ce9d4fe0f.json similarity index 90% rename from backend-rust/.sqlx/query-c6af2642c0b9f9a6ff49e03123f94381f8780ac10c2d6a898e3b47cc4b09f289.json rename to backend-rust/.sqlx/query-5100bc5f8caacc9d82428fec158eae1156386185ecd1cc218eb66c2ce9d4fe0f.json index e37d44e5..00adc85a 100644 --- a/backend-rust/.sqlx/query-c6af2642c0b9f9a6ff49e03123f94381f8780ac10c2d6a898e3b47cc4b09f289.json +++ b/backend-rust/.sqlx/query-5100bc5f8caacc9d82428fec158eae1156386185ecd1cc218eb66c2ce9d4fe0f.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT * FROM (\n SELECT\n contract_events.index,\n contract_events.transaction_index,\n trace_element_index,\n contract_events.block_height AS event_block_height,\n transactions.hash as transaction_hash,\n transactions.events,\n accounts.address as creator,\n blocks.slot_time as block_slot_time,\n blocks.height as block_height\n FROM contract_events\n JOIN transactions\n ON contract_events.block_height = transactions.block_height\n AND contract_events.transaction_index = transactions.index\n JOIN accounts\n ON transactions.sender = accounts.index\n JOIN blocks ON contract_events.block_height = blocks.height\n WHERE contract_events.contract_index = $1\n AND contract_events.contract_sub_index <= $2\n LIMIT $3\n ) AS contract_data\n ORDER BY contract_data.index DESC\n ", + "query": "\n SELECT * FROM (\n SELECT\n contract_events.index,\n contract_events.transaction_index,\n trace_element_index,\n contract_events.block_height AS event_block_height,\n transactions.hash as transaction_hash,\n transactions.events,\n accounts.address as creator,\n blocks.slot_time as block_slot_time,\n blocks.height as block_height\n FROM contract_events\n JOIN transactions\n ON contract_events.block_height = transactions.block_height\n AND contract_events.transaction_index = transactions.index\n JOIN accounts\n ON transactions.sender = accounts.index\n JOIN blocks ON contract_events.block_height = blocks.height\n WHERE contract_events.contract_index = $1\n AND contract_events.contract_sub_index <= $2\n LIMIT $3\n OFFSET $4\n ) AS contract_data\n ORDER BY contract_data.index DESC\n ", "describe": { "columns": [ { @@ -51,6 +51,7 @@ ], "parameters": { "Left": [ + "Int8", "Int8", "Int8", "Int8" @@ -68,5 +69,5 @@ false ] }, - "hash": "c6af2642c0b9f9a6ff49e03123f94381f8780ac10c2d6a898e3b47cc4b09f289" + "hash": "5100bc5f8caacc9d82428fec158eae1156386185ecd1cc218eb66c2ce9d4fe0f" } diff --git a/backend-rust/.sqlx/query-fc6b58746391e0be522913ceaa1cf61dbe52a9e65fa7bf3959d47c4d39fff8b3.json b/backend-rust/.sqlx/query-cc1deb6662e0e4fd90a46cf52e19d545ee861d33c92496edba4008a2a70688d9.json similarity index 58% rename from backend-rust/.sqlx/query-fc6b58746391e0be522913ceaa1cf61dbe52a9e65fa7bf3959d47c4d39fff8b3.json rename to backend-rust/.sqlx/query-cc1deb6662e0e4fd90a46cf52e19d545ee861d33c92496edba4008a2a70688d9.json index abc4fa29..31f9f700 100644 --- a/backend-rust/.sqlx/query-fc6b58746391e0be522913ceaa1cf61dbe52a9e65fa7bf3959d47c4d39fff8b3.json +++ b/backend-rust/.sqlx/query-cc1deb6662e0e4fd90a46cf52e19d545ee861d33c92496edba4008a2a70688d9.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\nSELECT\n module_reference,\n name as contract_name,\n contracts.amount as amount,\n blocks.slot_time as block_slot_time,\n init_block_height as block_height,\n transactions.hash as transaction_hash,\n accounts.address as creator,\n version\nFROM contracts\nJOIN blocks ON init_block_height=blocks.height\nJOIN transactions ON init_block_height=transactions.block_height AND init_transaction_index=transactions.index\nJOIN accounts ON transactions.sender=accounts.index\nWHERE contracts.index=$1 AND contracts.sub_index=$2\n", + "query": "\nSELECT\n module_reference,\n name as contract_name,\n contracts.amount as amount,\n blocks.slot_time as block_slot_time,\n init_block_height as block_height,\n transactions.events,\n transactions.hash as transaction_hash,\n accounts.address as creator,\n version\nFROM contracts\nJOIN blocks ON init_block_height=blocks.height\nJOIN transactions ON init_block_height=transactions.block_height AND init_transaction_index=transactions.index\nJOIN accounts ON transactions.sender=accounts.index\nWHERE contracts.index=$1 AND contracts.sub_index=$2\n", "describe": { "columns": [ { @@ -30,16 +30,21 @@ }, { "ordinal": 5, + "name": "events", + "type_info": "Jsonb" + }, + { + "ordinal": 6, "name": "transaction_hash", "type_info": "Bpchar" }, { - "ordinal": 6, + "ordinal": 7, "name": "creator", "type_info": "Bpchar" }, { - "ordinal": 7, + "ordinal": 8, "name": "version", "type_info": "Int4" } @@ -56,10 +61,11 @@ false, false, false, + true, false, false, false ] }, - "hash": "fc6b58746391e0be522913ceaa1cf61dbe52a9e65fa7bf3959d47c4d39fff8b3" + "hash": "cc1deb6662e0e4fd90a46cf52e19d545ee861d33c92496edba4008a2a70688d9" } diff --git a/backend-rust/.sqlx/query-6360a21563dcea7c919eb3c8e7fe193f269144b06676993e428ca7976b58fe11.json b/backend-rust/.sqlx/query-fda54688c61b83b30236f240a7c29cb898fc3a526fbdb26bc86fc312946e30b2.json similarity index 53% rename from backend-rust/.sqlx/query-6360a21563dcea7c919eb3c8e7fe193f269144b06676993e428ca7976b58fe11.json rename to backend-rust/.sqlx/query-fda54688c61b83b30236f240a7c29cb898fc3a526fbdb26bc86fc312946e30b2.json index 2fd0cb85..d4a4306b 100644 --- a/backend-rust/.sqlx/query-6360a21563dcea7c919eb3c8e7fe193f269144b06676993e428ca7976b58fe11.json +++ b/backend-rust/.sqlx/query-fda54688c61b83b30236f240a7c29cb898fc3a526fbdb26bc86fc312946e30b2.json @@ -1,13 +1,12 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO contract_events (\n transaction_index,\n trace_element_index,\n contract_logs,\n block_height,\n contract_index,\n contract_sub_index\n )\n VALUES (\n $1, $2, $3, $4, $5, $6\n )", + "query": "INSERT INTO contract_events (\n transaction_index,\n trace_element_index,\n block_height,\n contract_index,\n contract_sub_index\n )\n VALUES (\n $1, $2, $3, $4, $5\n )", "describe": { "columns": [], "parameters": { "Left": [ "Int8", "Int8", - "Jsonb", "Int8", "Int8", "Int8" @@ -15,5 +14,5 @@ }, "nullable": [] }, - "hash": "6360a21563dcea7c919eb3c8e7fe193f269144b06676993e428ca7976b58fe11" + "hash": "fda54688c61b83b30236f240a7c29cb898fc3a526fbdb26bc86fc312946e30b2" } From 06a8b198f5615f081a5cb8ae49916d384161bfff Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Thu, 31 Oct 2024 00:13:54 +0700 Subject: [PATCH 16/22] Clean up --- ...eae1156386185ecd1cc218eb66c2ce9d4fe0f.json | 2 +- ...9d545ee861d33c92496edba4008a2a70688d9.json | 2 +- .../migrations/0001_initialize.up.sql | 67 +------------------ backend-rust/src/graphql_api.rs | 24 +++---- 4 files changed, 15 insertions(+), 80 deletions(-) diff --git a/backend-rust/.sqlx/query-5100bc5f8caacc9d82428fec158eae1156386185ecd1cc218eb66c2ce9d4fe0f.json b/backend-rust/.sqlx/query-5100bc5f8caacc9d82428fec158eae1156386185ecd1cc218eb66c2ce9d4fe0f.json index 00adc85a..cbdf5442 100644 --- a/backend-rust/.sqlx/query-5100bc5f8caacc9d82428fec158eae1156386185ecd1cc218eb66c2ce9d4fe0f.json +++ b/backend-rust/.sqlx/query-5100bc5f8caacc9d82428fec158eae1156386185ecd1cc218eb66c2ce9d4fe0f.json @@ -41,7 +41,7 @@ { "ordinal": 7, "name": "block_slot_time", - "type_info": "Timestamp" + "type_info": "Timestamptz" }, { "ordinal": 8, diff --git a/backend-rust/.sqlx/query-cc1deb6662e0e4fd90a46cf52e19d545ee861d33c92496edba4008a2a70688d9.json b/backend-rust/.sqlx/query-cc1deb6662e0e4fd90a46cf52e19d545ee861d33c92496edba4008a2a70688d9.json index 31f9f700..02f778c8 100644 --- a/backend-rust/.sqlx/query-cc1deb6662e0e4fd90a46cf52e19d545ee861d33c92496edba4008a2a70688d9.json +++ b/backend-rust/.sqlx/query-cc1deb6662e0e4fd90a46cf52e19d545ee861d33c92496edba4008a2a70688d9.json @@ -21,7 +21,7 @@ { "ordinal": 3, "name": "block_slot_time", - "type_info": "Timestamp" + "type_info": "Timestamptz" }, { "ordinal": 4, diff --git a/backend-rust/migrations/0001_initialize.up.sql b/backend-rust/migrations/0001_initialize.up.sql index 6bbc09fa..c3d7e955 100644 --- a/backend-rust/migrations/0001_initialize.up.sql +++ b/backend-rust/migrations/0001_initialize.up.sql @@ -342,43 +342,7 @@ CREATE TABLE contracts( PRIMARY KEY (index, sub_index) ); --- Every event that links or unlinks a contract to a module. -CREATE TABLE module_contract_link_events( - -- An index/id for this event (row number). - index - BIGINT GENERATED ALWAYS AS IDENTITY - PRIMARY KEY - NOT NULL, - -- Event index of the event. - event_index - BIGINT - NOT NULL, - -- Transaction index including the event. - transaction_index - BIGINT - NOT NULL, - -- Module index that gets linked/unlinked. - module_index - BIGINT - NOT NULL, - -- Contract index that gets linked/unlinked. - contract_index - BIGINT - NOT NULL, - -- Contract subindex that gets linked/unlinked. - contract_sub_index - BIGINT - NOT NULL, - -- True if contract gets linked to the given module, false if contract gets unlinked to the given module. - is_linked - BOOLEAN - NOT NULL - - -- TODO: link_action = int? source = int? -); - -- Every successful event associated to a contract. --- TODO: add index over the contract (index,subindex) CREATE TABLE contract_events ( -- An index/id for this event (row number). index @@ -405,37 +369,10 @@ CREATE TABLE contract_events ( contract_sub_index BIGINT NOT NULL - - -- TODO: source = int? ); --- Every rejected event associated to a contract. --- TODO: add index over the contract (index,subindex) -CREATE TABLE contract_reject_events( - -- An index/id for this event (row number). - index - BIGINT GENERATED ALWAYS AS IDENTITY - PRIMARY KEY - NOT NULL, - -- Transaction index including the event. - transaction_index - BIGINT - NOT NULL, - -- Event index of the event. - event_index - BIGINT - NOT NULL, - -- Contract index that event is associated with. - contract_index - BIGINT - NOT NULL, - -- Contract subindex that event is associated with. - contract_sub_index - BIGINT - NOT NULL - - -- TODO: source = int? -); +-- Important for quickly filtering contract events by a specific contract. +CREATE INDEX contract_events_idx ON contract_events (contract_index, contract_sub_index); CREATE OR REPLACE FUNCTION block_added_notify_trigger_function() RETURNS trigger AS $trigger$ DECLARE diff --git a/backend-rust/src/graphql_api.rs b/backend-rust/src/graphql_api.rs index 3f41694d..7ca3a074 100644 --- a/backend-rust/src/graphql_api.rs +++ b/backend-rust/src/graphql_api.rs @@ -1355,7 +1355,6 @@ struct Contract { snapshot: ContractSnapshot, } -// TODOTODO #[ComplexObject] impl Contract { // This function returns events from the `contract_events` table as well as @@ -1453,6 +1452,7 @@ impl Contract { )); } + // Get the associated contract event from the `events` vector. let event = events.swap_remove(row.trace_element_index as usize); if let Event::Transferred(_) @@ -3713,12 +3713,12 @@ impl SearchResult { } fn decode_value_with_schema( - opt_schema: Option, + opt_schema: &Option, schema_name: &str, value: &Vec, value_name: &str, ) -> String { - match opt_schema.as_ref() { + match opt_schema { Some(schema) => { let mut cursor = Cursor::new(&value); match schema.to_json(&mut cursor) { @@ -3754,10 +3754,10 @@ fn decode_value_with_schema( } } } - // Note: Shall we use something better than this string if no init schema is + // Note: Shall we use something better than this string if no schema is // available for decoding. None => { - format!("No embedded {} schema in smart contract available for decoding", schema_name,) + format!("No embedded {} schema in smart contract available for decoding", schema_name) } } } @@ -4759,14 +4759,14 @@ impl ContractInitialized { // async fn message(&self) -> ApiResult { // // TODO: decode input-parameter/message with schema. - // Ok(hex::encode(self.input_parameter.clone())) + // Ok(self.input_parameter) // } // TODO: the send message/input parameter is missing and not exposed by the node // and rust SDK currently, it should be added when available. // // async fn message_as_hex(&self) -> ApiResult { - // Ok(hex::encode(self.input_parameter.clone())) } + // Ok(hex::encode(self.input_parameter)) } async fn events_as_hex(&self) -> ApiResult> { let mut connection = connection::Connection::new(true, true); @@ -4815,7 +4815,7 @@ WHERE index=$1 AND sub_index=$2 for (index, log) in self.contract_logs_raw.iter().enumerate() { let decoded_log = - decode_value_with_schema(opt_event_schema.clone(), "event", log, "contract_log"); + decode_value_with_schema(&opt_event_schema, "event", log, "contract_log"); connection.edges.push(connection::Edge::new(index.to_string(), decoded_log)); } @@ -5068,9 +5068,7 @@ pub struct ContractUpdated { #[ComplexObject] impl ContractUpdated { - async fn message_as_hex(&self) -> ApiResult { - Ok(hex::encode(self.input_parameter.clone())) - } + async fn message_as_hex(&self) -> ApiResult { Ok(hex::encode(&self.input_parameter)) } async fn message<'a>(&self, ctx: &Context<'a>) -> ApiResult { let pool = get_pool(ctx)?; @@ -5101,7 +5099,7 @@ WHERE index=$1 AND sub_index=$2 }); let decoded_input_parameter = decode_value_with_schema( - opt_init_schema, + &opt_init_schema, "init", &self.input_parameter, "init_parameter", @@ -5157,7 +5155,7 @@ WHERE index=$1 AND sub_index=$2 for (index, log) in self.contract_logs_raw.iter().enumerate() { let decoded_log = - decode_value_with_schema(opt_event_schema.clone(), "event", log, "contract_log"); + decode_value_with_schema(&opt_event_schema, "event", log, "contract_log"); connection.edges.push(connection::Edge::new(index.to_string(), decoded_log)); } From 0fd94956b45dc8d39f949583d420bc0977601de4 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Wed, 20 Nov 2024 19:10:24 +0700 Subject: [PATCH 17/22] Fix merge conflicts --- ...22f492ad5f4a1df57567c434feb683cb552a.json} | 30 ++++---- ...ad689b899c452b483e013fa5c0fd98a1ea297.json | 33 --------- ...b8e579f0869c6e393cda16bc8848ec5258c07.json | 33 --------- ...48b5144450b0b1f29315a4fdf9503f81d9eab.json | 20 ++++++ backend-rust/src/graphql_api.rs | 11 +-- backend-rust/src/indexer.rs | 70 +++++-------------- 6 files changed, 61 insertions(+), 136 deletions(-) rename backend-rust/.sqlx/{query-cc1deb6662e0e4fd90a46cf52e19d545ee861d33c92496edba4008a2a70688d9.json => query-167be8d2353e36e2ac761b53499c22f492ad5f4a1df57567c434feb683cb552a.json} (63%) delete mode 100644 backend-rust/.sqlx/query-4b16a5f1e8c54901560903d367ead689b899c452b483e013fa5c0fd98a1ea297.json delete mode 100644 backend-rust/.sqlx/query-c2769b7e5c028ba895fefc53744b8e579f0869c6e393cda16bc8848ec5258c07.json create mode 100644 backend-rust/.sqlx/query-f224484c5e510741504c91c5ecc48b5144450b0b1f29315a4fdf9503f81d9eab.json diff --git a/backend-rust/.sqlx/query-cc1deb6662e0e4fd90a46cf52e19d545ee861d33c92496edba4008a2a70688d9.json b/backend-rust/.sqlx/query-167be8d2353e36e2ac761b53499c22f492ad5f4a1df57567c434feb683cb552a.json similarity index 63% rename from backend-rust/.sqlx/query-cc1deb6662e0e4fd90a46cf52e19d545ee861d33c92496edba4008a2a70688d9.json rename to backend-rust/.sqlx/query-167be8d2353e36e2ac761b53499c22f492ad5f4a1df57567c434feb683cb552a.json index 02f778c8..56e18bb4 100644 --- a/backend-rust/.sqlx/query-cc1deb6662e0e4fd90a46cf52e19d545ee861d33c92496edba4008a2a70688d9.json +++ b/backend-rust/.sqlx/query-167be8d2353e36e2ac761b53499c22f492ad5f4a1df57567c434feb683cb552a.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\nSELECT\n module_reference,\n name as contract_name,\n contracts.amount as amount,\n blocks.slot_time as block_slot_time,\n init_block_height as block_height,\n transactions.events,\n transactions.hash as transaction_hash,\n accounts.address as creator,\n version\nFROM contracts\nJOIN blocks ON init_block_height=blocks.height\nJOIN transactions ON init_block_height=transactions.block_height AND init_transaction_index=transactions.index\nJOIN accounts ON transactions.sender=accounts.index\nWHERE contracts.index=$1 AND contracts.sub_index=$2\n", + "query": "\nSELECT\n module_reference,\n name as contract_name,\n contracts.amount as amount,\n contracts.transaction_index as transaction_index,\n transactions.events,\n transactions.hash as transaction_hash,\n transactions.block_height as block_height,\n blocks.slot_time as block_slot_time,\n accounts.address as creator,\n version\nFROM contracts\nJOIN transactions ON transaction_index=transactions.index\nJOIN blocks ON block_height=blocks.height\nJOIN accounts ON transactions.sender=accounts.index\nWHERE contracts.index=$1 AND contracts.sub_index=$2\n", "describe": { "columns": [ { @@ -20,31 +20,36 @@ }, { "ordinal": 3, - "name": "block_slot_time", - "type_info": "Timestamptz" - }, - { - "ordinal": 4, - "name": "block_height", + "name": "transaction_index", "type_info": "Int8" }, { - "ordinal": 5, + "ordinal": 4, "name": "events", "type_info": "Jsonb" }, { - "ordinal": 6, + "ordinal": 5, "name": "transaction_hash", "type_info": "Bpchar" }, + { + "ordinal": 6, + "name": "block_height", + "type_info": "Int8" + }, { "ordinal": 7, + "name": "block_slot_time", + "type_info": "Timestamptz" + }, + { + "ordinal": 8, "name": "creator", "type_info": "Bpchar" }, { - "ordinal": 8, + "ordinal": 9, "name": "version", "type_info": "Int4" } @@ -60,12 +65,13 @@ false, false, false, - false, true, false, false, + false, + false, false ] }, - "hash": "cc1deb6662e0e4fd90a46cf52e19d545ee861d33c92496edba4008a2a70688d9" + "hash": "167be8d2353e36e2ac761b53499c22f492ad5f4a1df57567c434feb683cb552a" } diff --git a/backend-rust/.sqlx/query-4b16a5f1e8c54901560903d367ead689b899c452b483e013fa5c0fd98a1ea297.json b/backend-rust/.sqlx/query-4b16a5f1e8c54901560903d367ead689b899c452b483e013fa5c0fd98a1ea297.json deleted file mode 100644 index 10e2eb64..00000000 --- a/backend-rust/.sqlx/query-4b16a5f1e8c54901560903d367ead689b899c452b483e013fa5c0fd98a1ea297.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "db_name": "PostgreSQL", -<<<<<<<< HEAD:backend-rust/.sqlx/query-c2769b7e5c028ba895fefc53744b8e579f0869c6e393cda16bc8848ec5258c07.json - "query": "INSERT INTO contracts (\n index,\n sub_index,\n module_reference,\n name,\n amount,\n transaction_index\n ) VALUES ($1, $2, $3, $4, $5, $6)", -======== - "query": "INSERT INTO contracts (\n index,\n sub_index,\n module_reference,\n name,\n amount,\n init_block_height,\n init_transaction_index,\n version\n )\n VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8\n )", ->>>>>>>> 4e5d9a0b (Add contract event query):backend-rust/.sqlx/query-4b16a5f1e8c54901560903d367ead689b899c452b483e013fa5c0fd98a1ea297.json - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8", - "Int8", - "Bpchar", - "Varchar", - "Int8", -<<<<<<<< HEAD:backend-rust/.sqlx/query-c2769b7e5c028ba895fefc53744b8e579f0869c6e393cda16bc8848ec5258c07.json - "Int8" -======== - "Int8", - "Int8", - "Int4" ->>>>>>>> 4e5d9a0b (Add contract event query):backend-rust/.sqlx/query-4b16a5f1e8c54901560903d367ead689b899c452b483e013fa5c0fd98a1ea297.json - ] - }, - "nullable": [] - }, -<<<<<<<< HEAD:backend-rust/.sqlx/query-c2769b7e5c028ba895fefc53744b8e579f0869c6e393cda16bc8848ec5258c07.json - "hash": "c2769b7e5c028ba895fefc53744b8e579f0869c6e393cda16bc8848ec5258c07" -======== - "hash": "4b16a5f1e8c54901560903d367ead689b899c452b483e013fa5c0fd98a1ea297" ->>>>>>>> 4e5d9a0b (Add contract event query):backend-rust/.sqlx/query-4b16a5f1e8c54901560903d367ead689b899c452b483e013fa5c0fd98a1ea297.json -} diff --git a/backend-rust/.sqlx/query-c2769b7e5c028ba895fefc53744b8e579f0869c6e393cda16bc8848ec5258c07.json b/backend-rust/.sqlx/query-c2769b7e5c028ba895fefc53744b8e579f0869c6e393cda16bc8848ec5258c07.json deleted file mode 100644 index 10e2eb64..00000000 --- a/backend-rust/.sqlx/query-c2769b7e5c028ba895fefc53744b8e579f0869c6e393cda16bc8848ec5258c07.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "db_name": "PostgreSQL", -<<<<<<<< HEAD:backend-rust/.sqlx/query-c2769b7e5c028ba895fefc53744b8e579f0869c6e393cda16bc8848ec5258c07.json - "query": "INSERT INTO contracts (\n index,\n sub_index,\n module_reference,\n name,\n amount,\n transaction_index\n ) VALUES ($1, $2, $3, $4, $5, $6)", -======== - "query": "INSERT INTO contracts (\n index,\n sub_index,\n module_reference,\n name,\n amount,\n init_block_height,\n init_transaction_index,\n version\n )\n VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8\n )", ->>>>>>>> 4e5d9a0b (Add contract event query):backend-rust/.sqlx/query-4b16a5f1e8c54901560903d367ead689b899c452b483e013fa5c0fd98a1ea297.json - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8", - "Int8", - "Bpchar", - "Varchar", - "Int8", -<<<<<<<< HEAD:backend-rust/.sqlx/query-c2769b7e5c028ba895fefc53744b8e579f0869c6e393cda16bc8848ec5258c07.json - "Int8" -======== - "Int8", - "Int8", - "Int4" ->>>>>>>> 4e5d9a0b (Add contract event query):backend-rust/.sqlx/query-4b16a5f1e8c54901560903d367ead689b899c452b483e013fa5c0fd98a1ea297.json - ] - }, - "nullable": [] - }, -<<<<<<<< HEAD:backend-rust/.sqlx/query-c2769b7e5c028ba895fefc53744b8e579f0869c6e393cda16bc8848ec5258c07.json - "hash": "c2769b7e5c028ba895fefc53744b8e579f0869c6e393cda16bc8848ec5258c07" -======== - "hash": "4b16a5f1e8c54901560903d367ead689b899c452b483e013fa5c0fd98a1ea297" ->>>>>>>> 4e5d9a0b (Add contract event query):backend-rust/.sqlx/query-4b16a5f1e8c54901560903d367ead689b899c452b483e013fa5c0fd98a1ea297.json -} diff --git a/backend-rust/.sqlx/query-f224484c5e510741504c91c5ecc48b5144450b0b1f29315a4fdf9503f81d9eab.json b/backend-rust/.sqlx/query-f224484c5e510741504c91c5ecc48b5144450b0b1f29315a4fdf9503f81d9eab.json new file mode 100644 index 00000000..fd518678 --- /dev/null +++ b/backend-rust/.sqlx/query-f224484c5e510741504c91c5ecc48b5144450b0b1f29315a4fdf9503f81d9eab.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO contracts (\n index,\n sub_index,\n module_reference,\n name,\n amount,\n transaction_index,\n version\n ) VALUES ($1, $2, $3, $4, $5, $6, $7)", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8", + "Int8", + "Bpchar", + "Varchar", + "Int8", + "Int8", + "Int4" + ] + }, + "nullable": [] + }, + "hash": "f224484c5e510741504c91c5ecc48b5144450b0b1f29315a4fdf9503f81d9eab" +} diff --git a/backend-rust/src/graphql_api.rs b/backend-rust/src/graphql_api.rs index 7ca3a074..4ee2e29a 100644 --- a/backend-rust/src/graphql_api.rs +++ b/backend-rust/src/graphql_api.rs @@ -1487,20 +1487,21 @@ impl Contract { // Get the `init_transaction_event`. if include_initial_event { let row = sqlx::query!( - r#" + r#" SELECT module_reference, name as contract_name, contracts.amount as amount, - blocks.slot_time as block_slot_time, - init_block_height as block_height, + contracts.transaction_index as transaction_index, transactions.events, transactions.hash as transaction_hash, + transactions.block_height as block_height, + blocks.slot_time as block_slot_time, accounts.address as creator, version FROM contracts -JOIN blocks ON init_block_height=blocks.height -JOIN transactions ON init_block_height=transactions.block_height AND init_transaction_index=transactions.index +JOIN transactions ON transaction_index=transactions.index +JOIN blocks ON block_height=blocks.height JOIN accounts ON transactions.sender=accounts.index WHERE contracts.index=$1 AND contracts.sub_index=$2 "#, diff --git a/backend-rust/src/indexer.rs b/backend-rust/src/indexer.rs index 573030b3..ebe75c09 100644 --- a/backend-rust/src/indexer.rs +++ b/backend-rust/src/indexer.rs @@ -870,12 +870,13 @@ struct PreparedBlockItem { /// Events of the block item. Is none for rejected block items. events: Option, /// Reject reason the block item. Is none for successful block items. - reject: Option, + reject: Option, /// All affected accounts for this transaction. Each entry is the `String` /// representation of an account address. affected_accounts: Vec, + // This is an option temporarily, until we are able to handle every type of event. /// Block item events prepared for inserting into the database. - prepared_event: PreparedEventData, + prepared_event: Option, } impl PreparedBlockItem { @@ -934,7 +935,7 @@ impl PreparedBlockItem { let affected_accounts = block_item.affected_addresses().into_iter().map(|a| a.to_string()).collect(); - let prepared_event = PreparedEventData::prepare(node_client, data, block_item).await?; + let prepared_event = PreparedEvent::prepare(node_client, data, block_item).await?; Ok(Self { block_item_index, @@ -1027,24 +1028,14 @@ impl PreparedBlockItem { .execute(tx.as_mut()) .await?; - self.prepared_event.save(tx).await?; + if let Some(prepared_event) = &self.prepared_event { + prepared_event.save(tx, tx_idx).await?; + } Ok(()) } } -/// Different types of block item events that can be prepared and the -/// status of the event (if the event was in a successful or rejected -/// transaction). -struct PreparedEventData { - /// The prepared event. Note: this is optional temporarily until we can - /// handle all events. - event: Option, - /// The status of the event (if the event belongs to a successful or - /// rejected transaction). - success: bool, -} - /// Different types of block item events that can be prepared. enum PreparedEvent { /// A new account got created. @@ -1060,12 +1051,12 @@ enum PreparedEvent { /// No changes in the database was caused by this event. NoOperation, } -impl PreparedEventData { +impl PreparedEvent { async fn prepare( node_client: &mut v2::Client, data: &BlockData, block_item: &BlockItemSummary, - ) -> anyhow::Result { + ) -> anyhow::Result> { let prepared_event = match &block_item.details { BlockItemSummaryDetails::AccountCreation(details) => { Some(PreparedEvent::AccountCreation(PreparedAccountCreation::prepare( @@ -1222,10 +1213,7 @@ impl PreparedEventData { None } }; - Ok(PreparedEventData { - event: prepared_event, - success: block_item.is_success(), - }) + Ok(prepared_event) } async fn save( @@ -1233,44 +1221,19 @@ impl PreparedEventData { tx: &mut sqlx::Transaction<'static, sqlx::Postgres>, tx_idx: i64, ) -> anyhow::Result<()> { - let Some(event) = &self.event else { - return Ok(()); - }; - - match event { - PreparedEvent::AccountCreation(event) => event.save(tx).await, - // TODO: need to handle `rejected` baker events properly. + match self { + PreparedEvent::AccountCreation(event) => event.save(tx, tx_idx).await, PreparedEvent::BakerEvents(events) => { for event in events { event.save(tx).await?; } Ok(()) } - PreparedEvent::ModuleDeployed(event) => - // Only save the module into the `modules` table if the transaction was - // successful. - { - if self.success { - event.save(tx).await - } else { - Ok(()) - } - } - PreparedEvent::ContractInitialized(event) => - // Only save the contract into the `contracts` table if the transaction was - // successful. - { - if self.success { - event.save(tx).await - } else { - Ok(()) - } - } + PreparedEvent::ModuleDeployed(event) => event.save(tx, tx_idx).await, + PreparedEvent::ContractInitialized(event) => event.save(tx, tx_idx).await, PreparedEvent::ContractUpdate(events) => { - if self.success { - for event in events { - event.save(tx).await?; - } + for event in events { + event.save(tx).await?; } Ok(()) } @@ -1706,6 +1669,7 @@ impl PreparedContractInitialized { self.module_reference, self.name, self.amount, + transaction_index, self.version ) .execute(tx.as_mut()) From eb635593651855dad6aec3d0ede5e0ae64592270 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Thu, 21 Nov 2024 19:19:14 +0700 Subject: [PATCH 18/22] Address comments --- backend-rust/src/graphql_api.rs | 206 ++++++++++++++++---------------- 1 file changed, 102 insertions(+), 104 deletions(-) diff --git a/backend-rust/src/graphql_api.rs b/backend-rust/src/graphql_api.rs index 4ee2e29a..02e1892d 100644 --- a/backend-rust/src/graphql_api.rs +++ b/backend-rust/src/graphql_api.rs @@ -65,7 +65,7 @@ pub struct ApiServiceConfig { env = "CCDSCAN_API_CONFIG_CONTRACT_EVENTS_COLLECTION_LIMIT", default_value = "100" )] - contract_events_collection_limit: i64, + contract_events_collection_limit: u64, #[arg( long, env = "CCDSCAN_API_CONFIG_TRANSACTION_EVENT_CONNECTION_LIMIT", @@ -1296,7 +1296,7 @@ impl Block { /// Number of transactions included in this block. async fn transaction_count<'a>(&self, ctx: &Context<'a>) -> ApiResult { let result = - sqlx::query!("SELECT COUNT(*) FROM transactions WHERE block_height=$1", self.height) + sqlx::query!("SELECT COUNT(*) FROM transactions WHERE block_height = $1", self.height) .fetch_one(get_pool(ctx)?) .await?; Ok(result.count.unwrap_or(0)) @@ -1363,8 +1363,8 @@ impl Contract { async fn contract_events( &self, ctx: &Context<'_>, - skip: i32, - take: i32, + skip: u32, + take: u32, ) -> ApiResult { let config = get_config(ctx)?; let pool = get_pool(ctx)?; @@ -1374,59 +1374,53 @@ impl Contract { let include_initial_event = skip == 0 && take > 0; // Adjust the `take` and `skip` values considering if the // `init_transaction_event` is requested to be included or not. - let take_without_initial_event = - std::cmp::max(0, take as i64 - include_initial_event as i64); - let skip_without_initial_event = std::cmp::max(0, skip as i64 - 1i64); + let take_without_initial_event = take.saturating_sub(include_initial_event as u32); + let skip_without_initial_event = skip.saturating_sub(1); // Limit the number of events to be fetched from the `contract_events` table. let limit = std::cmp::min( - take_without_initial_event, - std::cmp::max( - 0, - config.contract_events_collection_limit - include_initial_event as i64, - ), + take_without_initial_event as u64, + config.contract_events_collection_limit.saturating_sub(include_initial_event as u64), ); let mut contract_events = vec![]; let mut total_events_count = 0; // Get the events from the `contract_events` table. - let row_stream = sqlx::query!( - r#" - SELECT * FROM ( - SELECT - contract_events.index, - contract_events.transaction_index, - trace_element_index, - contract_events.block_height AS event_block_height, - transactions.hash as transaction_hash, - transactions.events, - accounts.address as creator, - blocks.slot_time as block_slot_time, - blocks.height as block_height - FROM contract_events - JOIN transactions - ON contract_events.block_height = transactions.block_height - AND contract_events.transaction_index = transactions.index - JOIN accounts - ON transactions.sender = accounts.index - JOIN blocks ON contract_events.block_height = blocks.height - WHERE contract_events.contract_index = $1 - AND contract_events.contract_sub_index <= $2 - LIMIT $3 - OFFSET $4 - ) AS contract_data - ORDER BY contract_data.index DESC - "#, + let mut rows = sqlx::query!( + "SELECT * FROM ( + SELECT + contract_events.index, + contract_events.transaction_index, + trace_element_index, + contract_events.block_height AS event_block_height, + transactions.hash as transaction_hash, + transactions.events, + accounts.address as creator, + blocks.slot_time as block_slot_time, + blocks.height as block_height + FROM contract_events + JOIN transactions + ON contract_events.block_height = transactions.block_height + AND contract_events.transaction_index = transactions.index + JOIN accounts + ON transactions.sender = accounts.index + JOIN blocks + ON contract_events.block_height = blocks.height + WHERE contract_events.contract_index = $1 AND contract_events.contract_sub_index \ + <= $2 + LIMIT $3 + OFFSET $4 + ) AS contract_data + ORDER BY contract_data.index DESC + ", self.contract_address_index.0 as i64, self.contract_address_sub_index.0 as i64, - limit + 1, - skip_without_initial_event + limit as i64 + 1, + skip_without_initial_event as i64 ) - .fetch(pool); - - // Collect the stream into a vector to inspect the number of rows. - let mut rows: Vec<_> = row_stream.take(limit as usize + 1).try_collect().await?; + .fetch_all(pool) + .await?; // Determine if there is a next page by checking if we got more than `limit` // rows. @@ -1455,19 +1449,17 @@ impl Contract { // Get the associated contract event from the `events` vector. let event = events.swap_remove(row.trace_element_index as usize); - if let Event::Transferred(_) - | Event::ContractInterrupted(_) - | Event::ContractResumed(_) - | Event::ContractUpgraded(_) - | Event::ContractUpdated(_) = event - { - Ok(()) - } else { - Err(ApiError::InternalError(format!( + match event { + Event::Transferred(_) + | Event::ContractInterrupted(_) + | Event::ContractResumed(_) + | Event::ContractUpgraded(_) + | Event::ContractUpdated(_) => Ok(()), + _ => Err(ApiError::InternalError(format!( "Not Transferred, ContractInterrupted, ContractResumed, ContractUpgraded, or \ ContractUpdated event; Wrong event enum tag: {:?}", mem::discriminant(&event) - ))) + ))), }?; let contract_event = ContractEvent { @@ -1487,24 +1479,24 @@ impl Contract { // Get the `init_transaction_event`. if include_initial_event { let row = sqlx::query!( - r#" -SELECT - module_reference, - name as contract_name, - contracts.amount as amount, - contracts.transaction_index as transaction_index, - transactions.events, - transactions.hash as transaction_hash, - transactions.block_height as block_height, - blocks.slot_time as block_slot_time, - accounts.address as creator, - version -FROM contracts -JOIN transactions ON transaction_index=transactions.index -JOIN blocks ON block_height=blocks.height -JOIN accounts ON transactions.sender=accounts.index -WHERE contracts.index=$1 AND contracts.sub_index=$2 -"#, + " + SELECT + module_reference, + name as contract_name, + contracts.amount as amount, + contracts.transaction_index as transaction_index, + transactions.events, + transactions.hash as transaction_hash, + transactions.block_height as block_height, + blocks.slot_time as block_slot_time, + accounts.address as creator, + version + FROM contracts + JOIN transactions ON transaction_index=transactions.index + JOIN blocks ON block_height = blocks.height + JOIN accounts ON transactions.sender = accounts.index + WHERE contracts.index = $1 AND contracts.sub_index = $2 + ", self.contract_address_index.0 as i64, self.contract_address_sub_index.0 as i64 ) @@ -1527,7 +1519,10 @@ WHERE contracts.index=$1 AND contracts.sub_index=$2 } // Contract init transactions have exactly one top-level event. - let event = events.swap_remove(0_usize); + let event = events.pop().ok_or(ApiError::InternalError( + "Contract init transactions are suppose to have exactly one top-level event" + .to_string(), + ))?; match event { Event::ContractInitialized(_) => Ok(()), @@ -1562,13 +1557,13 @@ WHERE contracts.index=$1 AND contracts.sub_index=$2 async fn contract_reject_events( &self, - _skip: i32, - _take: i32, + _skip: u32, + _take: u32, ) -> ApiResult { todo_api!() } - async fn tokens(&self, skip: i32, take: i32) -> ApiResult { + async fn tokens(&self, skip: u32, take: u32) -> ApiResult { todo_api!() } } @@ -4788,15 +4783,16 @@ impl ContractInitialized { let pool = get_pool(ctx)?; let row = sqlx::query!( - r#" -SELECT - contracts.module_reference as module_reference, - name as contract_name, - schema as display_schema -FROM contracts -JOIN smart_contract_modules ON smart_contract_modules.module_reference=contracts.module_reference -WHERE index=$1 AND sub_index=$2 -"#, + " + SELECT + contracts.module_reference as module_reference, + name as contract_name, + schema as display_schema + FROM contracts + JOIN smart_contract_modules ON smart_contract_modules.module_reference = \ + contracts.module_reference + WHERE index = $1 AND sub_index = $2 + ", self.contract_address.index.0 as i64, self.contract_address.sub_index.0 as i64 ) @@ -5075,15 +5071,16 @@ impl ContractUpdated { let pool = get_pool(ctx)?; let row = sqlx::query!( - r#" -SELECT - contracts.module_reference as module_reference, - name as contract_name, - schema as display_schema -FROM contracts -JOIN smart_contract_modules ON smart_contract_modules.module_reference=contracts.module_reference -WHERE index=$1 AND sub_index=$2 -"#, + " + SELECT + contracts.module_reference as module_reference, + name as contract_name, + schema as display_schema + FROM contracts + JOIN smart_contract_modules ON smart_contract_modules.module_reference = \ + contracts.module_reference + WHERE index = $1 AND sub_index = $2 + ", self.contract_address.index.0 as i64, self.contract_address.sub_index.0 as i64 ) @@ -5128,15 +5125,16 @@ WHERE index=$1 AND sub_index=$2 let pool = get_pool(ctx)?; let row = sqlx::query!( - r#" -SELECT - contracts.module_reference as module_reference, - name as contract_name, - schema as display_schema -FROM contracts -JOIN smart_contract_modules ON smart_contract_modules.module_reference=contracts.module_reference -WHERE index=$1 AND sub_index=$2 -"#, + " + SELECT + contracts.module_reference as module_reference, + name as contract_name, + schema as display_schema + FROM contracts + JOIN smart_contract_modules ON smart_contract_modules.module_reference = \ + contracts.module_reference + WHERE index = $1 AND sub_index = $2 + ", self.contract_address.index.0 as i64, self.contract_address.sub_index.0 as i64 ) From 08d548e97cdd487674a932694e71a1a4c5752f3a Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Thu, 21 Nov 2024 23:35:14 +0700 Subject: [PATCH 19/22] Address comments --- ...2890c824a1b587e6651b799275e1e5f85f14.json} | 4 +- ...eae1156386185ecd1cc218eb66c2ce9d4fe0f.json | 73 ----------- ...2a7ab3f1b5c44d30e6bee7f8a64b7128d853.json} | 4 +- ...f8d84b5f28a508ea56805afdb75782fd737d9.json | 73 +++++++++++ ...c8a3d57a48780dd97dedaed1e62669ac574c.json} | 4 +- .../migrations/0001_initialize.up.sql | 11 +- backend-rust/src/graphql_api.rs | 123 ++++++++---------- backend-rust/src/indexer.rs | 24 +--- 8 files changed, 138 insertions(+), 178 deletions(-) rename backend-rust/.sqlx/{query-167be8d2353e36e2ac761b53499c22f492ad5f4a1df57567c434feb683cb552a.json => query-4fb58ee027c14ec19fe4d3213a552890c824a1b587e6651b799275e1e5f85f14.json} (57%) delete mode 100644 backend-rust/.sqlx/query-5100bc5f8caacc9d82428fec158eae1156386185ecd1cc218eb66c2ce9d4fe0f.json rename backend-rust/.sqlx/{query-1ebcb64ad0ff478f246a8f46e46f811ab51ea8fba0888851c94ec377f68976ba.json => query-5aecbe0112190ae34b326f381f8a2a7ab3f1b5c44d30e6bee7f8a64b7128d853.json} (79%) create mode 100644 backend-rust/.sqlx/query-85419df3c3bfb7aa85e5878183bf8d84b5f28a508ea56805afdb75782fd737d9.json rename backend-rust/.sqlx/{query-e3ec36d7b5af0917cd7a68ce411a9516e7ce66b7c7b66172f12a22a7a624c96f.json => query-dabde8e21b68d1c2d0430bb63399c8a3d57a48780dd97dedaed1e62669ac574c.json} (53%) diff --git a/backend-rust/.sqlx/query-167be8d2353e36e2ac761b53499c22f492ad5f4a1df57567c434feb683cb552a.json b/backend-rust/.sqlx/query-4fb58ee027c14ec19fe4d3213a552890c824a1b587e6651b799275e1e5f85f14.json similarity index 57% rename from backend-rust/.sqlx/query-167be8d2353e36e2ac761b53499c22f492ad5f4a1df57567c434feb683cb552a.json rename to backend-rust/.sqlx/query-4fb58ee027c14ec19fe4d3213a552890c824a1b587e6651b799275e1e5f85f14.json index 56e18bb4..bd3b23aa 100644 --- a/backend-rust/.sqlx/query-167be8d2353e36e2ac761b53499c22f492ad5f4a1df57567c434feb683cb552a.json +++ b/backend-rust/.sqlx/query-4fb58ee027c14ec19fe4d3213a552890c824a1b587e6651b799275e1e5f85f14.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\nSELECT\n module_reference,\n name as contract_name,\n contracts.amount as amount,\n contracts.transaction_index as transaction_index,\n transactions.events,\n transactions.hash as transaction_hash,\n transactions.block_height as block_height,\n blocks.slot_time as block_slot_time,\n accounts.address as creator,\n version\nFROM contracts\nJOIN transactions ON transaction_index=transactions.index\nJOIN blocks ON block_height=blocks.height\nJOIN accounts ON transactions.sender=accounts.index\nWHERE contracts.index=$1 AND contracts.sub_index=$2\n", + "query": "\n SELECT\n module_reference,\n name as contract_name,\n contracts.amount as amount,\n contracts.transaction_index as transaction_index,\n transactions.events,\n transactions.hash as transaction_hash,\n transactions.block_height as block_height,\n blocks.slot_time as block_slot_time,\n accounts.address as creator,\n version\n FROM contracts\n JOIN transactions ON transaction_index=transactions.index\n JOIN blocks ON block_height = blocks.height\n JOIN accounts ON transactions.sender = accounts.index\n WHERE contracts.index = $1 AND contracts.sub_index = $2\n ", "describe": { "columns": [ { @@ -73,5 +73,5 @@ false ] }, - "hash": "167be8d2353e36e2ac761b53499c22f492ad5f4a1df57567c434feb683cb552a" + "hash": "4fb58ee027c14ec19fe4d3213a552890c824a1b587e6651b799275e1e5f85f14" } diff --git a/backend-rust/.sqlx/query-5100bc5f8caacc9d82428fec158eae1156386185ecd1cc218eb66c2ce9d4fe0f.json b/backend-rust/.sqlx/query-5100bc5f8caacc9d82428fec158eae1156386185ecd1cc218eb66c2ce9d4fe0f.json deleted file mode 100644 index cbdf5442..00000000 --- a/backend-rust/.sqlx/query-5100bc5f8caacc9d82428fec158eae1156386185ecd1cc218eb66c2ce9d4fe0f.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT * FROM (\n SELECT\n contract_events.index,\n contract_events.transaction_index,\n trace_element_index,\n contract_events.block_height AS event_block_height,\n transactions.hash as transaction_hash,\n transactions.events,\n accounts.address as creator,\n blocks.slot_time as block_slot_time,\n blocks.height as block_height\n FROM contract_events\n JOIN transactions\n ON contract_events.block_height = transactions.block_height\n AND contract_events.transaction_index = transactions.index\n JOIN accounts\n ON transactions.sender = accounts.index\n JOIN blocks ON contract_events.block_height = blocks.height\n WHERE contract_events.contract_index = $1\n AND contract_events.contract_sub_index <= $2\n LIMIT $3\n OFFSET $4\n ) AS contract_data\n ORDER BY contract_data.index DESC\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "index", - "type_info": "Int8" - }, - { - "ordinal": 1, - "name": "transaction_index", - "type_info": "Int8" - }, - { - "ordinal": 2, - "name": "trace_element_index", - "type_info": "Int8" - }, - { - "ordinal": 3, - "name": "event_block_height", - "type_info": "Int8" - }, - { - "ordinal": 4, - "name": "transaction_hash", - "type_info": "Bpchar" - }, - { - "ordinal": 5, - "name": "events", - "type_info": "Jsonb" - }, - { - "ordinal": 6, - "name": "creator", - "type_info": "Bpchar" - }, - { - "ordinal": 7, - "name": "block_slot_time", - "type_info": "Timestamptz" - }, - { - "ordinal": 8, - "name": "block_height", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [ - "Int8", - "Int8", - "Int8", - "Int8" - ] - }, - "nullable": [ - false, - false, - false, - false, - false, - true, - false, - false, - false - ] - }, - "hash": "5100bc5f8caacc9d82428fec158eae1156386185ecd1cc218eb66c2ce9d4fe0f" -} diff --git a/backend-rust/.sqlx/query-1ebcb64ad0ff478f246a8f46e46f811ab51ea8fba0888851c94ec377f68976ba.json b/backend-rust/.sqlx/query-5aecbe0112190ae34b326f381f8a2a7ab3f1b5c44d30e6bee7f8a64b7128d853.json similarity index 79% rename from backend-rust/.sqlx/query-1ebcb64ad0ff478f246a8f46e46f811ab51ea8fba0888851c94ec377f68976ba.json rename to backend-rust/.sqlx/query-5aecbe0112190ae34b326f381f8a2a7ab3f1b5c44d30e6bee7f8a64b7128d853.json index 6fd1ce37..25183274 100644 --- a/backend-rust/.sqlx/query-1ebcb64ad0ff478f246a8f46e46f811ab51ea8fba0888851c94ec377f68976ba.json +++ b/backend-rust/.sqlx/query-5aecbe0112190ae34b326f381f8a2a7ab3f1b5c44d30e6bee7f8a64b7128d853.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT COUNT(*) FROM transactions WHERE block_height=$1", + "query": "SELECT COUNT(*) FROM transactions WHERE block_height = $1", "describe": { "columns": [ { @@ -18,5 +18,5 @@ null ] }, - "hash": "1ebcb64ad0ff478f246a8f46e46f811ab51ea8fba0888851c94ec377f68976ba" + "hash": "5aecbe0112190ae34b326f381f8a2a7ab3f1b5c44d30e6bee7f8a64b7128d853" } diff --git a/backend-rust/.sqlx/query-85419df3c3bfb7aa85e5878183bf8d84b5f28a508ea56805afdb75782fd737d9.json b/backend-rust/.sqlx/query-85419df3c3bfb7aa85e5878183bf8d84b5f28a508ea56805afdb75782fd737d9.json new file mode 100644 index 00000000..716f582b --- /dev/null +++ b/backend-rust/.sqlx/query-85419df3c3bfb7aa85e5878183bf8d84b5f28a508ea56805afdb75782fd737d9.json @@ -0,0 +1,73 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT * FROM (\n SELECT\n contract_events.index,\n contract_events.transaction_index,\n trace_element_index,\n contract_events.block_height AS event_block_height,\n transactions.hash as transaction_hash,\n transactions.events,\n accounts.address as creator,\n blocks.slot_time as block_slot_time,\n blocks.height as block_height\n FROM contract_events\n JOIN transactions\n ON contract_events.block_height = transactions.block_height\n AND contract_events.transaction_index = transactions.index\n JOIN accounts\n ON transactions.sender = accounts.index\n JOIN blocks\n ON contract_events.block_height = blocks.height\n WHERE contract_events.contract_index = $1 AND contract_events.contract_sub_index <= $2\n LIMIT $3\n OFFSET $4\n ) AS contract_data\n ORDER BY contract_data.index DESC\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "index", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "transaction_index", + "type_info": "Int8" + }, + { + "ordinal": 2, + "name": "trace_element_index", + "type_info": "Int8" + }, + { + "ordinal": 3, + "name": "event_block_height", + "type_info": "Int8" + }, + { + "ordinal": 4, + "name": "transaction_hash", + "type_info": "Bpchar" + }, + { + "ordinal": 5, + "name": "events", + "type_info": "Jsonb" + }, + { + "ordinal": 6, + "name": "creator", + "type_info": "Bpchar" + }, + { + "ordinal": 7, + "name": "block_slot_time", + "type_info": "Timestamptz" + }, + { + "ordinal": 8, + "name": "block_height", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Int8", + "Int8", + "Int8", + "Int8" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + true, + false, + false, + false + ] + }, + "hash": "85419df3c3bfb7aa85e5878183bf8d84b5f28a508ea56805afdb75782fd737d9" +} diff --git a/backend-rust/.sqlx/query-e3ec36d7b5af0917cd7a68ce411a9516e7ce66b7c7b66172f12a22a7a624c96f.json b/backend-rust/.sqlx/query-dabde8e21b68d1c2d0430bb63399c8a3d57a48780dd97dedaed1e62669ac574c.json similarity index 53% rename from backend-rust/.sqlx/query-e3ec36d7b5af0917cd7a68ce411a9516e7ce66b7c7b66172f12a22a7a624c96f.json rename to backend-rust/.sqlx/query-dabde8e21b68d1c2d0430bb63399c8a3d57a48780dd97dedaed1e62669ac574c.json index 2b3fdbbd..e5c72ee2 100644 --- a/backend-rust/.sqlx/query-e3ec36d7b5af0917cd7a68ce411a9516e7ce66b7c7b66172f12a22a7a624c96f.json +++ b/backend-rust/.sqlx/query-dabde8e21b68d1c2d0430bb63399c8a3d57a48780dd97dedaed1e62669ac574c.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\nSELECT\n contracts.module_reference as module_reference,\n name as contract_name,\n schema as display_schema\nFROM contracts\nJOIN smart_contract_modules ON smart_contract_modules.module_reference=contracts.module_reference\nWHERE index=$1 AND sub_index=$2\n", + "query": "\n SELECT\n contracts.module_reference as module_reference,\n name as contract_name,\n schema as display_schema\n FROM contracts\n JOIN smart_contract_modules ON smart_contract_modules.module_reference = contracts.module_reference\n WHERE index = $1 AND sub_index = $2\n ", "describe": { "columns": [ { @@ -31,5 +31,5 @@ true ] }, - "hash": "e3ec36d7b5af0917cd7a68ce411a9516e7ce66b7c7b66172f12a22a7a624c96f" + "hash": "dabde8e21b68d1c2d0430bb63399c8a3d57a48780dd97dedaed1e62669ac574c" } diff --git a/backend-rust/migrations/0001_initialize.up.sql b/backend-rust/migrations/0001_initialize.up.sql index c3d7e955..cd32269b 100644 --- a/backend-rust/migrations/0001_initialize.up.sql +++ b/backend-rust/migrations/0001_initialize.up.sql @@ -70,8 +70,7 @@ CREATE TABLE blocks( -- The absolute height of the block. height BIGINT - PRIMARY KEY - NOT NULL, + PRIMARY KEY, -- Block hash encoded using HEX. hash CHAR(64) @@ -169,7 +168,6 @@ CREATE TABLE transactions( BOOLEAN NOT NULL, -- Transaction details. Events if success is true. - -- TODO: It would be nice to have every event in a separate row in a table to easily query a specific event from a specific transaction. events JSONB, -- Transaction details. Reject reason if success is false. @@ -256,7 +254,6 @@ CREATE TABLE bakers( id BIGINT PRIMARY KEY - NOT NULL REFERENCES accounts, -- Amount staked at present. staked @@ -294,8 +291,7 @@ CREATE TABLE smart_contract_modules( module_reference CHAR(64) UNIQUE - PRIMARY KEY - NOT NULL, + PRIMARY KEY, -- Index of the transaction deploying the module. transaction_index BIGINT @@ -347,8 +343,7 @@ CREATE TABLE contract_events ( -- An index/id for this event (row number). index BIGINT GENERATED ALWAYS AS IDENTITY - PRIMARY KEY - NOT NULL, + PRIMARY KEY, -- Transaction index including the event. transaction_index BIGINT diff --git a/backend-rust/src/graphql_api.rs b/backend-rust/src/graphql_api.rs index 02e1892d..e5332f77 100644 --- a/backend-rust/src/graphql_api.rs +++ b/backend-rust/src/graphql_api.rs @@ -37,7 +37,6 @@ use concordium_rust_sdk::{ }; use futures::prelude::*; use prometheus_client::registry::Registry; -use serde_json::to_string; use sqlx::{postgres::types::PgInterval, PgPool}; use std::{error::Error, mem, str::FromStr, sync::Arc}; use tokio::{net::TcpListener, sync::broadcast}; @@ -1388,7 +1387,8 @@ impl Contract { // Get the events from the `contract_events` table. let mut rows = sqlx::query!( - "SELECT * FROM ( + " + SELECT * FROM ( SELECT contract_events.index, contract_events.transaction_index, @@ -1508,22 +1508,14 @@ impl Contract { return Err(ApiError::InternalError("Missing events in database".to_string())); }; - let mut events: Vec = serde_json::from_value(events).map_err(|_| { - ApiError::InternalError("Failed to deserialize events from database".to_string()) + let [event]: [Event; 1] = serde_json::from_value(events).map_err(|_| { + ApiError::InternalError( + "Failed to deserialize events from database. Contract init transaction \ + expects exactly one event" + .to_string(), + ) })?; - if events.len() != 1 { - return Err(ApiError::InternalError( - "Contract init transaction expects exactly one event".to_string(), - )); - } - - // Contract init transactions have exactly one top-level event. - let event = events.pop().ok_or(ApiError::InternalError( - "Contract init transactions are suppose to have exactly one top-level event" - .to_string(), - ))?; - match event { Event::ContractInitialized(_) => Ok(()), _ => Err(ApiError::InternalError(format!( @@ -3709,51 +3701,51 @@ impl SearchResult { } fn decode_value_with_schema( - opt_schema: &Option, + opt_schema: Option<&Type>, schema_name: &str, - value: &Vec, + value: &[u8], value_name: &str, ) -> String { - match opt_schema { - Some(schema) => { - let mut cursor = Cursor::new(&value); - match schema.to_json(&mut cursor) { - Ok(v) => { - to_string(&v).unwrap_or_else(|e| { - // We don't return an error here since the query is correctly formed and - // the CCDScan backend is working as expected. - // A wrong/missing schema is a mistake by the smart contract - // developer which in general cannot be fixed after the deployment of - // the contract. We display the error message (instead of the decoded - // value) in the block explorer to make the info visible to the smart - // contract developer for debugging purposes here. - format!( - "Failed to deserialize {} with {} schema into string: {:?}", - value_name, schema_name, e - ) - }) - } - Err(e) => { - // We don't return an error here since the query is correctly formed and - // the CCDScan backend is working as expected. - // A wrong/missing schema is a mistake by the smart contract - // developer which in general cannot be fixed after the deployment of - // the contract. We display the error message (instead of the decoded - // value) in the block explorer to make the info visible to the smart - // contract developer for debugging purposes here. - format!( - "Failed to deserialize {} with {} schema: {:?}", - value_name, - schema_name, - e.display(true) - ) - } - } + let Some(schema) = opt_schema else { + // Note: There could be something better displayed than this string if no schema is + // available for decoding at the frontend long-term. + return format!( + "No embedded {} schema in smart contract available for decoding", + schema_name + ); + }; + + let mut cursor = Cursor::new(&value); + match schema.to_json(&mut cursor) { + Ok(v) => { + serde_json::to_string(&v).unwrap_or_else(|e| { + // We don't return an error here since the query is correctly formed and + // the CCDScan backend is working as expected. + // A wrong/missing schema is a mistake by the smart contract + // developer which in general cannot be fixed after the deployment of + // the contract. We display the error message (instead of the decoded + // value) in the block explorer to make the info visible to the smart + // contract developer for debugging purposes here. + format!( + "Failed to deserialize {} with {} schema into string: {:?}", + value_name, schema_name, e + ) + }) } - // Note: Shall we use something better than this string if no schema is - // available for decoding. - None => { - format!("No embedded {} schema in smart contract available for decoding", schema_name) + Err(e) => { + // We don't return an error here since the query is correctly formed and + // the CCDScan backend is working as expected. + // A wrong/missing schema is a mistake by the smart contract + // developer which in general cannot be fixed after the deployment of + // the contract. We display the error message (instead of the decoded + // value) in the block explorer to make the info visible to the smart + // contract developer for debugging purposes here. + format!( + "Failed to deserialize {} with {} schema: {:?}", + value_name, + schema_name, + e.display(true) + ) } } } @@ -4812,7 +4804,7 @@ impl ContractInitialized { for (index, log) in self.contract_logs_raw.iter().enumerate() { let decoded_log = - decode_value_with_schema(&opt_event_schema, "event", log, "contract_log"); + decode_value_with_schema(opt_event_schema.as_ref(), "event", log, "contract_log"); connection.edges.push(connection::Edge::new(index.to_string(), decoded_log)); } @@ -4840,11 +4832,8 @@ impl From for Contract } #[derive(Debug, thiserror::Error, Clone)] -#[error("Invalid contract version: {value}")] -pub struct InvalidContractVersionError { - value: i32, -} - +#[error("Invalid contract version: {0}")] +pub struct InvalidContractVersionError(i32); impl TryFrom for ContractVersion { type Error = InvalidContractVersionError; @@ -4852,9 +4841,7 @@ impl TryFrom for ContractVersion { match value { 0 => Ok(ContractVersion::V0), 1 => Ok(ContractVersion::V1), - _ => Err(InvalidContractVersionError { - value, - }), + _ => Err(InvalidContractVersionError(value)), } } } @@ -5097,7 +5084,7 @@ impl ContractUpdated { }); let decoded_input_parameter = decode_value_with_schema( - &opt_init_schema, + opt_init_schema.as_ref(), "init", &self.input_parameter, "init_parameter", @@ -5154,7 +5141,7 @@ impl ContractUpdated { for (index, log) in self.contract_logs_raw.iter().enumerate() { let decoded_log = - decode_value_with_schema(&opt_event_schema, "event", log, "contract_log"); + decode_value_with_schema(opt_event_schema.as_ref(), "event", log, "contract_log"); connection.edges.push(connection::Edge::new(index.to_string(), decoded_log)); } diff --git a/backend-rust/src/indexer.rs b/backend-rust/src/indexer.rs index ebe75c09..4d7e5eac 100644 --- a/backend-rust/src/indexer.rs +++ b/backend-rust/src/indexer.rs @@ -1694,29 +1694,7 @@ impl PreparedContractUpdate { event: &ContractTraceElement, trace_element_index: usize, ) -> anyhow::Result { - let contract_address = match event { - ContractTraceElement::Updated { - data, - } => data.address, - ContractTraceElement::Transferred { - from, - amount, - to, - } => *from, - ContractTraceElement::Interrupted { - address, - events, - } => *address, - ContractTraceElement::Resumed { - address, - success, - } => *address, - ContractTraceElement::Upgraded { - address, - from, - to, - } => *address, - }; + let contract_address = event.affected_address(); let tx_index = block_item.index.index.try_into()?; let trace_element_index = trace_element_index.try_into()?; From 53afa461627f9874a469c19166da1260014123b4 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Fri, 22 Nov 2024 18:48:46 +0700 Subject: [PATCH 20/22] Address comments --- ...c04dffeff2f80f315eec18cc04ef4b1a79a2.json} | 4 +- ...84927cc55e2e3bfde2be2d6aa39a5b4ff1664.json | 73 +++++++++++++++++++ ...94b67d4a79324e63504ee7ad35222b90e789.json} | 10 +-- ...f8d84b5f28a508ea56805afdb75782fd737d9.json | 73 ------------------- ...8e579f0869c6e393cda16bc8848ec5258c07.json} | 7 +- .../migrations/0001_initialize.up.sql | 22 +++--- backend-rust/src/graphql_api.rs | 13 ++-- backend-rust/src/indexer.rs | 19 ++--- 8 files changed, 105 insertions(+), 116 deletions(-) rename backend-rust/.sqlx/{query-fda54688c61b83b30236f240a7c29cb898fc3a526fbdb26bc86fc312946e30b2.json => query-33338d15dc5a20ff68e9f1476a60c04dffeff2f80f315eec18cc04ef4b1a79a2.json} (52%) create mode 100644 backend-rust/.sqlx/query-782d29aa16e457a1bdc9993e91d84927cc55e2e3bfde2be2d6aa39a5b4ff1664.json rename backend-rust/.sqlx/{query-4fb58ee027c14ec19fe4d3213a552890c824a1b587e6651b799275e1e5f85f14.json => query-7e9e5a80542156771d0a0a017fed94b67d4a79324e63504ee7ad35222b90e789.json} (75%) delete mode 100644 backend-rust/.sqlx/query-85419df3c3bfb7aa85e5878183bf8d84b5f28a508ea56805afdb75782fd737d9.json rename backend-rust/.sqlx/{query-f224484c5e510741504c91c5ecc48b5144450b0b1f29315a4fdf9503f81d9eab.json => query-c2769b7e5c028ba895fefc53744b8e579f0869c6e393cda16bc8848ec5258c07.json} (67%) diff --git a/backend-rust/.sqlx/query-fda54688c61b83b30236f240a7c29cb898fc3a526fbdb26bc86fc312946e30b2.json b/backend-rust/.sqlx/query-33338d15dc5a20ff68e9f1476a60c04dffeff2f80f315eec18cc04ef4b1a79a2.json similarity index 52% rename from backend-rust/.sqlx/query-fda54688c61b83b30236f240a7c29cb898fc3a526fbdb26bc86fc312946e30b2.json rename to backend-rust/.sqlx/query-33338d15dc5a20ff68e9f1476a60c04dffeff2f80f315eec18cc04ef4b1a79a2.json index d4a4306b..01134dfd 100644 --- a/backend-rust/.sqlx/query-fda54688c61b83b30236f240a7c29cb898fc3a526fbdb26bc86fc312946e30b2.json +++ b/backend-rust/.sqlx/query-33338d15dc5a20ff68e9f1476a60c04dffeff2f80f315eec18cc04ef4b1a79a2.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO contract_events (\n transaction_index,\n trace_element_index,\n block_height,\n contract_index,\n contract_sub_index\n )\n VALUES (\n $1, $2, $3, $4, $5\n )", + "query": "INSERT INTO contract_events (\n transaction_index,\n trace_element_index,\n block_height,\n contract_index,\n contract_sub_index,\n event_index_per_contract\n )\n VALUES (\n $1, $2, $3, $4, $5, (SELECT COALESCE(MAX(event_index_per_contract) + 1, 0) FROM contract_events WHERE contract_index = $4 AND contract_sub_index = $5)\n )", "describe": { "columns": [], "parameters": { @@ -14,5 +14,5 @@ }, "nullable": [] }, - "hash": "fda54688c61b83b30236f240a7c29cb898fc3a526fbdb26bc86fc312946e30b2" + "hash": "33338d15dc5a20ff68e9f1476a60c04dffeff2f80f315eec18cc04ef4b1a79a2" } diff --git a/backend-rust/.sqlx/query-782d29aa16e457a1bdc9993e91d84927cc55e2e3bfde2be2d6aa39a5b4ff1664.json b/backend-rust/.sqlx/query-782d29aa16e457a1bdc9993e91d84927cc55e2e3bfde2be2d6aa39a5b4ff1664.json new file mode 100644 index 00000000..ce49bf9e --- /dev/null +++ b/backend-rust/.sqlx/query-782d29aa16e457a1bdc9993e91d84927cc55e2e3bfde2be2d6aa39a5b4ff1664.json @@ -0,0 +1,73 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT * FROM (\n SELECT\n event_index_per_contract,\n contract_events.transaction_index,\n trace_element_index,\n contract_events.block_height AS event_block_height,\n transactions.hash as transaction_hash,\n transactions.events,\n accounts.address as creator,\n blocks.slot_time as block_slot_time,\n blocks.height as block_height\n FROM contract_events\n JOIN transactions\n ON contract_events.block_height = transactions.block_height\n AND contract_events.transaction_index = transactions.index\n JOIN accounts\n ON transactions.sender = accounts.index\n JOIN blocks\n ON contract_events.block_height = blocks.height\n WHERE contract_events.contract_index = $1 AND contract_events.contract_sub_index = $2\n AND event_index_per_contract >= $4\n LIMIT $3\n ) AS contract_data\n ORDER BY event_index_per_contract DESC\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "event_index_per_contract", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "transaction_index", + "type_info": "Int8" + }, + { + "ordinal": 2, + "name": "trace_element_index", + "type_info": "Int8" + }, + { + "ordinal": 3, + "name": "event_block_height", + "type_info": "Int8" + }, + { + "ordinal": 4, + "name": "transaction_hash", + "type_info": "Bpchar" + }, + { + "ordinal": 5, + "name": "events", + "type_info": "Jsonb" + }, + { + "ordinal": 6, + "name": "creator", + "type_info": "Bpchar" + }, + { + "ordinal": 7, + "name": "block_slot_time", + "type_info": "Timestamptz" + }, + { + "ordinal": 8, + "name": "block_height", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Int8", + "Int8", + "Int8", + "Int8" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + true, + false, + false, + false + ] + }, + "hash": "782d29aa16e457a1bdc9993e91d84927cc55e2e3bfde2be2d6aa39a5b4ff1664" +} diff --git a/backend-rust/.sqlx/query-4fb58ee027c14ec19fe4d3213a552890c824a1b587e6651b799275e1e5f85f14.json b/backend-rust/.sqlx/query-7e9e5a80542156771d0a0a017fed94b67d4a79324e63504ee7ad35222b90e789.json similarity index 75% rename from backend-rust/.sqlx/query-4fb58ee027c14ec19fe4d3213a552890c824a1b587e6651b799275e1e5f85f14.json rename to backend-rust/.sqlx/query-7e9e5a80542156771d0a0a017fed94b67d4a79324e63504ee7ad35222b90e789.json index bd3b23aa..e2f15163 100644 --- a/backend-rust/.sqlx/query-4fb58ee027c14ec19fe4d3213a552890c824a1b587e6651b799275e1e5f85f14.json +++ b/backend-rust/.sqlx/query-7e9e5a80542156771d0a0a017fed94b67d4a79324e63504ee7ad35222b90e789.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n module_reference,\n name as contract_name,\n contracts.amount as amount,\n contracts.transaction_index as transaction_index,\n transactions.events,\n transactions.hash as transaction_hash,\n transactions.block_height as block_height,\n blocks.slot_time as block_slot_time,\n accounts.address as creator,\n version\n FROM contracts\n JOIN transactions ON transaction_index=transactions.index\n JOIN blocks ON block_height = blocks.height\n JOIN accounts ON transactions.sender = accounts.index\n WHERE contracts.index = $1 AND contracts.sub_index = $2\n ", + "query": "\n SELECT\n module_reference,\n name as contract_name,\n contracts.amount as amount,\n contracts.transaction_index as transaction_index,\n transactions.events,\n transactions.hash as transaction_hash,\n transactions.block_height as block_height,\n blocks.slot_time as block_slot_time,\n accounts.address as creator\n FROM contracts\n JOIN transactions ON transaction_index=transactions.index\n JOIN blocks ON block_height = blocks.height\n JOIN accounts ON transactions.sender = accounts.index\n WHERE contracts.index = $1 AND contracts.sub_index = $2\n ", "describe": { "columns": [ { @@ -47,11 +47,6 @@ "ordinal": 8, "name": "creator", "type_info": "Bpchar" - }, - { - "ordinal": 9, - "name": "version", - "type_info": "Int4" } ], "parameters": { @@ -69,9 +64,8 @@ false, false, false, - false, false ] }, - "hash": "4fb58ee027c14ec19fe4d3213a552890c824a1b587e6651b799275e1e5f85f14" + "hash": "7e9e5a80542156771d0a0a017fed94b67d4a79324e63504ee7ad35222b90e789" } diff --git a/backend-rust/.sqlx/query-85419df3c3bfb7aa85e5878183bf8d84b5f28a508ea56805afdb75782fd737d9.json b/backend-rust/.sqlx/query-85419df3c3bfb7aa85e5878183bf8d84b5f28a508ea56805afdb75782fd737d9.json deleted file mode 100644 index 716f582b..00000000 --- a/backend-rust/.sqlx/query-85419df3c3bfb7aa85e5878183bf8d84b5f28a508ea56805afdb75782fd737d9.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT * FROM (\n SELECT\n contract_events.index,\n contract_events.transaction_index,\n trace_element_index,\n contract_events.block_height AS event_block_height,\n transactions.hash as transaction_hash,\n transactions.events,\n accounts.address as creator,\n blocks.slot_time as block_slot_time,\n blocks.height as block_height\n FROM contract_events\n JOIN transactions\n ON contract_events.block_height = transactions.block_height\n AND contract_events.transaction_index = transactions.index\n JOIN accounts\n ON transactions.sender = accounts.index\n JOIN blocks\n ON contract_events.block_height = blocks.height\n WHERE contract_events.contract_index = $1 AND contract_events.contract_sub_index <= $2\n LIMIT $3\n OFFSET $4\n ) AS contract_data\n ORDER BY contract_data.index DESC\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "index", - "type_info": "Int8" - }, - { - "ordinal": 1, - "name": "transaction_index", - "type_info": "Int8" - }, - { - "ordinal": 2, - "name": "trace_element_index", - "type_info": "Int8" - }, - { - "ordinal": 3, - "name": "event_block_height", - "type_info": "Int8" - }, - { - "ordinal": 4, - "name": "transaction_hash", - "type_info": "Bpchar" - }, - { - "ordinal": 5, - "name": "events", - "type_info": "Jsonb" - }, - { - "ordinal": 6, - "name": "creator", - "type_info": "Bpchar" - }, - { - "ordinal": 7, - "name": "block_slot_time", - "type_info": "Timestamptz" - }, - { - "ordinal": 8, - "name": "block_height", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [ - "Int8", - "Int8", - "Int8", - "Int8" - ] - }, - "nullable": [ - false, - false, - false, - false, - false, - true, - false, - false, - false - ] - }, - "hash": "85419df3c3bfb7aa85e5878183bf8d84b5f28a508ea56805afdb75782fd737d9" -} diff --git a/backend-rust/.sqlx/query-f224484c5e510741504c91c5ecc48b5144450b0b1f29315a4fdf9503f81d9eab.json b/backend-rust/.sqlx/query-c2769b7e5c028ba895fefc53744b8e579f0869c6e393cda16bc8848ec5258c07.json similarity index 67% rename from backend-rust/.sqlx/query-f224484c5e510741504c91c5ecc48b5144450b0b1f29315a4fdf9503f81d9eab.json rename to backend-rust/.sqlx/query-c2769b7e5c028ba895fefc53744b8e579f0869c6e393cda16bc8848ec5258c07.json index fd518678..fdb6ccf1 100644 --- a/backend-rust/.sqlx/query-f224484c5e510741504c91c5ecc48b5144450b0b1f29315a4fdf9503f81d9eab.json +++ b/backend-rust/.sqlx/query-c2769b7e5c028ba895fefc53744b8e579f0869c6e393cda16bc8848ec5258c07.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO contracts (\n index,\n sub_index,\n module_reference,\n name,\n amount,\n transaction_index,\n version\n ) VALUES ($1, $2, $3, $4, $5, $6, $7)", + "query": "INSERT INTO contracts (\n index,\n sub_index,\n module_reference,\n name,\n amount,\n transaction_index\n ) VALUES ($1, $2, $3, $4, $5, $6)", "describe": { "columns": [], "parameters": { @@ -10,11 +10,10 @@ "Bpchar", "Varchar", "Int8", - "Int8", - "Int4" + "Int8" ] }, "nullable": [] }, - "hash": "f224484c5e510741504c91c5ecc48b5144450b0b1f29315a4fdf9503f81d9eab" + "hash": "c2769b7e5c028ba895fefc53744b8e579f0869c6e393cda16bc8848ec5258c07" } diff --git a/backend-rust/migrations/0001_initialize.up.sql b/backend-rust/migrations/0001_initialize.up.sql index cd32269b..895f0207 100644 --- a/backend-rust/migrations/0001_initialize.up.sql +++ b/backend-rust/migrations/0001_initialize.up.sql @@ -329,10 +329,6 @@ CREATE TABLE contracts( BIGINT NOT NULL REFERENCES transactions, - -- The version of the smart contract. - version - INT - NOT NULL, -- Make the contract index and subindex the primary key. PRIMARY KEY (index, sub_index) @@ -340,10 +336,6 @@ CREATE TABLE contracts( -- Every successful event associated to a contract. CREATE TABLE contract_events ( - -- An index/id for this event (row number). - index - BIGINT GENERATED ALWAYS AS IDENTITY - PRIMARY KEY, -- Transaction index including the event. transaction_index BIGINT @@ -363,11 +355,19 @@ CREATE TABLE contract_events ( -- Contract subindex that event is associated with. contract_sub_index BIGINT - NOT NULL + NOT NULL, + -- Every time an event is added to a contract, the index is incremented for that contract. + -- This value is used to quickly filter/sort events by the order they were emitted by a contract. + event_index_per_contract + BIGINT + NOT NULL, + + -- Important for quickly filtering contract events by a specific contract. + PRIMARY KEY (contract_index, contract_sub_index) ); --- Important for quickly filtering contract events by a specific contract. -CREATE INDEX contract_events_idx ON contract_events (contract_index, contract_sub_index); +-- Important for quickly filtering/sorting events by the order they were emitted by a contract. +CREATE INDEX event_index_per_contract_idx ON contract_events (event_index_per_contract); CREATE OR REPLACE FUNCTION block_added_notify_trigger_function() RETURNS trigger AS $trigger$ DECLARE diff --git a/backend-rust/src/graphql_api.rs b/backend-rust/src/graphql_api.rs index e5332f77..65a60290 100644 --- a/backend-rust/src/graphql_api.rs +++ b/backend-rust/src/graphql_api.rs @@ -1390,7 +1390,7 @@ impl Contract { " SELECT * FROM ( SELECT - contract_events.index, + event_index_per_contract, contract_events.transaction_index, trace_element_index, contract_events.block_height AS event_block_height, @@ -1407,12 +1407,12 @@ impl Contract { ON transactions.sender = accounts.index JOIN blocks ON contract_events.block_height = blocks.height - WHERE contract_events.contract_index = $1 AND contract_events.contract_sub_index \ - <= $2 + WHERE contract_events.contract_index = $1 AND contract_events.contract_sub_index = \ + $2 + AND event_index_per_contract >= $4 LIMIT $3 - OFFSET $4 ) AS contract_data - ORDER BY contract_data.index DESC + ORDER BY event_index_per_contract DESC ", self.contract_address_index.0 as i64, self.contract_address_sub_index.0 as i64, @@ -1489,8 +1489,7 @@ impl Contract { transactions.hash as transaction_hash, transactions.block_height as block_height, blocks.slot_time as block_slot_time, - accounts.address as creator, - version + accounts.address as creator FROM contracts JOIN transactions ON transaction_index=transactions.index JOIN blocks ON block_height = blocks.height diff --git a/backend-rust/src/indexer.rs b/backend-rust/src/indexer.rs index 4d7e5eac..463c9026 100644 --- a/backend-rust/src/indexer.rs +++ b/backend-rust/src/indexer.rs @@ -1618,7 +1618,6 @@ struct PreparedContractInitialized { amount: i64, height: i64, tx_index: i64, - version: i32, } impl PreparedContractInitialized { @@ -1634,8 +1633,8 @@ impl PreparedContractInitialized { let sub_index = i64::try_from(event.address.subindex)?; let amount = i64::try_from(event.amount.micro_ccd)?; let module_reference = event.origin_ref; - let name = event.init_name.to_string().replace("init_", ""); - let version = i32::try_from(event.contract_version as u32)?; + // We remove the `init_` prefix from the name to get the contract name. + let name = event.init_name.as_contract_name().contract_name().to_string(); Ok(Self { index, @@ -1645,7 +1644,6 @@ impl PreparedContractInitialized { name, height, tx_index, - version, }) } @@ -1661,16 +1659,14 @@ impl PreparedContractInitialized { module_reference, name, amount, - transaction_index, - version - ) VALUES ($1, $2, $3, $4, $5, $6, $7)", + transaction_index + ) VALUES ($1, $2, $3, $4, $5, $6)", self.index, self.sub_index, self.module_reference, self.name, self.amount, - transaction_index, - self.version + transaction_index ) .execute(tx.as_mut()) .await?; @@ -1721,10 +1717,11 @@ impl PreparedContractUpdate { trace_element_index, block_height, contract_index, - contract_sub_index + contract_sub_index, + event_index_per_contract ) VALUES ( - $1, $2, $3, $4, $5 + $1, $2, $3, $4, $5, (SELECT COALESCE(MAX(event_index_per_contract) + 1, 0) FROM contract_events WHERE contract_index = $4 AND contract_sub_index = $5) )"#, self.tx_index, self.trace_element_index, From 66f5bffff6961fec398c953c4fbedf6f2d46708c Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Fri, 22 Nov 2024 21:54:54 +0700 Subject: [PATCH 21/22] Move reminaing TODOs into issue --- backend-rust/src/graphql_api.rs | 27 ++------------------------- 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/backend-rust/src/graphql_api.rs b/backend-rust/src/graphql_api.rs index 65a60290..e36bb79d 100644 --- a/backend-rust/src/graphql_api.rs +++ b/backend-rust/src/graphql_api.rs @@ -1541,7 +1541,7 @@ impl Contract { has_next_page, has_previous_page: skip > 0, }, - items: Some(contract_events), + items: contract_events, total_count: total_events_count, }) } @@ -2127,7 +2127,7 @@ struct ContractEventsCollectionSegment { /// Information to aid in pagination. page_info: CollectionSegmentInfo, /// A flattened list of the items. - items: Option>, + items: Vec, total_count: i32, } @@ -3852,10 +3852,6 @@ pub fn events_from_summary( amount: i64::try_from(data.amount.micro_ccd)?, init_name: data.init_name.to_string(), version: data.contract_version.into(), - // TODO: the send message/input parameter is missing and not exposed by the - // node and rust SDK currently, it should be added when available. - // - // input_parameter: data.message.as_ref().to_vec(), contract_logs_raw: data.events.iter().map(|e| e.as_ref().to_vec()).collect(), })] } @@ -4732,29 +4728,10 @@ pub struct ContractInitialized { version: ContractVersion, // All logged events by the smart contract during the transaction execution. contract_logs_raw: Vec>, - // TODO: the send message/input parameter is missing and not exposed by the node and rust SDK - // currently, it should be added when available. - // - // input_parameter: Vec, } #[ComplexObject] impl ContractInitialized { - // TODO: the send message/input parameter is missing and not exposed by the node - // and rust SDK currently, it should be added when available. - // - // async fn message(&self) -> ApiResult { - // // TODO: decode input-parameter/message with schema. - - // Ok(self.input_parameter) - // } - - // TODO: the send message/input parameter is missing and not exposed by the node - // and rust SDK currently, it should be added when available. - // - // async fn message_as_hex(&self) -> ApiResult { - // Ok(hex::encode(self.input_parameter)) } - async fn events_as_hex(&self) -> ApiResult> { let mut connection = connection::Connection::new(true, true); From 4176dad622db90c0905c2ccbbf5876f707af9d52 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Tue, 26 Nov 2024 00:53:11 +0700 Subject: [PATCH 22/22] Fix sync errors --- .../migrations/0001_initialize.up.sql | 18 +++++++------ backend-rust/src/graphql_api.rs | 25 +++++++++++++------ backend-rust/src/indexer.rs | 18 +++++-------- 3 files changed, 34 insertions(+), 27 deletions(-) diff --git a/backend-rust/migrations/0001_initialize.up.sql b/backend-rust/migrations/0001_initialize.up.sql index 895f0207..a4d8e945 100644 --- a/backend-rust/migrations/0001_initialize.up.sql +++ b/backend-rust/migrations/0001_initialize.up.sql @@ -336,6 +336,10 @@ CREATE TABLE contracts( -- Every successful event associated to a contract. CREATE TABLE contract_events ( + -- An index/id for this event (row number). + index + BIGINT GENERATED ALWAYS AS IDENTITY + PRIMARY KEY, -- Transaction index including the event. transaction_index BIGINT @@ -348,27 +352,27 @@ CREATE TABLE contract_events ( block_height BIGINT NOT NULL, - -- Contract index that event is associated with. + -- Contract index that the event is associated with. contract_index BIGINT NOT NULL, - -- Contract subindex that event is associated with. + -- Contract subindex that the event is associated with. contract_sub_index BIGINT NOT NULL, - -- Every time an event is added to a contract, the index is incremented for that contract. + -- Every time an event is associated with a contract, this index is incremented for that contract. -- This value is used to quickly filter/sort events by the order they were emitted by a contract. event_index_per_contract BIGINT - NOT NULL, - - -- Important for quickly filtering contract events by a specific contract. - PRIMARY KEY (contract_index, contract_sub_index) + NOT NULL ); -- Important for quickly filtering/sorting events by the order they were emitted by a contract. CREATE INDEX event_index_per_contract_idx ON contract_events (event_index_per_contract); +-- Important for quickly filtering contract events by a specific contract. +CREATE INDEX contract_events_idx ON contract_events (contract_index, contract_sub_index); + CREATE OR REPLACE FUNCTION block_added_notify_trigger_function() RETURNS trigger AS $trigger$ DECLARE rec blocks; diff --git a/backend-rust/src/graphql_api.rs b/backend-rust/src/graphql_api.rs index e36bb79d..6e13ce63 100644 --- a/backend-rust/src/graphql_api.rs +++ b/backend-rust/src/graphql_api.rs @@ -28,9 +28,12 @@ use async_graphql::{ use async_graphql_axum::GraphQLSubscription; use chrono::Duration; use concordium_rust_sdk::{ - base::contracts_common::{ - schema::{Type, VersionedModuleSchema, VersionedSchemaError}, - Cursor, + base::{ + contracts_common::{ + schema::{Type, VersionedModuleSchema, VersionedSchemaError}, + Cursor, + }, + smart_contracts::ReceiveName, }, id::types as sdk_types, types::AmountFraction, @@ -4810,6 +4813,7 @@ impl From for Contract #[derive(Debug, thiserror::Error, Clone)] #[error("Invalid contract version: {0}")] pub struct InvalidContractVersionError(i32); + impl TryFrom for ContractVersion { type Error = InvalidContractVersionError; @@ -5051,19 +5055,24 @@ impl ContractUpdated { .await? .ok_or(ApiError::NotFound)?; - let opt_init_schema = row + let opt_receive_param_schema = row .display_schema .as_ref() .and_then(|schema| VersionedModuleSchema::new(schema, &None).ok()) .and_then(|versioned_schema| { - versioned_schema.get_init_param_schema(&row.contract_name).ok() + versioned_schema + .get_receive_param_schema( + &row.contract_name, + ReceiveName::new_unchecked(&self.receive_name).entrypoint_name().into(), + ) + .ok() }); let decoded_input_parameter = decode_value_with_schema( - opt_init_schema.as_ref(), - "init", + opt_receive_param_schema.as_ref(), + "receive param", &self.input_parameter, - "init_parameter", + "input parameter of receive function", ); Ok(decoded_input_parameter) diff --git a/backend-rust/src/indexer.rs b/backend-rust/src/indexer.rs index 463c9026..43c013b0 100644 --- a/backend-rust/src/indexer.rs +++ b/backend-rust/src/indexer.rs @@ -1233,7 +1233,7 @@ impl PreparedEvent { PreparedEvent::ContractInitialized(event) => event.save(tx, tx_idx).await, PreparedEvent::ContractUpdate(events) => { for event in events { - event.save(tx).await?; + event.save(tx, tx_idx).await?; } Ok(()) } @@ -1617,7 +1617,6 @@ struct PreparedContractInitialized { name: String, amount: i64, height: i64, - tx_index: i64, } impl PreparedContractInitialized { @@ -1626,24 +1625,21 @@ impl PreparedContractInitialized { block_item: &BlockItemSummary, event: &ContractInitializedEvent, ) -> anyhow::Result { - let height = i64::try_from(data.finalized_block_info.height.height)?; - let tx_index = block_item.index.index.try_into()?; - let index = i64::try_from(event.address.index)?; let sub_index = i64::try_from(event.address.subindex)?; - let amount = i64::try_from(event.amount.micro_ccd)?; let module_reference = event.origin_ref; // We remove the `init_` prefix from the name to get the contract name. let name = event.init_name.as_contract_name().contract_name().to_string(); + let amount = i64::try_from(event.amount.micro_ccd)?; + let height = i64::try_from(data.finalized_block_info.height.height)?; Ok(Self { index, sub_index, module_reference: module_reference.into(), - amount, name, + amount, height, - tx_index, }) } @@ -1676,7 +1672,6 @@ impl PreparedContractInitialized { } struct PreparedContractUpdate { - tx_index: i64, trace_element_index: i64, height: i64, contract_index: i64, @@ -1692,14 +1687,12 @@ impl PreparedContractUpdate { ) -> anyhow::Result { let contract_address = event.affected_address(); - let tx_index = block_item.index.index.try_into()?; let trace_element_index = trace_element_index.try_into()?; let height = i64::try_from(data.finalized_block_info.height.height)?; let index = i64::try_from(contract_address.index)?; let sub_index = i64::try_from(contract_address.subindex)?; Ok(Self { - tx_index, trace_element_index, height, contract_index: index, @@ -1710,6 +1703,7 @@ impl PreparedContractUpdate { async fn save( &self, tx: &mut sqlx::Transaction<'static, sqlx::Postgres>, + transaction_index: i64, ) -> anyhow::Result<()> { sqlx::query!( r#"INSERT INTO contract_events ( @@ -1723,7 +1717,7 @@ impl PreparedContractUpdate { VALUES ( $1, $2, $3, $4, $5, (SELECT COALESCE(MAX(event_index_per_contract) + 1, 0) FROM contract_events WHERE contract_index = $4 AND contract_sub_index = $5) )"#, - self.tx_index, + transaction_index, self.trace_element_index, self.height, self.contract_index,