Skip to main content

ark/
rounds.rs

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