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,251 @@
|
|
|
1
|
+
//! Native Ruby bridge for a bounded Raft model, not a consensus service.
|
|
2
|
+
mod init;
|
|
3
|
+
mod model;
|
|
4
|
+
mod values;
|
|
5
|
+
use consensus_machines::{AppendRequest, Entry, Position, Role, Vote, VoteRequest, VoteResponse};
|
|
6
|
+
use magnus::{Error, RArray, Ruby, Value, prelude::*, typed_data::Obj};
|
|
7
|
+
use std::{cell::RefCell, sync::Arc};
|
|
8
|
+
use values::{Id, index};
|
|
9
|
+
|
|
10
|
+
struct State {
|
|
11
|
+
node: Box<dyn model::Model>,
|
|
12
|
+
revision: u64,
|
|
13
|
+
}
|
|
14
|
+
#[magnus::wrap(class = "ConsensusMachines::Checkpoint", free_immediately, size)]
|
|
15
|
+
struct Saved {
|
|
16
|
+
owner: Arc<()>,
|
|
17
|
+
revision: u64,
|
|
18
|
+
checkpoint: Box<dyn model::Durable>,
|
|
19
|
+
}
|
|
20
|
+
#[magnus::wrap(class = "ConsensusMachines::Raft", free_immediately, size)]
|
|
21
|
+
struct Wrapper {
|
|
22
|
+
owner: Arc<()>,
|
|
23
|
+
state: RefCell<State>,
|
|
24
|
+
}
|
|
25
|
+
const fn role(role: Role) -> &'static str {
|
|
26
|
+
match role {
|
|
27
|
+
Role::Follower => "follower",
|
|
28
|
+
Role::Candidate => "candidate",
|
|
29
|
+
Role::Leader => "leader",
|
|
30
|
+
_ => "unknown",
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
impl Wrapper {
|
|
34
|
+
fn new(ruby: &Ruby, members: RArray, own: Id) -> Result<Self, Error> {
|
|
35
|
+
let count = members.len();
|
|
36
|
+
if !(1..=9).contains(&count) {
|
|
37
|
+
return Err(Error::new(
|
|
38
|
+
ruby.exception_arg_error(),
|
|
39
|
+
"membership must contain 1..9 actual members",
|
|
40
|
+
));
|
|
41
|
+
}
|
|
42
|
+
let mut ids = [0; 9];
|
|
43
|
+
for (offset, id) in ids[..count].iter_mut().enumerate() {
|
|
44
|
+
*id = members.entry::<Id>(offset.cast_signed())?.0;
|
|
45
|
+
}
|
|
46
|
+
let node = model::new(&ids[..count], index(ruby, own)?).ok_or_else(|| {
|
|
47
|
+
Error::new(
|
|
48
|
+
ruby.exception_arg_error(),
|
|
49
|
+
"members must be unique and own index must be in membership",
|
|
50
|
+
)
|
|
51
|
+
})?;
|
|
52
|
+
Ok(Self {
|
|
53
|
+
owner: Arc::new(()),
|
|
54
|
+
state: RefCell::new(State { node, revision: 0 }),
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
fn next_revision(&self, ruby: &Ruby) -> Result<u64, Error> {
|
|
58
|
+
let revision = self.state.borrow().revision.checked_add(1);
|
|
59
|
+
revision.ok_or_else(|| Error::new(ruby.exception_range_error(), "revision exhausted"))
|
|
60
|
+
}
|
|
61
|
+
/// Runs `step` against the node under the next revision, which stales every
|
|
62
|
+
/// checkpoint taken before it.
|
|
63
|
+
fn advance<T>(
|
|
64
|
+
&self,
|
|
65
|
+
ruby: &Ruby,
|
|
66
|
+
step: impl FnOnce(&mut dyn model::Model) -> T,
|
|
67
|
+
) -> Result<T, Error> {
|
|
68
|
+
let revision = self.next_revision(ruby)?;
|
|
69
|
+
let mut state = self.state.borrow_mut();
|
|
70
|
+
state.revision = revision;
|
|
71
|
+
Ok(step(state.node.as_mut()))
|
|
72
|
+
}
|
|
73
|
+
fn role(&self) -> &'static str {
|
|
74
|
+
role(self.state.borrow().node.role())
|
|
75
|
+
}
|
|
76
|
+
fn term(&self) -> u64 {
|
|
77
|
+
self.state.borrow().node.term()
|
|
78
|
+
}
|
|
79
|
+
fn committed(&self) -> usize {
|
|
80
|
+
self.state.borrow().node.committed()
|
|
81
|
+
}
|
|
82
|
+
fn last(&self) -> (usize, u64) {
|
|
83
|
+
let last = self.state.borrow().node.last();
|
|
84
|
+
(last.index, last.term)
|
|
85
|
+
}
|
|
86
|
+
fn entry(ruby: &Ruby, rb_self: &Self, position: Id) -> Result<Option<(u64, u64)>, Error> {
|
|
87
|
+
let index = index(ruby, position)?;
|
|
88
|
+
Ok(rb_self
|
|
89
|
+
.state
|
|
90
|
+
.borrow()
|
|
91
|
+
.node
|
|
92
|
+
.entry(index)
|
|
93
|
+
.map(|entry| (entry.term, entry.command)))
|
|
94
|
+
}
|
|
95
|
+
fn begin_election(
|
|
96
|
+
ruby: &Ruby,
|
|
97
|
+
rb_self: Obj<Self>,
|
|
98
|
+
) -> Result<Option<(usize, u64, usize, u64)>, Error> {
|
|
99
|
+
rb_self.check_frozen()?;
|
|
100
|
+
let request = rb_self.advance(ruby, |node| node.begin_election())?;
|
|
101
|
+
Ok(request.map(|request| {
|
|
102
|
+
(
|
|
103
|
+
request.candidate,
|
|
104
|
+
request.term,
|
|
105
|
+
request.last.index,
|
|
106
|
+
request.last.term,
|
|
107
|
+
)
|
|
108
|
+
}))
|
|
109
|
+
}
|
|
110
|
+
fn request_vote(
|
|
111
|
+
ruby: &Ruby,
|
|
112
|
+
rb_self: Obj<Self>,
|
|
113
|
+
candidate: Id,
|
|
114
|
+
term: Id,
|
|
115
|
+
last_index: Id,
|
|
116
|
+
last_term: Id,
|
|
117
|
+
) -> Result<(u64, bool), Error> {
|
|
118
|
+
rb_self.check_frozen()?;
|
|
119
|
+
let request = VoteRequest {
|
|
120
|
+
candidate: index(ruby, candidate)?,
|
|
121
|
+
term: term.0,
|
|
122
|
+
last: Position {
|
|
123
|
+
index: index(ruby, last_index)?,
|
|
124
|
+
term: last_term.0,
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
let response = rb_self.advance(ruby, |node| node.request_vote(request))?;
|
|
128
|
+
Ok((response.term, response.vote == Vote::Granted))
|
|
129
|
+
}
|
|
130
|
+
fn receive_vote(
|
|
131
|
+
ruby: &Ruby,
|
|
132
|
+
rb_self: Obj<Self>,
|
|
133
|
+
peer: Id,
|
|
134
|
+
term: Id,
|
|
135
|
+
ballot: Id,
|
|
136
|
+
) -> Result<&'static str, Error> {
|
|
137
|
+
rb_self.check_frozen()?;
|
|
138
|
+
let peer = index(ruby, peer)?;
|
|
139
|
+
let vote = match ballot.0 {
|
|
140
|
+
0 => Vote::Denied,
|
|
141
|
+
1 => Vote::Granted,
|
|
142
|
+
_ => {
|
|
143
|
+
return Err(Error::new(
|
|
144
|
+
ruby.exception_arg_error(),
|
|
145
|
+
"ballot must be Integer 0 or 1",
|
|
146
|
+
));
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
let result = rb_self.advance(ruby, |node| {
|
|
150
|
+
node.receive_vote(peer, VoteResponse { term: term.0, vote })
|
|
151
|
+
})?;
|
|
152
|
+
Ok(role(result))
|
|
153
|
+
}
|
|
154
|
+
fn propose(ruby: &Ruby, rb_self: Obj<Self>, command: Id) -> Result<Option<usize>, Error> {
|
|
155
|
+
rb_self.check_frozen()?;
|
|
156
|
+
rb_self.advance(ruby, |node| node.propose(command.0))
|
|
157
|
+
}
|
|
158
|
+
// Mirrors AppendEntries metadata without Ruby policy objects.
|
|
159
|
+
#[allow(clippy::too_many_arguments)]
|
|
160
|
+
fn append_entries(
|
|
161
|
+
ruby: &Ruby,
|
|
162
|
+
rb_self: Obj<Self>,
|
|
163
|
+
leader: Id,
|
|
164
|
+
term: Id,
|
|
165
|
+
previous_index: Id,
|
|
166
|
+
previous_term: Id,
|
|
167
|
+
committed: Id,
|
|
168
|
+
entries: RArray,
|
|
169
|
+
) -> Result<(u64, Option<usize>), Error> {
|
|
170
|
+
rb_self.check_frozen()?;
|
|
171
|
+
let count = entries.len();
|
|
172
|
+
if count > 64 {
|
|
173
|
+
return Err(Error::new(ruby.exception_arg_error(), "at most 64 entries"));
|
|
174
|
+
}
|
|
175
|
+
let mut decoded = [Entry {
|
|
176
|
+
term: 0,
|
|
177
|
+
command: 0,
|
|
178
|
+
}; 64];
|
|
179
|
+
for (offset, entry) in decoded[..count].iter_mut().enumerate() {
|
|
180
|
+
// RArray::try_convert can invoke to_ary, permitting reentrant input
|
|
181
|
+
// mutation or freezing. Nested entries must already be Arrays.
|
|
182
|
+
let value = entries.entry::<Value>(offset.cast_signed())?;
|
|
183
|
+
let pair = RArray::from_value(value)
|
|
184
|
+
.ok_or_else(|| Error::new(ruby.exception_type_error(), "entry must be an Array"))?;
|
|
185
|
+
if pair.len() != 2 {
|
|
186
|
+
return Err(Error::new(
|
|
187
|
+
ruby.exception_arg_error(),
|
|
188
|
+
"entry must be [term, command_id]",
|
|
189
|
+
));
|
|
190
|
+
}
|
|
191
|
+
*entry = Entry {
|
|
192
|
+
term: pair.entry::<Id>(0)?.0,
|
|
193
|
+
command: pair.entry::<Id>(1)?.0,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
let request = AppendRequest {
|
|
197
|
+
leader: index(ruby, leader)?,
|
|
198
|
+
term: term.0,
|
|
199
|
+
previous: Position {
|
|
200
|
+
index: index(ruby, previous_index)?,
|
|
201
|
+
term: previous_term.0,
|
|
202
|
+
},
|
|
203
|
+
committed: index(ruby, committed)?,
|
|
204
|
+
};
|
|
205
|
+
rb_self.check_frozen()?;
|
|
206
|
+
let response =
|
|
207
|
+
rb_self.advance(ruby, |node| node.append_entries(request, &decoded[..count]))?;
|
|
208
|
+
Ok((response.term, response.matched))
|
|
209
|
+
}
|
|
210
|
+
fn acknowledge(
|
|
211
|
+
ruby: &Ruby,
|
|
212
|
+
rb_self: Obj<Self>,
|
|
213
|
+
peer: Id,
|
|
214
|
+
term: Id,
|
|
215
|
+
position: Id,
|
|
216
|
+
) -> Result<bool, Error> {
|
|
217
|
+
rb_self.check_frozen()?;
|
|
218
|
+
let peer = index(ruby, peer)?;
|
|
219
|
+
let position = index(ruby, position)?;
|
|
220
|
+
rb_self.advance(ruby, |node| node.acknowledge(peer, term.0, position))
|
|
221
|
+
}
|
|
222
|
+
fn checkpoint(&self) -> Saved {
|
|
223
|
+
let (checkpoint, revision) = {
|
|
224
|
+
let state = self.state.borrow();
|
|
225
|
+
(state.node.checkpoint(), state.revision)
|
|
226
|
+
};
|
|
227
|
+
Saved {
|
|
228
|
+
owner: Arc::clone(&self.owner),
|
|
229
|
+
revision,
|
|
230
|
+
checkpoint,
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
fn restore(ruby: &Ruby, rb_self: Obj<Self>, saved: &Saved) -> Result<bool, Error> {
|
|
234
|
+
rb_self.check_frozen()?;
|
|
235
|
+
if !Arc::ptr_eq(&rb_self.owner, &saved.owner)
|
|
236
|
+
|| rb_self.state.borrow().revision != saved.revision
|
|
237
|
+
{
|
|
238
|
+
return Err(Error::new(
|
|
239
|
+
ruby.exception_arg_error(),
|
|
240
|
+
"cross-instance or stale checkpoint",
|
|
241
|
+
));
|
|
242
|
+
}
|
|
243
|
+
let revision = rb_self.next_revision(ruby)?;
|
|
244
|
+
let node = saved
|
|
245
|
+
.checkpoint
|
|
246
|
+
.restore()
|
|
247
|
+
.ok_or_else(|| Error::new(ruby.exception_arg_error(), "invalid checkpoint"))?;
|
|
248
|
+
*rb_self.state.borrow_mut() = State { node, revision };
|
|
249
|
+
Ok(true)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
//! Exact-membership type erasure; all transitions delegate to the core.
|
|
2
|
+
use consensus_machines::{
|
|
3
|
+
AppendRequest, AppendResponse, Checkpoint, Entry, Position, Raft, Role, VoteRequest,
|
|
4
|
+
VoteResponse,
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
pub trait Durable: Send {
|
|
8
|
+
fn restore(&self) -> Option<Box<dyn Model>>;
|
|
9
|
+
}
|
|
10
|
+
pub trait Model: Send {
|
|
11
|
+
fn role(&self) -> Role;
|
|
12
|
+
fn term(&self) -> u64;
|
|
13
|
+
fn committed(&self) -> usize;
|
|
14
|
+
fn last(&self) -> Position;
|
|
15
|
+
fn entry(&self, index: usize) -> Option<Entry>;
|
|
16
|
+
fn begin_election(&mut self) -> Option<VoteRequest>;
|
|
17
|
+
fn request_vote(&mut self, request: VoteRequest) -> VoteResponse;
|
|
18
|
+
fn receive_vote(&mut self, peer: usize, response: VoteResponse) -> Role;
|
|
19
|
+
fn propose(&mut self, command: u64) -> Option<usize>;
|
|
20
|
+
fn append_entries(&mut self, request: AppendRequest, entries: &[Entry]) -> AppendResponse;
|
|
21
|
+
fn acknowledge(&mut self, peer: usize, term: u64, index: usize) -> bool;
|
|
22
|
+
fn checkpoint(&self) -> Box<dyn Durable>;
|
|
23
|
+
}
|
|
24
|
+
impl<const N: usize> Durable for Checkpoint<N, 64> {
|
|
25
|
+
fn restore(&self) -> Option<Box<dyn Model>> {
|
|
26
|
+
Raft::restore(self.clone()).map(|node| Box::new(node) as Box<dyn Model>)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
impl<const N: usize> Model for Raft<N, 64> {
|
|
30
|
+
fn role(&self) -> Role {
|
|
31
|
+
self.role()
|
|
32
|
+
}
|
|
33
|
+
fn term(&self) -> u64 {
|
|
34
|
+
self.term()
|
|
35
|
+
}
|
|
36
|
+
fn committed(&self) -> usize {
|
|
37
|
+
self.committed()
|
|
38
|
+
}
|
|
39
|
+
fn last(&self) -> Position {
|
|
40
|
+
self.last()
|
|
41
|
+
}
|
|
42
|
+
fn entry(&self, index: usize) -> Option<Entry> {
|
|
43
|
+
self.entry(index)
|
|
44
|
+
}
|
|
45
|
+
fn begin_election(&mut self) -> Option<VoteRequest> {
|
|
46
|
+
self.begin_election()
|
|
47
|
+
}
|
|
48
|
+
fn request_vote(&mut self, request: VoteRequest) -> VoteResponse {
|
|
49
|
+
self.request_vote(request)
|
|
50
|
+
}
|
|
51
|
+
fn receive_vote(&mut self, peer: usize, response: VoteResponse) -> Role {
|
|
52
|
+
self.receive_vote(peer, response)
|
|
53
|
+
}
|
|
54
|
+
fn propose(&mut self, command: u64) -> Option<usize> {
|
|
55
|
+
self.propose(command)
|
|
56
|
+
}
|
|
57
|
+
fn append_entries(&mut self, request: AppendRequest, entries: &[Entry]) -> AppendResponse {
|
|
58
|
+
self.append_entries(request, entries)
|
|
59
|
+
}
|
|
60
|
+
fn acknowledge(&mut self, peer: usize, term: u64, index: usize) -> bool {
|
|
61
|
+
self.acknowledge(peer, term, index)
|
|
62
|
+
}
|
|
63
|
+
fn checkpoint(&self) -> Box<dyn Durable> {
|
|
64
|
+
Box::new(self.checkpoint())
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
fn sized<const N: usize>(members: &[u64], own: usize) -> Option<Box<dyn Model>> {
|
|
68
|
+
let members: [u64; N] = members.try_into().ok()?;
|
|
69
|
+
Raft::<N, 64>::new(members, own).map(|node| Box::new(node) as Box<dyn Model>)
|
|
70
|
+
}
|
|
71
|
+
pub fn new(members: &[u64], own: usize) -> Option<Box<dyn Model>> {
|
|
72
|
+
match members.len() {
|
|
73
|
+
1 => sized::<1>(members, own),
|
|
74
|
+
2 => sized::<2>(members, own),
|
|
75
|
+
3 => sized::<3>(members, own),
|
|
76
|
+
4 => sized::<4>(members, own),
|
|
77
|
+
5 => sized::<5>(members, own),
|
|
78
|
+
6 => sized::<6>(members, own),
|
|
79
|
+
7 => sized::<7>(members, own),
|
|
80
|
+
8 => sized::<8>(members, own),
|
|
81
|
+
9 => sized::<9>(members, own),
|
|
82
|
+
_ => None,
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//! Strict integer decoding before any state transition.
|
|
2
|
+
use magnus::{Error, Integer, Ruby, TryConvert, Value};
|
|
3
|
+
#[derive(Clone, Copy)]
|
|
4
|
+
pub struct Id(pub u64);
|
|
5
|
+
impl TryConvert for Id {
|
|
6
|
+
fn try_convert(value: Value) -> Result<Self, Error> {
|
|
7
|
+
if Integer::from_value(value).is_none() {
|
|
8
|
+
return Err(Error::new(
|
|
9
|
+
Ruby::get_with(value).exception_type_error(),
|
|
10
|
+
"expected an Integer",
|
|
11
|
+
));
|
|
12
|
+
}
|
|
13
|
+
u64::try_convert(value).map(Self)
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
pub fn index(ruby: &Ruby, id: Id) -> Result<usize, Error> {
|
|
17
|
+
usize::try_from(id.0)
|
|
18
|
+
.map_err(|_| Error::new(ruby.exception_range_error(), "index outside native range"))
|
|
19
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module ConsensusMachines
|
|
2
|
+
VERSION: String
|
|
3
|
+
class Checkpoint
|
|
4
|
+
end
|
|
5
|
+
class Raft
|
|
6
|
+
def self.new: (Array[Integer], Integer) -> Raft
|
|
7
|
+
def role: () -> String
|
|
8
|
+
def term: () -> Integer
|
|
9
|
+
def committed: () -> Integer
|
|
10
|
+
def last: () -> [Integer, Integer]
|
|
11
|
+
def entry: (Integer) -> [Integer, Integer]?
|
|
12
|
+
def begin_election: () -> [Integer, Integer, Integer, Integer]?
|
|
13
|
+
def request_vote: (Integer, Integer, Integer, Integer) -> [Integer, bool]
|
|
14
|
+
def receive_vote: (Integer, Integer, Integer) -> String
|
|
15
|
+
def propose: (Integer) -> Integer?
|
|
16
|
+
def append_entries: (Integer, Integer, Integer, Integer, Integer, Array[[Integer, Integer]]) -> [Integer, Integer?]
|
|
17
|
+
def acknowledge: (Integer, Integer, Integer) -> bool
|
|
18
|
+
def checkpoint: () -> Checkpoint
|
|
19
|
+
def restore!: (Checkpoint) -> bool
|
|
20
|
+
end
|
|
21
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: consensus_machines
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.4
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Abdelkader Boudih
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: rb_sys
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.9'
|
|
19
|
+
- - ">="
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: 0.9.130
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - "~>"
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '0.9'
|
|
29
|
+
- - ">="
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: 0.9.130
|
|
32
|
+
description: A thin bridge to an allocation-free fixed-membership Rust Raft model.
|
|
33
|
+
Not a production consensus service.
|
|
34
|
+
email:
|
|
35
|
+
- oss@seuros.com
|
|
36
|
+
executables: []
|
|
37
|
+
extensions:
|
|
38
|
+
- ext/consensus_machines_native/extconf.rb
|
|
39
|
+
extra_rdoc_files: []
|
|
40
|
+
files:
|
|
41
|
+
- CHANGELOG.md
|
|
42
|
+
- Cargo.toml
|
|
43
|
+
- LICENSE.txt
|
|
44
|
+
- README.md
|
|
45
|
+
- ext/consensus_machines_native/core/CHANGELOG.md
|
|
46
|
+
- ext/consensus_machines_native/core/Cargo.toml
|
|
47
|
+
- ext/consensus_machines_native/core/README.md
|
|
48
|
+
- ext/consensus_machines_native/core/src/lib.rs
|
|
49
|
+
- ext/consensus_machines_native/core/src/log.rs
|
|
50
|
+
- ext/consensus_machines_native/extconf.rb
|
|
51
|
+
- ext/consensus_machines_native/ffi/Cargo.toml
|
|
52
|
+
- ext/consensus_machines_native/ffi/extconf.rb
|
|
53
|
+
- ext/consensus_machines_native/ffi/src/init.rs
|
|
54
|
+
- ext/consensus_machines_native/ffi/src/lib.rs
|
|
55
|
+
- ext/consensus_machines_native/ffi/src/model.rs
|
|
56
|
+
- ext/consensus_machines_native/ffi/src/values.rs
|
|
57
|
+
- lib/consensus_machines.rb
|
|
58
|
+
- lib/consensus_machines/version.rb
|
|
59
|
+
- sig/consensus_machines.rbs
|
|
60
|
+
homepage: https://github.com/seuros/consensus_machines
|
|
61
|
+
licenses:
|
|
62
|
+
- MIT
|
|
63
|
+
metadata:
|
|
64
|
+
source_code_uri: https://github.com/seuros/consensus_machines
|
|
65
|
+
bug_tracker_uri: https://github.com/seuros/consensus_machines/issues
|
|
66
|
+
rubygems_mfa_required: 'true'
|
|
67
|
+
cargo_crate_name: consensus_machines_native
|
|
68
|
+
cargo_manifest_path: ext/consensus_machines_native/ffi/Cargo.toml
|
|
69
|
+
rdoc_options: []
|
|
70
|
+
require_paths:
|
|
71
|
+
- lib
|
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
73
|
+
requirements:
|
|
74
|
+
- - ">="
|
|
75
|
+
- !ruby/object:Gem::Version
|
|
76
|
+
version: 4.0.0
|
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0'
|
|
82
|
+
requirements: []
|
|
83
|
+
rubygems_version: 4.0.20
|
|
84
|
+
specification_version: 4
|
|
85
|
+
summary: Native bounded Raft transition model for Ruby
|
|
86
|
+
test_files: []
|