mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-07-18 13:49:25 +00:00
436 lines
14 KiB
Rust
436 lines
14 KiB
Rust
//! This crate defines the protocol types used by the indexer service.
|
|
//!
|
|
//! Currently it mostly mimics types from `lee_core`, but it's important to have a separate crate
|
|
//! to define a stable interface for the indexer service RPCs which evolves in its own way.
|
|
|
|
use std::{fmt::Display, str::FromStr};
|
|
|
|
use anyhow::anyhow;
|
|
use base58::{FromBase58 as _, ToBase58 as _};
|
|
use schemars::JsonSchema;
|
|
use serde::{Deserialize, Serialize};
|
|
use serde_with::{DeserializeFromStr, SerializeDisplay};
|
|
|
|
#[cfg(feature = "convert")]
|
|
mod convert;
|
|
|
|
mod base64 {
|
|
use base64::prelude::{BASE64_STANDARD, Engine as _};
|
|
use serde::{Deserialize as _, Deserializer, Serialize as _, Serializer};
|
|
|
|
pub mod arr {
|
|
use super::{Deserializer, Serializer};
|
|
|
|
pub fn serialize<S: Serializer>(v: &[u8], s: S) -> Result<S::Ok, S::Error> {
|
|
super::serialize(v, s)
|
|
}
|
|
|
|
pub fn deserialize<'de, const N: usize, D: Deserializer<'de>>(
|
|
d: D,
|
|
) -> Result<[u8; N], D::Error> {
|
|
let vec = super::deserialize(d)?;
|
|
vec.try_into().map_err(|_bytes| {
|
|
serde::de::Error::custom(format!("Invalid length, expected {N} bytes"))
|
|
})
|
|
}
|
|
}
|
|
|
|
pub fn serialize<S: Serializer>(v: &[u8], s: S) -> Result<S::Ok, S::Error> {
|
|
let base64 = BASE64_STANDARD.encode(v);
|
|
String::serialize(&base64, s)
|
|
}
|
|
|
|
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<u8>, D::Error> {
|
|
let base64 = String::deserialize(d)?;
|
|
BASE64_STANDARD
|
|
.decode(base64.as_bytes())
|
|
.map_err(serde::de::Error::custom)
|
|
}
|
|
}
|
|
|
|
pub type Nonce = u128;
|
|
|
|
#[derive(
|
|
Debug, Copy, Clone, PartialEq, Eq, Hash, SerializeDisplay, DeserializeFromStr, JsonSchema,
|
|
)]
|
|
pub struct ProgramId(pub [u32; 8]);
|
|
|
|
impl Display for ProgramId {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
let bytes: Vec<u8> = self.0.iter().flat_map(|n| n.to_le_bytes()).collect();
|
|
write!(f, "{}", bytes.to_base58())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum ProgramIdParseError {
|
|
InvalidBase58(base58::FromBase58Error),
|
|
InvalidLength(usize),
|
|
}
|
|
|
|
impl Display for ProgramIdParseError {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
Self::InvalidBase58(err) => write!(f, "invalid base58: {err:?}"),
|
|
Self::InvalidLength(len) => {
|
|
write!(f, "invalid length: expected 32 bytes, got {len}")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl FromStr for ProgramId {
|
|
type Err = ProgramIdParseError;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
let bytes = s
|
|
.from_base58()
|
|
.map_err(ProgramIdParseError::InvalidBase58)?;
|
|
if bytes.len() != 32 {
|
|
return Err(ProgramIdParseError::InvalidLength(bytes.len()));
|
|
}
|
|
let mut arr = [0_u32; 8];
|
|
for (i, chunk) in bytes.chunks_exact(4).enumerate() {
|
|
arr[i] = u32::from_le_bytes(chunk.try_into().unwrap());
|
|
}
|
|
Ok(Self(arr))
|
|
}
|
|
}
|
|
|
|
#[derive(
|
|
Debug, Copy, Clone, PartialEq, Eq, Hash, SerializeDisplay, DeserializeFromStr, JsonSchema,
|
|
)]
|
|
pub struct AccountId {
|
|
pub value: [u8; 32],
|
|
}
|
|
|
|
impl Display for AccountId {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "{}", self.value.to_base58())
|
|
}
|
|
}
|
|
|
|
impl FromStr for AccountId {
|
|
type Err = anyhow::Error;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
let bytes = s
|
|
.from_base58()
|
|
.map_err(|err| anyhow!("invalid base58: {err:?}"))?;
|
|
if bytes.len() != 32 {
|
|
return Err(anyhow!(
|
|
"invalid length: expected 32 bytes, got {}",
|
|
bytes.len()
|
|
));
|
|
}
|
|
let mut value = [0_u8; 32];
|
|
value.copy_from_slice(&bytes);
|
|
Ok(Self { value })
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
|
|
pub struct Account {
|
|
pub program_owner: ProgramId,
|
|
pub balance: u128,
|
|
pub data: Data,
|
|
pub nonce: Nonce,
|
|
}
|
|
|
|
pub type BlockId = u64;
|
|
pub type Timestamp = u64;
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
|
|
pub struct Block {
|
|
pub header: BlockHeader,
|
|
pub body: BlockBody,
|
|
pub bedrock_status: BedrockStatus,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
|
|
pub struct BlockHeader {
|
|
pub block_id: BlockId,
|
|
pub prev_block_hash: HashType,
|
|
pub hash: HashType,
|
|
pub timestamp: Timestamp,
|
|
pub signature: Signature,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, SerializeDisplay, DeserializeFromStr, JsonSchema)]
|
|
pub struct Signature(
|
|
#[schemars(with = "String", description = "hex-encoded signature")] pub [u8; 64],
|
|
);
|
|
|
|
impl Display for Signature {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "{}", hex::encode(self.0))
|
|
}
|
|
}
|
|
|
|
impl FromStr for Signature {
|
|
type Err = hex::FromHexError;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
let mut bytes = [0_u8; 64];
|
|
hex::decode_to_slice(s, &mut bytes)?;
|
|
Ok(Self(bytes))
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
|
|
pub struct BlockBody {
|
|
pub transactions: Vec<Transaction>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
|
|
pub enum Transaction {
|
|
Public(PublicTransaction),
|
|
PrivacyPreserving(PrivacyPreservingTransaction),
|
|
ProgramDeployment(ProgramDeploymentTransaction),
|
|
}
|
|
|
|
impl Transaction {
|
|
/// Get the hash of the transaction.
|
|
#[expect(clippy::same_name_method, reason = "This is handy")]
|
|
#[must_use]
|
|
pub const fn hash(&self) -> &self::HashType {
|
|
match self {
|
|
Self::Public(tx) => &tx.hash,
|
|
Self::PrivacyPreserving(tx) => &tx.hash,
|
|
Self::ProgramDeployment(tx) => &tx.hash,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
|
|
pub struct PublicTransaction {
|
|
pub hash: HashType,
|
|
pub message: PublicMessage,
|
|
pub witness_set: WitnessSet,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
|
|
pub struct PrivacyPreservingTransaction {
|
|
pub hash: HashType,
|
|
pub message: PrivacyPreservingMessage,
|
|
pub witness_set: WitnessSet,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
|
|
pub struct PublicMessage {
|
|
pub program_id: ProgramId,
|
|
pub account_ids: Vec<AccountId>,
|
|
pub nonces: Vec<Nonce>,
|
|
pub instruction_data: InstructionData,
|
|
}
|
|
|
|
pub type InstructionData = Vec<u32>;
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
|
|
pub struct PrivacyPreservingMessage {
|
|
pub public_account_ids: Vec<AccountId>,
|
|
pub nonces: Vec<Nonce>,
|
|
pub public_post_states: Vec<Account>,
|
|
pub encrypted_private_post_states: Vec<EncryptedAccountData>,
|
|
pub new_commitments: Vec<Commitment>,
|
|
pub new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>,
|
|
pub block_validity_window: ValidityWindow,
|
|
pub timestamp_validity_window: ValidityWindow,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
|
|
pub struct WitnessSet {
|
|
pub signatures_and_public_keys: Vec<(Signature, PublicKey)>,
|
|
pub proof: Option<Proof>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
|
|
pub struct Proof(
|
|
#[serde(with = "base64")]
|
|
#[schemars(with = "String", description = "base64-encoded proof")]
|
|
pub Vec<u8>,
|
|
);
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
|
|
pub struct EncryptedAccountData {
|
|
pub ciphertext: Ciphertext,
|
|
pub epk: EphemeralPublicKey,
|
|
pub view_tag: ViewTag,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
|
|
pub struct ProgramDeploymentTransaction {
|
|
pub hash: HashType,
|
|
pub message: ProgramDeploymentMessage,
|
|
}
|
|
|
|
pub type ViewTag = u8;
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
|
|
pub struct Ciphertext(
|
|
#[serde(with = "base64")]
|
|
#[schemars(with = "String", description = "base64-encoded ciphertext")]
|
|
pub Vec<u8>,
|
|
);
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
|
|
pub struct PublicKey(
|
|
#[serde(with = "base64::arr")]
|
|
#[schemars(with = "String", description = "base64-encoded public key")]
|
|
pub [u8; 32],
|
|
);
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
|
|
pub struct EphemeralPublicKey(
|
|
#[serde(with = "base64")]
|
|
#[schemars(with = "String", description = "base64-encoded ephemeral public key")]
|
|
pub Vec<u8>,
|
|
);
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
|
|
pub struct Commitment(
|
|
#[serde(with = "base64::arr")]
|
|
#[schemars(with = "String", description = "base64-encoded commitment")]
|
|
pub [u8; 32],
|
|
);
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
|
|
pub struct Nullifier(
|
|
#[serde(with = "base64::arr")]
|
|
#[schemars(with = "String", description = "base64-encoded nullifier")]
|
|
pub [u8; 32],
|
|
);
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
|
|
pub struct ValidityWindow(pub (Option<BlockId>, Option<BlockId>));
|
|
|
|
impl Display for ValidityWindow {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self.0 {
|
|
(Some(start), Some(end)) => write!(f, "[{start}, {end})"),
|
|
(Some(start), None) => write!(f, "[{start}, \u{221e})"),
|
|
(None, Some(end)) => write!(f, "(-\u{221e}, {end})"),
|
|
(None, None) => write!(f, "(-\u{221e}, \u{221e})"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
|
|
pub struct CommitmentSetDigest(
|
|
#[serde(with = "base64::arr")]
|
|
#[schemars(with = "String", description = "base64-encoded commitment set digest")]
|
|
pub [u8; 32],
|
|
);
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
|
|
pub struct ProgramDeploymentMessage {
|
|
#[serde(with = "base64")]
|
|
#[schemars(with = "String", description = "base64-encoded program bytecode")]
|
|
pub bytecode: Vec<u8>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
|
|
pub struct Data(
|
|
#[serde(with = "base64")]
|
|
#[schemars(with = "String", description = "base64-encoded account data")]
|
|
pub Vec<u8>,
|
|
);
|
|
|
|
#[derive(
|
|
Debug, Copy, Clone, PartialEq, Eq, Hash, SerializeDisplay, DeserializeFromStr, JsonSchema,
|
|
)]
|
|
pub struct HashType(pub [u8; 32]);
|
|
|
|
impl Display for HashType {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "{}", hex::encode(self.0))
|
|
}
|
|
}
|
|
|
|
impl FromStr for HashType {
|
|
type Err = hex::FromHexError;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
let mut bytes = [0_u8; 32];
|
|
hex::decode_to_slice(s, &mut bytes)?;
|
|
Ok(Self(bytes))
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
|
|
pub enum BedrockStatus {
|
|
Pending,
|
|
Safe,
|
|
Finalized,
|
|
}
|
|
|
|
/// Coarse lifecycle state of the indexer's ingestion loop, so a client can tell
|
|
/// "still catching up" apart from "something went wrong".
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
|
|
pub enum IndexerSyncState {
|
|
/// Booted; no ingestion cycle has run yet.
|
|
Starting,
|
|
/// Streaming finalized messages toward the L1 frontier.
|
|
Syncing,
|
|
/// Drained the stream up to the last finalized block; idle until new blocks finalize.
|
|
CaughtUp,
|
|
/// The last cycle failed (e.g. the L1 node is unreachable). See `last_error`.
|
|
Error,
|
|
/// Parked on a stall reason: the validated tip is frozen awaiting a valid
|
|
/// continuation. See `last_error` and `stall_reason`.
|
|
Stalled,
|
|
}
|
|
|
|
/// Why the indexer could not apply an L2 block from the channel.
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
|
|
pub enum BlockIngestError {
|
|
Deserialize(String),
|
|
UnexpectedBlockId {
|
|
expected: u64,
|
|
got: u64,
|
|
},
|
|
BrokenChainLink {
|
|
expected_prev: HashType,
|
|
got_prev: HashType,
|
|
},
|
|
HashMismatch {
|
|
computed: HashType,
|
|
header: HashType,
|
|
},
|
|
EmptyBlock,
|
|
InvalidClockTransaction,
|
|
NonPublicGenesisTransaction,
|
|
StateTransition {
|
|
/// Index of the failing transaction within the block body.
|
|
tx_index: u64,
|
|
reason: String,
|
|
},
|
|
}
|
|
|
|
/// Diagnostic record of the first block that broke the L2 chain.
|
|
///
|
|
/// The block-derived fields are `None` for a deserialize break (no header was
|
|
/// ever parsed). `l1_slot` is the L1 slot the breaking inscription was read at.
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
|
|
pub struct StallReason {
|
|
pub block_id: Option<u64>,
|
|
pub block_hash: Option<HashType>,
|
|
pub prev_block_hash: Option<HashType>,
|
|
pub l1_slot: u64,
|
|
pub error: BlockIngestError,
|
|
/// The breaking block's L2 timestamp (`None` for a deserialize break).
|
|
pub first_seen: Option<Timestamp>,
|
|
/// Number of later non-chaining blocks seen while the tip is frozen.
|
|
pub orphans_since: u64,
|
|
}
|
|
|
|
/// Status snapshot returned by `getStatus`: the ingestion state plus the
|
|
/// indexed L2 tip and, when stalled, the stall diagnostics.
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
|
|
pub struct IndexerStatus {
|
|
pub state: IndexerSyncState,
|
|
pub last_error: Option<String>,
|
|
pub indexed_block_id: Option<BlockId>,
|
|
pub stall_reason: Option<StallReason>,
|
|
}
|