ark/
rounds.rs

1pub use crate::challenges::RoundAttemptChallenge;
2
3use std::fmt;
4use std::str::FromStr;
5
6use bitcoin::{Transaction, Txid};
7use bitcoin::hashes::Hash as _;
8use bitcoin::hex::DisplayHex;
9use bitcoin::secp256k1::schnorr;
10
11use crate::musig;
12use crate::tree::signed::VtxoTreeSpec;
13
14/// A round tx must have at least vtxo tree and connector chain outputs.
15/// Can also have several offboard outputs on next indices.
16pub const MIN_ROUND_TX_OUTPUTS: usize = 2;
17
18/// The output index of the vtxo tree root in the round tx.
19pub const ROUND_TX_VTXO_TREE_VOUT: u32 = 0;
20
21/// Unique identifier for a round.
22///
23/// It is a simple sequence number.
24///
25/// It is used to identify a round before we have a round tx.
26/// [RoundId] should be used as soon as we have a round tx.
27#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
28pub struct RoundSeq(u64);
29
30impl RoundSeq {
31	pub const fn new(seq: u64) -> Self {
32		Self(seq)
33	}
34
35	pub fn increment(&mut self) {
36		self.0 += 1;
37	}
38
39	pub fn inner(&self) -> u64 {
40		self.0
41	}
42}
43
44impl From<u64> for RoundSeq {
45	fn from(v: u64) -> Self {
46	    Self::new(v)
47	}
48}
49
50impl From<RoundSeq> for u64 {
51	fn from(v: RoundSeq) -> u64 {
52	    v.0
53	}
54}
55
56impl fmt::Display for RoundSeq {
57	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.0) }
58}
59
60impl fmt::Debug for RoundSeq {
61	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(self, f) }
62}
63
64/// Identifier for a past round.
65///
66/// It is the txid of the round tx.
67#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
68pub struct RoundId(Txid);
69
70impl RoundId {
71	/// Create a new [RoundId] from the round tx's [Txid].
72	pub const fn new(txid: Txid) -> RoundId {
73		RoundId(txid)
74	}
75
76	pub fn from_slice(bytes: &[u8]) -> Result<RoundId, bitcoin::hashes::FromSliceError> {
77		Txid::from_slice(bytes).map(RoundId::new)
78	}
79
80	pub fn as_round_txid(&self) -> Txid {
81		self.0
82	}
83}
84
85impl From<Txid> for RoundId {
86	fn from(txid: Txid) -> RoundId {
87		RoundId::new(txid)
88	}
89}
90
91impl std::ops::Deref for RoundId {
92	type Target = Txid;
93	fn deref(&self) -> &Self::Target {
94		&self.0
95	}
96}
97
98impl fmt::Display for RoundId {
99	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
100		write!(f, "{}", self.0)
101	}
102}
103
104impl FromStr for RoundId {
105	type Err = bitcoin::hashes::hex::HexToArrayError;
106	fn from_str(s: &str) -> Result<Self, Self::Err> {
107		Txid::from_str(s).map(RoundId::new)
108	}
109}
110
111impl serde::Serialize for RoundId {
112	fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
113		if s.is_human_readable() {
114			s.collect_str(self)
115		} else {
116			s.serialize_bytes(self.as_ref())
117		}
118	}
119}
120
121impl<'de> serde::Deserialize<'de> for RoundId {
122	fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
123		struct Visitor;
124		impl<'de> serde::de::Visitor<'de> for Visitor {
125			type Value = RoundId;
126			fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127				write!(f, "a RoundId, which is a Txid")
128			}
129			fn visit_bytes<E: serde::de::Error>(self, v: &[u8]) -> Result<Self::Value, E> {
130				RoundId::from_slice(v).map_err(serde::de::Error::custom)
131			}
132			fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
133				RoundId::from_str(v).map_err(serde::de::Error::custom)
134			}
135		}
136		if d.is_human_readable() {
137			d.deserialize_str(Visitor)
138		} else {
139			d.deserialize_bytes(Visitor)
140		}
141	}
142}
143
144#[derive(Debug, Clone)]
145pub struct RoundAttempt {
146	pub round_seq: RoundSeq,
147	pub attempt_seq: usize,
148	pub challenge: RoundAttemptChallenge,
149}
150
151#[derive(Debug, Clone)]
152pub struct VtxoProposal {
153	pub round_seq: RoundSeq,
154	pub attempt_seq: usize,
155	pub unsigned_round_tx: Transaction,
156	pub vtxos_spec: VtxoTreeSpec,
157	pub cosign_agg_nonces: Vec<musig::AggregatedNonce>,
158}
159
160#[derive(Debug, Clone)]
161pub struct RoundFinished {
162	pub round_seq: RoundSeq,
163	pub attempt_seq: usize,
164	pub cosign_sigs: Vec<schnorr::Signature>,
165	pub signed_round_tx: Transaction,
166}
167
168#[derive(Debug, Clone)]
169pub struct RoundFailed {
170	pub round_seq: RoundSeq,
171}
172
173#[derive(Debug, Clone)]
174pub enum RoundEvent {
175	Attempt(RoundAttempt),
176	VtxoProposal(VtxoProposal),
177	Finished(RoundFinished),
178	Failed(RoundFailed),
179}
180
181impl RoundEvent {
182	/// String representation of the kind of event
183	pub fn kind(&self) -> &'static str {
184		match self {
185			Self::Attempt(_) => "RoundAttempt",
186			Self::VtxoProposal { .. } => "VtxoProposal",
187			Self::Finished { .. } => "Finished",
188			Self::Failed { .. } => "Failed",
189		}
190	}
191
192	pub fn round_seq(&self) -> RoundSeq {
193		match self {
194			Self::Attempt(e) => e.round_seq,
195			Self::VtxoProposal(e) => e.round_seq,
196			Self::Finished(e) => e.round_seq,
197			Self::Failed(e) => e.round_seq,
198		}
199	}
200
201	pub fn attempt_seq(&self) -> usize {
202		match self {
203			Self::Attempt(e) => e.attempt_seq,
204			Self::VtxoProposal(e) => e.attempt_seq,
205			Self::Finished(e) => e.attempt_seq,
206			Self::Failed(_) => 0,
207		}
208	}
209}
210
211/// A more concise way to display [RoundEvent].
212impl fmt::Display for RoundEvent {
213	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
214		match self {
215			Self::Attempt(RoundAttempt { round_seq, attempt_seq, challenge }) => {
216				f.debug_struct("RoundAttempt")
217					.field("round_seq", round_seq)
218					.field("attempt_seq", attempt_seq)
219					.field("challenge", &challenge.inner().as_hex())
220					.finish()
221			},
222			Self::VtxoProposal(VtxoProposal { round_seq, attempt_seq, unsigned_round_tx, .. }) => {
223				f.debug_struct("VtxoProposal")
224					.field("round_seq", round_seq)
225					.field("attempt_seq", attempt_seq)
226					.field("unsigned_round_txid", &unsigned_round_tx.compute_txid())
227					.finish()
228			},
229			Self::Finished(RoundFinished {
230				round_seq, attempt_seq, signed_round_tx, ..
231			}) => {
232				f.debug_struct("Finished")
233					.field("round_seq", round_seq)
234					.field("attempt_seq", attempt_seq)
235					.field("signed_round_txid", &signed_round_tx.compute_txid())
236					.finish()
237			},
238			Self::Failed(RoundFailed { round_seq }) => {
239				f.debug_struct("Failed")
240					.field("round_seq", round_seq)
241					.finish()
242			},
243		}
244	}
245}