Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

151 changes: 148 additions & 3 deletions bin/strata/src/rpc/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ use strata_ledger_types::{IAccountState, ISnarkAccountState};
use strata_ol_chain_types_new::{OLBlock, OLTransaction, TransactionPayload};
use strata_ol_rpc_api::{OLClientRpcServer, OLFullNodeRpcServer};
use strata_ol_rpc_types::{
OLBlockOrTag, OLRpcProvider, RpcAccountBlockSummary, RpcAccountEpochSummary, RpcBlockEntry,
RpcBlockHeaderEntry, RpcCheckpointConfStatus, RpcCheckpointInfo, RpcCheckpointL1Ref,
RpcOLBlockInfo, RpcOLChainStatus, RpcOLTransaction, RpcSnarkAccountState, RpcUpdateInputData,
OLBlockOrTag, OLRpcProvider, RpcAccountBlockSummary, RpcAccountEntry, RpcAccountEpochSummary,
RpcAccountListPage, RpcBlockEntry, RpcBlockHeaderEntry, RpcCheckpointConfStatus,
RpcCheckpointInfo, RpcCheckpointL1Ref, RpcOLBlockDetail, RpcOLBlockInfo, RpcOLBlockSummary,
RpcOLChainStatus, RpcOLTransaction, RpcOLTxDetail, RpcSnarkAccountState, RpcUpdateInputData,
};
use strata_ol_state_types::OLState;
use strata_primitives::{HexBytes, HexBytes32};
Expand Down Expand Up @@ -151,6 +152,49 @@ impl<P: OLRpcProvider> OLRpcServer<P> {
})
}

/// Resolves an [`OLBlockOrTag`] to a concrete [`OLBlockCommitment`].
///
/// `Latest`, `Confirmed`, and `Finalized` come from the OL sync status.
/// `OLBlockId` requires fetching the block to read its slot. `Slot` looks
/// up the canonical block at that slot.
async fn resolve_block_or_tag(
&self,
block_or_tag: OLBlockOrTag,
) -> RpcResult<OLBlockCommitment> {
Ok(match block_or_tag {
OLBlockOrTag::Latest => {
self.provider
.get_ol_sync_status()
.ok_or_else(|| internal_error("OL sync status not available"))?
.tip
}
OLBlockOrTag::Confirmed => {
self.provider
.get_ol_sync_status()
.ok_or_else(|| internal_error("OL sync status not available"))?
.confirmed_epoch
.to_block_commitment()
}
OLBlockOrTag::Finalized => {
self.provider
.get_ol_sync_status()
.ok_or_else(|| internal_error("OL sync status not available"))?
.finalized_epoch
.to_block_commitment()
}
OLBlockOrTag::OLBlockId(block_id) => {
let block = self.get_block(block_id).await?;
OLBlockCommitment::new(block.header().slot(), block_id)
}
OLBlockOrTag::Slot(slot) => self
.provider
.get_canonical_block_at(slot)
.await
.map_err(db_error)?
.ok_or_else(|| not_found_error(format!("No block found at slot {slot}")))?,
})
}

/// Walks the canonical chain backwards from `end_slot` to `start_slot`,
/// returning blocks in ascending slot order. Each entry carries
/// `(slot, blkid, epoch)`; epoch is read off the header during the walk.
Expand Down Expand Up @@ -894,4 +938,105 @@ impl<P: OLRpcProvider> OLFullNodeRpcServer for OLRpcServer<P> {

Ok(entries)
}

async fn get_block_by_slot(&self, slot: u64) -> RpcResult<Option<RpcOLBlockDetail>> {
let Some(blkid) = self.get_canonical_block_at_height(slot).await? else {
return Ok(None);
};
let block = self.get_block(blkid).await?;
Ok(Some(RpcOLBlockDetail::from(&block)))
}

async fn get_recent_blocks(&self, count: u64) -> RpcResult<Vec<RpcOLBlockSummary>> {
if count == 0 {
return Ok(Vec::new());
}
if count as usize > self.max_headers_range {
return Err(invalid_params_error(format!(
"count {} exceeds max_headers_range {}",
count, self.max_headers_range
)));
}

// Walk parents from the sync-status tip directly so we read a consistent
// chain anchored to the tip we observed (rather than re-resolving the
// canonical block at the tip slot, which costs an extra DB hit and could
// disagree with the snapshot if a reorg races us).
let mut cur_blkid = *self
.provider
.get_ol_sync_status()
.ok_or_else(|| internal_error("OL sync status not available"))?
.tip
.blkid();

let mut summaries = Vec::with_capacity(count as usize);
for _ in 0..count {
let block = self.get_block(cur_blkid).await?;
let header = block.header();
summaries.push(RpcOLBlockSummary::from(&block));
if header.slot() == 0 {
break;
}
cur_blkid = *header.parent_blkid();
}
summaries.reverse();
Ok(summaries)
}

async fn get_block_transactions(&self, slot: u64) -> RpcResult<Vec<RpcOLTxDetail>> {
let blkid = self
.get_canonical_block_at_height(slot)
.await?
.ok_or_else(|| not_found_error(format!("No block found at slot {slot}")))?;
let block = self.get_block(blkid).await?;
let txs = block
.body()
.tx_segment()
.map(|seg| seg.txs().iter().map(RpcOLTxDetail::from).collect())
.unwrap_or_default();
Ok(txs)
}

async fn list_accounts(
&self,
block_or_tag: OLBlockOrTag,
start: u64,
count: u64,
) -> RpcResult<RpcAccountListPage> {
if count == 0 {
return Ok(RpcAccountListPage::new(Vec::new(), 0, None));
}
if count as usize > self.max_headers_range {
return Err(invalid_params_error(format!(
"count {} exceeds max_headers_range {}",
count, self.max_headers_range
)));
}

let block_commitment = self.resolve_block_or_tag(block_or_tag).await?;
let ol_state = self
.provider
.get_toplevel_ol_state(block_commitment)
.await
.map_err(db_error)?
.ok_or_else(|| {
not_found_error(format!("No OL state found for block {block_commitment}"))
})?;

let ledger = ol_state.ledger();
let total = ledger.len() as u64;
let entries: Vec<_> = ledger
.entries()
.skip(start as usize)
.take(count as usize)
.map(RpcAccountEntry::from)
.collect();
let consumed = start.saturating_add(entries.len() as u64);
let next_offset = if consumed < total && (entries.len() as u64) == count {
Some(consumed)
} else {
None
};
Ok(RpcAccountListPage::new(entries, total, next_offset))
}
}
Loading
Loading