ido: use preinit_txid hex as public id (rename internal id)
Rename ido.id -> ido.internal_id in the schema and IdoDBRecord, and
expose the preinit_txid hex as the public "id" in all RPC responses.
Public API shape:
- IdoRpcRecord.id: i64 -> String (hex of preinit_txid). preinit_txid
field is preserved unchanged.
- IdoEntryRpcRecord.ido_id: i64 -> String (hex of parent preinit_txid).
- IdoTxChainRpcRecord.ido_id: i64 -> String (hex of parent preinit_txid).
- IdoTxChainRpcRecord gains ido_internal_id: i64 (debug endpoint only).
- Routes /<id>/entries and /<id>/txchain accept the preinit_txid hex
as the path id; returns 404 IDO_NOT_FOUND on miss.
Internals:
- New lookup_internal_id_by_preinit_txid helper.
- list_ido_entries / list_ido_txchain take preinit_txid_hex as input
so the caller (which already parsed it from the path) avoids the
extra "preinit_txid by internal_id" lookup.
- Child table FK columns (ido_entry.ido_id, ido_txchain.ido_id) keep
their names; only the parent PK and field accesses were renamed.
Notes:
- Breaking API change for /ido/* endpoints. Clients reading "id" or
"ido_id" as integers must switch to strings.
- ido.db has no migration framework: drop the file and re-index on
deploy.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
210a51f274
commit
6728753db0
3 changed files with 137 additions and 80 deletions
|
|
@ -677,7 +677,7 @@ pub fn pad_minimally_encoded_vm_number(bin: &[u8], length: usize) -> Vec<u8> {
|
||||||
pub async fn prepare_tables(pool: &SqlitePool) {
|
pub async fn prepare_tables(pool: &SqlitePool) {
|
||||||
sqlx::query(
|
sqlx::query(
|
||||||
"CREATE TABLE ido (
|
"CREATE TABLE ido (
|
||||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
internal_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
preinit_txid BLOB NOT NULL UNIQUE,
|
preinit_txid BLOB NOT NULL UNIQUE,
|
||||||
init_txid BLOB NULL,
|
init_txid BLOB NULL,
|
||||||
launch_txid BLOB NULL,
|
launch_txid BLOB NULL,
|
||||||
|
|
@ -728,7 +728,7 @@ pub async fn prepare_tables(pool: &SqlitePool) {
|
||||||
"CREATE TABLE ido_txchain (
|
"CREATE TABLE ido_txchain (
|
||||||
id INTEGER NOT NULL PRIMARY KEY,
|
id INTEGER NOT NULL PRIMARY KEY,
|
||||||
prev_id INTEGER NULL,
|
prev_id INTEGER NULL,
|
||||||
ido_id INTEGER NOT NULL REFERENCES ido(id),
|
ido_id INTEGER NOT NULL REFERENCES ido(internal_id),
|
||||||
txid BLOB NOT NULL UNIQUE,
|
txid BLOB NOT NULL UNIQUE,
|
||||||
tx BLOB NOT NULL
|
tx BLOB NOT NULL
|
||||||
)",
|
)",
|
||||||
|
|
@ -771,7 +771,7 @@ pub struct IdoEntryDBRecord {
|
||||||
|
|
||||||
#[derive(Debug, Clone, serde::Serialize)]
|
#[derive(Debug, Clone, serde::Serialize)]
|
||||||
pub struct IdoDBRecord {
|
pub struct IdoDBRecord {
|
||||||
id: i64,
|
internal_id: i64,
|
||||||
preinit_txid: Vec<u8>,
|
preinit_txid: Vec<u8>,
|
||||||
init_txid: Option<Vec<u8>>,
|
init_txid: Option<Vec<u8>>,
|
||||||
launch_txid: Option<Vec<u8>>,
|
launch_txid: Option<Vec<u8>>,
|
||||||
|
|
@ -788,7 +788,7 @@ pub struct IdoDBRecord {
|
||||||
impl IdoDBRecord {
|
impl IdoDBRecord {
|
||||||
fn from_row(row: &sqlx::sqlite::SqliteRow) -> Result<Self> {
|
fn from_row(row: &sqlx::sqlite::SqliteRow) -> Result<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
id: row.get(0),
|
internal_id: row.get(0),
|
||||||
preinit_txid: row.get(1),
|
preinit_txid: row.get(1),
|
||||||
init_txid: row.get(2),
|
init_txid: row.get(2),
|
||||||
launch_txid: row.get(3),
|
launch_txid: row.get(3),
|
||||||
|
|
@ -827,13 +827,13 @@ impl IdoTxChainDBRecord {
|
||||||
|
|
||||||
async fn get_ido_txchain_list(
|
async fn get_ido_txchain_list(
|
||||||
pool: &SqlitePool,
|
pool: &SqlitePool,
|
||||||
ido_id: i64,
|
internal_id: i64,
|
||||||
) -> Result<Vec<IdoTxChainDBRecord>> {
|
) -> Result<Vec<IdoTxChainDBRecord>> {
|
||||||
sqlx::query(
|
sqlx::query(
|
||||||
"SELECT id, prev_id, ido_id, txid, tx
|
"SELECT id, prev_id, ido_id, txid, tx
|
||||||
FROM ido_txchain WHERE ido_id = ?"
|
FROM ido_txchain WHERE ido_id = ?"
|
||||||
)
|
)
|
||||||
.bind(ido_id)
|
.bind(internal_id)
|
||||||
.fetch_all(pool)
|
.fetch_all(pool)
|
||||||
.await?
|
.await?
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -881,14 +881,14 @@ async fn get_txchain_item_by_txid(
|
||||||
|
|
||||||
async fn ido_lookup(
|
async fn ido_lookup(
|
||||||
pool: &SqlitePool,
|
pool: &SqlitePool,
|
||||||
ido_id: i64,
|
internal_id: i64,
|
||||||
) -> Result<Option<IdoDBRecord>> {
|
) -> Result<Option<IdoDBRecord>> {
|
||||||
let row = sqlx::query(
|
let row = sqlx::query(
|
||||||
"SELECT id, preinit_txid, init_txid, launch_txid, offering_token_id, offered_token_id,
|
"SELECT internal_id, preinit_txid, init_txid, launch_txid, offering_token_id, offered_token_id,
|
||||||
status, parameters, state, txchain_entrypoint, txchain_head, is_valid, is_token_created_at_preinit
|
status, parameters, state, txchain_entrypoint, txchain_head, is_valid, is_token_created_at_preinit
|
||||||
FROM ido WHERE id = ?",
|
FROM ido WHERE internal_id = ?",
|
||||||
)
|
)
|
||||||
.bind(ido_id)
|
.bind(internal_id)
|
||||||
.fetch_optional(pool)
|
.fetch_optional(pool)
|
||||||
.await?;
|
.await?;
|
||||||
if let Some(row) = row {
|
if let Some(row) = row {
|
||||||
|
|
@ -1533,16 +1533,16 @@ async fn on_create_ido(
|
||||||
match result {
|
match result {
|
||||||
Ok(r) => {
|
Ok(r) => {
|
||||||
// insert txchain entrypoint
|
// insert txchain entrypoint
|
||||||
let ido_id = r.last_insert_rowid();
|
let internal_id = r.last_insert_rowid();
|
||||||
let txchain_item_id = upsert_ido_txchain(&mut dbtx, tx, ido_id, None::<i64>, block_height, 1)
|
let txchain_item_id = upsert_ido_txchain(&mut dbtx, tx, internal_id, None::<i64>, block_height, 1)
|
||||||
.await?;
|
.await?;
|
||||||
sqlx::query(
|
sqlx::query(
|
||||||
"UPDATE ido SET txchain_entrypoint = ?, txchain_head = ?
|
"UPDATE ido SET txchain_entrypoint = ?, txchain_head = ?
|
||||||
WHERE id = ?",
|
WHERE internal_id = ?",
|
||||||
)
|
)
|
||||||
.bind(txchain_item_id)
|
.bind(txchain_item_id)
|
||||||
.bind(txchain_item_id)
|
.bind(txchain_item_id)
|
||||||
.bind(ido_id)
|
.bind(internal_id)
|
||||||
.execute(&mut *dbtx)
|
.execute(&mut *dbtx)
|
||||||
.await?;
|
.await?;
|
||||||
dbtx.commit().await?;
|
dbtx.commit().await?;
|
||||||
|
|
@ -1934,13 +1934,13 @@ fn ido_add_tx(
|
||||||
|
|
||||||
async fn update_ido(
|
async fn update_ido(
|
||||||
dbtx: &mut sqlx::Transaction<'_, sqlx::Sqlite>,
|
dbtx: &mut sqlx::Transaction<'_, sqlx::Sqlite>,
|
||||||
ido_id: i64,
|
internal_id: i64,
|
||||||
context: &IdoContext,
|
context: &IdoContext,
|
||||||
txchain_head: i64,
|
txchain_head: i64,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
sqlx::query(
|
sqlx::query(
|
||||||
"UPDATE ido set init_txid = ?, launch_txid = ?, status = ?, parameters = ?, state = ?,
|
"UPDATE ido set init_txid = ?, launch_txid = ?, status = ?, parameters = ?, state = ?,
|
||||||
offering_token_id = ?, offered_token_id = ?, txchain_head = ?, is_valid = ? WHERE id = ?",
|
offering_token_id = ?, offered_token_id = ?, txchain_head = ?, is_valid = ? WHERE internal_id = ?",
|
||||||
)
|
)
|
||||||
.bind(&context.init_txid)
|
.bind(&context.init_txid)
|
||||||
.bind(&context.launch_txid)
|
.bind(&context.launch_txid)
|
||||||
|
|
@ -1951,7 +1951,7 @@ async fn update_ido(
|
||||||
.bind(&context.offered_token_id)
|
.bind(&context.offered_token_id)
|
||||||
.bind(txchain_head)
|
.bind(txchain_head)
|
||||||
.bind(context.is_valid_ido)
|
.bind(context.is_valid_ido)
|
||||||
.bind(ido_id)
|
.bind(internal_id)
|
||||||
.execute(&mut **dbtx)
|
.execute(&mut **dbtx)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -1991,7 +1991,7 @@ fn apply_updates_to_context(
|
||||||
|
|
||||||
async fn upsert_updates_to_ido_entries(
|
async fn upsert_updates_to_ido_entries(
|
||||||
dbtx: &mut sqlx::Transaction<'_, sqlx::Sqlite>,
|
dbtx: &mut sqlx::Transaction<'_, sqlx::Sqlite>,
|
||||||
ido_id: i64,
|
internal_id: i64,
|
||||||
updates: &[IdoUpdate],
|
updates: &[IdoUpdate],
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut insert_list: Vec<IdoUpdateEntry> = Vec::new();
|
let mut insert_list: Vec<IdoUpdateEntry> = Vec::new();
|
||||||
|
|
@ -2018,7 +2018,7 @@ async fn upsert_updates_to_ido_entries(
|
||||||
"INSERT INTO ido_entry (ido_id, txid, owner_nfthash, commitment, supply_amount, demand_amount, lockup_timeval, discount, distributed)
|
"INSERT INTO ido_entry (ido_id, txid, owner_nfthash, commitment, supply_amount, demand_amount, lockup_timeval, discount, distributed)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||||
)
|
)
|
||||||
.bind(ido_id)
|
.bind(internal_id)
|
||||||
.bind(entry.txid)
|
.bind(entry.txid)
|
||||||
.bind(entry.owner_nfthash)
|
.bind(entry.owner_nfthash)
|
||||||
.bind(entry.commitment)
|
.bind(entry.commitment)
|
||||||
|
|
@ -2045,7 +2045,7 @@ async fn upsert_updates_to_ido_entries(
|
||||||
async fn upsert_ido_txchain(
|
async fn upsert_ido_txchain(
|
||||||
dbtx: &mut sqlx::Transaction<'_, sqlx::Sqlite>,
|
dbtx: &mut sqlx::Transaction<'_, sqlx::Sqlite>,
|
||||||
tx: &Transaction,
|
tx: &Transaction,
|
||||||
ido_id: i64,
|
internal_id: i64,
|
||||||
prev_txchain_id: Option<i64>,
|
prev_txchain_id: Option<i64>,
|
||||||
block_height: i64,
|
block_height: i64,
|
||||||
next_output_index: i32,
|
next_output_index: i32,
|
||||||
|
|
@ -2056,7 +2056,7 @@ async fn upsert_ido_txchain(
|
||||||
(?, ?, ?, ?)"
|
(?, ?, ?, ?)"
|
||||||
)
|
)
|
||||||
.bind(prev_txchain_id)
|
.bind(prev_txchain_id)
|
||||||
.bind(ido_id)
|
.bind(internal_id)
|
||||||
.bind(tx.txid().to_blob())
|
.bind(tx.txid().to_blob())
|
||||||
.bind(serialized_tx)
|
.bind(serialized_tx)
|
||||||
.execute(&mut **dbtx)
|
.execute(&mut **dbtx)
|
||||||
|
|
@ -2110,7 +2110,7 @@ async fn on_add_ido_tx(
|
||||||
.await?;
|
.await?;
|
||||||
} else if prev_txchain_item.id != ido.txchain_head.unwrap_or(0) {
|
} else if prev_txchain_item.id != ido.txchain_head.unwrap_or(0) {
|
||||||
// rebuild the state, txchain is broken, recreate the chain
|
// rebuild the state, txchain is broken, recreate the chain
|
||||||
let items = get_ido_txchain_list(pool, ido.id)
|
let items = get_ido_txchain_list(pool, ido.internal_id)
|
||||||
.await?;
|
.await?;
|
||||||
let mut items_tx_map: HashMap<Vec<u8>, Transaction> = HashMap::new();
|
let mut items_tx_map: HashMap<Vec<u8>, Transaction> = HashMap::new();
|
||||||
for item in &items {
|
for item in &items {
|
||||||
|
|
@ -2148,19 +2148,19 @@ async fn on_add_ido_tx(
|
||||||
let result = ido_add_tx(&context, tx)?;
|
let result = ido_add_tx(&context, tx)?;
|
||||||
apply_updates_to_context(&mut context, &result.updates);
|
apply_updates_to_context(&mut context, &result.updates);
|
||||||
updates.extend_from_slice(&result.updates);
|
updates.extend_from_slice(&result.updates);
|
||||||
let txchain_item_id = upsert_ido_txchain(&mut dbtx, tx, ido.id, Some(prev_txchain_item.id), block_height, result.next_output_index)
|
let txchain_item_id = upsert_ido_txchain(&mut dbtx, tx, ido.internal_id, Some(prev_txchain_item.id), block_height, result.next_output_index)
|
||||||
.await?;
|
.await?;
|
||||||
update_ido(&mut dbtx, ido.id, &context, txchain_item_id)
|
update_ido(&mut dbtx, ido.internal_id, &context, txchain_item_id)
|
||||||
.await?;
|
.await?;
|
||||||
// delete all entries
|
// delete all entries
|
||||||
sqlx::query(
|
sqlx::query(
|
||||||
"DELETE FROM ido_entry WHERE ido_id = ?",
|
"DELETE FROM ido_entry WHERE ido_id = ?",
|
||||||
)
|
)
|
||||||
.bind(ido.id)
|
.bind(ido.internal_id)
|
||||||
.execute(&mut *dbtx)
|
.execute(&mut *dbtx)
|
||||||
.await?;
|
.await?;
|
||||||
// insert the entries of the new state
|
// insert the entries of the new state
|
||||||
upsert_updates_to_ido_entries(&mut dbtx, ido.id, &updates)
|
upsert_updates_to_ido_entries(&mut dbtx, ido.internal_id, &updates)
|
||||||
.await?;
|
.await?;
|
||||||
} else {
|
} else {
|
||||||
// create context from ido
|
// create context from ido
|
||||||
|
|
@ -2183,11 +2183,11 @@ async fn on_add_ido_tx(
|
||||||
// add to the chain
|
// add to the chain
|
||||||
let result = ido_add_tx(&context, tx)?;
|
let result = ido_add_tx(&context, tx)?;
|
||||||
apply_updates_to_context(&mut context, &result.updates);
|
apply_updates_to_context(&mut context, &result.updates);
|
||||||
let txchain_item_id = upsert_ido_txchain(&mut dbtx, tx, ido.id, Some(prev_txchain_item.id), block_height, result.next_output_index)
|
let txchain_item_id = upsert_ido_txchain(&mut dbtx, tx, ido.internal_id, Some(prev_txchain_item.id), block_height, result.next_output_index)
|
||||||
.await?;
|
.await?;
|
||||||
update_ido(&mut dbtx, ido.id, &context, txchain_item_id)
|
update_ido(&mut dbtx, ido.internal_id, &context, txchain_item_id)
|
||||||
.await?;
|
.await?;
|
||||||
upsert_updates_to_ido_entries(&mut dbtx, ido.id, &result.updates)
|
upsert_updates_to_ido_entries(&mut dbtx, ido.internal_id, &result.updates)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
dbtx.commit().await?;
|
dbtx.commit().await?;
|
||||||
|
|
@ -2262,7 +2262,7 @@ pub async fn index_block(
|
||||||
|
|
||||||
#[derive(Serialize, Clone)]
|
#[derive(Serialize, Clone)]
|
||||||
pub struct IdoRpcRecord {
|
pub struct IdoRpcRecord {
|
||||||
pub id: i64,
|
pub id: String,
|
||||||
pub preinit_txid: String,
|
pub preinit_txid: String,
|
||||||
pub init_txid: Option<String>,
|
pub init_txid: Option<String>,
|
||||||
pub launch_txid: Option<String>,
|
pub launch_txid: Option<String>,
|
||||||
|
|
@ -2278,7 +2278,7 @@ pub struct IdoRpcRecord {
|
||||||
|
|
||||||
#[derive(Serialize, Clone)]
|
#[derive(Serialize, Clone)]
|
||||||
pub struct IdoEntryRpcRecord {
|
pub struct IdoEntryRpcRecord {
|
||||||
pub ido_id: i64,
|
pub ido_id: String,
|
||||||
pub txid: String,
|
pub txid: String,
|
||||||
pub owner_nfthash: String,
|
pub owner_nfthash: String,
|
||||||
pub commitment: Option<String>,
|
pub commitment: Option<String>,
|
||||||
|
|
@ -2293,7 +2293,8 @@ pub struct IdoEntryRpcRecord {
|
||||||
pub struct IdoTxChainRpcRecord {
|
pub struct IdoTxChainRpcRecord {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
pub prev_id: Option<i64>,
|
pub prev_id: Option<i64>,
|
||||||
pub ido_id: i64,
|
pub ido_id: String,
|
||||||
|
pub ido_internal_id: i64,
|
||||||
pub txid: String,
|
pub txid: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2307,9 +2308,10 @@ pub struct IdoTrackerMapRpcRecord {
|
||||||
|
|
||||||
impl IdoDBRecord {
|
impl IdoDBRecord {
|
||||||
fn into_rpc_record(self) -> Result<IdoRpcRecord> {
|
fn into_rpc_record(self) -> Result<IdoRpcRecord> {
|
||||||
|
let preinit_txid_hex = blob_to_display_hex::<Txid>(&self.preinit_txid)?;
|
||||||
Ok(IdoRpcRecord {
|
Ok(IdoRpcRecord {
|
||||||
id: self.id,
|
id: preinit_txid_hex.clone(),
|
||||||
preinit_txid: blob_to_display_hex::<Txid>(&self.preinit_txid)?,
|
preinit_txid: preinit_txid_hex,
|
||||||
init_txid: self.init_txid.as_deref().map(blob_to_display_hex::<Txid>).transpose()?,
|
init_txid: self.init_txid.as_deref().map(blob_to_display_hex::<Txid>).transpose()?,
|
||||||
launch_txid: self.launch_txid.as_deref().map(blob_to_display_hex::<Txid>).transpose()?,
|
launch_txid: self.launch_txid.as_deref().map(blob_to_display_hex::<Txid>).transpose()?,
|
||||||
offering_token_id: self.offering_token_id.as_deref().map(blob_to_display_hex::<TokenID>).transpose()?,
|
offering_token_id: self.offering_token_id.as_deref().map(blob_to_display_hex::<TokenID>).transpose()?,
|
||||||
|
|
@ -2398,6 +2400,17 @@ mod tests {
|
||||||
|
|
||||||
// ─── Public RPC query functions ──────────────────────────────────────────────
|
// ─── Public RPC query functions ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
pub async fn lookup_internal_id_by_preinit_txid(
|
||||||
|
pool: &SqlitePool,
|
||||||
|
preinit_txid: &[u8],
|
||||||
|
) -> Result<Option<i64>> {
|
||||||
|
let row = sqlx::query("SELECT internal_id FROM ido WHERE preinit_txid = ?")
|
||||||
|
.bind(preinit_txid)
|
||||||
|
.fetch_optional(pool)
|
||||||
|
.await?;
|
||||||
|
Ok(row.map(|r| r.get(0)))
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn list_idos(
|
pub async fn list_idos(
|
||||||
pool: &SqlitePool,
|
pool: &SqlitePool,
|
||||||
is_valid: Option<bool>,
|
is_valid: Option<bool>,
|
||||||
|
|
@ -2407,7 +2420,7 @@ pub async fn list_idos(
|
||||||
limit: i64,
|
limit: i64,
|
||||||
) -> Result<Vec<IdoRpcRecord>> {
|
) -> Result<Vec<IdoRpcRecord>> {
|
||||||
let mut qb: sqlx::QueryBuilder<sqlx::Sqlite> = sqlx::QueryBuilder::new(
|
let mut qb: sqlx::QueryBuilder<sqlx::Sqlite> = sqlx::QueryBuilder::new(
|
||||||
"SELECT id, preinit_txid, init_txid, launch_txid, offering_token_id, offered_token_id, \
|
"SELECT internal_id, preinit_txid, init_txid, launch_txid, offering_token_id, offered_token_id, \
|
||||||
status, parameters, state, txchain_entrypoint, txchain_head, is_valid, is_token_created_at_preinit
|
status, parameters, state, txchain_entrypoint, txchain_head, is_valid, is_token_created_at_preinit
|
||||||
FROM ido WHERE 1=1",
|
FROM ido WHERE 1=1",
|
||||||
);
|
);
|
||||||
|
|
@ -2423,7 +2436,7 @@ pub async fn list_idos(
|
||||||
qb.push(" AND offered_token_id = ");
|
qb.push(" AND offered_token_id = ");
|
||||||
qb.push_bind(v);
|
qb.push_bind(v);
|
||||||
}
|
}
|
||||||
qb.push(" ORDER BY id ASC LIMIT ");
|
qb.push(" ORDER BY internal_id ASC LIMIT ");
|
||||||
qb.push_bind(limit);
|
qb.push_bind(limit);
|
||||||
qb.push(" OFFSET ");
|
qb.push(" OFFSET ");
|
||||||
qb.push_bind(offset);
|
qb.push_bind(offset);
|
||||||
|
|
@ -2441,7 +2454,7 @@ pub async fn get_ido_by_offering_token_id(
|
||||||
offering_token_id_blob: Vec<u8>,
|
offering_token_id_blob: Vec<u8>,
|
||||||
) -> Result<Option<IdoRpcRecord>> {
|
) -> Result<Option<IdoRpcRecord>> {
|
||||||
let row = sqlx::query(
|
let row = sqlx::query(
|
||||||
"SELECT id, preinit_txid, init_txid, launch_txid, offering_token_id, offered_token_id, \
|
"SELECT internal_id, preinit_txid, init_txid, launch_txid, offering_token_id, offered_token_id, \
|
||||||
status, parameters, state, txchain_entrypoint, txchain_head, is_valid, is_token_created_at_preinit \
|
status, parameters, state, txchain_entrypoint, txchain_head, is_valid, is_token_created_at_preinit \
|
||||||
FROM ido WHERE offering_token_id = ?",
|
FROM ido WHERE offering_token_id = ?",
|
||||||
)
|
)
|
||||||
|
|
@ -2453,16 +2466,17 @@ pub async fn get_ido_by_offering_token_id(
|
||||||
|
|
||||||
pub async fn list_ido_entries(
|
pub async fn list_ido_entries(
|
||||||
pool: &SqlitePool,
|
pool: &SqlitePool,
|
||||||
ido_id: i64,
|
internal_id: i64,
|
||||||
|
preinit_txid_hex: &str,
|
||||||
distributed: Option<bool>,
|
distributed: Option<bool>,
|
||||||
offset: i64,
|
offset: i64,
|
||||||
limit: i64,
|
limit: i64,
|
||||||
) -> Result<Vec<IdoEntryRpcRecord>> {
|
) -> Result<Vec<IdoEntryRpcRecord>> {
|
||||||
let mut qb: sqlx::QueryBuilder<sqlx::Sqlite> = sqlx::QueryBuilder::new(
|
let mut qb: sqlx::QueryBuilder<sqlx::Sqlite> = sqlx::QueryBuilder::new(
|
||||||
"SELECT ido_id, txid, owner_nfthash, commitment, supply_amount, demand_amount, \
|
"SELECT txid, owner_nfthash, commitment, supply_amount, demand_amount, \
|
||||||
lockup_timeval, discount, distributed FROM ido_entry WHERE ido_id = ",
|
lockup_timeval, discount, distributed FROM ido_entry WHERE ido_id = ",
|
||||||
);
|
);
|
||||||
qb.push_bind(ido_id);
|
qb.push_bind(internal_id);
|
||||||
if let Some(v) = distributed {
|
if let Some(v) = distributed {
|
||||||
qb.push(" AND distributed = ");
|
qb.push(" AND distributed = ");
|
||||||
qb.push_bind(v as i64);
|
qb.push_bind(v as i64);
|
||||||
|
|
@ -2477,19 +2491,19 @@ pub async fn list_ido_entries(
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|row| {
|
.map(|row| {
|
||||||
let txid_blob: Vec<u8> = row.get(1);
|
let txid_blob: Vec<u8> = row.get(0);
|
||||||
let owner_nfthash: Vec<u8> = row.get(2);
|
let owner_nfthash: Vec<u8> = row.get(1);
|
||||||
let commitment: Option<Vec<u8>> = row.get(3);
|
let commitment: Option<Vec<u8>> = row.get(2);
|
||||||
let distributed_int: i64 = row.get(8);
|
let distributed_int: i64 = row.get(7);
|
||||||
Ok(IdoEntryRpcRecord {
|
Ok(IdoEntryRpcRecord {
|
||||||
ido_id: row.get(0),
|
ido_id: preinit_txid_hex.to_string(),
|
||||||
txid: blob_to_display_hex::<Txid>(&txid_blob)?,
|
txid: blob_to_display_hex::<Txid>(&txid_blob)?,
|
||||||
owner_nfthash: hex::encode(&owner_nfthash),
|
owner_nfthash: hex::encode(&owner_nfthash),
|
||||||
commitment: commitment.map(|c| hex::encode(&c)),
|
commitment: commitment.map(|c| hex::encode(&c)),
|
||||||
supply_amount: row.get(4),
|
supply_amount: row.get(3),
|
||||||
demand_amount: row.get(5),
|
demand_amount: row.get(4),
|
||||||
lockup_timeval: row.get(6),
|
lockup_timeval: row.get(5),
|
||||||
discount: row.get(7),
|
discount: row.get(6),
|
||||||
distributed: distributed_int != 0,
|
distributed: distributed_int != 0,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
@ -2498,12 +2512,13 @@ pub async fn list_ido_entries(
|
||||||
|
|
||||||
pub async fn list_ido_txchain(
|
pub async fn list_ido_txchain(
|
||||||
pool: &SqlitePool,
|
pool: &SqlitePool,
|
||||||
ido_id: i64,
|
internal_id: i64,
|
||||||
|
preinit_txid_hex: &str,
|
||||||
offset: i64,
|
offset: i64,
|
||||||
limit: i64,
|
limit: i64,
|
||||||
) -> Result<Vec<IdoTxChainRpcRecord>> {
|
) -> Result<Vec<IdoTxChainRpcRecord>> {
|
||||||
let head_id: Option<i64> = sqlx::query("SELECT txchain_head FROM ido WHERE id = ?")
|
let head_id: Option<i64> = sqlx::query("SELECT txchain_head FROM ido WHERE internal_id = ?")
|
||||||
.bind(ido_id)
|
.bind(internal_id)
|
||||||
.fetch_optional(pool)
|
.fetch_optional(pool)
|
||||||
.await?
|
.await?
|
||||||
.and_then(|row| row.get(0));
|
.and_then(|row| row.get(0));
|
||||||
|
|
@ -2514,9 +2529,9 @@ pub async fn list_ido_txchain(
|
||||||
};
|
};
|
||||||
|
|
||||||
let rows = sqlx::query(
|
let rows = sqlx::query(
|
||||||
"SELECT id, prev_id, ido_id, txid FROM ido_txchain WHERE ido_id = ?",
|
"SELECT id, prev_id, txid FROM ido_txchain WHERE ido_id = ?",
|
||||||
)
|
)
|
||||||
.bind(ido_id)
|
.bind(internal_id)
|
||||||
.fetch_all(pool)
|
.fetch_all(pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
|
@ -2524,11 +2539,12 @@ pub async fn list_ido_txchain(
|
||||||
let records: Vec<IdoTxChainRpcRecord> = rows
|
let records: Vec<IdoTxChainRpcRecord> = rows
|
||||||
.iter()
|
.iter()
|
||||||
.map(|row| {
|
.map(|row| {
|
||||||
let txid_blob: Vec<u8> = row.get(3);
|
let txid_blob: Vec<u8> = row.get(2);
|
||||||
Ok(IdoTxChainRpcRecord {
|
Ok(IdoTxChainRpcRecord {
|
||||||
id: row.get(0),
|
id: row.get(0),
|
||||||
prev_id: row.get(1),
|
prev_id: row.get(1),
|
||||||
ido_id: row.get(2),
|
ido_id: preinit_txid_hex.to_string(),
|
||||||
|
ido_internal_id: internal_id,
|
||||||
txid: blob_to_display_hex::<Txid>(&txid_blob)?,
|
txid: blob_to_display_hex::<Txid>(&txid_blob)?,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
@ -2851,9 +2867,13 @@ mod querytests {
|
||||||
assert_eq!(page1.len(), 2);
|
assert_eq!(page1.len(), 2);
|
||||||
assert_eq!(page2.len(), 2);
|
assert_eq!(page2.len(), 2);
|
||||||
assert_eq!(page3.len(), 1);
|
assert_eq!(page3.len(), 1);
|
||||||
// IDs are ascending
|
// Pages reflect internal_id ascending insertion order: preinit_txid hex of txid(i) bytes
|
||||||
assert!(page1[0].id < page1[1].id);
|
// For txid(i) = [i; 32], the display hex is "ii".repeat(32).
|
||||||
assert!(page1[1].id < page2[0].id);
|
assert_eq!(page1[0].id, hex::encode(txid(1)));
|
||||||
|
assert_eq!(page1[1].id, hex::encode(txid(2)));
|
||||||
|
assert_eq!(page2[0].id, hex::encode(txid(3)));
|
||||||
|
assert_eq!(page2[1].id, hex::encode(txid(4)));
|
||||||
|
assert_eq!(page3[0].id, hex::encode(txid(5)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── DB: get_ido_by_offering_token_id ────────────────────────────────────
|
// ─── DB: get_ido_by_offering_token_id ────────────────────────────────────
|
||||||
|
|
@ -2897,9 +2917,10 @@ mod querytests {
|
||||||
async fn list_ido_entries_all() {
|
async fn list_ido_entries_all() {
|
||||||
let pool = make_pool().await;
|
let pool = make_pool().await;
|
||||||
let ido_id = insert_test_ido(&pool, txid(1), "ACTIVE", true, true, None, None).await;
|
let ido_id = insert_test_ido(&pool, txid(1), "ACTIVE", true, true, None, None).await;
|
||||||
|
let preinit_hex = hex::encode(txid(1));
|
||||||
insert_test_entry(&pool, ido_id, txid(10), false).await;
|
insert_test_entry(&pool, ido_id, txid(10), false).await;
|
||||||
insert_test_entry(&pool, ido_id, txid(11), true).await;
|
insert_test_entry(&pool, ido_id, txid(11), true).await;
|
||||||
let all = list_ido_entries(&pool, ido_id, None, 0, 100).await.unwrap();
|
let all = list_ido_entries(&pool, ido_id, &preinit_hex, None, 0, 100).await.unwrap();
|
||||||
assert_eq!(all.len(), 2);
|
assert_eq!(all.len(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2907,13 +2928,14 @@ mod querytests {
|
||||||
async fn list_ido_entries_filter_distributed() {
|
async fn list_ido_entries_filter_distributed() {
|
||||||
let pool = make_pool().await;
|
let pool = make_pool().await;
|
||||||
let ido_id = insert_test_ido(&pool, txid(1), "ACTIVE", true, true, None, None).await;
|
let ido_id = insert_test_ido(&pool, txid(1), "ACTIVE", true, true, None, None).await;
|
||||||
|
let preinit_hex = hex::encode(txid(1));
|
||||||
insert_test_entry(&pool, ido_id, txid(10), false).await;
|
insert_test_entry(&pool, ido_id, txid(10), false).await;
|
||||||
insert_test_entry(&pool, ido_id, txid(11), true).await;
|
insert_test_entry(&pool, ido_id, txid(11), true).await;
|
||||||
insert_test_entry(&pool, ido_id, txid(12), true).await;
|
insert_test_entry(&pool, ido_id, txid(12), true).await;
|
||||||
let dist = list_ido_entries(&pool, ido_id, Some(true), 0, 100).await.unwrap();
|
let dist = list_ido_entries(&pool, ido_id, &preinit_hex, Some(true), 0, 100).await.unwrap();
|
||||||
assert_eq!(dist.len(), 2);
|
assert_eq!(dist.len(), 2);
|
||||||
assert!(dist.iter().all(|e| e.distributed));
|
assert!(dist.iter().all(|e| e.distributed));
|
||||||
let undist = list_ido_entries(&pool, ido_id, Some(false), 0, 100).await.unwrap();
|
let undist = list_ido_entries(&pool, ido_id, &preinit_hex, Some(false), 0, 100).await.unwrap();
|
||||||
assert_eq!(undist.len(), 1);
|
assert_eq!(undist.len(), 1);
|
||||||
assert!(!undist[0].distributed);
|
assert!(!undist[0].distributed);
|
||||||
}
|
}
|
||||||
|
|
@ -2923,11 +2945,13 @@ mod querytests {
|
||||||
let pool = make_pool().await;
|
let pool = make_pool().await;
|
||||||
let ido1 = insert_test_ido(&pool, txid(1), "ACTIVE", true, true, None, None).await;
|
let ido1 = insert_test_ido(&pool, txid(1), "ACTIVE", true, true, None, None).await;
|
||||||
let ido2 = insert_test_ido(&pool, txid(2), "ACTIVE", true, true, None, None).await;
|
let ido2 = insert_test_ido(&pool, txid(2), "ACTIVE", true, true, None, None).await;
|
||||||
|
let preinit_hex_1 = hex::encode(txid(1));
|
||||||
|
let preinit_hex_2 = hex::encode(txid(2));
|
||||||
insert_test_entry(&pool, ido1, txid(10), false).await;
|
insert_test_entry(&pool, ido1, txid(10), false).await;
|
||||||
insert_test_entry(&pool, ido1, txid(11), false).await;
|
insert_test_entry(&pool, ido1, txid(11), false).await;
|
||||||
insert_test_entry(&pool, ido2, txid(12), false).await;
|
insert_test_entry(&pool, ido2, txid(12), false).await;
|
||||||
let e1 = list_ido_entries(&pool, ido1, None, 0, 100).await.unwrap();
|
let e1 = list_ido_entries(&pool, ido1, &preinit_hex_1, None, 0, 100).await.unwrap();
|
||||||
let e2 = list_ido_entries(&pool, ido2, None, 0, 100).await.unwrap();
|
let e2 = list_ido_entries(&pool, ido2, &preinit_hex_2, None, 0, 100).await.unwrap();
|
||||||
assert_eq!(e1.len(), 2);
|
assert_eq!(e1.len(), 2);
|
||||||
assert_eq!(e2.len(), 1);
|
assert_eq!(e2.len(), 1);
|
||||||
}
|
}
|
||||||
|
|
@ -2954,7 +2978,7 @@ mod querytests {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_txchain_head(pool: &SqlitePool, ido_id: i64, head_id: i64) {
|
async fn set_txchain_head(pool: &SqlitePool, ido_id: i64, head_id: i64) {
|
||||||
sqlx::query("UPDATE ido SET txchain_head = ? WHERE id = ?")
|
sqlx::query("UPDATE ido SET txchain_head = ? WHERE internal_id = ?")
|
||||||
.bind(head_id)
|
.bind(head_id)
|
||||||
.bind(ido_id)
|
.bind(ido_id)
|
||||||
.execute(pool)
|
.execute(pool)
|
||||||
|
|
@ -2966,12 +2990,13 @@ mod querytests {
|
||||||
async fn list_ido_txchain_ordered_oldest_first() {
|
async fn list_ido_txchain_ordered_oldest_first() {
|
||||||
let pool = make_pool().await;
|
let pool = make_pool().await;
|
||||||
let ido_id = insert_test_ido(&pool, txid(1), "ACTIVE", true, true, None, None).await;
|
let ido_id = insert_test_ido(&pool, txid(1), "ACTIVE", true, true, None, None).await;
|
||||||
|
let preinit_hex = hex::encode(txid(1));
|
||||||
let id_a = insert_txchain_item(&pool, ido_id, txid(10), None).await;
|
let id_a = insert_txchain_item(&pool, ido_id, txid(10), None).await;
|
||||||
let id_b = insert_txchain_item(&pool, ido_id, txid(11), Some(id_a)).await;
|
let id_b = insert_txchain_item(&pool, ido_id, txid(11), Some(id_a)).await;
|
||||||
let id_c = insert_txchain_item(&pool, ido_id, txid(12), Some(id_b)).await;
|
let id_c = insert_txchain_item(&pool, ido_id, txid(12), Some(id_b)).await;
|
||||||
set_txchain_head(&pool, ido_id, id_c).await;
|
set_txchain_head(&pool, ido_id, id_c).await;
|
||||||
|
|
||||||
let chain = list_ido_txchain(&pool, ido_id, 0, 100).await.unwrap();
|
let chain = list_ido_txchain(&pool, ido_id, &preinit_hex, 0, 100).await.unwrap();
|
||||||
assert_eq!(chain.len(), 3);
|
assert_eq!(chain.len(), 3);
|
||||||
assert_eq!(chain[0].id, id_a);
|
assert_eq!(chain[0].id, id_a);
|
||||||
assert_eq!(chain[1].id, id_b);
|
assert_eq!(chain[1].id, id_b);
|
||||||
|
|
@ -2982,14 +3007,15 @@ mod querytests {
|
||||||
async fn list_ido_txchain_pagination() {
|
async fn list_ido_txchain_pagination() {
|
||||||
let pool = make_pool().await;
|
let pool = make_pool().await;
|
||||||
let ido_id = insert_test_ido(&pool, txid(1), "ACTIVE", true, true, None, None).await;
|
let ido_id = insert_test_ido(&pool, txid(1), "ACTIVE", true, true, None, None).await;
|
||||||
|
let preinit_hex = hex::encode(txid(1));
|
||||||
let id_a = insert_txchain_item(&pool, ido_id, txid(10), None).await;
|
let id_a = insert_txchain_item(&pool, ido_id, txid(10), None).await;
|
||||||
let id_b = insert_txchain_item(&pool, ido_id, txid(11), Some(id_a)).await;
|
let id_b = insert_txchain_item(&pool, ido_id, txid(11), Some(id_a)).await;
|
||||||
let id_c = insert_txchain_item(&pool, ido_id, txid(12), Some(id_b)).await;
|
let id_c = insert_txchain_item(&pool, ido_id, txid(12), Some(id_b)).await;
|
||||||
let id_d = insert_txchain_item(&pool, ido_id, txid(13), Some(id_c)).await;
|
let id_d = insert_txchain_item(&pool, ido_id, txid(13), Some(id_c)).await;
|
||||||
set_txchain_head(&pool, ido_id, id_d).await;
|
set_txchain_head(&pool, ido_id, id_d).await;
|
||||||
|
|
||||||
let page1 = list_ido_txchain(&pool, ido_id, 0, 2).await.unwrap();
|
let page1 = list_ido_txchain(&pool, ido_id, &preinit_hex, 0, 2).await.unwrap();
|
||||||
let page2 = list_ido_txchain(&pool, ido_id, 2, 2).await.unwrap();
|
let page2 = list_ido_txchain(&pool, ido_id, &preinit_hex, 2, 2).await.unwrap();
|
||||||
assert_eq!(page1.len(), 2);
|
assert_eq!(page1.len(), 2);
|
||||||
assert_eq!(page2.len(), 2);
|
assert_eq!(page2.len(), 2);
|
||||||
assert_eq!(page1[0].id, id_a);
|
assert_eq!(page1[0].id, id_a);
|
||||||
|
|
@ -3002,15 +3028,16 @@ mod querytests {
|
||||||
async fn list_ido_txchain_no_head_returns_empty() {
|
async fn list_ido_txchain_no_head_returns_empty() {
|
||||||
let pool = make_pool().await;
|
let pool = make_pool().await;
|
||||||
let ido_id = insert_test_ido(&pool, txid(1), "ACTIVE", true, true, None, None).await;
|
let ido_id = insert_test_ido(&pool, txid(1), "ACTIVE", true, true, None, None).await;
|
||||||
|
let preinit_hex = hex::encode(txid(1));
|
||||||
// txchain_head is NULL (default from insert_test_ido)
|
// txchain_head is NULL (default from insert_test_ido)
|
||||||
let chain = list_ido_txchain(&pool, ido_id, 0, 100).await.unwrap();
|
let chain = list_ido_txchain(&pool, ido_id, &preinit_hex, 0, 100).await.unwrap();
|
||||||
assert_eq!(chain.len(), 0);
|
assert_eq!(chain.len(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rocket::async_test]
|
#[rocket::async_test]
|
||||||
async fn list_ido_txchain_unknown_ido_returns_empty() {
|
async fn list_ido_txchain_unknown_ido_returns_empty() {
|
||||||
let pool = make_pool().await;
|
let pool = make_pool().await;
|
||||||
let chain = list_ido_txchain(&pool, 9999, 0, 100).await.unwrap();
|
let chain = list_ido_txchain(&pool, 9999, "deadbeef", 0, 100).await.unwrap();
|
||||||
assert_eq!(chain.len(), 0);
|
assert_eq!(chain.len(), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ pub enum ApiErrorCode {
|
||||||
// 404 Not Found codes
|
// 404 Not Found codes
|
||||||
PriceNotFound,
|
PriceNotFound,
|
||||||
PoolNotFound,
|
PoolNotFound,
|
||||||
|
IdoNotFound,
|
||||||
|
|
||||||
// 500 Internal Server Error codes
|
// 500 Internal Server Error codes
|
||||||
InternalError,
|
InternalError,
|
||||||
|
|
@ -61,6 +62,7 @@ impl fmt::Display for ApiErrorCode {
|
||||||
// 404 codes
|
// 404 codes
|
||||||
Self::PriceNotFound => "PRICE_NOT_FOUND",
|
Self::PriceNotFound => "PRICE_NOT_FOUND",
|
||||||
Self::PoolNotFound => "POOL_NOT_FOUND",
|
Self::PoolNotFound => "POOL_NOT_FOUND",
|
||||||
|
Self::IdoNotFound => "IDO_NOT_FOUND",
|
||||||
// 500 codes
|
// 500 codes
|
||||||
Self::InternalError => "INTERNAL_ERROR",
|
Self::InternalError => "INTERNAL_ERROR",
|
||||||
// 503 codes
|
// 503 codes
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,11 @@
|
||||||
// This software is licensed under the GNU Affero General Public License (AGPL), version 3.0 or later.
|
// This software is licensed under the GNU Affero General Public License (AGPL), version 3.0 or later.
|
||||||
// A copy of the license can be found in the LICENSE file or at https://www.gnu.org/licenses/agpl-3.0.html
|
// A copy of the license can be found in the LICENSE file or at https://www.gnu.org/licenses/agpl-3.0.html
|
||||||
|
|
||||||
use crate::db::blob::display_hex_to_blob;
|
use crate::db::blob::{blob_to_display_hex, display_hex_to_blob};
|
||||||
use crate::db::DB;
|
use crate::db::DB;
|
||||||
use crate::rpc::err::{bad_request, db_error, ApiErrorCode, CachedApiResult};
|
use crate::rpc::err::{bad_request, db_error, not_found, ApiErrorCode, CachedApiResult};
|
||||||
use crate::rpc::response::{cached_ok, CACHE_NONE};
|
use crate::rpc::response::{cached_ok, CACHE_NONE};
|
||||||
use bitcoincash::TokenID;
|
use bitcoincash::{TokenID, Txid};
|
||||||
use rocket::{get, State};
|
use rocket::{get, State};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
|
|
@ -78,13 +78,13 @@ pub async fn get_ido_by_offering_token(
|
||||||
|
|
||||||
/// List entries for an IDO.
|
/// List entries for an IDO.
|
||||||
///
|
///
|
||||||
/// - `ido_id`: the integer IDO ID from the ido table
|
/// - `id`: the IDO's public id (preinit txid, 64-char hex)
|
||||||
/// - `distributed`: optional boolean filter
|
/// - `distributed`: optional boolean filter
|
||||||
///
|
///
|
||||||
/// Pagination: `offset` (default 0), `limit` (default 20, max 100)
|
/// Pagination: `offset` (default 0), `limit` (default 20, max 100)
|
||||||
#[get("/<ido_id>/entries?<distributed>&<offset>&<limit>")]
|
#[get("/<id>/entries?<distributed>&<offset>&<limit>")]
|
||||||
pub async fn list_ido_entries(
|
pub async fn list_ido_entries(
|
||||||
ido_id: i64,
|
id: &str,
|
||||||
distributed: Option<bool>,
|
distributed: Option<bool>,
|
||||||
offset: Option<i64>,
|
offset: Option<i64>,
|
||||||
limit: Option<i64>,
|
limit: Option<i64>,
|
||||||
|
|
@ -93,7 +93,9 @@ pub async fn list_ido_entries(
|
||||||
let offset = offset.unwrap_or(0).max(0);
|
let offset = offset.unwrap_or(0).max(0);
|
||||||
let limit = limit.unwrap_or(LIST_DEFAULT_LIMIT).clamp(1, LIST_MAX_LIMIT);
|
let limit = limit.unwrap_or(LIST_DEFAULT_LIMIT).clamp(1, LIST_MAX_LIMIT);
|
||||||
|
|
||||||
let items = crate::db::ido::list_ido_entries(&db.ido_r, ido_id, distributed, offset, limit)
|
let (internal_id, preinit_txid_hex) = resolve_ido(id, &db.ido_r).await?;
|
||||||
|
|
||||||
|
let items = crate::db::ido::list_ido_entries(&db.ido_r, internal_id, &preinit_txid_hex, distributed, offset, limit)
|
||||||
.await
|
.await
|
||||||
.map_err(db_error)?;
|
.map_err(db_error)?;
|
||||||
|
|
||||||
|
|
@ -102,10 +104,12 @@ pub async fn list_ido_entries(
|
||||||
|
|
||||||
/// [Debug] List the txchain for an IDO, sorted oldest-first (head is last).
|
/// [Debug] List the txchain for an IDO, sorted oldest-first (head is last).
|
||||||
///
|
///
|
||||||
|
/// - `id`: the IDO's public id (preinit txid, 64-char hex)
|
||||||
|
///
|
||||||
/// Pagination: `offset` (default 0), `limit` (default 100, max 10000)
|
/// Pagination: `offset` (default 0), `limit` (default 100, max 10000)
|
||||||
#[get("/<ido_id>/txchain?<offset>&<limit>")]
|
#[get("/<id>/txchain?<offset>&<limit>")]
|
||||||
pub async fn list_ido_txchain(
|
pub async fn list_ido_txchain(
|
||||||
ido_id: i64,
|
id: &str,
|
||||||
offset: Option<i64>,
|
offset: Option<i64>,
|
||||||
limit: Option<i64>,
|
limit: Option<i64>,
|
||||||
db: &State<DB>,
|
db: &State<DB>,
|
||||||
|
|
@ -113,13 +117,37 @@ pub async fn list_ido_txchain(
|
||||||
let offset = offset.unwrap_or(0).max(0);
|
let offset = offset.unwrap_or(0).max(0);
|
||||||
let limit = limit.unwrap_or(DEBUG_DEFAULT_LIMIT).clamp(1, DEBUG_MAX_LIMIT);
|
let limit = limit.unwrap_or(DEBUG_DEFAULT_LIMIT).clamp(1, DEBUG_MAX_LIMIT);
|
||||||
|
|
||||||
let items = crate::db::ido::list_ido_txchain(&db.ido_r, ido_id, offset, limit)
|
let (internal_id, preinit_txid_hex) = resolve_ido(id, &db.ido_r).await?;
|
||||||
|
|
||||||
|
let items = crate::db::ido::list_ido_txchain(&db.ido_r, internal_id, &preinit_txid_hex, offset, limit)
|
||||||
.await
|
.await
|
||||||
.map_err(db_error)?;
|
.map_err(db_error)?;
|
||||||
|
|
||||||
Ok(cached_ok(serde_json::to_value(items).unwrap(), CACHE_NONE))
|
Ok(cached_ok(serde_json::to_value(items).unwrap(), CACHE_NONE))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn resolve_ido(
|
||||||
|
id: &str,
|
||||||
|
pool: &sqlx::SqlitePool,
|
||||||
|
) -> Result<(i64, String), rocket::response::status::Custom<rocket::serde::json::Json<Value>>> {
|
||||||
|
let preinit_blob = display_hex_to_blob::<Txid>(id).map_err(|e| {
|
||||||
|
bad_request(
|
||||||
|
ApiErrorCode::InvalidParameters,
|
||||||
|
&format!("Invalid IDO id (expected preinit_txid hex): {e}"),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let internal_id = crate::db::ido::lookup_internal_id_by_preinit_txid(pool, &preinit_blob)
|
||||||
|
.await
|
||||||
|
.map_err(db_error)?
|
||||||
|
.ok_or_else(|| not_found(ApiErrorCode::IdoNotFound, "IDO not found"))?;
|
||||||
|
|
||||||
|
let preinit_txid_hex = blob_to_display_hex::<Txid>(&preinit_blob)
|
||||||
|
.map_err(|e| bad_request(ApiErrorCode::InvalidParameters, &format!("Invalid IDO id: {e}")))?;
|
||||||
|
|
||||||
|
Ok((internal_id, preinit_txid_hex))
|
||||||
|
}
|
||||||
|
|
||||||
/// [Debug] Get the raw transaction hex for a txchain item by its ID.
|
/// [Debug] Get the raw transaction hex for a txchain item by its ID.
|
||||||
/// Returns `{"tx": "<hex>"}` or `{"tx": null}` if not found.
|
/// Returns `{"tx": "<hex>"}` or `{"tx": null}` if not found.
|
||||||
#[get("/txchain/<txchain_item_id>/tx")]
|
#[get("/txchain/<txchain_item_id>/tx")]
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue