@@ -7,19 +7,24 @@ use std::{
77use bitcoin:: { self , hashes:: Hash as _} ;
88use fallible_iterator:: { FallibleIterator , IteratorExt } ;
99use heed:: types:: SerdeBincode ;
10+ use serde:: { Deserialize , Serialize } ;
1011use sneed:: {
1112 DatabaseUnique , EnvError , RoTxn , RwTxn , RwTxnError , UnitKey ,
1213 db:: { self , error:: Error as DbError } ,
1314 env, rwtxn,
1415} ;
1516
1617use crate :: types:: {
17- Block , BlockHash , BmmResult , Body , Header , Tip , Txid , VERSION , Version ,
18+ Address , AddressTxidKey , Block , BlockHash , BmmResult , Body ,
19+ FilledTransaction , Header , MerkleProof , Tip , Txid , VERSION , Version ,
1820 proto:: mainchain,
1921} ;
2022
2123#[ allow( clippy:: duplicated_attributes) ]
2224#[ derive( thiserror:: Error , transitive:: Transitive , Debug ) ]
25+ #[ transitive( from( db:: error:: Delete , DbError ) ) ]
26+ #[ transitive( from( db:: error:: IterInit , DbError ) ) ]
27+ #[ transitive( from( db:: error:: IterItem , DbError ) ) ]
2328#[ transitive( from( db:: error:: Put , DbError ) ) ]
2429#[ transitive( from( db:: error:: TryGet , DbError ) ) ]
2530#[ transitive( from( env:: error:: CreateDb , EnvError ) ) ]
@@ -73,6 +78,19 @@ pub enum Error {
7378 NoMainHeight ( bitcoin:: BlockHash ) ,
7479 #[ error( "no tx with txid {0}" ) ]
7580 NoTx ( Txid ) ,
81+ #[ error(
82+ "txdb filled transaction length mismatch: expected {expected}, actual {actual}"
83+ ) ]
84+ TxDbFilledTxLenMismatch { expected : usize , actual : usize } ,
85+ }
86+
87+ #[ derive( Clone , Debug , Deserialize , Serialize ) ]
88+ pub struct FilledTxEntry {
89+ pub tx : FilledTransaction ,
90+ pub merkle_proof : MerkleProof ,
91+ pub block_hash : BlockHash ,
92+ pub block_height : u32 ,
93+ pub unix_stamp : u64 ,
7694}
7795
7896#[ derive( Clone ) ]
@@ -148,11 +166,13 @@ pub struct Archive {
148166 SerdeBincode < Txid > ,
149167 SerdeBincode < BTreeMap < BlockHash , u32 > > ,
150168 > ,
169+ /// Capped tx cache, keyed by (address, txid).
170+ txdb : DatabaseUnique < AddressTxidKey , SerdeBincode < FilledTxEntry > > ,
151171 _version : DatabaseUnique < UnitKey , SerdeBincode < Version > > ,
152172}
153173
154174impl Archive {
155- pub const NUM_DBS : u32 = 14 ;
175+ pub const NUM_DBS : u32 = 15 ;
156176
157177 pub fn new ( env : & sneed:: Env ) -> Result < Self , Error > {
158178 let mut rwtxn = env. write_txn ( ) ?;
@@ -224,6 +244,7 @@ impl Archive {
224244 let total_work = DatabaseUnique :: create ( env, & mut rwtxn, "total_work" ) ?;
225245 let txid_to_inclusions =
226246 DatabaseUnique :: create ( env, & mut rwtxn, "txid_to_inclusions" ) ?;
247+ let txdb = DatabaseUnique :: create ( env, & mut rwtxn, "txdb" ) ?;
227248 rwtxn. commit ( ) ?;
228249 Ok ( Self {
229250 block_hash_to_height,
@@ -239,6 +260,7 @@ impl Archive {
239260 successors,
240261 total_work,
241262 txid_to_inclusions,
263+ txdb,
242264 _version : version,
243265 } )
244266 }
@@ -672,6 +694,125 @@ impl Archive {
672694 Ok ( inclusions)
673695 }
674696
697+ pub fn prune_txdb_block (
698+ & self ,
699+ rwtxn : & mut RwTxn ,
700+ block_hash : BlockHash ,
701+ ) -> Result < usize , Error > {
702+ let keys = {
703+ let mut keys = Vec :: new ( ) ;
704+ let mut iter = self . txdb . iter ( rwtxn) . map_err ( DbError :: from) ?;
705+ while let Some ( ( key, entry) ) = iter. next ( ) . map_err ( DbError :: from) ? {
706+ if entry. block_hash == block_hash {
707+ keys. push ( key) ;
708+ }
709+ }
710+ keys
711+ } ;
712+ for key in & keys {
713+ let _ = self . txdb . delete ( rwtxn, key) ?;
714+ }
715+ Ok ( keys. len ( ) )
716+ }
717+
718+ pub fn prune_txdb_older_than (
719+ & self ,
720+ rwtxn : & mut RwTxn ,
721+ cutoff_unix : u64 ,
722+ ) -> Result < usize , Error > {
723+ let keys = {
724+ let mut keys = Vec :: new ( ) ;
725+ let mut iter = self . txdb . iter ( rwtxn) . map_err ( DbError :: from) ?;
726+ while let Some ( ( key, entry) ) = iter. next ( ) . map_err ( DbError :: from) ? {
727+ if entry. unix_stamp < cutoff_unix {
728+ keys. push ( key) ;
729+ }
730+ }
731+ keys
732+ } ;
733+ for key in & keys {
734+ let _ = self . txdb . delete ( rwtxn, key) ?;
735+ }
736+ Ok ( keys. len ( ) )
737+ }
738+
739+ pub fn put_txdb_for_connected_block (
740+ & self ,
741+ rwtxn : & mut RwTxn ,
742+ block_hash : BlockHash ,
743+ block_height : u32 ,
744+ body : & Body ,
745+ filled_txs : & [ FilledTransaction ] ,
746+ unix_stamp : u64 ,
747+ ) -> Result < ( ) , Error > {
748+ if body. transactions . len ( ) != filled_txs. len ( ) {
749+ return Err ( Error :: TxDbFilledTxLenMismatch {
750+ expected : body. transactions . len ( ) ,
751+ actual : filled_txs. len ( ) ,
752+ } ) ;
753+ }
754+ for ( tx_index, filled_tx) in filled_txs. iter ( ) . enumerate ( ) {
755+ let txid = filled_tx. txid ( ) ;
756+ debug_assert_eq ! ( body. transactions[ tx_index] . txid( ) , txid) ;
757+ let merkle_proof = Body :: compute_tx_merkle_proof (
758+ & body. coinbase ,
759+ & body. transactions ,
760+ tx_index,
761+ ) ;
762+ debug_assert_eq ! ( merkle_proof. leaf_index, tx_index + 1 ) ;
763+
764+ let mut addresses = HashSet :: new ( ) ;
765+ addresses. extend (
766+ filled_tx. spent_utxos . iter ( ) . map ( |output| output. address ) ,
767+ ) ;
768+ addresses. extend (
769+ filled_tx
770+ . transaction
771+ . outputs
772+ . iter ( )
773+ . map ( |output| output. address ) ,
774+ ) ;
775+
776+ for address in addresses {
777+ let key = AddressTxidKey :: new ( address, txid) ;
778+ let entry = FilledTxEntry {
779+ tx : filled_tx. clone ( ) ,
780+ merkle_proof : merkle_proof. clone ( ) ,
781+ block_hash,
782+ block_height,
783+ unix_stamp,
784+ } ;
785+ self . txdb . put ( rwtxn, & key, & entry) ?;
786+ }
787+ }
788+ Ok ( ( ) )
789+ }
790+
791+ pub fn get_txdb_entries_by_address (
792+ & self ,
793+ rotxn : & RoTxn ,
794+ addresses : & HashSet < Address > ,
795+ height_threshold : u32 ,
796+ ) -> Result < Vec < FilledTxEntry > , Error > {
797+ let mut entries = Vec :: new ( ) ;
798+ for address in addresses {
799+ let start = AddressTxidKey :: start ( * address) ;
800+ let end = AddressTxidKey :: end ( * address) ;
801+ let mut iter = self
802+ . txdb
803+ . range ( rotxn, & ( start..=end) )
804+ . map_err ( DbError :: from) ?;
805+ while let Some ( ( _key, entry) ) =
806+ iter. next ( ) . map_err ( DbError :: from) ?
807+ {
808+ if entry. block_height >= height_threshold {
809+ entries. push ( entry) ;
810+ }
811+ }
812+ }
813+ Ok ( entries)
814+ }
815+
675816 /// Store a block body. The header must already exist.
676817 pub fn put_body (
677818 & self ,
0 commit comments