blockworx_server/
writer.rs1use ahash::HashMap;
7use blockworx_doc::{
8 commit::CommitEnvelope,
9 protocol::{Nonce, ServerMsg},
10 rev::Rev,
11 session::Host,
12};
13use tokio::sync::{mpsc, oneshot};
14
15use crate::store::Store;
16
17#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
18pub struct ConnectionId(u64);
19
20pub type Outbox = mpsc::UnboundedSender<ServerMsg>;
25
26pub enum Request {
27 Connect {
32 outbox: Outbox,
33 reply: oneshot::Sender<ConnectionId>,
34 },
35 Submit {
36 from: ConnectionId,
37 nonce: Nonce,
38 commit: CommitEnvelope,
39 },
40 Disconnect(ConnectionId),
41}
42
43pub struct Writer {
44 host: Host,
45 store: Store,
46 connections: HashMap<ConnectionId, Outbox>,
47 next_connection: u64,
48}
49
50impl Writer {
51 pub fn new(host: Host, store: Store) -> Self {
52 Self {
53 host,
54 store,
55 connections: HashMap::default(),
56 next_connection: 0,
57 }
58 }
59
60 pub async fn run(mut self, mut requests: mpsc::UnboundedReceiver<Request>) {
61 while let Some(request) = requests.recv().await {
62 match request {
63 Request::Connect { outbox, reply } => self.connect(outbox, reply),
64 Request::Submit {
65 from,
66 nonce,
67 commit,
68 } => self.submit(from, nonce, commit),
69 Request::Disconnect(id) => {
70 self.connections.remove(&id);
71 }
72 }
73 }
74 }
75
76 fn connect(&mut self, outbox: Outbox, reply: oneshot::Sender<ConnectionId>) {
77 let id = ConnectionId(self.next_connection);
78 self.next_connection += 1;
79
80 let welcome = ServerMsg::Welcome {
81 rev: self.host.rev(),
82 commits: self
83 .host
84 .commits_after(Rev::ZERO)
85 .iter()
86 .cloned()
87 .map(CommitEnvelope::CommitV1)
88 .collect(),
89 };
90 if outbox.send(welcome).is_ok() {
93 self.connections.insert(id, outbox);
94 }
95 let _ = reply.send(id);
96 }
97
98 fn submit(&mut self, from: ConnectionId, nonce: Nonce, envelope: CommitEnvelope) {
99 let CommitEnvelope::CommitV1(commit) = envelope;
100
101 let accepted = match self.host.accept(commit) {
102 Ok(accepted) => accepted,
103 Err(refusal) => {
104 self.send(
105 from,
106 &ServerMsg::Rejected {
107 nonce,
108 reason: refusal.to_string(),
109 },
110 );
111 return;
112 }
113 };
114
115 if let Err(error) = self.store.append(accepted.rev(), accepted.commit()) {
120 tracing::error!("could not append to the log: {error:#}");
121 self.send(
122 from,
123 &ServerMsg::Rejected {
124 nonce,
125 reason: "the server could not persist the commit".into(),
126 },
127 );
128 return;
129 }
130
131 let broadcast = CommitEnvelope::CommitV1(accepted.commit().clone());
132 let rev = self.host.publish(accepted);
133 self.send(from, &ServerMsg::Committed { nonce, rev });
134 for (id, outbox) in &self.connections {
135 if *id != from {
136 let _ = outbox.send(ServerMsg::Apply {
137 rev,
138 commit: broadcast.clone(),
139 });
140 }
141 }
142 }
143
144 fn send(&self, to: ConnectionId, message: &ServerMsg) {
145 if let Some(outbox) = self.connections.get(&to) {
146 let _ = outbox.send(message.clone());
147 }
148 }
149}