-
Notifications
You must be signed in to change notification settings - Fork 22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add task that tracks stake delegation #157
Changes from 2 commits
e276231
9d1dda3
d09923b
df472ed
50b497f
d7a1078
b9ad7ab
21785b0
02f0174
dc5338b
d474564
ac1aa29
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
use sea_orm::entity::prelude::*; | ||
use serde::{Deserialize, Serialize}; | ||
|
||
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Deserialize, Serialize)] | ||
#[sea_orm(table_name = "StakeDelegationCredentialRelation")] | ||
pub struct Model { | ||
#[sea_orm(primary_key, column_type = "BigInteger")] | ||
pub id: i64, | ||
pub stake_credential: i64, | ||
// pool registrations are not tracked in StakeCredentials, | ||
pub pool_credential: Option<Vec<u8>>, | ||
pub tx_id: i64, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. here as well |
||
} | ||
|
||
#[derive(Copy, Clone, Debug, DeriveRelation, EnumIter)] | ||
pub enum Relation { | ||
#[sea_orm( | ||
belongs_to = "super::stake_credential::Entity", | ||
from = "Column::StakeCredential", | ||
to = "super::stake_credential::Column::Id" | ||
)] | ||
StakeCredential, | ||
#[sea_orm( | ||
belongs_to = "super::transaction::Entity", | ||
from = "Column::TxId", | ||
to = "super::transaction::Column::Id" | ||
)] | ||
Transaction, | ||
} | ||
|
||
impl Related<super::stake_credential::Entity> for Entity { | ||
fn to() -> RelationDef { | ||
Relation::StakeCredential.def() | ||
} | ||
} | ||
|
||
impl ActiveModelBehavior for ActiveModel {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -63,3 +63,5 @@ readonly=false | |
readonly=false | ||
|
||
[MultieraCip25EntryTask] | ||
|
||
[MultieraAddressDelegationTask] |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,7 @@ mod m20220528_000012_create_plutus_data_table; | |
mod m20220808_000013_create_transaction_reference_input_table; | ||
mod m20221031_000014_create_dex_table; | ||
mod m20230223_000015_modify_block_table; | ||
mod m20230927_231206_create_stake_delegation_table; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. let's probably keep the naming convention (date + number)? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good catch, I didn't notice that. The weird thing is that I created this file with sea-orm-cli, so I'm not sure why it's choosing that name |
||
|
||
pub struct Migrator; | ||
|
||
|
@@ -41,6 +42,7 @@ impl MigratorTrait for Migrator { | |
Box::new(m20220808_000013_create_transaction_reference_input_table::Migration), | ||
Box::new(m20221031_000014_create_dex_table::Migration), | ||
Box::new(m20230223_000015_modify_block_table::Migration), | ||
Box::new(m20230927_231206_create_stake_delegation_table::Migration), | ||
] | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
use entity::prelude::{StakeCredential, StakeCredentialColumn, Transaction, TransactionColumn}; | ||
use entity::stake_delegation::*; | ||
use sea_schema::migration::prelude::*; | ||
|
||
pub struct Migration; | ||
|
||
impl MigrationName for Migration { | ||
fn name(&self) -> &str { | ||
"m20230927_231206_create_stake_delegation_table" | ||
} | ||
} | ||
|
||
#[async_trait::async_trait] | ||
impl MigrationTrait for Migration { | ||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { | ||
manager | ||
.create_table( | ||
Table::create() | ||
.table(Entity) | ||
.if_not_exists() | ||
.col( | ||
ColumnDef::new(Column::Id) | ||
.big_integer() | ||
.not_null() | ||
.auto_increment(), | ||
) | ||
.col( | ||
ColumnDef::new(Column::StakeCredential) | ||
.big_integer() | ||
.not_null(), | ||
) | ||
.col(ColumnDef::new(Column::TxId).big_integer().not_null()) | ||
.col(ColumnDef::new(Column::PoolCredential).binary()) | ||
.foreign_key( | ||
ForeignKey::create() | ||
.name("fk-stake_delegation-credential_id") | ||
.from(Entity, Column::StakeCredential) | ||
.to(StakeCredential, StakeCredentialColumn::Id) | ||
.on_delete(ForeignKeyAction::Cascade), | ||
) | ||
.foreign_key( | ||
ForeignKey::create() | ||
.name("fk-stake_delegation-tx_id") | ||
.from(Entity, Column::TxId) | ||
.to(Transaction, TransactionColumn::Id) | ||
.on_delete(ForeignKeyAction::Cascade), | ||
) | ||
.primary_key( | ||
Index::create() | ||
.table(Entity) | ||
.name("stake_delegation_credential-pk") | ||
.col(Column::Id), | ||
) | ||
.to_owned(), | ||
) | ||
.await | ||
|
||
// manager | ||
ecioppettini marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// .create_index( | ||
// Index::create() | ||
// .table(Entity) | ||
// .name("index-address_credential-credential") | ||
// .col(Column::CredentialId) | ||
// .to_owned(), | ||
// ) | ||
// .await | ||
} | ||
|
||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { | ||
manager | ||
.drop_table(Table::drop().table(Entity).to_owned()) | ||
.await | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
use crate::{ | ||
multiera::multiera_stake_credentials::MultieraStakeCredentialTask, | ||
types::{AddressCredentialRelationValue, TxCredentialRelationValue}, | ||
}; | ||
use cardano_multiplatform_lib::{ | ||
address::{BaseAddress, EnterpriseAddress, PointerAddress, RewardAddress}, | ||
byron::ByronAddress, | ||
}; | ||
use entity::{ | ||
prelude::*, | ||
sea_orm::{prelude::*, DatabaseTransaction}, | ||
}; | ||
use pallas::ledger::{ | ||
primitives::{alonzo::Certificate, Fragment}, | ||
traverse::{MultiEraBlock, MultiEraCert, MultiEraOutput, MultiEraTx}, | ||
}; | ||
use sea_orm::{Order, QueryOrder, Set}; | ||
use std::collections::{BTreeMap, BTreeSet}; | ||
use std::ops::Deref; | ||
|
||
use super::{ | ||
multiera_address_credential_relations::QueuedAddressCredentialRelation, | ||
multiera_txs::MultieraTransactionTask, relation_map::RelationMap, | ||
}; | ||
use crate::config::EmptyConfig::EmptyConfig; | ||
use crate::dsl::database_task::BlockGlobalInfo; | ||
use crate::dsl::task_macro::*; | ||
|
||
carp_task! { | ||
name MultieraAddressDelegationTask; | ||
configuration EmptyConfig; | ||
doc "Tracks stake delegation actions to pools."; | ||
era multiera; | ||
dependencies [MultieraStakeCredentialTask]; | ||
read [multiera_txs, multiera_stake_credential]; | ||
write []; | ||
should_add_task |block, _properties| { | ||
// recall: txs may have no outputs if they just burn all inputs as fee | ||
// TODO: this runs slightly more than it should | ||
!block.1.is_empty() | ||
}; | ||
execute |previous_data, task| handle( | ||
task.db_tx, | ||
task.block, | ||
&previous_data.multiera_txs, | ||
&previous_data.multiera_stake_credential, | ||
); | ||
merge_result |_previous_data, _result| {}; | ||
} | ||
|
||
async fn handle( | ||
db_tx: &DatabaseTransaction, | ||
block: BlockInfo<'_, MultiEraBlock<'_>, BlockGlobalInfo>, | ||
multiera_txs: &[TransactionModel], | ||
multiera_stake_credential: &BTreeMap<Vec<u8>, StakeCredentialModel>, | ||
) -> Result<(), DbErr> { | ||
for (tx_body, cardano_transaction) in block.1.txs().iter().zip(multiera_txs) { | ||
for cert in tx_body.certs() { | ||
{ | ||
let tx_id = cardano_transaction.id; | ||
let cert = &cert; | ||
match cert.as_alonzo().unwrap() { | ||
Certificate::StakeDelegation(credential, pool) => { | ||
let credential = credential.encode_fragment().unwrap(); | ||
|
||
let stake_credential_id = multiera_stake_credential | ||
.get(&credential.to_vec()) | ||
.unwrap() | ||
.id; | ||
|
||
entity::stake_delegation::ActiveModel { | ||
stake_credential: Set(stake_credential_id), | ||
pool_credential: Set(Some(pool.to_vec())), | ||
tx_id: Set(tx_id), | ||
..Default::default() | ||
} | ||
.save(db_tx) | ||
.await?; | ||
} | ||
Certificate::StakeRegistration(credential) => {} | ||
Check warning on line 80 in indexer/tasks/src/multiera/multiera_address_delegation.rs GitHub Actions / ci (stable)
Check warning on line 80 in indexer/tasks/src/multiera/multiera_address_delegation.rs GitHub Actions / ci (stable)
Check warning on line 80 in indexer/tasks/src/multiera/multiera_address_delegation.rs GitHub Actions / ci (stable)
|
||
Certificate::StakeDeregistration(credential) => { | ||
let credential = credential.encode_fragment().unwrap(); | ||
|
||
let stake_credential_id = multiera_stake_credential | ||
.get(&credential.to_vec()) | ||
.unwrap() | ||
.id; | ||
|
||
entity::stake_delegation::ActiveModel { | ||
stake_credential: Set(stake_credential_id), | ||
pool_credential: Set(None), | ||
tx_id: Set(tx_id), | ||
..Default::default() | ||
} | ||
.save(db_tx) | ||
.await?; | ||
} | ||
Certificate::PoolRegistration { | ||
ecioppettini marked this conversation as resolved.
Show resolved
Hide resolved
|
||
operator, | ||
Check warning on line 99 in indexer/tasks/src/multiera/multiera_address_delegation.rs GitHub Actions / ci (stable)
Check warning on line 99 in indexer/tasks/src/multiera/multiera_address_delegation.rs GitHub Actions / ci (stable)
Check warning on line 99 in indexer/tasks/src/multiera/multiera_address_delegation.rs GitHub Actions / ci (stable)
|
||
pool_owners, | ||
Check warning on line 100 in indexer/tasks/src/multiera/multiera_address_delegation.rs GitHub Actions / ci (stable)
Check warning on line 100 in indexer/tasks/src/multiera/multiera_address_delegation.rs GitHub Actions / ci (stable)
Check warning on line 100 in indexer/tasks/src/multiera/multiera_address_delegation.rs GitHub Actions / ci (stable)
|
||
reward_account, | ||
Check warning on line 101 in indexer/tasks/src/multiera/multiera_address_delegation.rs GitHub Actions / ci (stable)
Check warning on line 101 in indexer/tasks/src/multiera/multiera_address_delegation.rs GitHub Actions / ci (stable)
Check warning on line 101 in indexer/tasks/src/multiera/multiera_address_delegation.rs GitHub Actions / ci (stable)
|
||
.. | ||
} => {} | ||
Certificate::PoolRetirement(key_hash, _) => {} | ||
Check warning on line 104 in indexer/tasks/src/multiera/multiera_address_delegation.rs GitHub Actions / ci (stable)
Check warning on line 104 in indexer/tasks/src/multiera/multiera_address_delegation.rs GitHub Actions / ci (stable)
Check warning on line 104 in indexer/tasks/src/multiera/multiera_address_delegation.rs GitHub Actions / ci (stable)
|
||
Certificate::GenesisKeyDelegation(_, _, _) => {} | ||
Certificate::MoveInstantaneousRewardsCert(mir) => {} | ||
Check warning on line 106 in indexer/tasks/src/multiera/multiera_address_delegation.rs GitHub Actions / ci (stable)
Check warning on line 106 in indexer/tasks/src/multiera/multiera_address_delegation.rs GitHub Actions / ci (stable)
Check warning on line 106 in indexer/tasks/src/multiera/multiera_address_delegation.rs GitHub Actions / ci (stable)
|
||
}; | ||
}; | ||
} | ||
} | ||
|
||
Ok(()) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { Body, Controller, TsoaResponse, Res, Post, Route, SuccessResponse } from 'tsoa'; | ||
import { StatusCodes } from 'http-status-codes'; | ||
import tx from 'pg-tx'; | ||
import pool from '../services/PgPoolSingleton'; | ||
import type { ErrorShape } from '../../../shared/errors'; | ||
import { genErrorMessage } from '../../../shared/errors'; | ||
import { Errors } from '../../../shared/errors'; | ||
import type { EndpointTypes } from '../../../shared/routes'; | ||
import { Routes } from '../../../shared/routes'; | ||
import { getAddressTypes } from '../models/utils'; | ||
import { delegationForAddress } from '../services/Delegation'; | ||
import { DelegationForAddressResponse } from '../../../shared/models/DelegationForAddress'; | ||
|
||
const route = Routes.delegationForAddress; | ||
|
||
@Route('delegation/address') | ||
export class DelegationForAddressController extends Controller { | ||
@SuccessResponse(`${StatusCodes.OK}`) | ||
@Post() | ||
public async delegationForAddress( | ||
@Body() | ||
requestBody: EndpointTypes[typeof route]['input'], | ||
@Res() | ||
errorResponse: TsoaResponse< | ||
StatusCodes.BAD_REQUEST | StatusCodes.CONFLICT | StatusCodes.UNPROCESSABLE_ENTITY, | ||
ErrorShape | ||
> | ||
): Promise<EndpointTypes[typeof route]['response']> { | ||
const addressTypes = getAddressTypes([requestBody.address]); | ||
|
||
if (addressTypes.invalid.length > 0) { | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return | ||
return errorResponse( | ||
StatusCodes.UNPROCESSABLE_ENTITY, | ||
genErrorMessage(Errors.IncorrectAddressFormat, { | ||
addresses: addressTypes.invalid, | ||
}) | ||
); | ||
} | ||
|
||
const response = await tx< | ||
DelegationForAddressResponse | ||
>(pool, async dbTx => { | ||
const data = await delegationForAddress({ | ||
address: addressTypes.credentialHex.map(addr => Buffer.from(addr, 'hex'))[0], | ||
until: requestBody.until, | ||
dbTx | ||
}); | ||
|
||
return { | ||
pool: data ? data.pool : null, | ||
txId: data ? data.tx_id : null, | ||
} | ||
}); | ||
|
||
return response; | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
/** Types generated for queries found in "app/models/delegation/delegationForAddress.sql" */ | ||
import { PreparedQuery } from '@pgtyped/query'; | ||
|
||
/** 'SqlStakeDelegation' parameters type */ | ||
export interface ISqlStakeDelegationParams { | ||
credential: Buffer; | ||
slot: number; | ||
} | ||
|
||
/** 'SqlStakeDelegation' return type */ | ||
export interface ISqlStakeDelegationResult { | ||
pool: string | null; | ||
tx_id: string | null; | ||
} | ||
|
||
/** 'SqlStakeDelegation' query type */ | ||
export interface ISqlStakeDelegationQuery { | ||
params: ISqlStakeDelegationParams; | ||
result: ISqlStakeDelegationResult; | ||
} | ||
|
||
const sqlStakeDelegationIR: any = {"usedParamSet":{"credential":true,"slot":true},"params":[{"name":"credential","required":true,"transform":{"type":"scalar"},"locs":[{"a":371,"b":382}]},{"name":"slot","required":true,"transform":{"type":"scalar"},"locs":[{"a":405,"b":410}]}],"statement":"SELECT encode(pool_credential, 'hex') as pool, encode(\"Transaction\".hash, 'hex') as tx_id\nFROM \"StakeDelegationCredentialRelation\"\nJOIN \"StakeCredential\" ON stake_credential = \"StakeCredential\".id\nJOIN \"Transaction\" ON \"Transaction\".id = \"StakeDelegationCredentialRelation\".tx_id\nJOIN \"Block\" ON \"Transaction\".block_id = \"Block\".id\nWHERE \n\t\"StakeCredential\".credential = :credential! AND\n\t\"Block\".slot <= :slot!\nORDER BY (\"Block\".height, \"Transaction\".tx_index) DESC\nLIMIT 1"}; | ||
|
||
/** | ||
* Query generated from SQL: | ||
* ``` | ||
* SELECT encode(pool_credential, 'hex') as pool, encode("Transaction".hash, 'hex') as tx_id | ||
* FROM "StakeDelegationCredentialRelation" | ||
* JOIN "StakeCredential" ON stake_credential = "StakeCredential".id | ||
* JOIN "Transaction" ON "Transaction".id = "StakeDelegationCredentialRelation".tx_id | ||
* JOIN "Block" ON "Transaction".block_id = "Block".id | ||
* WHERE | ||
* "StakeCredential".credential = :credential! AND | ||
* "Block".slot <= :slot! | ||
* ORDER BY ("Block".height, "Transaction".tx_index) DESC | ||
* LIMIT 1 | ||
* ``` | ||
*/ | ||
export const sqlStakeDelegation = new PreparedQuery<ISqlStakeDelegationParams,ISqlStakeDelegationResult>(sqlStakeDelegationIR); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
/* @name sqlStakeDelegation */ | ||
SELECT encode(pool_credential, 'hex') as pool, encode("Transaction".hash, 'hex') as tx_id | ||
FROM "StakeDelegationCredentialRelation" | ||
JOIN "StakeCredential" ON stake_credential = "StakeCredential".id | ||
JOIN "Transaction" ON "Transaction".id = "StakeDelegationCredentialRelation".tx_id | ||
JOIN "Block" ON "Transaction".block_id = "Block".id | ||
WHERE | ||
"StakeCredential".credential = :credential! AND | ||
"Block".slot <= :slot! | ||
ORDER BY ("Block".height, "Transaction".tx_index) DESC | ||
LIMIT 1; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
|
||
import type { PoolClient } from 'pg'; | ||
import { ISqlStakeDelegationResult, sqlStakeDelegation } from '../models/delegation/delegationForAddress.queries'; | ||
|
||
|
||
export async function delegationForAddress(request: { | ||
address: Buffer, | ||
until: { absoluteSlot: number }, | ||
dbTx: PoolClient, | ||
}): Promise<ISqlStakeDelegationResult> { | ||
return (await sqlStakeDelegation.run({ credential: request.address, slot: request.until.absoluteSlot }, request.dbTx))[0]; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't we add
#[sea_orm(column_type = "BigInteger")]
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure that does anything, to be honest. The columns already have
bigint
type for me, and it's what the docs say should happen I think (https://www.sea-ql.org/SeaORM/docs/next/generate-entity/entity-structure/).But I can add it just in case anyway.