Skip to content
Merged
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 crates/agglayer-settlement-service/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ tracing.workspace = true
ulid.workspace = true

[dev-dependencies]
agglayer-storage = { workspace = true, features = ["testutils"] }
alloy = { workspace = true, features = ["node-bindings"] }
serde_json.workspace = true
tokio = { workspace = true, features = ["test-util"] }
Expand Down
145 changes: 144 additions & 1 deletion crates/agglayer-settlement-service/src/settlement_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,28 @@ impl<
Some(result) => Ok(RetrievedSettlementResult::Completed(result.clone())),
};
}
// TODO: check rocksdb for completed settlement job results

if let Some(result) = self
.store
.get_settlement_job_result(&job_id)
.wrap_err_with(|| {
format!("Failed to read settlement job terminal result for id {job_id}")
})?
{
return Ok(RetrievedSettlementResult::Completed(result));
}

if self
.store
.get_settlement_job(&job_id)
.wrap_err_with(|| format!("Failed to check settlement job existence for id {job_id}"))?
.is_none()
{
eyre::bail!("No settlement job found for id {job_id}");
}

// TODO: Spawn a settlement task for a recovered pending job.
// XREF: https://github.com/agglayer/agglayer/issues/1230
todo!()
}
}
Expand Down Expand Up @@ -269,3 +290,125 @@ impl<
})
}
}

#[cfg(test)]
mod tests {
use std::sync::Arc;

use agglayer_storage::tests::mocks::MockStateStore;
use agglayer_types::{
ContractCallOutcome, ContractCallResult, Digest, SettlementJobId, SettlementJobResult,
SettlementTxHash, B256,
};
use alloy::providers::ProviderBuilder;

use super::*;

fn mk_provider() -> impl Provider + 'static {
ProviderBuilder::new().connect_http(
"http://127.0.0.1:0"
.parse()
.expect("test provider URL should parse"),
)
}

async fn mk_service(
store: Arc<MockStateStore>,
) -> SettlementService<impl Provider + 'static, MockStateStore> {
SettlementService::start(
SettlementServiceConfig::default(),
Arc::new(SettlementTransactionConfig::default()),
Arc::new(mk_provider()),
store,
CancellationToken::new(),
)
.await
.expect("settlement service should start")
}

fn mk_job_id(seed: u128) -> SettlementJobId {
SettlementJobId::from(ulid::Ulid::from(seed))
}

fn mk_result(seed: u8, outcome: ContractCallOutcome) -> SettlementJobResult {
SettlementJobResult::ContractCall(ContractCallResult {
outcome,
metadata: vec![seed, seed.wrapping_add(1)].into(),
block_hash: B256::from([seed; 32]),
block_number: seed as u64,
tx_hash: SettlementTxHash::new(Digest::from([seed.wrapping_add(2); 32])),
})
}

#[tokio::test]
async fn retrieve_uses_in_memory_watcher_before_storage() {
let store = Arc::new(MockStateStore::new());
let service = mk_service(store.clone()).await;
let job_id = mk_job_id(1);
let in_memory_result = mk_result(2, ContractCallOutcome::Revert);

let (_sender, watcher) = watch::channel(Some(in_memory_result.clone()));
service.result_watchers.lock().await.insert(job_id, watcher);

let retrieved = service
.retrieve_settlement_result(job_id)
.await
.expect("retrieval should succeed");

match retrieved {
RetrievedSettlementResult::Completed(result) => assert_eq!(result, in_memory_result),
RetrievedSettlementResult::Pending(_) => panic!("expected completed result"),
}
}

#[tokio::test]
async fn retrieve_uses_stored_terminal_result_without_watcher() {
let mut store = MockStateStore::new();
let job_id = mk_job_id(2);
let stored_result = mk_result(3, ContractCallOutcome::Success);
let stored_result_for_store = stored_result.clone();

store
.expect_get_settlement_job_result()
.once()
.withf(move |requested_job_id| requested_job_id == &job_id)
.return_once(move |_| Ok(Some(stored_result_for_store)));

let service = mk_service(Arc::new(store)).await;

let retrieved = service
.retrieve_settlement_result(job_id)
.await
.expect("retrieval should succeed");

match retrieved {
RetrievedSettlementResult::Completed(result) => assert_eq!(result, stored_result),
RetrievedSettlementResult::Pending(_) => panic!("expected completed result"),
}
}

#[tokio::test]
async fn retrieve_fails_for_unknown_job_id() {
let mut store = MockStateStore::new();
let job_id = mk_job_id(4);

store
.expect_get_settlement_job_result()
.once()
.withf(move |requested_job_id| requested_job_id == &job_id)
.return_once(|_| Ok(None));
store
.expect_get_settlement_job()
.once()
.withf(move |requested_job_id| requested_job_id == &job_id)
.return_once(|_| Ok(None));

let service = mk_service(Arc::new(store)).await;

let result = service.retrieve_settlement_result(job_id).await;
assert!(result.is_err(), "unknown job should fail");
let error = result.err().expect("result should be an error");

assert!(error.to_string().contains("No settlement job found for id"));
}
}
2 changes: 2 additions & 0 deletions scripts/make/Makefile.pp.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ args = [
"test",
"-ppessimistic-proof-test-suite",
"--test=cycle-tracker",
"--",
"--test-threads=1",
]

[tasks.pp-check-vkey-change]
Expand Down
Loading