@@ -898,4 +898,195 @@ impl DatabaseChecker {
898898
899899 Ok ( ( ) )
900900 }
901+
902+ /// Diagnostic function to analyze timestamp buffer issues during RAV generation
903+ /// This simulates the exact logic used in tap_core's Manager::collect_receipts
904+ pub async fn diagnose_timestamp_buffer (
905+ & self ,
906+ payer : & str ,
907+ identifier : & str , // collection_id for V2, allocation_id for V1
908+ buffer_seconds : u64 ,
909+ version : TapVersion ,
910+ ) -> Result < ( ) > {
911+ let normalized_payer = payer. trim_start_matches ( "0x" ) . to_lowercase ( ) ;
912+
913+ // Get current timestamp in nanoseconds (simulating tap_core logic)
914+ let current_time_ns = std:: time:: SystemTime :: now ( )
915+ . duration_since ( std:: time:: UNIX_EPOCH )
916+ . unwrap ( )
917+ . as_nanos ( ) as u64 ;
918+
919+ let buffer_ns = buffer_seconds * 1_000_000_000 ; // Convert to nanoseconds
920+ let max_timestamp_ns = current_time_ns - buffer_ns;
921+
922+ println ! ( "\n === TIMESTAMP BUFFER ANALYSIS ===" ) ;
923+ println ! ( "Current time: {} ns" , current_time_ns) ;
924+ println ! ( "Buffer: {} seconds = {} ns" , buffer_seconds, buffer_ns) ;
925+ println ! ( "Max eligible timestamp: {} ns" , max_timestamp_ns) ;
926+ println ! (
927+ "Time difference: {:.2} seconds ago" ,
928+ buffer_ns as f64 / 1_000_000_000.0
929+ ) ;
930+
931+ // Get last RAV timestamp to determine min_timestamp_ns
932+ let last_rav_timestamp = match version {
933+ TapVersion :: V2 => {
934+ sqlx:: query_scalar :: < _ , Option < BigDecimal > > (
935+ r#"
936+ SELECT MAX(timestamp_ns)
937+ FROM tap_horizon_ravs
938+ WHERE collection_id = $1 AND LOWER(payer) = $2
939+ "# ,
940+ )
941+ . bind ( identifier)
942+ . bind ( & normalized_payer)
943+ . fetch_one ( & self . pool )
944+ . await ?
945+ }
946+ TapVersion :: V1 => sqlx:: query_scalar :: < _ , Option < BigDecimal > > (
947+ r#"
948+ SELECT MAX(timestamp_ns)
949+ FROM scalar_tap_ravs
950+ WHERE allocation_id = $1 AND LOWER(sender_address) = $2
951+ "# ,
952+ )
953+ . bind ( identifier)
954+ . bind ( & normalized_payer)
955+ . fetch_optional ( & self . pool )
956+ . await ?
957+ . flatten ( ) ,
958+ } ;
959+
960+ let min_timestamp_ns = last_rav_timestamp
961+ . clone ( )
962+ . map ( |ts| ts. to_string ( ) . parse :: < u64 > ( ) . unwrap_or ( 0 ) + 1 )
963+ . unwrap_or ( 0 ) ;
964+
965+ println ! ( "Last RAV timestamp: {:?}" , last_rav_timestamp) ;
966+ println ! ( "Min eligible timestamp: {} ns" , min_timestamp_ns) ;
967+ println ! (
968+ "Eligible range: {} to {} ns" ,
969+ min_timestamp_ns, max_timestamp_ns
970+ ) ;
971+
972+ // Analyze receipts in the identifier
973+ let receipt_analysis = match version {
974+ TapVersion :: V2 => {
975+ sqlx:: query (
976+ r#"
977+ SELECT
978+ id,
979+ timestamp_ns,
980+ value,
981+ CASE
982+ WHEN timestamp_ns >= $1 AND timestamp_ns < $2 THEN 'ELIGIBLE'
983+ WHEN timestamp_ns >= $2 THEN 'TOO_RECENT'
984+ ELSE 'TOO_OLD'
985+ END as status
986+ FROM tap_horizon_receipts
987+ WHERE collection_id = $3 AND LOWER(payer) = $4
988+ ORDER BY timestamp_ns ASC
989+ "# ,
990+ )
991+ . bind ( min_timestamp_ns as i64 )
992+ . bind ( max_timestamp_ns as i64 )
993+ . bind ( identifier)
994+ . bind ( & normalized_payer)
995+ . fetch_all ( & self . pool )
996+ . await ?
997+ }
998+ TapVersion :: V1 => {
999+ sqlx:: query (
1000+ r#"
1001+ SELECT
1002+ id,
1003+ timestamp_ns,
1004+ value,
1005+ CASE
1006+ WHEN timestamp_ns >= $1 AND timestamp_ns < $2 THEN 'ELIGIBLE'
1007+ WHEN timestamp_ns >= $2 THEN 'TOO_RECENT'
1008+ ELSE 'TOO_OLD'
1009+ END as status
1010+ FROM scalar_tap_receipts
1011+ WHERE allocation_id = $3 AND LOWER(signer_address) = $4
1012+ ORDER BY timestamp_ns ASC
1013+ "# ,
1014+ )
1015+ . bind ( min_timestamp_ns as i64 )
1016+ . bind ( max_timestamp_ns as i64 )
1017+ . bind ( identifier)
1018+ . bind ( & normalized_payer)
1019+ . fetch_all ( & self . pool )
1020+ . await ?
1021+ }
1022+ } ;
1023+
1024+ let mut eligible_count = 0 ;
1025+ let mut too_recent_count = 0 ;
1026+ let mut too_old_count = 0 ;
1027+ let mut eligible_value = BigDecimal :: from_str ( "0" ) . unwrap ( ) ;
1028+ let mut too_recent_value = BigDecimal :: from_str ( "0" ) . unwrap ( ) ;
1029+
1030+ println ! ( "\n 📋 RECEIPT ANALYSIS:" ) ;
1031+ for row in & receipt_analysis {
1032+ let id: i64 = row. get ( "id" ) ;
1033+ let timestamp_ns: BigDecimal = row. get ( "timestamp_ns" ) ;
1034+ let value: BigDecimal = row. get ( "value" ) ;
1035+ let status: String = row. get ( "status" ) ;
1036+
1037+ let timestamp_u64 = timestamp_ns. to_string ( ) . parse :: < u64 > ( ) . unwrap_or ( 0 ) ;
1038+ let age_seconds = ( current_time_ns - timestamp_u64) as f64 / 1_000_000_000.0 ;
1039+
1040+ match status. as_str ( ) {
1041+ "ELIGIBLE" => {
1042+ eligible_count += 1 ;
1043+ eligible_value += & value;
1044+ }
1045+ "TOO_RECENT" => {
1046+ too_recent_count += 1 ;
1047+ too_recent_value += & value;
1048+ }
1049+ "TOO_OLD" => {
1050+ too_old_count += 1 ;
1051+ }
1052+ _ => { }
1053+ }
1054+
1055+ println ! (
1056+ " Receipt {}: {} wei, {:.2}s ago [{}]" ,
1057+ id, value, age_seconds, status
1058+ ) ;
1059+ }
1060+
1061+ println ! ( "\n 📊 SUMMARY:" ) ;
1062+ println ! (
1063+ " ELIGIBLE for RAV: {} receipts, {} wei" ,
1064+ eligible_count, eligible_value
1065+ ) ;
1066+ println ! (
1067+ " TOO RECENT (in buffer): {} receipts, {} wei" ,
1068+ too_recent_count, too_recent_value
1069+ ) ;
1070+ println ! ( " TOO OLD (before last RAV): {} receipts" , too_old_count) ;
1071+
1072+ if eligible_count == 0 && too_recent_count > 0 {
1073+ println ! ( "\n ⚠️ DIAGNOSIS: All receipts are too recent (within buffer)" ) ;
1074+ println ! (
1075+ " 💡 SOLUTION: Wait {} more seconds for receipts to exit buffer" ,
1076+ buffer_seconds
1077+ ) ;
1078+ } else if eligible_count == 0 && too_old_count > 0 {
1079+ println ! ( "\n ⚠️ DIAGNOSIS: All receipts are too old (already covered by RAV)" ) ;
1080+ println ! ( " 💡 SOLUTION: Send new receipts after the last RAV timestamp" ) ;
1081+ } else if eligible_count > 0 {
1082+ println ! (
1083+ "\n ✅ DIAGNOSIS: {} receipts are eligible for RAV generation" ,
1084+ eligible_count
1085+ ) ;
1086+ } else {
1087+ println ! ( "\n ❓ DIAGNOSIS: No receipts found for this identifier" ) ;
1088+ }
1089+
1090+ Ok ( ( ) )
1091+ }
9011092}
0 commit comments