From 7250056f15f7239cb946c6a61b376dd51f984818 Mon Sep 17 00:00:00 2001 From: Thompsonmina Date: Thu, 7 May 2026 04:17:51 +0100 Subject: [PATCH] feat(indexer): add getAccountAtBlock RPC method --- indexer/core/src/block_store.rs | 66 +++++++++++++++++++++++++++++ indexer/service/rpc/src/lib.rs | 7 +++ indexer/service/src/mock_service.rs | 16 +++++++ indexer/service/src/service.rs | 13 ++++++ 4 files changed, 102 insertions(+) diff --git a/indexer/core/src/block_store.rs b/indexer/core/src/block_store.rs index 71ddfd82..bfba5ed2 100644 --- a/indexer/core/src/block_store.rs +++ b/indexer/core/src/block_store.rs @@ -135,6 +135,12 @@ impl IndexerStore { .get_account_by_id(*account_id)) } + pub fn account_state_at_block(&self, account_id: &AccountId, block_id: u64) -> Result { + Ok(self + .get_state_at_block(block_id)? + .get_account_by_id(*account_id)) + } + pub async fn put_block(&self, mut block: Block, l1_header: HeaderId) -> Result<()> { { let mut state_guard = self.current_state.write().await; @@ -277,4 +283,64 @@ mod tests { assert_eq!(acc1_val.balance, 9920); assert_eq!(acc2_val.balance, 20080); } + + #[tokio::test] + async fn account_state_at_block() { + let home = tempdir().unwrap(); + + let storage = IndexerStore::open_db_with_genesis( + home.as_ref(), + &genesis_block(), + &nssa::V03State::new_with_genesis_accounts( + &[(acc1(), 10000), (acc2(), 20000)], + vec![], + 0, + ), + ) + .unwrap(); + + let mut prev_hash = genesis_block().header.hash; + + let from = acc1(); + let to = acc2(); + let sign_key = acc1_sign_key(); + + for i in 2..10 { + let tx = common::test_utils::create_transaction_native_token_transfer( + from, + i - 2, + to, + 10, + &sign_key, + ); + let block_id = u64::try_from(i).unwrap(); + + let next_block = + common::test_utils::produce_dummy_block(block_id, Some(prev_hash), vec![tx]); + prev_hash = next_block.header.hash; + + storage + .put_block(next_block, HeaderId::from([u8::try_from(i).unwrap(); 32])) + .await + .unwrap(); + } + + // Genesis block: no transfers applied yet. + let acc1_at_1 = storage.account_state_at_block(&acc1(), 1).unwrap(); + let acc2_at_1 = storage.account_state_at_block(&acc2(), 1).unwrap(); + assert_eq!(acc1_at_1.balance, 10000); + assert_eq!(acc2_at_1.balance, 20000); + + // After block 5: 4 transfers of 10 applied (one each in blocks 2..=5). + let acc1_at_5 = storage.account_state_at_block(&acc1(), 5).unwrap(); + let acc2_at_5 = storage.account_state_at_block(&acc2(), 5).unwrap(); + assert_eq!(acc1_at_5.balance, 9960); + assert_eq!(acc2_at_5.balance, 20040); + + // After final block 9: 8 transfers applied; should match current state. + let acc1_at_9 = storage.account_state_at_block(&acc1(), 9).unwrap(); + let acc2_at_9 = storage.account_state_at_block(&acc2(), 9).unwrap(); + assert_eq!(acc1_at_9.balance, 9920); + assert_eq!(acc2_at_9.balance, 20080); + } } diff --git a/indexer/service/rpc/src/lib.rs b/indexer/service/rpc/src/lib.rs index 217c60d4..a6476ebd 100644 --- a/indexer/service/rpc/src/lib.rs +++ b/indexer/service/rpc/src/lib.rs @@ -41,6 +41,13 @@ pub trait Rpc { #[method(name = "getAccount")] async fn get_account(&self, account_id: AccountId) -> Result; + #[method(name = "getAccountAtBlock")] + async fn get_account_at_block( + &self, + account_id: AccountId, + block_id: BlockId, + ) -> Result; + #[method(name = "getTransaction")] async fn get_transaction( &self, diff --git a/indexer/service/src/mock_service.rs b/indexer/service/src/mock_service.rs index c4a099b8..a413973d 100644 --- a/indexer/service/src/mock_service.rs +++ b/indexer/service/src/mock_service.rs @@ -239,6 +239,22 @@ impl indexer_service_rpc::RpcServer for MockIndexerService { .ok_or_else(|| ErrorObjectOwned::owned(-32001, "Account not found", None::<()>)) } + async fn get_account_at_block( + &self, + account_id: AccountId, + _block_id: BlockId, + ) -> Result { + // Mock service does not track historical state; returns current state regardless of + // block_id. + self.state + .read() + .await + .accounts + .get(&account_id) + .cloned() + .ok_or_else(|| ErrorObjectOwned::owned(-32001, "Account not found", None::<()>)) + } + async fn get_transaction( &self, tx_hash: HashType, diff --git a/indexer/service/src/service.rs b/indexer/service/src/service.rs index e2f8a321..8d079265 100644 --- a/indexer/service/src/service.rs +++ b/indexer/service/src/service.rs @@ -83,6 +83,19 @@ impl indexer_service_rpc::RpcServer for IndexerService { .into()) } + async fn get_account_at_block( + &self, + account_id: AccountId, + block_id: BlockId, + ) -> Result { + Ok(self + .indexer + .store + .account_state_at_block(&account_id.into(), block_id) + .map_err(db_error)? + .into()) + } + async fn get_transaction( &self, tx_hash: HashType,