use std::collections::{BTreeMap, BTreeSet};
use bitcoin::psbt::raw::ProprietaryKey;
use bitcoin::{Script, TxOut};
use commit_verify::{lnpbp4, TaggedHash};
use rgb_core::bundle::NoDataError;
use rgb_core::{reveal, ContractId, MergeReveal, Node, NodeId, Transition, TransitionBundle};
use strict_encoding::{StrictDecode, StrictEncode};
use wallet::psbt;
use wallet::psbt::{Output, Psbt};
use crate::Contract;
pub const PSBT_RGB_PREFIX: &[u8] = b"RGB";
pub const PSBT_GLOBAL_RGB_CONTRACT: u8 = 0x00;
pub const PSBT_GLOBAL_RGB_TRANSITION: u8 = 0x01;
pub const PSBT_IN_RGB_CONSUMED_BY: u8 = 0x03;
pub trait ProprietaryKeyRgb {
fn rgb_contract(contract_id: ContractId) -> ProprietaryKey {
ProprietaryKey {
prefix: PSBT_RGB_PREFIX.to_vec(),
subtype: PSBT_GLOBAL_RGB_CONTRACT,
key: contract_id.to_vec(),
}
}
fn rgb_transition(node_id: NodeId) -> ProprietaryKey {
ProprietaryKey {
prefix: PSBT_RGB_PREFIX.to_vec(),
subtype: PSBT_GLOBAL_RGB_TRANSITION,
key: node_id.to_vec(),
}
}
fn rgb_in_consumed_by(contract_id: ContractId) -> ProprietaryKey {
ProprietaryKey {
prefix: PSBT_RGB_PREFIX.to_vec(),
subtype: PSBT_IN_RGB_CONSUMED_BY,
key: contract_id.to_vec(),
}
}
}
impl ProprietaryKeyRgb for ProprietaryKey {}
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, Error, From)]
#[display(doc_comments)]
pub enum KeyError {
#[from(strict_encoding::Error)]
#[from(bitcoin::hashes::Error)]
InvalidKeyValue,
AlreadySet,
InternalMismatch(String),
UnrelatedTransitions(NodeId, NodeId, reveal::Error),
#[from(NoDataError)]
EmptyData,
}
pub trait RgbExt {
fn has_rgb_contract(&self, contract_id: ContractId) -> bool {
self.rgb_contract_ids().contains(&contract_id)
}
fn rgb_contract_ids(&self) -> BTreeSet<ContractId>;
fn rgb_contract(&self, contract_id: ContractId) -> Result<Option<Contract>, KeyError>;
fn rgb_contract_consumers(
&self,
contract_id: ContractId,
) -> Result<BTreeSet<(NodeId, u16)>, KeyError>;
fn set_rgb_contract(&mut self, contract: Contract) -> Result<(), KeyError>;
fn rgb_node_ids(&self, contract_id: ContractId) -> BTreeSet<NodeId>;
fn rgb_transitions(&self, contract_id: ContractId) -> BTreeMap<NodeId, Transition> {
self.rgb_node_ids(contract_id)
.into_iter()
.filter_map(|node_id| {
self.rgb_transition(node_id)
.ok()
.flatten()
.map(|ts| (node_id, ts))
})
.collect()
}
fn rgb_transition(&self, node_id: NodeId) -> Result<Option<Transition>, KeyError>;
fn push_rgb_transition(&mut self, transition: Transition) -> Result<bool, KeyError>;
fn rgb_bundles(&self) -> Result<BTreeMap<ContractId, TransitionBundle>, KeyError> {
self.rgb_contract_ids()
.into_iter()
.map(|contract_id| {
let mut revealed: BTreeMap<Transition, BTreeSet<u16>> = bmap!();
let mut concealed: BTreeMap<NodeId, BTreeSet<u16>> = bmap!();
for (node_id, no) in self.rgb_contract_consumers(contract_id)? {
if let Some(transition) = self.rgb_transition(node_id)? {
revealed.entry(transition).or_default().insert(no);
} else {
concealed.entry(node_id).or_default().insert(no);
}
}
let bundle = TransitionBundle::with(revealed, concealed)?;
Ok((contract_id, bundle))
})
.collect()
}
fn rgb_bundle_to_lnpbp4(&mut self) -> Result<usize, KeyError>;
}
impl RgbExt for Psbt {
fn rgb_contract_ids(&self) -> BTreeSet<ContractId> {
self.proprietary
.keys()
.filter(|prop_key| {
prop_key.prefix == PSBT_RGB_PREFIX && prop_key.subtype == PSBT_GLOBAL_RGB_CONTRACT
})
.map(|prop_key| &prop_key.key)
.filter_map(|key| ContractId::from_bytes(key).ok())
.collect()
}
fn rgb_contract(&self, contract_id: ContractId) -> Result<Option<Contract>, KeyError> {
self.proprietary
.get(&ProprietaryKey::rgb_contract(contract_id))
.map(|val| Contract::strict_deserialize(val).map_err(KeyError::from))
.transpose()
}
fn rgb_contract_consumers(
&self,
contract_id: ContractId,
) -> Result<BTreeSet<(NodeId, u16)>, KeyError> {
let mut consumers: BTreeSet<(NodeId, u16)> = bset! {};
for (no, input) in self.inputs.iter().enumerate() {
if let Some(node_id) = input.rgb_consumer(contract_id)? {
consumers.insert((node_id, no as u16));
}
}
Ok(consumers)
}
fn set_rgb_contract(&mut self, contract: Contract) -> Result<(), KeyError> {
let contract_id = contract.contract_id();
if self.has_rgb_contract(contract_id) {
return Err(KeyError::AlreadySet);
}
let serialized_contract = contract.strict_serialize().map_err(KeyError::from)?;
self.proprietary.insert(
ProprietaryKey::rgb_contract(contract_id),
serialized_contract,
);
Ok(())
}
fn rgb_node_ids(&self, contract_id: ContractId) -> BTreeSet<NodeId> {
self.inputs
.iter()
.filter_map(|input| {
input
.proprietary
.get(&ProprietaryKey::rgb_contract(contract_id))
.and_then(|val| NodeId::strict_deserialize(val).ok())
})
.collect()
}
fn rgb_transition(&self, node_id: NodeId) -> Result<Option<Transition>, KeyError> {
self.proprietary
.get(&ProprietaryKey::rgb_transition(node_id))
.map(|val| Transition::strict_deserialize(val).map_err(KeyError::from))
.transpose()
}
fn push_rgb_transition(&mut self, mut transition: Transition) -> Result<bool, KeyError> {
let node_id = transition.node_id();
let prev_transition = self.rgb_transition(node_id).ok().flatten();
if let Some(ref prev_transition) = prev_transition {
transition = transition
.merge_reveal(prev_transition.clone())
.map_err(|err| {
KeyError::UnrelatedTransitions(prev_transition.node_id(), node_id, err)
})?;
}
let serialized_transition = transition.strict_serialize()?;
self.proprietary.insert(
ProprietaryKey::rgb_transition(node_id),
serialized_transition,
);
Ok(prev_transition.is_none())
}
fn rgb_bundle_to_lnpbp4(&mut self) -> Result<usize, KeyError> {
let bundles = self.rgb_bundles()?;
let output = match self
.outputs
.iter_mut()
.find(|output| output.is_tapret_host() | output.is_opret_host())
{
Some(output) => output,
None => {
let output = Output::new(self.outputs.len(), TxOut {
value: 0,
script_pubkey: Script::new_op_return(&[0u8; 32]),
});
self.outputs.push(output);
self.outputs.last_mut().expect("just inserted")
}
};
let len = bundles.len();
for (contract_id, bundle) in bundles {
output
.set_lnpbp4_message(
lnpbp4::ProtocolId::from(contract_id),
bundle.bundle_id().into(),
)
.map_err(|_| KeyError::AlreadySet)?;
}
Ok(len)
}
}
pub trait RgbInExt {
fn rgb_consumer(&self, contract_id: ContractId) -> Result<Option<NodeId>, KeyError>;
fn set_rgb_consumer(
&mut self,
contract_id: ContractId,
node_id: NodeId,
) -> Result<bool, KeyError>;
}
impl RgbInExt for psbt::Input {
fn rgb_consumer(&self, contract_id: ContractId) -> Result<Option<NodeId>, KeyError> {
self.proprietary
.get(&ProprietaryKey::rgb_in_consumed_by(contract_id))
.map(NodeId::from_bytes)
.transpose()
.map_err(KeyError::from)
}
fn set_rgb_consumer(
&mut self,
contract_id: ContractId,
node_id: NodeId,
) -> Result<bool, KeyError> {
match self.rgb_consumer(contract_id)? {
None => {
self.proprietary.insert(
ProprietaryKey::rgb_in_consumed_by(contract_id),
node_id.as_slice().to_vec(),
);
Ok(true)
}
Some(id) if id == node_id => Ok(false),
Some(_) => Err(KeyError::AlreadySet),
}
}
}