From 537723be992828a461571a2f4fb2160f944d66af Mon Sep 17 00:00:00 2001 From: Raycho Mukelov Date: Thu, 27 Jul 2023 23:49:03 +0300 Subject: [PATCH] Add Road-Map. Make more concise API definition. --- .gitignore | 8 ++ doc/RAFT_Library_Roadmap_Proposal.odt | Bin 0 -> 32020 bytes raft_consensus/raft_consensus_api.nim | 148 ++++++++++++++++++++++++++ tests/all_tests.nim | 9 ++ 4 files changed, 165 insertions(+) create mode 100644 .gitignore create mode 100644 doc/RAFT_Library_Roadmap_Proposal.odt create mode 100644 tests/all_tests.nim diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1c3fa9c --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# ignore all executable files +* +!*.* +!*/ +*.exe +*.out +nimcache/ +build/ \ No newline at end of file diff --git a/doc/RAFT_Library_Roadmap_Proposal.odt b/doc/RAFT_Library_Roadmap_Proposal.odt new file mode 100644 index 0000000000000000000000000000000000000000..f65665b8faf59d6c5e73d7e81250cd54ce70ad36 GIT binary patch literal 32020 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 N7=AdNWS8Gt{|B@)TlD|{ literal 0 HcmV?d00001 diff --git a/raft_consensus/raft_consensus_api.nim b/raft_consensus/raft_consensus_api.nim index e69de29..b669cd0 100644 --- a/raft_consensus/raft_consensus_api.nim +++ b/raft_consensus/raft_consensus_api.nim @@ -0,0 +1,148 @@ +# nim-raft-consesnsus +# Copyright (c) 2023 Status Research & Development GmbH +# Licensed under either of +# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) +# * MIT license ([LICENSE-MIT](LICENSE-MIT)) +# at your option. +# This file may not be copied, modified, or distributed except according to +# those terms. + + +# RAFT Node Public Types. +# I guess that at some point these can be moved to a separate file called raft_consensus_types.nim for example +type + # RAFT Node basic definitions + Blob* = seq[byte] + + RAFTNodeState* = enum + UNKNOWN = 0, + FOLLOWER = 1, + LEADER = 2 + + RAFTNodeId* = object # Some kind of UUID uniquely identifying every RAFT Node + RAFTNodePeers* = seq[RAFTNodeId] # List of RAFT Node Peers IDs + RAFTNodeTerm* = uint64 # RAFT Node Term Type + RAFTLogIndex* = uint64 # RAFT Node Log Index Type + + # RAFT Node basic Log definitions + RAFTNodeLogEntry* = ref object # Abstarct RAFT Node Log entry containing opaque binary data (Blob) + term*: RAFTNodeTerm + data*: Blob + + RAFTNodeLog* = ref object # Needs more elaborate definition. Probably this will be a RocksDB/MDBX/SQLite Store Wrapper etc. + log_data*: seq[RAFTNodeLogEntry] # RAFT Node Log Data + + # RAFT Node basic Messages definitions + RAFTMessageId* = object # Some Kind of UUID assigned to every RAFT Node Message, + # so it can be matched with it's coresponding response etc. + + RAFTMessageOps* = enum + REQUEST_VOTE = 0, + APPEND_LOG_ENTRY = 1, + INSTALL_SNAPSHOT = 2 # For dynamic adding of new RAFT Nodes + + RAFTMessagePayloadChecksum* = object # Checksum probably will be a SHA3 hash not sure about this at this point + RAFTMessagePayload* = ref object + data*: RAFTNodeLogEntry + checksum*: RAFTMessagePayloadChecksum + + RAFTMessageBase* = ref object of RootObj # Base Type for RAFT Node Messages + msg_id*: RAFTMessageId # Message UUID + sender_id*: RAFTNodeId # Sender RAFT Node ID + sender_term*: RAFTNodeTerm # Sender RAFT Node Term + peers*: RAFTNodePeers # List of RAFT Node IDs, which should receive this message + + RAFTMessage* = ref object of RAFTMessageBase + op*: RAFTMessageOps # Message Op - Ask For Votes, Append Entry(ies) or Install Snapshot + payload*: seq[RAFTMessagePayload] # Message Payload(s) - e.g. log entry(ies) etc. Will be empty for a Heart-Beat # Heart-Beat will be a message with Append Entry(ies) Op and empty payload + + RAFTMessageResponse* = ref object of RAFTMessageBase + success*: bool # Indicates success/failure + + RAFTMessageSendCallback* = proc (raft_message: RAFTMessageBase) {.nimcall, gcsafe.} # Callback for Sending RAFT Node Messages + # out of this RAFT Node. Can be used for broadcasting + # (a Heart-Beat for example) + # RAFT Node Client Request/Response basic definitions + RAFTNodeClientRequestOps = enum + REQUEST_STATE = 0, + APPEND_NEW_ENTRY = 1 + + RAFTNodeClientRequest* = ref object + op*: RAFTNodeClientRequestOps + payload*: RAFTNodeLogEntry + + RAFTNodeClientResponse* = ref object + success*: bool # Indicate succcess + raft_node_redirect_id*: RAFTNodeId # RAFT Node ID to redirect the request to in case of failure + + # RAFT Node State Machine basic definitions + RAFTNodeStateMachineState* = object # State Machine State + RAFTNodeStateMachine* = ref object # Some probably opaque State Machine Impelementation to be used by the RAFT Node + # providing at minimum operations for initialization, querying the current state + # and RAFTNodeLogEntry application + state: RAFTNodeStateMachineState + + # RAFT Node Persistent Storage basic definition + RAFTNodePersistentStorage* = ref object # Should be some kind of Persistent Transactional Store Wrapper + + # RAFT Node Object definitions + RAFTNode* = object + # Timers definitions goes here + # ... + + msg_send_callback: RAFTMessageSendCallback + persistent_storage: RAFTNodePersistentStorage + + # Persistent state + id: RAFTNodeId # This RAFT Node ID + state: RAFTNodeState # This RAFT Node State + current_term: RAFTNodeTerm # Latest term this RAFT Node has seen (initialized to 0 on first boot, increases monotonically) + log: RAFTNodeLog # This RAFT Node Log + voted_for: RAFTNodeId # Candidate RAFTNodeId that received vote in current term (or nil/zero if none) + peers: RAFTNodePeers # This RAFT Node Peers IDs. I am not sure if this must be persistent or volatile but making it persistent + # makes sense for the moment + state_machine: RAFTNodeStateMachine # Not sure for now putting it here. I assume that persisting the State Machine's state is enough + # to consider it 'persisted' + + # Volatile state + commit_index: RAFTLogIndex # Index of highest log entry known to be committed (initialized to 0, increases monotonically) + last_applied: RAFTLogIndex # Index of highest log entry applied to state machine (initialized to 0, increases monotonically) + current_leader_id: RAFTNodeId # Current RAFT Node Leader ID (used to redirect Client Requests in case this RAFT Node is not the leader) + + # Volatile state on leaders + next_index: seq[RAFTLogIndex] # For each peer RAFT Node, index of the next log entry to send to that Node + # (initialized to leader last log index + 1) + match_index: seq[RAFTLogIndex] # For each peer RAFT Node, index of highest log entry known to be replicated on Node + # (initialized to 0, increases monotonically) + +# RAFT Node Public API procedures / functions +proc RAFTNodeCreateNew*(id: RAFTNodeId, peers: RAFTNodePeers, state_machine: RAFTNodeStateMachine, # Create New RAFT Node + log: RAFTNodeLog, persistent_storage: RAFTNodePersistentStorage, + msg_send_callback: RAFTMessageSendCallback): RAFTNode = + discard + +proc RAFTNodeLoad*(state_machine: RAFTNodeStateMachine, log: RAFTNodeLog, # Load RAFT Node From Storage + persistent_storage: RAFTNodePersistentStorage, msg_send_callback: RAFTMessageSendCallback): RAFTNode = + discard + + +func RAFTNodeIdGet*(node: RAFTNode): RAFTNodeId = # Get RAFT Node ID + discard + +func RAFTNodeStateGet*(node: RAFTNode): RAFTNodeState = # Get RAFT Node State + discard + +func RAFTNodeTermGet*(node: RAFTNode): RAFTNodeTerm = # Get RAFT Node Term + discard + +func RAFTNodePeersGet*(node: RAFTNode): RAFTNodePeers = # Get RAFT Node Peers + discard + +func RAFTNodeIsLeader*(node: RAFTNode): bool = # Check if RAFT Node is Leader + discard + +proc RAFTNodeMessageDeliver*(node: RAFTNode, raft_message: RAFTMessageBase): RAFTMessageResponse {.discardable.} = # Deliver RAFT Message to the RAFT Node + discard + +proc RAFTNodeRequest*(node: RAFTNode, req: RAFTNodeClientRequest): RAFTNodeClientResponse = # Process RAFTNodeClientRequest + discard \ No newline at end of file diff --git a/tests/all_tests.nim b/tests/all_tests.nim new file mode 100644 index 0000000..92b84b9 --- /dev/null +++ b/tests/all_tests.nim @@ -0,0 +1,9 @@ +# nim-raft-consesnsus +# Copyright (c) 2023 Status Research & Development GmbH +# Licensed under either of +# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) +# * MIT license ([LICENSE-MIT](LICENSE-MIT)) +# at your option. +# This file may not be copied, modified, or distributed except according to +# those terms. +