From 2954e697182468a82ef08d3f9d4d62c28d624aa0 Mon Sep 17 00:00:00 2001 From: Armando Dutra Date: Wed, 28 Sep 2022 20:52:05 -0300 Subject: [PATCH] add consume with reveal argument --- Cargo.lock | 1 + cli/src/command.rs | 8 ++- cli/src/opts.rs | 14 ++++- rpc/Cargo.toml | 1 + rpc/src/client.rs | 7 ++- rpc/src/lib.rs | 2 + rpc/src/messages.rs | 3 +- rpc/src/reveal.rs | 117 +++++++++++++++++++++++++++++++++++++++ shell/_rgb-cli | 2 + shell/_rgb-cli.ps1 | 2 + shell/rgb-cli.bash | 10 +++- src/bucketd/processor.rs | 113 +++++++++++++++++++++++++++++++++---- src/bucketd/service.rs | 11 ++-- src/bus/ctl.rs | 3 +- src/rgbd/service.rs | 11 +++- 15 files changed, 279 insertions(+), 26 deletions(-) create mode 100644 rpc/src/reveal.rs diff --git a/Cargo.lock b/Cargo.lock index 5474f21..06bfcad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2010,6 +2010,7 @@ version = "0.8.0" dependencies = [ "amplify", "bitcoin", + "bp-core", "colored", "internet2", "lnpbp", diff --git a/cli/src/command.rs b/cli/src/command.rs index 7f5c67d..3cdd78c 100644 --- a/cli/src/command.rs +++ b/cli/src/command.rs @@ -279,9 +279,13 @@ impl Exec for Opts { fs::write(psbt_out.unwrap_or(psbt_in), psbt_bytes)?; } - TransferCommand::Consume { force, consignment } => { + TransferCommand::Consume { + force, + consignment, + reveal, + } => { let consignment = StateTransfer::strict_file_load(&consignment)?; - let status = client.consume_transfer(consignment, force, progress)?; + let status = client.consume_transfer(consignment, force, reveal, progress)?; report_validation(status); } }, diff --git a/cli/src/opts.rs b/cli/src/opts.rs index 286485c..d82ef53 100644 --- a/cli/src/opts.rs +++ b/cli/src/opts.rs @@ -15,7 +15,7 @@ use internet2::addr::{NodeAddr, ServiceAddr}; use lnpbp::chain::Chain; use rgb::schema::TransitionType; use rgb::{Contract, ContractId, SealEndpoint}; -use rgb_rpc::RGB_NODE_RPC_ENDPOINT; +use rgb_rpc::{Reveal, RGB_NODE_RPC_ENDPOINT}; /// Command-line tool for working with RGB node #[derive(Parser, Clone, PartialEq, Eq, Debug)] @@ -185,6 +185,18 @@ pub enum TransferCommand { /// State transfer consignment send by the payee. consignment: PathBuf, + + /// Try reveal the conceal seal. + /// + /// The reveal infomration contains the outpoint and close method used + /// to generated blind utxo and blinding_factor generated + /// + /// Examples: + /// + /// tapret1st@# + /// opret1st@# + #[clap(short, long)] + reveal: Option, }, } diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 5970b63..daa331d 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -22,6 +22,7 @@ lnpbp = "0.8.0" bitcoin = "0.28.1" psbt = "0.8.4" internet2 = "0.8.3" +bp-core = { version = "0.8.0", features = ["psbt"] } microservices = { version = "0.8.10", default-features = false, features = ["client"] } serde_crate = { package = "serde", version = "1", features = ["derive"], optional = true } serde_with = { version = "1.8", optional = true } diff --git a/rpc/src/client.rs b/rpc/src/client.rs index 6c9dc54..922f3ce 100644 --- a/rpc/src/client.rs +++ b/rpc/src/client.rs @@ -24,8 +24,8 @@ use rgb::{Contract, ContractId, ContractState, ContractStateMap, SealEndpoint, S use crate::messages::{HelloReq, TransferFinalize}; use crate::{ - AcceptReq, BusMsg, ComposeReq, ContractValidity, Error, FailureCode, OutpointFilter, RpcMsg, - ServiceId, TransferReq, + AcceptReq, BusMsg, ComposeReq, ContractValidity, Error, FailureCode, OutpointFilter, Reveal, + RpcMsg, ServiceId, TransferReq, }; // We have just a single service bus (RPC), so we can use any id @@ -133,6 +133,7 @@ impl Client { self.request(RpcMsg::ConsumeContract(AcceptReq { consignment: contract, force, + reveal: None, }))?; loop { match self.response()?.failure_to_error()? { @@ -244,10 +245,12 @@ impl Client { &mut self, transfer: StateTransfer, force: bool, + reveal: Option, progress: impl Fn(String), ) -> Result { self.request(RpcMsg::ConsumeTransfer(AcceptReq { consignment: transfer, + reveal, force, }))?; loop { diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index 5a83948..0e14305 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -26,6 +26,7 @@ pub mod client; mod error; mod messages; mod service_id; +mod reveal; pub use client::Client; pub use error::{Error, FailureCode}; @@ -34,6 +35,7 @@ pub use messages::{ AcceptReq, ComposeReq, ContractValidity, HelloReq, OutpointFilter, RpcMsg, TransferFinalize, TransferReq, }; +pub use reveal::Reveal; pub use service_id::ServiceId; pub const RGB_NODE_RPC_ENDPOINT: &str = "0.0.0.0:63963"; diff --git a/rpc/src/messages.rs b/rpc/src/messages.rs index bb8072d..d46efcc 100644 --- a/rpc/src/messages.rs +++ b/rpc/src/messages.rs @@ -23,7 +23,7 @@ use rgb::{ ContractStateMap, InmemConsignment, SealEndpoint, StateTransfer, TransferConsignment, }; -use crate::FailureCode; +use crate::{FailureCode, Reveal}; /// We need this wrapper type to be compatible with RGB Node having multiple message buses #[derive(Clone, Debug, Display, From, Api)] @@ -161,6 +161,7 @@ impl OutpointFilter { pub struct AcceptReq { pub consignment: InmemConsignment, pub force: bool, + pub reveal: Option, } #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Display)] diff --git a/rpc/src/reveal.rs b/rpc/src/reveal.rs new file mode 100644 index 0000000..cafc9d1 --- /dev/null +++ b/rpc/src/reveal.rs @@ -0,0 +1,117 @@ +// RGB node providing smart contracts functionality for Bitcoin & Lightning. +// +// Written in 2022 by +// Dr. Maxim Orlovsky +// +// Copyright (C) 2022 by LNP/BP Standards Association, Switzerland. +// +// You should have received a copy of the MIT License along with this software. +// If not, see . + +use bitcoin::OutPoint; +use bp::seals::txout::CloseMethod; + +#[derive(From, PartialEq, Eq, Debug, Clone, StrictEncode, StrictDecode)] +pub struct Reveal { + /// Outpoint blinding factor (generated when the utxo blinded was created) + pub blinding_factor: u64, + + /// Locally-controlled outpoint (specified when the utxo blinded was created) + pub outpoint: OutPoint, + + /// method (specified when the utxo blinded was created) + pub close_method: CloseMethod, +} + +impl std::fmt::Display for Reveal { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}@{}#{}", self.close_method, self.outpoint, self.blinding_factor) + } +} + +/// Parses a blinding factor. +fn parse_blind(s: &str) -> Result { + s.parse().map_err(ParseRevealError::BlindingFactor) +} + +impl ::core::str::FromStr for Reveal { + type Err = ParseRevealError; + + fn from_str(s: &str) -> Result { + // 9 + 19 + 1 + 64 + 1 + 10 + if s.len() > 97 { + return Err(ParseRevealError::TooLong); + } + let find_method = s.find('@'); + if find_method == None { + return Err(ParseRevealError::Format); + } + + let colon_method = find_method.unwrap(); + if colon_method == 0 || colon_method == s.len() - 1 { + return Err(ParseRevealError::Format); + } + + let find_blind = s.find('#'); + if find_blind == None { + return Err(ParseRevealError::Format); + } + + let colon_blind = find_blind.unwrap(); + if colon_blind == 0 || colon_blind == s.len() - 1 { + return Err(ParseRevealError::Format); + } + + Ok(Reveal { + close_method: match CloseMethod::from_str(&s[..colon_method]) { + Ok(it) => it, + Err(_) => return Err(ParseRevealError::CloseMethod), + }, + outpoint: match OutPoint::from_str(&s[colon_method + 1..colon_blind]) { + Ok(it) => it, + Err(_) => return Err(ParseRevealError::Outpoint), + }, + blinding_factor: parse_blind(&s[colon_blind + 1..])?, + }) + } +} + +/// An error in parsing an OutPoint. +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum ParseRevealError { + /// Error in outpoint part. + CloseMethod, + /// Error in outpoint part. + Outpoint, + /// Error in blinding factor part. + BlindingFactor(::core::num::ParseIntError), + /// Error in general format. + Format, + /// Size exceeds max. + TooLong, +} + +impl std::fmt::Display for ParseRevealError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match *self { + ParseRevealError::CloseMethod => write!(f, "error parsing CloseMethod"), + ParseRevealError::Outpoint => write!(f, "error parsing OutPoint"), + ParseRevealError::BlindingFactor(ref e) => { + write!(f, "error parsing blinding_factor: {}", e) + } + ParseRevealError::Format => { + write!(f, "Reveal not in @: format") + } + ParseRevealError::TooLong => write!(f, "reveal should be at most 95 digits"), + } + } +} + +impl ::std::error::Error for ParseRevealError { + fn cause(&self) -> Option<&dyn ::std::error::Error> { + match *self { + ParseRevealError::BlindingFactor(ref e) => Some(e), + _ => None, + } + } +} diff --git a/shell/_rgb-cli b/shell/_rgb-cli index 7991196..55ee5d8 100644 --- a/shell/_rgb-cli +++ b/shell/_rgb-cli @@ -268,6 +268,8 @@ _arguments "${_arguments_options[@]}" \ ;; (consume) _arguments "${_arguments_options[@]}" \ +'-r+[Try reveal the conceal seal]:REVEAL: ' \ +'--reveal=[Try reveal the conceal seal]:REVEAL: ' \ '-R+[ZMQ socket for connecting daemon RPC interface]:CONNECT: ' \ '--rpc=[ZMQ socket for connecting daemon RPC interface]:CONNECT: ' \ '-n+[Blockchain to use]:CHAIN: ' \ diff --git a/shell/_rgb-cli.ps1 b/shell/_rgb-cli.ps1 index 23dcc2e..44c65e4 100644 --- a/shell/_rgb-cli.ps1 +++ b/shell/_rgb-cli.ps1 @@ -220,6 +220,8 @@ Register-ArgumentCompleter -Native -CommandName 'rgb-cli' -ScriptBlock { break } 'rgb-cli;transfer;consume' { + [CompletionResult]::new('-r', 'r', [CompletionResultType]::ParameterName, 'Try reveal the conceal seal') + [CompletionResult]::new('--reveal', 'reveal', [CompletionResultType]::ParameterName, 'Try reveal the conceal seal') [CompletionResult]::new('-R', 'R', [CompletionResultType]::ParameterName, 'ZMQ socket for connecting daemon RPC interface') [CompletionResult]::new('--rpc', 'rpc', [CompletionResultType]::ParameterName, 'ZMQ socket for connecting daemon RPC interface') [CompletionResult]::new('-n', 'n', [CompletionResultType]::ParameterName, 'Blockchain to use') diff --git a/shell/rgb-cli.bash b/shell/rgb-cli.bash index 7388045..eb6cdb8 100644 --- a/shell/rgb-cli.bash +++ b/shell/rgb-cli.bash @@ -556,12 +556,20 @@ _rgb-cli() { return 0 ;; rgb__cli__transfer__consume) - opts="-f -h -R -n -v --force --help --rpc --chain --verbose " + opts="-f -r -h -R -n -v --force --reveal --help --rpc --chain --verbose " if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 fi case "${prev}" in + --reveal) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + -r) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; --rpc) COMPREPLY=($(compgen -f "${cur}")) return 0 diff --git a/src/bucketd/processor.rs b/src/bucketd/processor.rs index 7400915..efaf06a 100644 --- a/src/bucketd/processor.rs +++ b/src/bucketd/processor.rs @@ -13,16 +13,18 @@ use std::io; use std::io::Write; use bitcoin::{OutPoint, Txid}; -use commit_verify::{lnpbp4, TaggedHash}; +use commit_verify::{lnpbp4, CommitConceal, TaggedHash}; use psbt::Psbt; use rgb::psbt::RgbExt; -use rgb::schema::TransitionType; +use rgb::schema::{OwnedRightType, TransitionType}; +use rgb::seal::Revealed; use rgb::{ - bundle, validation, Anchor, BundleId, Consignment, ConsignmentType, ContractId, ContractState, - ContractStateMap, Disclosure, Genesis, InmemConsignment, Node, NodeId, Schema, SchemaId, - SealEndpoint, StateTransfer, Transition, TransitionBundle, Validator, Validity, + bundle, validation, Anchor, Assignment, BundleId, Consignment, ConsignmentType, ContractId, + ContractState, ContractStateMap, Disclosure, Genesis, InmemConsignment, Node, NodeId, + OwnedRights, PedersenStrategy, Schema, SchemaId, SealEndpoint, StateTransfer, Transition, + TransitionBundle, TypedAssignments, Validator, Validity, }; -use rgb_rpc::{OutpointFilter, TransferFinalize}; +use rgb_rpc::{OutpointFilter, Reveal, TransferFinalize}; use storm::chunk::ChunkIdExt; use storm::{ChunkId, Container, ContainerId}; use strict_encoding::StrictDecode; @@ -113,6 +115,9 @@ pub enum FinalizeError { #[display(inner)] #[from] Anchor(bp::dbc::anchor::Error), + + #[display(inner)] + Conceal, } impl Runtime { @@ -139,13 +144,14 @@ impl Runtime { } let consignment = StateTransfer::strict_deserialize(writer.into_inner())?; - self.process_consignment(consignment, true) + self.process_consignment(consignment, true, None) } pub(super) fn process_consignment( &mut self, consignment: InmemConsignment, force: bool, + reveal: Option, ) -> Result { let contract_id = consignment.contract_id(); let id = consignment.id(); @@ -193,6 +199,33 @@ impl Runtime { self.store.store_sten(db::SCHEMATA, root_schema.schema_id(), root_schema)?; } + if let Some(Reveal { + blinding_factor, + outpoint, + close_method, + }) = reveal + { + let reveal_outpoint = Revealed { + method: close_method, + blinding: blinding_factor, + txid: Some(outpoint.txid), + vout: outpoint.vout as u32, + }; + + let concealed_seals = consignment + .endpoints() + .filter(|&&(_, seal)| reveal_outpoint.to_concealed_seal() == seal.commit_conceal()) + .clone(); + + if concealed_seals.count() == 0 { + error!( + "The provided outpoint and blinding factors does not match with outpoint from \ + the consignment" + ); + Err(DaemonError::Finalize(FinalizeError::Conceal))? + } + }; + let genesis = consignment.genesis(); debug!("Indexing genesis"); trace!("Genesis: {:?}", genesis); @@ -226,14 +259,70 @@ impl Runtime { let node_id = transition.node_id(); let transition_type = transition.transition_type(); debug!("Processing state transition {}", node_id); - trace!("State transition: {:?}", transition); - state.add_transition(witness_txid, transition); + // TODO: refactoring this and move to rgb-core + let new_transition = match reveal { + Some(Reveal { + blinding_factor, + outpoint, + close_method, + }) => { + let reveal_outpoint = Revealed { + method: close_method, + blinding: blinding_factor, + txid: Some(outpoint.txid), + vout: outpoint.vout as u32, + }; + + let mut owned_rights: BTreeMap = bmap! {}; + for (owned_type, assignments) in transition.owned_rights().iter() { + let outpoints = assignments.to_value_assignments(); + + let mut revealed_assignment: Vec> = + empty!(); + + for out in outpoints { + if out.commit_conceal().to_confidential_seal() + != reveal_outpoint.to_concealed_seal() + { + revealed_assignment.push(out); + } else { + let accept = match out.as_revealed_state() { + Some(seal) => Assignment::Revealed { + seal: reveal_outpoint, + state: *seal, + }, + _ => out, + }; + revealed_assignment.push(accept); + } + } + + owned_rights + .insert(*owned_type, TypedAssignments::Value(revealed_assignment)); + } + + let tmp: Transition = Transition::with( + transition.transition_type(), + transition.metadata().clone(), + transition.parent_public_rights().clone(), + OwnedRights::from(owned_rights), + transition.public_rights().clone(), + transition.parent_owned_rights().clone(), + ); + + tmp + } + _ => transition.to_owned(), + }; + + trace!("State transition: {:?}", new_transition); + state.add_transition(witness_txid, &new_transition); trace!("Contract state now is {:?}", state); trace!("Storing state transition data"); - revealed.insert(transition.clone(), inputs.clone()); - self.store.store_merge(db::TRANSITIONS, node_id, transition.clone())?; + revealed.insert(new_transition.clone(), inputs.clone()); + self.store.store_merge(db::TRANSITIONS, node_id, new_transition.clone())?; self.store.store_sten(db::TRANSITION_WITNESS, node_id, &witness_txid)?; trace!("Indexing transition"); @@ -246,7 +335,7 @@ impl Runtime { self.store.store_sten(db::NODE_CONTRACTS, node_id, &contract_id)?; - for seal in transition.filter_revealed_seals() { + for seal in new_transition.filter_revealed_seals() { let index_id = ChunkId::with_fixed_fragments( seal.txid.expect("seal should contain revealed txid"), seal.vout, diff --git a/src/bucketd/service.rs b/src/bucketd/service.rs index 416c2fb..869f8b3 100644 --- a/src/bucketd/service.rs +++ b/src/bucketd/service.rs @@ -30,7 +30,7 @@ use rgb::{ ConsignmentType, ContractConsignment, ContractId, InmemConsignment, SealEndpoint, StateTransfer, TransferConsignment, Validity, }; -use rgb_rpc::{OutpointFilter, RpcMsg}; +use rgb_rpc::{OutpointFilter, Reveal, RpcMsg}; use stens::AsciiString; use storm::{Chunk, Container, ContainerFullId, ContainerHeader, ContainerId, MesgId}; use storm_ext::ExtMsg as StormMsg; @@ -168,15 +168,17 @@ impl Runtime { client_id, consignment, force, + .. }) => { - self.handle_consignment(endpoints, client_id, consignment, force)?; + self.handle_consignment(endpoints, client_id, consignment, force, None)?; } CtlMsg::ProcessTransfer(ProcessReq { client_id, consignment, force, + reveal, }) => { - self.handle_consignment(endpoints, client_id, consignment, force)?; + self.handle_consignment(endpoints, client_id, consignment, force, reveal)?; } CtlMsg::ProcessTransferContainer(container_id) => { self.handle_container(endpoints, container_id)?; @@ -277,9 +279,10 @@ impl Runtime { client_id: ClientId, consignment: InmemConsignment, force: bool, + reveal: Option, ) -> Result<(), DaemonError> { let id = consignment.consensus_commit(); - match self.process_consignment(consignment, force) { + match self.process_consignment(consignment, force, reveal) { Err(err) => { let _ = self.send_rpc(endpoints, client_id, err); self.send_ctl(endpoints, ServiceId::rgbd(), CtlMsg::ProcessingFailed)? diff --git a/src/bus/ctl.rs b/src/bus/ctl.rs index 73b18e7..767a0ad 100644 --- a/src/bus/ctl.rs +++ b/src/bus/ctl.rs @@ -19,7 +19,7 @@ use rgb::{ validation, ConsignmentId, ConsignmentType, ContractConsignment, ContractId, InmemConsignment, SealEndpoint, StateTransfer, TransferConsignment, }; -use rgb_rpc::OutpointFilter; +use rgb_rpc::{OutpointFilter, Reveal}; use storm::ContainerId; /// RPC API requests over CTL message bus between RGB Node daemons. @@ -68,6 +68,7 @@ pub struct ProcessReq { pub client_id: ClientId, pub consignment: InmemConsignment, pub force: bool, + pub reveal: Option, } #[derive(Clone, Debug, Display, StrictEncode, StrictDecode)] diff --git a/src/rgbd/service.rs b/src/rgbd/service.rs index e2c4016..e8abee8 100644 --- a/src/rgbd/service.rs +++ b/src/rgbd/service.rs @@ -26,7 +26,9 @@ use rgb::schema::TransitionType; use rgb::{ Contract, ContractConsignment, ContractId, SealEndpoint, StateTransfer, TransferConsignment, }; -use rgb_rpc::{AcceptReq, ComposeReq, FailureCode, HelloReq, OutpointFilter, RpcMsg, TransferReq}; +use rgb_rpc::{ + AcceptReq, ComposeReq, FailureCode, HelloReq, OutpointFilter, Reveal, RpcMsg, TransferReq, +}; use storm::ContainerId; use storm_ext::ExtMsg as StormMsg; use storm_rpc::AddressedMsg; @@ -245,14 +247,16 @@ impl Runtime { RpcMsg::ConsumeContract(AcceptReq { consignment: contract, force, + .. }) => { self.accept_contract(endpoints, client_id, contract, force)?; } RpcMsg::ConsumeTransfer(AcceptReq { consignment: transfer, force, + reveal, }) => { - self.accept_transfer(endpoints, client_id, transfer, force)?; + self.accept_transfer(endpoints, client_id, transfer, force, reveal)?; } RpcMsg::Transfer(TransferReq { @@ -488,6 +492,7 @@ impl Runtime { client_id, consignment: contract, force, + reveal: None, })); self.pick_or_start(endpoints, client_id) } @@ -498,11 +503,13 @@ impl Runtime { client_id: ClientId, transfer: StateTransfer, force: bool, + reveal: Option, ) -> Result<(), DaemonError> { self.ctl_queue.push_back(CtlMsg::ProcessTransfer(ProcessReq { client_id, consignment: transfer, force, + reveal, })); self.pick_or_start(endpoints, client_id) }