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 0000000..f65665b Binary files /dev/null and b/doc/RAFT_Library_Roadmap_Proposal.odt differ 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. +