14
14
// limitations under the License.
15
15
//
16
16
//!
17
- //! # Blockchain DB for a node
17
+ //! # Blockchain DB API for a node
18
18
//!
19
19
20
20
use std:: sync:: { Arc , RwLock } ;
21
- use std:: path:: Path ;
22
21
23
- use bitcoin:: { BitcoinHash , Network } ;
22
+ use bitcoin:: BitcoinHash ;
24
23
use bitcoin:: blockdata:: block:: BlockHeader ;
25
- use bitcoin:: blockdata:: constants:: genesis_block;
26
24
27
25
use bitcoin_hashes:: sha256d;
28
- use hammersbald:: { BitcoinAdaptor , HammersbaldAPI , persistent, transient} ;
29
26
30
27
use crate :: error:: Error ;
31
- use crate :: headercache:: { CachedHeader , HeaderCache } ;
32
- use log :: { debug , info , warn , error } ;
28
+ use crate :: headercache:: CachedHeader ;
29
+
33
30
use serde_derive:: { Serialize , Deserialize } ;
34
31
35
32
/// Shared handle to a database storing the block chain
36
33
/// protected by an RwLock
37
- pub type SharedChainDB = Arc < RwLock < ChainDB > > ;
38
-
39
- /// Database storing the block chain
40
- pub struct ChainDB {
41
- db : BitcoinAdaptor ,
42
- headercache : HeaderCache ,
43
- network : Network ,
44
- }
45
-
46
- impl ChainDB {
47
- /// Create an in-memory database instance
48
- pub fn mem ( network : Network ) -> Result < ChainDB , Error > {
49
- info ! ( "working with in memory chain db" ) ;
50
- let db = BitcoinAdaptor :: new ( transient ( 2 ) ?) ;
51
- let headercache = HeaderCache :: new ( network) ;
52
- Ok ( ChainDB { db, network, headercache } )
53
- }
34
+ pub type SharedChainDB = Arc < RwLock < Box < dyn ChainDB > > > ;
54
35
55
- /// Create or open a persistent database instance identified by the path
56
- pub fn new ( path : & Path , network : Network ) -> Result < ChainDB , Error > {
57
- let basename = path. to_str ( ) . unwrap ( ) . to_string ( ) ;
58
- let db = BitcoinAdaptor :: new ( persistent ( ( basename. clone ( ) ) . as_str ( ) , 100 , 2 ) ?) ;
59
- let headercache = HeaderCache :: new ( network) ;
60
- Ok ( ChainDB { db, network, headercache } )
61
- }
36
+ /// Blockchain DB API for a client node.
37
+ pub trait ChainDB : Send + Sync {
62
38
63
- /// Initialize caches
64
- pub fn init ( & mut self ) -> Result < ( ) , Error > {
65
- self . init_headers ( ) ?;
66
- Ok ( ( ) )
67
- }
39
+ /// Initialize caches.
40
+ fn init ( & mut self ) -> Result < ( ) , Error > ;
68
41
69
42
/// Batch updates. Updates are permanent after finishing a batch.
70
- pub fn batch ( & mut self ) -> Result < ( ) , Error > {
71
- self . db . batch ( ) ?;
72
- Ok ( ( ) )
73
- }
43
+ fn batch ( & mut self ) -> Result < ( ) , Error > ;
74
44
75
- fn init_headers ( & mut self ) -> Result < ( ) , Error > {
76
- if let Some ( tip) = self . fetch_header_tip ( ) ? {
77
- info ! ( "reading stored header chain from tip {}" , tip) ;
78
- if self . fetch_header ( & tip) ?. is_some ( ) {
79
- let mut h = tip;
80
- while let Some ( stored) = self . fetch_header ( & h) ? {
81
- debug ! ( "read stored header {}" , & stored. bitcoin_hash( ) ) ;
82
- self . headercache . add_header_unchecked ( & h, & stored) ;
83
- if stored. header . prev_blockhash != sha256d:: Hash :: default ( ) {
84
- h = stored. header . prev_blockhash ;
85
- } else {
86
- break ;
87
- }
88
- }
89
- self . headercache . reverse_trunk ( ) ;
90
- info ! ( "read {} headers" , self . headercache. len( ) ) ;
91
- } else {
92
- warn ! ( "unable to read header for tip {}" , tip) ;
93
- self . init_to_genesis ( ) ?;
94
- }
95
- } else {
96
- info ! ( "no header tip found" ) ;
97
- self . init_to_genesis ( ) ?;
98
- }
99
- Ok ( ( ) )
100
- }
45
+ /// Store a header.
46
+ fn add_header ( & mut self , header : & BlockHeader ) -> Result < Option < ( StoredHeader , Option < Vec < sha256d:: Hash > > , Option < Vec < sha256d:: Hash > > ) > , Error > ;
101
47
102
- fn init_to_genesis ( & mut self ) -> Result < ( ) , Error > {
103
- let genesis = genesis_block ( self . network ) . header ;
104
- if let Some ( ( cached, _, _) ) = self . headercache . add_header ( & genesis) ? {
105
- info ! ( "initialized with genesis header {}" , genesis. bitcoin_hash( ) ) ;
106
- self . db . put_hash_keyed ( & cached. stored ) ?;
107
- self . db . batch ( ) ?;
108
- self . store_header_tip ( & cached. bitcoin_hash ( ) ) ?;
109
- self . db . batch ( ) ?;
110
- } else {
111
- error ! ( "failed to initialize with genesis header" ) ;
112
- return Err ( Error :: NoTip ) ;
113
- }
114
- Ok ( ( ) )
115
- }
48
+ /// Return position of hash on trunk if hash is on trunk.
49
+ fn pos_on_trunk ( & self , hash : & sha256d:: Hash ) -> Option < u32 > ;
116
50
117
- /// Store a header
118
- pub fn add_header ( & mut self , header : & BlockHeader ) -> Result < Option < ( StoredHeader , Option < Vec < sha256d:: Hash > > , Option < Vec < sha256d:: Hash > > ) > , Error > {
119
- if let Some ( ( cached, unwinds, forward) ) = self . headercache . add_header ( header) ? {
120
- self . db . put_hash_keyed ( & cached. stored ) ?;
121
- if let Some ( forward) = forward. clone ( ) {
122
- if forward. len ( ) > 0 {
123
- self . store_header_tip ( forward. last ( ) . unwrap ( ) ) ?;
124
- }
125
- }
126
- return Ok ( Some ( ( cached. stored , unwinds, forward) ) ) ;
127
- }
128
- Ok ( None )
129
- }
51
+ /// Iterate trunk [from .. tip].
52
+ fn iter_trunk < ' a > ( & ' a self , from : u32 ) -> Box < dyn Iterator < Item =& ' a CachedHeader > + ' a > ;
130
53
131
- /// return position of hash on trunk if hash is on trunk
132
- pub fn pos_on_trunk ( & self , hash : & sha256d:: Hash ) -> Option < u32 > {
133
- self . headercache . pos_on_trunk ( hash)
134
- }
54
+ /// Iterate trunk [genesis .. from] in reverse order from is the tip if not specified.
55
+ fn iter_trunk_rev < ' a > ( & ' a self , from : Option < u32 > ) -> Box < dyn Iterator < Item =& ' a CachedHeader > + ' a > ;
135
56
136
- /// iterate trunk [from .. tip]
137
- pub fn iter_trunk < ' a > ( & ' a self , from : u32 ) -> impl Iterator < Item =& ' a CachedHeader > + ' a {
138
- self . headercache . iter_trunk ( from)
139
- }
57
+ /// Retrieve the id of the block/header with most work.
58
+ fn header_tip ( & self ) -> Option < CachedHeader > ;
140
59
141
- /// iterate trunk [genesis .. from] in reverse order from is the tip if not specified
142
- pub fn iter_trunk_rev < ' a > ( & ' a self , from : Option < u32 > ) -> impl Iterator < Item =& ' a CachedHeader > + ' a {
143
- self . headercache . iter_trunk_rev ( from)
144
- }
60
+ /// Fetch a header by its id from cache.
61
+ fn get_header ( & self , id : & sha256d:: Hash ) -> Option < CachedHeader > ;
145
62
146
- /// retrieve the id of the block/header with most work
147
- pub fn header_tip ( & self ) -> Option < CachedHeader > {
148
- self . headercache . tip ( )
149
- }
63
+ /// Fetch a header by its id from cache.
64
+ fn get_header_for_height ( & self , height : u32 ) -> Option < CachedHeader > ;
150
65
151
- /// Fetch a header by its id from cache
152
- pub fn get_header ( & self , id : & sha256d:: Hash ) -> Option < CachedHeader > {
153
- self . headercache . get_header ( id)
154
- }
66
+ /// Locator for getheaders message.
67
+ fn header_locators ( & self ) -> Vec < sha256d:: Hash > ;
155
68
156
- /// Fetch a header by its id from cache
157
- pub fn get_header_for_height ( & self , height : u32 ) -> Option < CachedHeader > {
158
- self . headercache . get_header_for_height ( height)
159
- }
69
+ /// Store the header id with most work.
70
+ fn store_header_tip ( & mut self , tip : & sha256d:: Hash ) -> Result < ( ) , Error > ;
160
71
161
- /// locator for getheaders message
162
- pub fn header_locators ( & self ) -> Vec < sha256d:: Hash > {
163
- self . headercache . locator_hashes ( )
164
- }
72
+ /// Find header id with most work.
73
+ fn fetch_header_tip ( & self ) -> Result < Option < sha256d:: Hash > , Error > ;
165
74
166
- /// Store the header id with most work
167
- pub fn store_header_tip ( & mut self , tip : & sha256d:: Hash ) -> Result < ( ) , Error > {
168
- self . db . put_keyed_encodable ( HEADER_TIP_KEY , tip) ?;
169
- Ok ( ( ) )
170
- }
75
+ /// Read header from the DB.
76
+ fn fetch_header ( & self , id : & sha256d:: Hash ) -> Result < Option < StoredHeader > , Error > ;
171
77
172
- /// Find header id with most work
173
- pub fn fetch_header_tip ( & self ) -> Result < Option < sha256d:: Hash > , Error > {
174
- Ok ( self . db . get_keyed_decodable :: < sha256d:: Hash > ( HEADER_TIP_KEY ) ?. map ( |( _, h) | h. clone ( ) ) )
175
- }
176
-
177
- /// Read header from the DB
178
- pub fn fetch_header ( & self , id : & sha256d:: Hash ) -> Result < Option < StoredHeader > , Error > {
179
- Ok ( self . db . get_hash_keyed :: < StoredHeader > ( id) ?. map ( |( _, header) | header) )
180
- }
181
-
182
- /// Shutdown db
183
- pub fn shutdown ( & mut self ) {
184
- self . db . shutdown ( ) ;
185
- debug ! ( "shutdown chain db" )
186
- }
78
+ /// Shutdown the DB.
79
+ fn shutdown ( & mut self ) ;
187
80
}
188
81
189
82
/// A header enriched with information about its position on the blockchain
@@ -204,45 +97,4 @@ impl BitcoinHash for StoredHeader {
204
97
}
205
98
}
206
99
207
- const HEADER_TIP_KEY : & [ u8 ] = & [ 0u8 ; 1 ] ;
208
-
209
- #[ cfg( test) ]
210
- mod test {
211
- use bitcoin:: { Network , BitcoinHash } ;
212
- use bitcoin_hashes:: sha256d:: Hash ;
213
- use bitcoin:: blockdata:: constants:: genesis_block;
214
-
215
- use crate :: chaindb:: ChainDB ;
216
-
217
- #[ test]
218
- fn init_tip_header ( ) {
219
- let network = Network :: Testnet ;
220
- let genesis_header = genesis_block ( network) . header ;
221
-
222
- let mut chaindb = ChainDB :: mem ( network) . unwrap ( ) ;
223
- chaindb. init ( ) . unwrap ( ) ;
224
- chaindb. init ( ) . unwrap ( ) ;
225
-
226
- let header_tip = chaindb. header_tip ( ) ;
227
- assert ! ( header_tip. is_some( ) , "failed to get header for tip" ) ;
228
- assert ! ( header_tip. unwrap( ) . stored. bitcoin_hash( ) . eq( & genesis_header. bitcoin_hash( ) ) )
229
- }
230
-
231
- #[ test]
232
- fn init_recover_if_missing_tip_header ( ) {
233
- let network = Network :: Testnet ;
234
- let genesis_header = genesis_block ( network) . header ;
235
-
236
- let mut chaindb = ChainDB :: mem ( network) . unwrap ( ) ;
237
- let missing_tip_header_hash: Hash = "6cfb35868c4465b7c289d7d5641563aa973db6a929655282a7bf95c8257f53ef" . parse ( ) . unwrap ( ) ;
238
- chaindb. store_header_tip ( & missing_tip_header_hash) . unwrap ( ) ;
239
-
240
- chaindb. init ( ) . unwrap ( ) ;
241
-
242
- let header_tip = chaindb. header_tip ( ) ;
243
- assert ! ( header_tip. is_some( ) , "failed to get header for tip" ) ;
244
- assert ! ( header_tip. unwrap( ) . stored. bitcoin_hash( ) . eq( & genesis_header. bitcoin_hash( ) ) )
245
- }
246
- }
247
-
248
100
0 commit comments