@@ -29,14 +29,15 @@ use std::io;
29
29
use amplify:: hex:: ToHex ;
30
30
use amplify:: Bytes16 ;
31
31
use commit_verify:: ReservedBytes ;
32
- use hypersonic:: expect:: Expect ;
33
32
use hypersonic:: { AuthToken , CellAddr , CodexId , ContractId , Opid , Schema , Supply } ;
33
+ use rgb:: RgbSeal ;
34
34
use single_use_seals:: { PublishedWitness , SingleUseSeal } ;
35
35
use strict_encoding:: {
36
- ReadRaw , StrictDecode , StrictDumb , StrictEncode , StrictReader , StrictWriter , WriteRaw ,
36
+ DecodeError , ReadRaw , StrictDecode , StrictDumb , StrictEncode , StrictReader , StrictWriter ,
37
+ WriteRaw ,
37
38
} ;
38
39
39
- use crate :: { ConsumeError , ContractInfo , CreateParams , Pile , StateCell , Stockpile } ;
40
+ use crate :: { Consensus , ConsumeError , ContractInfo , CreateParams , Pile , StateCell , Stockpile } ;
40
41
41
42
pub const MAGIC_BYTES_CONSIGNMENT : [ u8 ; 16 ] = * b"RGB CONSIGNMENT\0 " ;
42
43
@@ -47,25 +48,19 @@ pub trait Excavate<S: Supply, P: Pile> {
47
48
48
49
/// Mound is a collection of smart contracts which have homogenous capabilities.
49
50
pub struct Mound < S : Supply , P : Pile , X : Excavate < S , P > > {
51
+ consensus : Consensus ,
52
+ testnet : bool ,
50
53
schemata : BTreeMap < CodexId , Schema > ,
51
54
contracts : BTreeMap < ContractId , Stockpile < S , P > > ,
52
55
/// Persistence does loading of a stockpiles and their storage when a new contract is added.
53
56
persistence : X ,
54
57
}
55
58
56
- impl < S : Supply , P : Pile , X : Excavate < S , P > + Default > Default for Mound < S , P , X > {
57
- fn default ( ) -> Self {
58
- Self {
59
- schemata : BTreeMap :: new ( ) ,
60
- contracts : BTreeMap :: new ( ) ,
61
- persistence : default ! ( ) ,
62
- }
63
- }
64
- }
65
-
66
59
impl < S : Supply , P : Pile , X : Excavate < S , P > + Default > Mound < S , P , X > {
67
- pub fn new ( ) -> Self {
60
+ pub fn bitcoin_testnet ( ) -> Self {
68
61
Self {
62
+ testnet : true ,
63
+ consensus : Consensus :: Bitcoin ,
69
64
schemata : BTreeMap :: new ( ) ,
70
65
contracts : BTreeMap :: new ( ) ,
71
66
persistence : default ! ( ) ,
@@ -74,30 +69,49 @@ impl<S: Supply, P: Pile, X: Excavate<S, P> + Default> Mound<S, P, X> {
74
69
}
75
70
76
71
impl < S : Supply , P : Pile , X : Excavate < S , P > > Mound < S , P , X > {
77
- pub fn with ( persistence : X ) -> Self {
72
+ pub fn with_testnet ( consensus : Consensus , persistence : X ) -> Self {
78
73
Self {
74
+ testnet : true ,
75
+ consensus,
79
76
schemata : BTreeMap :: new ( ) ,
80
77
contracts : BTreeMap :: new ( ) ,
81
78
persistence,
82
79
}
83
80
}
84
81
85
- pub fn open ( mut persistance : X ) -> Self {
82
+ pub fn open_testnet ( consensus : Consensus , mut persistance : X ) -> Self {
86
83
Self {
84
+ testnet : true ,
85
+ consensus,
87
86
schemata : persistance. schemata ( ) . collect ( ) ,
88
87
contracts : persistance. contracts ( ) . collect ( ) ,
89
88
persistence : persistance,
90
89
}
91
90
}
92
91
93
- pub fn issue ( & mut self , params : CreateParams < P :: Seal > , supply : S , pile : P ) -> ContractId {
92
+ pub fn issue (
93
+ & mut self ,
94
+ params : CreateParams < P :: Seal > ,
95
+ supply : S ,
96
+ pile : P ,
97
+ ) -> Result < ContractId , IssueError > {
98
+ if params. consensus != self . consensus {
99
+ return Err ( IssueError :: ConsensusMismatch ) ;
100
+ }
101
+ if params. testnet != self . testnet {
102
+ return Err ( if params. testnet {
103
+ IssueError :: TestnetMismatch
104
+ } else {
105
+ IssueError :: MainnetMismatch
106
+ } ) ;
107
+ }
94
108
let schema = self
95
109
. schema ( params. codex_id )
96
- . expect_or_else ( || format ! ( "Unknown codex `{}`" , params. codex_id) ) ;
110
+ . ok_or ( IssueError :: UnknownCodex ( params. codex_id ) ) ? ;
97
111
let stockpile = Stockpile :: issue ( schema. clone ( ) , params, supply, pile) ;
98
112
let id = stockpile. contract_id ( ) ;
99
113
self . contracts . insert ( id, stockpile) ;
100
- id
114
+ Ok ( id )
101
115
}
102
116
103
117
pub fn codex_ids ( & self ) -> impl Iterator < Item = CodexId > + use < ' _ , S , P , X > {
@@ -184,29 +198,57 @@ impl<S: Supply, P: Pile, X: Excavate<S, P>> Mound<S, P, X> {
184
198
& mut self ,
185
199
reader : & mut StrictReader < impl ReadRaw > ,
186
200
seal_resolver : impl FnMut ( & [ StateCell ] ) -> Vec < P :: Seal > ,
187
- ) -> Result < ( ) , ConsumeError < P :: Seal > >
201
+ ) -> Result < ( ) , MoundConsumeError < P :: Seal > >
188
202
where
189
203
<P :: Seal as SingleUseSeal >:: CliWitness : StrictDecode ,
190
204
<P :: Seal as SingleUseSeal >:: PubWitness : StrictDecode ,
191
205
<<P :: Seal as SingleUseSeal >:: PubWitness as PublishedWitness < P :: Seal > >:: PubId : StrictDecode ,
192
206
{
193
207
let magic_bytes = Bytes16 :: strict_decode ( reader) ?;
194
208
if magic_bytes. to_byte_array ( ) != MAGIC_BYTES_CONSIGNMENT {
195
- return Err ( ConsumeError :: UnrecognizedMagic ( magic_bytes. to_hex ( ) ) ) ;
209
+ return Err ( MoundConsumeError :: UnrecognizedMagic ( magic_bytes. to_hex ( ) ) ) ;
196
210
}
197
211
// Version
198
212
ReservedBytes :: < 2 > :: strict_decode ( reader) ?;
199
213
let contract_id = ContractId :: strict_decode ( reader) ?;
200
214
let contract = if self . has_contract ( contract_id) {
201
215
self . contract_mut ( contract_id)
202
216
} else {
203
- // TODO: Create new contract
204
- todo ! ( )
217
+ return Err ( MoundConsumeError :: UnknownContract ( contract_id) ) ;
205
218
} ;
206
- contract. consume ( reader, seal_resolver)
219
+ contract
220
+ . consume ( reader, seal_resolver)
221
+ . map_err ( MoundConsumeError :: Inner )
207
222
}
208
223
}
209
224
225
+ #[ derive( Copy , Clone , Ord , PartialOrd , Eq , PartialEq , Hash , Debug , Display , Error ) ]
226
+ #[ display( doc_comments) ]
227
+ pub enum IssueError {
228
+ /// proof of publication layer mismatch.
229
+ ConsensusMismatch ,
230
+ /// unable to consume a testnet contract for mainnet.
231
+ TestnetMismatch ,
232
+ /// unable to consume a mainnet contract for testnet.
233
+ MainnetMismatch ,
234
+ /// unknown codex for contract issue {0}.
235
+ UnknownCodex ( CodexId ) ,
236
+ }
237
+
238
+ #[ derive( Display , From ) ]
239
+ #[ display( doc_comments) ]
240
+ pub enum MoundConsumeError < Seal : RgbSeal > {
241
+ /// unrecognized magic bytes in consignment stream ({0})
242
+ UnrecognizedMagic ( String ) ,
243
+
244
+ /// unknown {0} can't be consumed; please import contract articles first.
245
+ UnknownContract ( ContractId ) ,
246
+
247
+ #[ display( inner) ]
248
+ #[ from( DecodeError ) ]
249
+ Inner ( ConsumeError < Seal > ) ,
250
+ }
251
+
210
252
#[ cfg( feature = "fs" ) ]
211
253
pub mod file {
212
254
use std:: ffi:: OsStr ;
@@ -226,15 +268,32 @@ pub mod file {
226
268
227
269
pub struct DirExcavator < Seal : RgbSeal > {
228
270
dir : PathBuf ,
271
+ consensus : Consensus ,
272
+ testnet : bool ,
273
+ no_prefix : bool ,
229
274
_phantom : PhantomData < Seal > ,
230
275
}
231
276
232
277
impl < Seal : RgbSeal > DirExcavator < Seal > {
233
- pub fn new ( dir : PathBuf ) -> Self { Self { dir, _phantom : PhantomData } }
278
+ pub fn new ( consensus : Consensus , testnet : bool , dir : PathBuf , no_prefix : bool ) -> Self {
279
+ Self { dir, consensus, testnet, no_prefix, _phantom : PhantomData }
280
+ }
234
281
235
- fn contents ( & mut self ) -> impl Iterator < Item = ( FileType , PathBuf ) > {
236
- fs:: read_dir ( & self . dir )
237
- . expect_or_else ( || format ! ( "unable to read directory `{}`" , self . dir. display( ) ) )
282
+ fn consensus_dir ( & self ) -> PathBuf {
283
+ if self . no_prefix {
284
+ return self . dir . to_owned ( ) ;
285
+ }
286
+ let mut dir = self . dir . join ( self . consensus . to_string ( ) ) ;
287
+ if self . testnet {
288
+ dir. set_extension ( "testnet" ) ;
289
+ }
290
+ dir
291
+ }
292
+
293
+ fn contents ( & mut self , top : bool ) -> impl Iterator < Item = ( FileType , PathBuf ) > {
294
+ let dir =
295
+ if top { fs:: read_dir ( & self . dir ) } else { fs:: read_dir ( self . consensus_dir ( ) ) } ;
296
+ dir. expect_or_else ( || format ! ( "unable to read directory `{}`" , self . dir. display( ) ) )
238
297
. map ( |entry| {
239
298
let entry = entry. expect ( "unable to read directory" ) ;
240
299
let ty = entry. file_type ( ) . expect ( "unable to read file type" ) ;
@@ -250,17 +309,11 @@ pub mod file {
250
309
<Seal :: PubWitness as PublishedWitness < Seal > >:: PubId : Ord + From < [ u8 ; 32 ] > + Into < [ u8 ; 32 ] > ,
251
310
{
252
311
fn schemata ( & mut self ) -> impl Iterator < Item = ( CodexId , Schema ) > {
253
- self . contents ( ) . filter_map ( |( ty, path) | {
312
+ self . contents ( true ) . filter_map ( |( ty, path) | {
254
313
if ty. is_file ( ) && path. extension ( ) . and_then ( OsStr :: to_str) == Some ( "issuer" ) {
255
314
Schema :: load ( path)
256
315
. ok ( )
257
316
. map ( |schema| ( schema. codex . codex_id ( ) , schema) )
258
- } else if ty. is_dir ( )
259
- && path. extension ( ) . and_then ( OsStr :: to_str) == Some ( "contract" )
260
- {
261
- let contract = Stockpile :: < FileSupply , FilePile < Seal > > :: load ( path) ;
262
- let schema = contract. stock ( ) . articles ( ) . schema . clone ( ) ;
263
- Some ( ( schema. codex . codex_id ( ) , schema) )
264
317
} else {
265
318
None
266
319
}
@@ -270,13 +323,15 @@ pub mod file {
270
323
fn contracts (
271
324
& mut self ,
272
325
) -> impl Iterator < Item = ( ContractId , Stockpile < FileSupply , FilePile < Seal > > ) > {
273
- self . contents ( ) . filter_map ( |( ty, path) | {
326
+ self . contents ( false ) . filter_map ( |( ty, path) | {
274
327
if ty. is_dir ( ) && path. extension ( ) . and_then ( OsStr :: to_str) == Some ( "contract" ) {
275
328
let contract = Stockpile :: load ( path) ;
276
- Some ( ( contract. contract_id ( ) , contract) )
277
- } else {
278
- None
329
+ let meta = & contract. stock ( ) . articles ( ) . contract . meta ;
330
+ if meta. consensus == self . consensus && meta. testnet == self . testnet {
331
+ return Some ( ( contract. contract_id ( ) , contract) ) ;
332
+ }
279
333
}
334
+ None
280
335
} )
281
336
}
282
337
}
@@ -289,13 +344,16 @@ pub mod file {
289
344
Seal :: PubWitness : StrictEncode + StrictDecode ,
290
345
<Seal :: PubWitness as PublishedWitness < Seal > >:: PubId : Ord + From < [ u8 ; 32 ] > + Into < [ u8 ; 32 ] > ,
291
346
{
292
- pub fn load ( path : impl AsRef < Path > ) -> Self {
347
+ pub fn load_testnet ( consensus : Consensus , path : impl AsRef < Path > , no_prefix : bool ) -> Self {
293
348
let path = path. as_ref ( ) ;
294
- let excavator = DirExcavator :: new ( path. to_owned ( ) ) ;
295
- Self :: open ( excavator)
349
+ let excavator = DirExcavator :: new ( consensus , true , path. to_owned ( ) , no_prefix ) ;
350
+ Self :: open_testnet ( consensus , excavator)
296
351
}
297
352
298
- pub fn issue_to_file ( & mut self , params : CreateParams < Seal > ) -> ContractId {
353
+ pub fn issue_to_file (
354
+ & mut self ,
355
+ params : CreateParams < Seal > ,
356
+ ) -> Result < ContractId , IssueError > {
299
357
let supply = FileSupply :: new ( params. name . as_str ( ) , & self . persistence . dir ) ;
300
358
let pile = FilePile :: < Seal > :: new ( params. name . as_str ( ) , & self . persistence . dir ) ;
301
359
self . issue ( params, supply, pile)
0 commit comments