Skip to content

Commit 53fb9b5

Browse files
committed
support consensus and testnet checks
1 parent 657bf0a commit 53fb9b5

File tree

5 files changed

+116
-55
lines changed

5 files changed

+116
-55
lines changed

cli/src/dump.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ use commit_verify::ReservedBytes;
3535
use hypersonic::aora::Aora;
3636
use hypersonic::{Articles, ContractId, FileSupply, Operation};
3737
use rgb::{
38-
ConsumeError, FilePile, Index, Pile, PublishedWitness, RgbSeal, SealWitness, Stockpile,
38+
FilePile, Index, MoundConsumeError, Pile, PublishedWitness, RgbSeal, SealWitness, Stockpile,
3939
MAGIC_BYTES_CONSIGNMENT,
4040
};
4141
use serde::{Deserialize, Serialize};
@@ -135,7 +135,7 @@ where
135135
let magic_bytes = Bytes16::strict_decode(&mut stream)?;
136136
if magic_bytes.to_byte_array() != MAGIC_BYTES_CONSIGNMENT {
137137
return Err(anyhow!(
138-
ConsumeError::<Seal>::UnrecognizedMagic(magic_bytes.to_hex()).to_string()
138+
MoundConsumeError::<Seal>::UnrecognizedMagic(magic_bytes.to_hex()).to_string()
139139
));
140140
}
141141
// Version

src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ pub mod popls;
5151
pub use bp::{Outpoint, Txid};
5252
pub use hypersonic::*;
5353
pub use info::ContractInfo;
54-
pub use mound::{Excavate, Mound, MAGIC_BYTES_CONSIGNMENT};
54+
pub use mound::{Excavate, IssueError, Mound, MoundConsumeError, MAGIC_BYTES_CONSIGNMENT};
5555
#[cfg(feature = "fs")]
5656
pub use pile::fs::FilePile;
5757
pub use pile::{Index, Pile};

src/mound.rs

+101-43
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,15 @@ use std::io;
2929
use amplify::hex::ToHex;
3030
use amplify::Bytes16;
3131
use commit_verify::ReservedBytes;
32-
use hypersonic::expect::Expect;
3332
use hypersonic::{AuthToken, CellAddr, CodexId, ContractId, Opid, Schema, Supply};
33+
use rgb::RgbSeal;
3434
use single_use_seals::{PublishedWitness, SingleUseSeal};
3535
use strict_encoding::{
36-
ReadRaw, StrictDecode, StrictDumb, StrictEncode, StrictReader, StrictWriter, WriteRaw,
36+
DecodeError, ReadRaw, StrictDecode, StrictDumb, StrictEncode, StrictReader, StrictWriter,
37+
WriteRaw,
3738
};
3839

39-
use crate::{ConsumeError, ContractInfo, CreateParams, Pile, StateCell, Stockpile};
40+
use crate::{Consensus, ConsumeError, ContractInfo, CreateParams, Pile, StateCell, Stockpile};
4041

4142
pub const MAGIC_BYTES_CONSIGNMENT: [u8; 16] = *b"RGB CONSIGNMENT\0";
4243

@@ -47,25 +48,19 @@ pub trait Excavate<S: Supply, P: Pile> {
4748

4849
/// Mound is a collection of smart contracts which have homogenous capabilities.
4950
pub struct Mound<S: Supply, P: Pile, X: Excavate<S, P>> {
51+
consensus: Consensus,
52+
testnet: bool,
5053
schemata: BTreeMap<CodexId, Schema>,
5154
contracts: BTreeMap<ContractId, Stockpile<S, P>>,
5255
/// Persistence does loading of a stockpiles and their storage when a new contract is added.
5356
persistence: X,
5457
}
5558

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-
6659
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 {
6861
Self {
62+
testnet: true,
63+
consensus: Consensus::Bitcoin,
6964
schemata: BTreeMap::new(),
7065
contracts: BTreeMap::new(),
7166
persistence: default!(),
@@ -74,30 +69,49 @@ impl<S: Supply, P: Pile, X: Excavate<S, P> + Default> Mound<S, P, X> {
7469
}
7570

7671
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 {
7873
Self {
74+
testnet: true,
75+
consensus,
7976
schemata: BTreeMap::new(),
8077
contracts: BTreeMap::new(),
8178
persistence,
8279
}
8380
}
8481

85-
pub fn open(mut persistance: X) -> Self {
82+
pub fn open_testnet(consensus: Consensus, mut persistance: X) -> Self {
8683
Self {
84+
testnet: true,
85+
consensus,
8786
schemata: persistance.schemata().collect(),
8887
contracts: persistance.contracts().collect(),
8988
persistence: persistance,
9089
}
9190
}
9291

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+
}
94108
let schema = self
95109
.schema(params.codex_id)
96-
.expect_or_else(|| format!("Unknown codex `{}`", params.codex_id));
110+
.ok_or(IssueError::UnknownCodex(params.codex_id))?;
97111
let stockpile = Stockpile::issue(schema.clone(), params, supply, pile);
98112
let id = stockpile.contract_id();
99113
self.contracts.insert(id, stockpile);
100-
id
114+
Ok(id)
101115
}
102116

103117
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> {
184198
&mut self,
185199
reader: &mut StrictReader<impl ReadRaw>,
186200
seal_resolver: impl FnMut(&[StateCell]) -> Vec<P::Seal>,
187-
) -> Result<(), ConsumeError<P::Seal>>
201+
) -> Result<(), MoundConsumeError<P::Seal>>
188202
where
189203
<P::Seal as SingleUseSeal>::CliWitness: StrictDecode,
190204
<P::Seal as SingleUseSeal>::PubWitness: StrictDecode,
191205
<<P::Seal as SingleUseSeal>::PubWitness as PublishedWitness<P::Seal>>::PubId: StrictDecode,
192206
{
193207
let magic_bytes = Bytes16::strict_decode(reader)?;
194208
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()));
196210
}
197211
// Version
198212
ReservedBytes::<2>::strict_decode(reader)?;
199213
let contract_id = ContractId::strict_decode(reader)?;
200214
let contract = if self.has_contract(contract_id) {
201215
self.contract_mut(contract_id)
202216
} else {
203-
// TODO: Create new contract
204-
todo!()
217+
return Err(MoundConsumeError::UnknownContract(contract_id));
205218
};
206-
contract.consume(reader, seal_resolver)
219+
contract
220+
.consume(reader, seal_resolver)
221+
.map_err(MoundConsumeError::Inner)
207222
}
208223
}
209224

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+
210252
#[cfg(feature = "fs")]
211253
pub mod file {
212254
use std::ffi::OsStr;
@@ -226,15 +268,32 @@ pub mod file {
226268

227269
pub struct DirExcavator<Seal: RgbSeal> {
228270
dir: PathBuf,
271+
consensus: Consensus,
272+
testnet: bool,
273+
no_prefix: bool,
229274
_phantom: PhantomData<Seal>,
230275
}
231276

232277
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+
}
234281

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()))
238297
.map(|entry| {
239298
let entry = entry.expect("unable to read directory");
240299
let ty = entry.file_type().expect("unable to read file type");
@@ -250,17 +309,11 @@ pub mod file {
250309
<Seal::PubWitness as PublishedWitness<Seal>>::PubId: Ord + From<[u8; 32]> + Into<[u8; 32]>,
251310
{
252311
fn schemata(&mut self) -> impl Iterator<Item = (CodexId, Schema)> {
253-
self.contents().filter_map(|(ty, path)| {
312+
self.contents(true).filter_map(|(ty, path)| {
254313
if ty.is_file() && path.extension().and_then(OsStr::to_str) == Some("issuer") {
255314
Schema::load(path)
256315
.ok()
257316
.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))
264317
} else {
265318
None
266319
}
@@ -270,13 +323,15 @@ pub mod file {
270323
fn contracts(
271324
&mut self,
272325
) -> impl Iterator<Item = (ContractId, Stockpile<FileSupply, FilePile<Seal>>)> {
273-
self.contents().filter_map(|(ty, path)| {
326+
self.contents(false).filter_map(|(ty, path)| {
274327
if ty.is_dir() && path.extension().and_then(OsStr::to_str) == Some("contract") {
275328
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+
}
279333
}
334+
None
280335
})
281336
}
282337
}
@@ -289,13 +344,16 @@ pub mod file {
289344
Seal::PubWitness: StrictEncode + StrictDecode,
290345
<Seal::PubWitness as PublishedWitness<Seal>>::PubId: Ord + From<[u8; 32]> + Into<[u8; 32]>,
291346
{
292-
pub fn load(path: impl AsRef<Path>) -> Self {
347+
pub fn load_testnet(consensus: Consensus, path: impl AsRef<Path>, no_prefix: bool) -> Self {
293348
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)
296351
}
297352

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> {
299357
let supply = FileSupply::new(params.name.as_str(), &self.persistence.dir);
300358
let pile = FilePile::<Seal>::new(params.name.as_str(), &self.persistence.dir);
301359
self.issue(params, supply, pile)

src/popls/bp.rs

+12-4
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ use strict_encoding::{ReadRaw, StrictDecode, StrictDeserialize, StrictReader, St
4646
use strict_types::StrictVal;
4747

4848
use crate::stockpile::{ContractState, EitherSeal};
49-
use crate::{Assignment, ConsumeError, CreateParams, Excavate, Mound, Pile};
49+
use crate::{Assignment, CreateParams, Excavate, IssueError, Mound, MoundConsumeError, Pile};
5050

5151
/// Trait abstracting specific implementation of a bitcoin wallet.
5252
pub trait WalletProvider {
@@ -287,7 +287,12 @@ impl<W: WalletProvider, S: Supply, P: Pile<Seal = TxoSeal>, X: Excavate<S, P>> B
287287

288288
pub fn unbind(self) -> (W, Mound<S, P, X>) { (self.wallet, self.mound) }
289289

290-
pub fn issue(&mut self, params: CreateParams<Outpoint>, supply: S, pile: P) -> ContractId {
290+
pub fn issue(
291+
&mut self,
292+
params: CreateParams<Outpoint>,
293+
supply: S,
294+
pile: P,
295+
) -> Result<ContractId, IssueError> {
291296
self.mound
292297
.issue(params.transform(self.noise_engine()), supply, pile)
293298
}
@@ -523,7 +528,7 @@ impl<W: WalletProvider, S: Supply, P: Pile<Seal = TxoSeal>, X: Excavate<S, P>> B
523528
pub fn consume(
524529
&mut self,
525530
reader: &mut StrictReader<impl ReadRaw>,
526-
) -> Result<(), ConsumeError<TxoSeal>> {
531+
) -> Result<(), MoundConsumeError<TxoSeal>> {
527532
self.mound.consume(reader, |cells| {
528533
self.wallet
529534
.resolve_seals(cells.iter().map(|cell| cell.auth))
@@ -548,7 +553,10 @@ pub mod file {
548553
pub type DirBarrow<W> = Barrow<W, FileSupply, FilePile<TxoSeal>, DirExcavator<TxoSeal>>;
549554

550555
impl<W: WalletProvider> DirBarrow<W> {
551-
pub fn issue_to_file(&mut self, params: CreateParams<Outpoint>) -> ContractId {
556+
pub fn issue_to_file(
557+
&mut self,
558+
params: CreateParams<Outpoint>,
559+
) -> Result<ContractId, IssueError> {
552560
// TODO: check that if the issue belongs to the wallet add it to the unspents
553561
self.mound
554562
.issue_to_file(params.transform(self.noise_engine()))

src/stockpile.rs

-5
Original file line numberDiff line numberDiff line change
@@ -312,8 +312,6 @@ impl<S: Supply, P: Pile> Stockpile<S, P> {
312312
<P::Seal as SingleUseSeal>::PubWitness: StrictDecode,
313313
<<P::Seal as SingleUseSeal>::PubWitness as PublishedWitness<P::Seal>>::PubId: StrictDecode,
314314
{
315-
// TODO: Add version
316-
317315
// We need to read articles field by field since we have to evaluate genesis separately
318316
let schema = Schema::strict_decode(stream)?;
319317
let contract_sigs = ContentSigs::strict_decode(stream)?;
@@ -421,9 +419,6 @@ impl<S: Supply, P: Pile> ContractApi<P::Seal> for Stockpile<S, P> {
421419
#[derive(Display, From)]
422420
#[display(inner)]
423421
pub enum ConsumeError<Seal: RgbSeal> {
424-
#[display("unrecognized magic bytes in consignment stream ({0})")]
425-
UnrecognizedMagic(String),
426-
427422
#[from]
428423
#[from(io::Error)]
429424
Io(IoError),

0 commit comments

Comments
 (0)