Skip to content

Commit 86c948d

Browse files
test(drive): pin that a cached contract read after an in-block update bills like a cold read (#5052)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 3f15eea commit 86c948d

1 file changed

Lines changed: 151 additions & 0 deletions

File tree

  • packages/rs-drive/src/drive/contract/get_fetch/get_contract_with_fetch_info

‎packages/rs-drive/src/drive/contract/get_fetch/get_contract_with_fetch_info/mod.rs‎

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,9 @@ mod tests {
195195
use dpp::tests::json_document::json_document_to_contract;
196196

197197
use crate::util::batch::{DataContractOperationType, DriveOperation};
198+
use crate::util::object_size_info::DocumentInfo::DocumentRefInfo;
199+
use crate::util::object_size_info::{DocumentAndContractInfo, OwnedDocumentInfo};
200+
use dpp::data_contract::document_type::random_document::CreateRandomDocument;
198201
use dpp::version::PlatformVersion;
199202
use std::borrow::Cow;
200203
use std::sync::Arc;
@@ -719,4 +722,152 @@ mod tests {
719722
1
720723
);
721724
}
725+
726+
/// A contract updated in the block is afterwards served from the block cache, seeded by
727+
/// the update's refresh. Billing that read from the cost recorded at the refresh must
728+
/// charge what a cold read of the contract through the same transaction charges (which is
729+
/// what a node that evicted the contract on update used to bill), even when documents were
730+
/// written under the contract and other contracts rebalanced the contracts tree between
731+
/// the refresh and the read. Run at the shipped protocol version 13 and at the latest.
732+
#[test]
733+
fn should_bill_a_block_cache_hit_after_an_update_like_a_cold_read() {
734+
for platform_version in [
735+
PlatformVersion::get(13).expect("expected protocol version 13"),
736+
PlatformVersion::latest(),
737+
] {
738+
let drive = setup_drive_with_initial_state_structure(None);
739+
let contract = json_document_to_contract(
740+
"tests/supporting_files/contract/references/references.json",
741+
false,
742+
platform_version,
743+
)
744+
.expect("expected to get a contract");
745+
drive
746+
.apply_contract(
747+
&contract,
748+
BlockInfo::default(),
749+
true,
750+
StorageFlags::optional_default_as_cow(),
751+
None,
752+
platform_version,
753+
)
754+
.expect("expected to apply contract successfully");
755+
let contract_id = contract.id().to_buffer();
756+
let epoch = Epoch::new(0).expect("epoch 0");
757+
758+
let transaction = drive.grove.start_transaction();
759+
760+
let mut updated = contract.clone();
761+
updated.increment_version();
762+
drive
763+
.apply_drive_operations(
764+
vec![DriveOperation::DataContractOperation(
765+
DataContractOperationType::ApplyContract {
766+
contract: Cow::Borrowed(&updated),
767+
storage_flags: None,
768+
},
769+
)],
770+
true,
771+
&BlockInfo::default(),
772+
Some(&transaction),
773+
platform_version,
774+
None,
775+
)
776+
.expect("expected to apply the update");
777+
assert!(drive.cache.data_contracts.is_modified_in_block(contract_id));
778+
// The entry the refresh seeded, with the cost recorded before the writes below.
779+
let seeded = drive
780+
.cache
781+
.data_contracts
782+
.get(contract_id, true)
783+
.expect("the refresh must seed the block cache");
784+
785+
// Documents written under the contract after the refresh.
786+
let note_type = updated
787+
.document_type_for_name("note")
788+
.expect("expected the note type");
789+
for seed in 0..10u64 {
790+
let note = note_type
791+
.random_document(Some(seed), platform_version)
792+
.expect("expected a random note");
793+
drive
794+
.add_document_for_contract(
795+
DocumentAndContractInfo {
796+
owned_document_info: OwnedDocumentInfo {
797+
document_info: DocumentRefInfo((&note, None)),
798+
owner_id: None,
799+
},
800+
contract: &updated,
801+
document_type: note_type,
802+
},
803+
false,
804+
BlockInfo::default(),
805+
true,
806+
Some(&transaction),
807+
platform_version,
808+
None,
809+
)
810+
.expect("expected to insert a note");
811+
}
812+
813+
// Other contracts that rebalance the contracts tree after the refresh.
814+
let mut other = contract.clone();
815+
for i in 100..140u8 {
816+
other.set_id(Identifier::from([i; 32]));
817+
drive
818+
.apply_contract(
819+
&other,
820+
BlockInfo::default(),
821+
true,
822+
StorageFlags::optional_default_as_cow(),
823+
Some(&transaction),
824+
platform_version,
825+
)
826+
.expect("expected to apply contract successfully");
827+
}
828+
829+
// The billed read of the updated contract, served from the block cache.
830+
let (hit_fee, hit) = drive
831+
.get_contract_with_fetch_info_and_fee(
832+
contract_id,
833+
Some(&epoch),
834+
true,
835+
Some(&transaction),
836+
platform_version,
837+
)
838+
.expect("expected the read to succeed");
839+
let hit = hit.expect("expected the contract");
840+
assert_eq!(hit.contract.version(), 2);
841+
// The read must be served by the entry the refresh seeded, not by a cold fetch that
842+
// would make this test compare two cold reads.
843+
assert!(
844+
Arc::ptr_eq(&seeded, &hit),
845+
"protocol version {}: the billed read must be the refresh-seeded cache hit",
846+
platform_version.protocol_version
847+
);
848+
849+
// A cold read of the same contract through the same transaction.
850+
let cold = drive
851+
.fetch_contract_and_add_operations(
852+
contract_id,
853+
Some(&epoch),
854+
Some(&transaction),
855+
&mut vec![],
856+
platform_version,
857+
)
858+
.expect("expected the read to succeed")
859+
.expect("expected the contract");
860+
861+
assert_eq!(
862+
hit.cost, cold.cost,
863+
"protocol version {}: the cached cost must equal a cold read's cost",
864+
platform_version.protocol_version
865+
);
866+
assert_eq!(
867+
hit_fee, cold.fee,
868+
"protocol version {}: the cached read must bill like a cold read",
869+
platform_version.protocol_version
870+
);
871+
}
872+
}
722873
}

0 commit comments

Comments
 (0)