@@ -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