consensus_machines 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +29 -0
- data/Cargo.toml +23 -0
- data/LICENSE.txt +21 -0
- data/README.md +220 -0
- data/ext/consensus_machines_native/core/CHANGELOG.md +22 -0
- data/ext/consensus_machines_native/core/Cargo.toml +15 -0
- data/ext/consensus_machines_native/core/README.md +53 -0
- data/ext/consensus_machines_native/core/src/lib.rs +340 -0
- data/ext/consensus_machines_native/core/src/log.rs +81 -0
- data/ext/consensus_machines_native/extconf.rb +2 -0
- data/ext/consensus_machines_native/ffi/Cargo.toml +20 -0
- data/ext/consensus_machines_native/ffi/extconf.rb +13 -0
- data/ext/consensus_machines_native/ffi/src/init.rs +25 -0
- data/ext/consensus_machines_native/ffi/src/lib.rs +251 -0
- data/ext/consensus_machines_native/ffi/src/model.rs +84 -0
- data/ext/consensus_machines_native/ffi/src/values.rs +19 -0
- data/lib/consensus_machines/version.rb +4 -0
- data/lib/consensus_machines.rb +3 -0
- data/sig/consensus_machines.rbs +21 -0
- metadata +86 -0
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
#![no_std]
|
|
2
|
+
#![doc = include_str!("../README.md")]
|
|
3
|
+
mod log;
|
|
4
|
+
use log::Log;
|
|
5
|
+
use quorum_machines::Quorum;
|
|
6
|
+
|
|
7
|
+
/// A log position; index zero has term zero.
|
|
8
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
9
|
+
pub struct Position {
|
|
10
|
+
/// One-based index, or zero for the empty prefix.
|
|
11
|
+
pub index: usize,
|
|
12
|
+
/// Entry term, or zero for the empty prefix.
|
|
13
|
+
pub term: u64,
|
|
14
|
+
}
|
|
15
|
+
/// One immutable command identity in the bounded log.
|
|
16
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
17
|
+
pub struct Entry {
|
|
18
|
+
/// Term of the leader that created the entry.
|
|
19
|
+
pub term: u64,
|
|
20
|
+
/// Stable identity of command data stored by the runtime.
|
|
21
|
+
pub command: u64,
|
|
22
|
+
}
|
|
23
|
+
/// Raft role under one static membership.
|
|
24
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
25
|
+
#[non_exhaustive]
|
|
26
|
+
pub enum Role {
|
|
27
|
+
/// Not campaigning.
|
|
28
|
+
Follower,
|
|
29
|
+
/// Requesting votes.
|
|
30
|
+
Candidate,
|
|
31
|
+
/// Won a distinct-member majority in this term.
|
|
32
|
+
Leader,
|
|
33
|
+
}
|
|
34
|
+
/// Vote value, excluding transport/persistence success.
|
|
35
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
36
|
+
#[non_exhaustive]
|
|
37
|
+
pub enum Vote {
|
|
38
|
+
/// Vote granted.
|
|
39
|
+
Granted,
|
|
40
|
+
/// Vote denied.
|
|
41
|
+
Denied,
|
|
42
|
+
}
|
|
43
|
+
/// `RequestVote` input or a proposed outgoing request.
|
|
44
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
45
|
+
pub struct VoteRequest {
|
|
46
|
+
/// Candidate membership index.
|
|
47
|
+
pub candidate: usize,
|
|
48
|
+
/// Candidate term.
|
|
49
|
+
pub term: u64,
|
|
50
|
+
/// Candidate's final log position.
|
|
51
|
+
pub last: Position,
|
|
52
|
+
}
|
|
53
|
+
/// Proposed `RequestVote` reply; persist node state before exposing it.
|
|
54
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
55
|
+
pub struct VoteResponse {
|
|
56
|
+
/// Responder's current term.
|
|
57
|
+
pub term: u64,
|
|
58
|
+
/// Ballot.
|
|
59
|
+
pub vote: Vote,
|
|
60
|
+
}
|
|
61
|
+
/// `AppendEntries` metadata; entry data is passed as a slice.
|
|
62
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
63
|
+
pub struct AppendRequest {
|
|
64
|
+
/// Leader membership index.
|
|
65
|
+
pub leader: usize,
|
|
66
|
+
/// Leader term.
|
|
67
|
+
pub term: u64,
|
|
68
|
+
/// Position immediately preceding incoming entries.
|
|
69
|
+
pub previous: Position,
|
|
70
|
+
/// Leader's committed index.
|
|
71
|
+
pub committed: usize,
|
|
72
|
+
}
|
|
73
|
+
/// Proposed append reply, requiring durable state before acknowledgement.
|
|
74
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
75
|
+
pub struct AppendResponse {
|
|
76
|
+
/// Responder's term.
|
|
77
|
+
pub term: u64,
|
|
78
|
+
/// Last matched incoming index, or `None` for rejection.
|
|
79
|
+
pub matched: Option<usize>,
|
|
80
|
+
}
|
|
81
|
+
/// Opaque persistent state, with no stable wire/disk representation.
|
|
82
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
83
|
+
pub struct Checkpoint<const N: usize, const L: usize> {
|
|
84
|
+
members: [u64; N],
|
|
85
|
+
own: usize,
|
|
86
|
+
term: u64,
|
|
87
|
+
voted_for: Option<usize>,
|
|
88
|
+
log: Log<L>,
|
|
89
|
+
committed: usize,
|
|
90
|
+
}
|
|
91
|
+
/// Deterministic Raft transition model with bounded log and static peers.
|
|
92
|
+
#[derive(Debug)]
|
|
93
|
+
pub struct Raft<const N: usize, const L: usize> {
|
|
94
|
+
state: Checkpoint<N, L>,
|
|
95
|
+
role: Role,
|
|
96
|
+
votes: Quorum<N>,
|
|
97
|
+
matched: [usize; N],
|
|
98
|
+
}
|
|
99
|
+
impl<const N: usize, const L: usize> Raft<N, L> {
|
|
100
|
+
/// Validate unique membership and this node's membership index.
|
|
101
|
+
#[must_use]
|
|
102
|
+
pub fn new(members: [u64; N], own: usize) -> Option<Self> {
|
|
103
|
+
if own >= N {
|
|
104
|
+
return None;
|
|
105
|
+
}
|
|
106
|
+
for (index, member) in members.iter().enumerate() {
|
|
107
|
+
if members[..index].contains(member) {
|
|
108
|
+
return None;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
Some(Self {
|
|
112
|
+
state: Checkpoint {
|
|
113
|
+
members,
|
|
114
|
+
own,
|
|
115
|
+
term: 0,
|
|
116
|
+
voted_for: None,
|
|
117
|
+
log: Log::new(),
|
|
118
|
+
committed: 0,
|
|
119
|
+
},
|
|
120
|
+
role: Role::Follower,
|
|
121
|
+
votes: Quorum::majority()?,
|
|
122
|
+
matched: [0; N],
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
/// Restore newest committed state, always losing volatile leadership.
|
|
126
|
+
#[must_use]
|
|
127
|
+
pub fn restore(checkpoint: Checkpoint<N, L>) -> Option<Self> {
|
|
128
|
+
let mut node = Self::new(checkpoint.members, checkpoint.own)?;
|
|
129
|
+
node.state = checkpoint;
|
|
130
|
+
Some(node)
|
|
131
|
+
}
|
|
132
|
+
/// Capture state to durably commit before using any proposed decisions.
|
|
133
|
+
#[must_use]
|
|
134
|
+
pub fn checkpoint(&self) -> Checkpoint<N, L> {
|
|
135
|
+
self.state.clone()
|
|
136
|
+
}
|
|
137
|
+
/// Current role.
|
|
138
|
+
#[must_use]
|
|
139
|
+
pub const fn role(&self) -> Role {
|
|
140
|
+
self.role
|
|
141
|
+
}
|
|
142
|
+
/// Current term, never wrapping.
|
|
143
|
+
#[must_use]
|
|
144
|
+
pub const fn term(&self) -> u64 {
|
|
145
|
+
self.state.term
|
|
146
|
+
}
|
|
147
|
+
/// Highest committed index, not an application acknowledgement.
|
|
148
|
+
#[must_use]
|
|
149
|
+
pub const fn committed(&self) -> usize {
|
|
150
|
+
self.state.committed
|
|
151
|
+
}
|
|
152
|
+
/// Last local log position.
|
|
153
|
+
#[must_use]
|
|
154
|
+
pub fn last(&self) -> Position {
|
|
155
|
+
self.state.log.last()
|
|
156
|
+
}
|
|
157
|
+
/// Read one entry; the runtime must not apply uncommitted entries.
|
|
158
|
+
#[must_use]
|
|
159
|
+
pub const fn entry(&self, index: usize) -> Option<Entry> {
|
|
160
|
+
self.state.log.entry(index)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/// Start a new term and self-vote. Persist before broadcasting this request.
|
|
164
|
+
///
|
|
165
|
+
/// # Panics
|
|
166
|
+
/// Panics only if the validated own-member invariant is violated.
|
|
167
|
+
pub fn begin_election(&mut self) -> Option<VoteRequest> {
|
|
168
|
+
let term = self.state.term.checked_add(1)?;
|
|
169
|
+
self.state.term = term;
|
|
170
|
+
self.state.voted_for = Some(self.state.own);
|
|
171
|
+
self.role = Role::Candidate;
|
|
172
|
+
self.votes.reset();
|
|
173
|
+
assert!(self.votes.acknowledge(self.state.own));
|
|
174
|
+
if self.votes.reached() {
|
|
175
|
+
self.become_leader();
|
|
176
|
+
}
|
|
177
|
+
Some(VoteRequest {
|
|
178
|
+
candidate: self.state.own,
|
|
179
|
+
term,
|
|
180
|
+
last: self.last(),
|
|
181
|
+
})
|
|
182
|
+
}
|
|
183
|
+
/// Evaluate an authenticated `RequestVote` and propose a reply.
|
|
184
|
+
#[must_use]
|
|
185
|
+
pub fn request_vote(&mut self, request: VoteRequest) -> VoteResponse {
|
|
186
|
+
if request.candidate >= N || request.candidate == self.state.own || request.term == 0 {
|
|
187
|
+
return VoteResponse {
|
|
188
|
+
term: self.term(),
|
|
189
|
+
vote: Vote::Denied,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
if request.term > self.term() {
|
|
193
|
+
self.step_down(request.term);
|
|
194
|
+
}
|
|
195
|
+
let local = self.last();
|
|
196
|
+
let valid_position = request.last.term <= request.term
|
|
197
|
+
&& ((request.last.index == 0) == (request.last.term == 0));
|
|
198
|
+
let fresh = (request.last.term, request.last.index) >= (local.term, local.index);
|
|
199
|
+
let available = self
|
|
200
|
+
.state
|
|
201
|
+
.voted_for
|
|
202
|
+
.is_none_or(|peer| peer == request.candidate);
|
|
203
|
+
let vote = if request.term == self.term() && valid_position && fresh && available {
|
|
204
|
+
self.state.voted_for = Some(request.candidate);
|
|
205
|
+
self.role = Role::Follower;
|
|
206
|
+
Vote::Granted
|
|
207
|
+
} else {
|
|
208
|
+
Vote::Denied
|
|
209
|
+
};
|
|
210
|
+
VoteResponse {
|
|
211
|
+
term: self.term(),
|
|
212
|
+
vote,
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/// Fold one authenticated, term-correlated ballot into the election.
|
|
216
|
+
#[must_use]
|
|
217
|
+
pub fn receive_vote(&mut self, peer: usize, response: VoteResponse) -> Role {
|
|
218
|
+
if peer >= N || peer == self.state.own {
|
|
219
|
+
return self.role;
|
|
220
|
+
}
|
|
221
|
+
if response.term > self.term() {
|
|
222
|
+
self.step_down(response.term);
|
|
223
|
+
}
|
|
224
|
+
if self.role == Role::Candidate
|
|
225
|
+
&& response.term == self.term()
|
|
226
|
+
&& response.vote == Vote::Granted
|
|
227
|
+
&& self.votes.acknowledge(peer)
|
|
228
|
+
&& self.votes.reached()
|
|
229
|
+
{
|
|
230
|
+
self.become_leader();
|
|
231
|
+
}
|
|
232
|
+
self.role
|
|
233
|
+
}
|
|
234
|
+
/// Add a current-term command identity. Persist before replication/application.
|
|
235
|
+
pub fn propose(&mut self, command: u64) -> Option<usize> {
|
|
236
|
+
if self.role != Role::Leader {
|
|
237
|
+
return None;
|
|
238
|
+
}
|
|
239
|
+
let index = self.state.log.push(Entry {
|
|
240
|
+
term: self.term(),
|
|
241
|
+
command,
|
|
242
|
+
})?;
|
|
243
|
+
self.matched[self.state.own] = index;
|
|
244
|
+
self.advance_commit();
|
|
245
|
+
Some(index)
|
|
246
|
+
}
|
|
247
|
+
/// Match and append an authenticated leader prefix, never truncating commitment.
|
|
248
|
+
#[must_use]
|
|
249
|
+
pub fn append_entries(&mut self, request: AppendRequest, entries: &[Entry]) -> AppendResponse {
|
|
250
|
+
if request.leader >= N || request.leader == self.state.own || request.term == 0 {
|
|
251
|
+
return AppendResponse {
|
|
252
|
+
term: self.term(),
|
|
253
|
+
matched: None,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
if request.term > self.term() {
|
|
257
|
+
self.step_down(request.term);
|
|
258
|
+
}
|
|
259
|
+
if request.term < self.term() {
|
|
260
|
+
return AppendResponse {
|
|
261
|
+
term: self.term(),
|
|
262
|
+
matched: None,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
self.role = Role::Follower;
|
|
266
|
+
let mut previous_term = request.previous.term;
|
|
267
|
+
for entry in entries {
|
|
268
|
+
if entry.term == 0 || entry.term < previous_term || entry.term > request.term {
|
|
269
|
+
return AppendResponse {
|
|
270
|
+
term: self.term(),
|
|
271
|
+
matched: None,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
previous_term = entry.term;
|
|
275
|
+
}
|
|
276
|
+
let matched = self
|
|
277
|
+
.state
|
|
278
|
+
.log
|
|
279
|
+
.append(request.previous, entries, self.state.committed);
|
|
280
|
+
if let Some(index) = matched {
|
|
281
|
+
self.state.committed = self.state.committed.max(request.committed.min(index));
|
|
282
|
+
}
|
|
283
|
+
AppendResponse {
|
|
284
|
+
term: self.term(),
|
|
285
|
+
matched,
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
/// Record successful durable replication, authenticated and correlated by runtime.
|
|
289
|
+
#[must_use]
|
|
290
|
+
pub fn acknowledge(&mut self, peer: usize, term: u64, index: usize) -> bool {
|
|
291
|
+
if peer >= N || peer == self.state.own {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
if term > self.term() {
|
|
295
|
+
self.step_down(term);
|
|
296
|
+
}
|
|
297
|
+
if term != self.term() || self.role != Role::Leader || index > self.state.log.len() {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
self.matched[peer] = self.matched[peer].max(index);
|
|
301
|
+
self.advance_commit();
|
|
302
|
+
true
|
|
303
|
+
}
|
|
304
|
+
const fn step_down(&mut self, term: u64) {
|
|
305
|
+
self.state.term = term;
|
|
306
|
+
self.state.voted_for = None;
|
|
307
|
+
self.role = Role::Follower;
|
|
308
|
+
self.votes.reset();
|
|
309
|
+
}
|
|
310
|
+
fn become_leader(&mut self) {
|
|
311
|
+
self.role = Role::Leader;
|
|
312
|
+
self.matched = [0; N];
|
|
313
|
+
self.matched[self.state.own] = self.state.log.len();
|
|
314
|
+
self.advance_commit();
|
|
315
|
+
}
|
|
316
|
+
fn advance_commit(&mut self) {
|
|
317
|
+
for index in (self.state.committed + 1..=self.state.log.len()).rev() {
|
|
318
|
+
if self
|
|
319
|
+
.entry(index)
|
|
320
|
+
.is_none_or(|entry| entry.term != self.term())
|
|
321
|
+
{
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
let Some(mut quorum) = Quorum::<N>::majority() else {
|
|
325
|
+
return;
|
|
326
|
+
};
|
|
327
|
+
for (peer, matched) in self.matched.iter().enumerate() {
|
|
328
|
+
if *matched >= index && !quorum.acknowledge(peer) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if quorum.reached() {
|
|
333
|
+
self.state.committed = index;
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
#[cfg(test)]
|
|
340
|
+
mod tests;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
use crate::{Entry, Position};
|
|
2
|
+
|
|
3
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
4
|
+
pub struct Log<const L: usize> {
|
|
5
|
+
entries: [Option<Entry>; L],
|
|
6
|
+
len: usize,
|
|
7
|
+
}
|
|
8
|
+
impl<const L: usize> Log<L> {
|
|
9
|
+
pub(crate) const fn new() -> Self {
|
|
10
|
+
Self {
|
|
11
|
+
entries: [None; L],
|
|
12
|
+
len: 0,
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
pub(crate) const fn len(&self) -> usize {
|
|
16
|
+
self.len
|
|
17
|
+
}
|
|
18
|
+
pub(crate) const fn entry(&self, index: usize) -> Option<Entry> {
|
|
19
|
+
if index == 0 || index > self.len {
|
|
20
|
+
None
|
|
21
|
+
} else {
|
|
22
|
+
self.entries[index - 1]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
pub(crate) fn last(&self) -> Position {
|
|
26
|
+
Position {
|
|
27
|
+
index: self.len,
|
|
28
|
+
term: self.entry(self.len).map_or(0, |entry| entry.term),
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
pub(crate) const fn push(&mut self, entry: Entry) -> Option<usize> {
|
|
32
|
+
if self.len == L {
|
|
33
|
+
return None;
|
|
34
|
+
}
|
|
35
|
+
self.entries[self.len] = Some(entry);
|
|
36
|
+
self.len += 1;
|
|
37
|
+
Some(self.len)
|
|
38
|
+
}
|
|
39
|
+
pub(crate) fn append(
|
|
40
|
+
&mut self,
|
|
41
|
+
previous: Position,
|
|
42
|
+
incoming: &[Entry],
|
|
43
|
+
committed: usize,
|
|
44
|
+
) -> Option<usize> {
|
|
45
|
+
if previous.index > self.len
|
|
46
|
+
|| self.entry(previous.index).map_or(0, |entry| entry.term) != previous.term
|
|
47
|
+
{
|
|
48
|
+
return None;
|
|
49
|
+
}
|
|
50
|
+
let end = previous.index.checked_add(incoming.len())?;
|
|
51
|
+
if end > L {
|
|
52
|
+
return None;
|
|
53
|
+
}
|
|
54
|
+
let mut replace = None;
|
|
55
|
+
for (offset, entry) in incoming.iter().enumerate() {
|
|
56
|
+
let index = previous.index + offset + 1;
|
|
57
|
+
match self.entry(index) {
|
|
58
|
+
Some(old) if old.term == entry.term => {
|
|
59
|
+
// Equal term/index with different command data is an invalid history.
|
|
60
|
+
if old.command != entry.command {
|
|
61
|
+
return None;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
Some(_) if index <= committed => return None,
|
|
65
|
+
Some(_) | None => {
|
|
66
|
+
replace = Some(offset);
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if let Some(offset) = replace {
|
|
72
|
+
let keep = previous.index + offset;
|
|
73
|
+
self.entries[keep..self.len].fill(None);
|
|
74
|
+
self.len = keep;
|
|
75
|
+
for entry in &incoming[offset..] {
|
|
76
|
+
self.push(*entry)?;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
Some(end)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "consensus_machines_native"
|
|
3
|
+
version = "0.1.3"
|
|
4
|
+
edition.workspace = true
|
|
5
|
+
rust-version.workspace = true
|
|
6
|
+
license.workspace = true
|
|
7
|
+
publish = false
|
|
8
|
+
|
|
9
|
+
[lib]
|
|
10
|
+
crate-type = ["cdylib"]
|
|
11
|
+
test = false
|
|
12
|
+
doctest = false
|
|
13
|
+
|
|
14
|
+
[dependencies]
|
|
15
|
+
consensus-machines.workspace = true
|
|
16
|
+
magnus.workspace = true
|
|
17
|
+
rb-sys.workspace = true
|
|
18
|
+
|
|
19
|
+
[lints]
|
|
20
|
+
workspace = true
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'mkmf'
|
|
3
|
+
require 'pathname'
|
|
4
|
+
require 'rb_sys/mkmf'
|
|
5
|
+
create_rust_makefile('consensus_machines_native/consensus_machines_native') do |extension|
|
|
6
|
+
extension.ext_dir = Pathname(__dir__).relative_path_from(Pathname(Dir.pwd)).to_s
|
|
7
|
+
extension.profile = ENV.fetch('RB_SYS_CARGO_PROFILE', :release).to_sym
|
|
8
|
+
end
|
|
9
|
+
makefile = File.join(Dir.pwd, 'Makefile')
|
|
10
|
+
File.write(makefile, File.read(makefile).gsub(
|
|
11
|
+
/^RB_SYS_CARGO_MANIFEST_DIR \?=.*$/,
|
|
12
|
+
"RB_SYS_CARGO_MANIFEST_DIR ?= #{File.expand_path(__dir__)}"
|
|
13
|
+
))
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
//! Private initialization boundary.
|
|
2
|
+
use magnus::{Error, Ruby, function, method, prelude::*};
|
|
3
|
+
#[magnus::init]
|
|
4
|
+
fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
5
|
+
let module = ruby.define_module("ConsensusMachines")?;
|
|
6
|
+
module
|
|
7
|
+
.define_class("Checkpoint", ruby.class_object())?
|
|
8
|
+
.undef_default_alloc_func();
|
|
9
|
+
let class = module.define_class("Raft", ruby.class_object())?;
|
|
10
|
+
class.undef_default_alloc_func();
|
|
11
|
+
class.define_singleton_method("new", function!(crate::Wrapper::new, 2))?;
|
|
12
|
+
class.define_method("role", method!(crate::Wrapper::role, 0))?;
|
|
13
|
+
class.define_method("term", method!(crate::Wrapper::term, 0))?;
|
|
14
|
+
class.define_method("committed", method!(crate::Wrapper::committed, 0))?;
|
|
15
|
+
class.define_method("last", method!(crate::Wrapper::last, 0))?;
|
|
16
|
+
class.define_method("entry", method!(crate::Wrapper::entry, 1))?;
|
|
17
|
+
class.define_method("begin_election", method!(crate::Wrapper::begin_election, 0))?;
|
|
18
|
+
class.define_method("request_vote", method!(crate::Wrapper::request_vote, 4))?;
|
|
19
|
+
class.define_method("receive_vote", method!(crate::Wrapper::receive_vote, 3))?;
|
|
20
|
+
class.define_method("propose", method!(crate::Wrapper::propose, 1))?;
|
|
21
|
+
class.define_method("append_entries", method!(crate::Wrapper::append_entries, 6))?;
|
|
22
|
+
class.define_method("acknowledge", method!(crate::Wrapper::acknowledge, 3))?;
|
|
23
|
+
class.define_method("checkpoint", method!(crate::Wrapper::checkpoint, 0))?;
|
|
24
|
+
class.define_method("restore!", method!(crate::Wrapper::restore, 1))
|
|
25
|
+
}
|