Skip to content

Commit b10906f

Browse files
store: Add tests for rebuild_chain_storage
Covers four scenarios: existing namespace (drop + rebuild), missing namespace, missing ethereum_networks row (full upsert path), and has_namespace returning false after a manual schema drop.
1 parent df9f957 commit b10906f

File tree

1 file changed

+225
-0
lines changed

1 file changed

+225
-0
lines changed

store/test-store/tests/postgres/chain_head.rs

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,3 +660,228 @@ fn test_transaction_receipts_in_block_function() {
660660
assert!(receipts.is_empty())
661661
})
662662
}
663+
664+
// ---- rebuild_storage tests ----
665+
666+
/// Helper that runs a test only on NETWORK_NAME (private storage).
667+
/// rebuild_storage is not supported on shared storage.
668+
fn run_rebuild_test<R, F>(chain: FakeBlockList, test: F)
669+
where
670+
F: Fn(Arc<DieselChainStore>, Arc<DieselStore>) -> R + Send + Sync + 'static,
671+
R: Future<Output = ()> + Send + 'static,
672+
{
673+
run_test_sequentially(|store| async move {
674+
block_store::set_chain(chain.clone(), NETWORK_NAME).await;
675+
676+
let chain_store = store
677+
.block_store()
678+
.chain_store(NETWORK_NAME)
679+
.await
680+
.expect("chain store for NETWORK_NAME");
681+
682+
test(chain_store, store).await;
683+
});
684+
}
685+
686+
#[test]
687+
fn rebuild_storage_with_existing_namespace() {
688+
let chain = vec![&*GENESIS_BLOCK, &*BLOCK_ONE, &*BLOCK_TWO];
689+
690+
run_rebuild_test(chain, |chain_store, store| async move {
691+
let block_store = store.block_store();
692+
693+
// Look up the chain from primary metadata
694+
let mut conn = PRIMARY_POOL.get().await.unwrap();
695+
let chain = graph_store_postgres::find_chain(&mut conn, NETWORK_NAME)
696+
.await
697+
.unwrap()
698+
.expect("chain exists in public.chains");
699+
drop(conn);
700+
701+
let ident = chain.network_identifier().unwrap();
702+
703+
// Verify blocks are present before rebuild
704+
let hashes = chain_store.block_hashes_by_block_number(1).await.unwrap();
705+
assert!(!hashes.is_empty(), "block 1 should exist before rebuild");
706+
707+
// Verify namespace exists
708+
assert!(
709+
block_store.has_namespace(&chain).await.unwrap(),
710+
"namespace should exist before rebuild"
711+
);
712+
713+
// Rebuild storage (should drop and recreate)
714+
block_store
715+
.rebuild_chain_storage(NETWORK_NAME, &ident)
716+
.await
717+
.expect("rebuild_chain_storage succeeds");
718+
719+
// Verify namespace still exists after rebuild
720+
assert!(
721+
block_store.has_namespace(&chain).await.unwrap(),
722+
"namespace should exist after rebuild"
723+
);
724+
725+
// Verify blocks are gone after rebuild (tables are fresh)
726+
let hashes = chain_store.block_hashes_by_block_number(1).await.unwrap();
727+
assert!(hashes.is_empty(), "blocks should be gone after rebuild");
728+
729+
// Verify chain identity is intact
730+
let ident_after = chain_store.chain_identifier().await.unwrap();
731+
assert_eq!(ident.net_version, ident_after.net_version);
732+
assert_eq!(ident.genesis_block_hash, ident_after.genesis_block_hash);
733+
734+
// Verify head is reset to null
735+
let head = chain_store
736+
.clone()
737+
.chain_head_ptr()
738+
.await
739+
.expect("chain_head_ptr succeeds");
740+
assert!(head.is_none(), "head should be null after rebuild");
741+
});
742+
}
743+
744+
#[test]
745+
fn rebuild_storage_with_missing_namespace() {
746+
let chain = vec![&*GENESIS_BLOCK, &*BLOCK_ONE];
747+
748+
run_rebuild_test(chain, |chain_store, store| async move {
749+
let block_store = store.block_store();
750+
751+
let mut conn = PRIMARY_POOL.get().await.unwrap();
752+
let chain = graph_store_postgres::find_chain(&mut conn, NETWORK_NAME)
753+
.await
754+
.unwrap()
755+
.expect("chain exists in public.chains");
756+
drop(conn);
757+
758+
let ident = chain.network_identifier().unwrap();
759+
let nsp = chain.storage.to_string();
760+
761+
// Drop the namespace manually to simulate missing storage
762+
{
763+
let mut conn = chain_store.get_conn_for_test().await.unwrap();
764+
diesel::sql_query(format!("DROP SCHEMA IF EXISTS {nsp} CASCADE"))
765+
.execute(&mut conn)
766+
.await
767+
.unwrap();
768+
}
769+
770+
// Verify namespace is gone
771+
assert!(
772+
!block_store.has_namespace(&chain).await.unwrap(),
773+
"namespace should be gone after manual drop"
774+
);
775+
776+
// Rebuild should recreate the missing namespace
777+
block_store
778+
.rebuild_chain_storage(NETWORK_NAME, &ident)
779+
.await
780+
.expect("rebuild_chain_storage succeeds on missing namespace");
781+
782+
// Verify namespace exists again
783+
assert!(
784+
block_store.has_namespace(&chain).await.unwrap(),
785+
"namespace should exist after rebuild"
786+
);
787+
788+
// Verify chain identity is correct
789+
let ident_after = chain_store.chain_identifier().await.unwrap();
790+
assert_eq!(ident.net_version, ident_after.net_version);
791+
assert_eq!(ident.genesis_block_hash, ident_after.genesis_block_hash);
792+
});
793+
}
794+
795+
#[test]
796+
fn rebuild_storage_repairs_ethereum_networks_row() {
797+
let chain = vec![&*GENESIS_BLOCK];
798+
799+
run_rebuild_test(chain, |chain_store, store| async move {
800+
let block_store = store.block_store();
801+
802+
let mut conn = PRIMARY_POOL.get().await.unwrap();
803+
let chain = graph_store_postgres::find_chain(&mut conn, NETWORK_NAME)
804+
.await
805+
.unwrap()
806+
.expect("chain exists in public.chains");
807+
drop(conn);
808+
809+
let ident = chain.network_identifier().unwrap();
810+
let nsp = chain.storage.to_string();
811+
812+
// Drop the namespace AND delete the ethereum_networks row to
813+
// simulate a fully broken state
814+
{
815+
let mut conn = chain_store.get_conn_for_test().await.unwrap();
816+
diesel::sql_query(format!("DROP SCHEMA IF EXISTS {nsp} CASCADE"))
817+
.execute(&mut conn)
818+
.await
819+
.unwrap();
820+
diesel::sql_query(format!(
821+
"DELETE FROM public.ethereum_networks WHERE name = '{}'",
822+
NETWORK_NAME
823+
))
824+
.execute(&mut conn)
825+
.await
826+
.unwrap();
827+
}
828+
829+
// Rebuild should recreate both the namespace and the
830+
// ethereum_networks row via upsert
831+
block_store
832+
.rebuild_chain_storage(NETWORK_NAME, &ident)
833+
.await
834+
.expect("rebuild_chain_storage succeeds with missing ethereum_networks row");
835+
836+
// Verify the chain identity was restored from the ident we passed in
837+
let ident_after = chain_store.chain_identifier().await.unwrap();
838+
assert_eq!(ident.net_version, ident_after.net_version);
839+
assert_eq!(ident.genesis_block_hash, ident_after.genesis_block_hash);
840+
841+
// Verify namespace exists
842+
assert!(
843+
block_store.has_namespace(&chain).await.unwrap(),
844+
"namespace should exist after rebuild"
845+
);
846+
});
847+
}
848+
849+
#[test]
850+
fn has_namespace_returns_false_for_missing_schema() {
851+
let chain = vec![&*GENESIS_BLOCK];
852+
853+
run_rebuild_test(chain, |chain_store, store| async move {
854+
let block_store = store.block_store();
855+
856+
let mut conn = PRIMARY_POOL.get().await.unwrap();
857+
let chain = graph_store_postgres::find_chain(&mut conn, NETWORK_NAME)
858+
.await
859+
.unwrap()
860+
.expect("chain exists");
861+
drop(conn);
862+
863+
// Namespace should exist initially
864+
assert!(block_store.has_namespace(&chain).await.unwrap());
865+
866+
let nsp = chain.storage.to_string();
867+
868+
// Drop the schema
869+
{
870+
let mut conn = chain_store.get_conn_for_test().await.unwrap();
871+
diesel::sql_query(format!("DROP SCHEMA IF EXISTS {nsp} CASCADE"))
872+
.execute(&mut conn)
873+
.await
874+
.unwrap();
875+
}
876+
877+
// Should return false now
878+
assert!(!block_store.has_namespace(&chain).await.unwrap());
879+
880+
// Rebuild to leave things clean for other tests
881+
let ident = chain.network_identifier().unwrap();
882+
block_store
883+
.rebuild_chain_storage(NETWORK_NAME, &ident)
884+
.await
885+
.unwrap();
886+
});
887+
}

0 commit comments

Comments
 (0)