Implement a single blockchain node program. When multiple instances of this program run, they form a peer-to-peer (P2P) network that collectively maintains a distributed, consistent blockchain ledger by agreeing on blocks of transactions using a crash fault-tolerant consensus protocol.
-
Node Functionality:
- Act as both a TCP server (listen for incoming peer connections) and a TCP client (initiate connections to other peers).
- Maintain persistent TCP connections with all known, non-crashed peers in a fully connected topology.
- Manage a transaction pool (mempool) for pending transactions.
- Validate incoming transactions based on strict rules (nonce, signature, format).
- Participate in a synchronous, round-based consensus protocol to agree on the next block to add to the chain.
- Maintain the local blockchain (append agreed-upon blocks).
- Handle peer crashes and network issues (timeouts, disconnects) gracefully.
-
Programming Language & Libraries:
- Use Python in this assignment
- Submission must include a
Run.shwrapper script as the single entry point. - Strict Library Restriction: Only use Python Standard library plus
PyNaCl/ed25519for signatures. Using disallowed libraries results in zero marks.
-
Input/Output:
-
Input: Command-line arguments (
<Port-Server> <Node-List-File>), network messages (transactions, consensus values). -
Output: Standard Output (stdout) for logging accepted transactions and decided blocks in a specific JSON format. Network messages to peers. Boolean (
True/False) responses over TCP for received transactions. -
Strict Formatting: All JSON output to stdout must be pretty-printed with 2-space indentation and keys sorted lexicographically. Use double quotes for keys and strings. Adhere strictly to the examples:
-
{ "type": "transaction", "payload": { "sender": " a57819938feb51bb3f923496c9dacde3e9f667b214a0fb1653b6bfc0f185363b", "message": "Never Gonna Give You Up", "nonce": 0, "signature": "142e395895e0bf4e4a3aabf2f59c3abc45c9c2b..." } }
-
-
-
Transaction: A JSON object representing a request.
sender: 64-char hex Ed25519 public key.(hex-encoded 32-byte Ed25519 public key )message: Up to 70 alphanumeric chars + spaces (UTF-8).nonce: Integer, sequence number for the sender (starts at 0, increments per confirmed transaction).signature: 128-char hex Ed25519 signature oversender+message+nonce.(The hex-encoded 64-byte Ed25519 signature of the transaction)
-
Block: A JSON object containing a batch of confirmed transactions.
index: Integer, block's position in the chain (Genesis is 1).transactions: List of transaction objects included in the block.previous_hash: 64-char hex SHA-256 hash of the previous block. For Genesis, it's 64 zeros.current_hash: 64-char hex SHA-256 hash of this block's content (index, transactions, previous_hash), computed over a specific canonical JSON representation (keys sorted, specific separators - see Appendix D).
-
Transaction Pool (Mempool): Temporary storage for valid transactions waiting to be included in a block.
-
Blockchain: The local list/ledger of confirmed blocks. Starts with the Genesis block.
-
Genesis Block (Block 1):
index: 1transactions: []previous_hash: "0000..."(64 zeros)current_hash:Calculated based on the above fields using the specified hashing method.- Must be initialized by every node. Not printed to stdout.
-
Networking Protocol:
- TCP connections.
- Messages are length-prefixed: 2-byte unsigned big-endian length, followed by the JSON message body.
2 bytesfield : indicate specifies the number of bytes in the message body- message body: The message body immediately follows the length field and consists of a JSON-encoded object. The maximum message length is 65535 bytes (0xFFFF)
- Message Body Format:
{"type": "...", "payload": ...} - Types:
"transaction": Payload is a single transaction object."values": Payload is a list of block proposal objects. Used for consensus exchange.
-
Validation Rules (Transactions):
- Nonce: Must be exactly
sender's current confirmed nonce count + 1.- If a received transaction’s nonce is not exactly one greater than the sender’s confirmed nonce (or 0 if the sender has none yet), then the transaction is out-of-order or duplicate and must be rejected. There must be no two different transactions with the same sender and same nonce in the pool. (The nonce is monotonically increasing per sender, starting from 0.)
- Signature: Must be a valid Ed25519 signature corresponding to the
senderpublic key over the transaction's content (sender+message+nonce). - Format: Fields must match expected types and constraints (lengths, characters).
-
Rejected transactions: Return
Falseover TCP, do not add to pool or any blocks, do not print to stdout. -
Accepted transactions: Return
Trueover TCP, add to pool, print transaction JSON to stdout
- Nonce: Must be exactly
- Goal: All correct nodes agree on the same block for each round (chain height).
- Fault Tolerance: Tolerates up to
f = floor(n/2) - 1crash failures (n= total nodes). - Round Structure: Triggered by events (new transaction, peer request), not a global clock.
- Steps per Round:
- Trigger: Start a round if the local transaction pool is non-empty OR if a
"values"request is received from a peer. If triggered by a request with an empty pool, prepare an "empty block" proposal (correct index/hash, empty transaction list). - Propose: Create a block proposal containing valid transactions from the pool, the next
index, theprevious_hashfrom the current latest block, and the calculatedcurrent_hash. - Exchange:
- For each non-crashed peer: Send your proposal in a
"values"message. - Wait exactly 2 seconds for a
"values"response containing their proposal. - If response received: Store the peer's proposal.
- If timeout: Mark the peer as crashed (persistently for future rounds) and ignore them for this round's decision.
- Concurrently: Respond immediately to any incoming
"values"requests with your own current proposal.
- For each non-crashed peer: Send your proposal in a
- Decide:
- Gather all proposals received within the timeout (plus your own).
- Filter proposals: If any proposal contains transactions, discard all empty block proposals.
- Selection: Among the remaining (non-discarded) proposals, choose the one whose
current_hashis lexicographically smallest. - (If all proposals were empty, choose the empty one with the lexicographically smallest
current_hash).
- Commit:
- Append the decided block to the local blockchain.
- Print the decided block JSON to stdout.
- Update node state: Increment nonces for senders whose transactions were included.
- Clean up pool: Remove committed transactions. Remove any newly invalid transactions (e.g., conflicting nonces).
- Next Round: Return to step 1, waiting for a trigger.
- Trigger: Start a round if the local transaction pool is non-empty OR if a
- Concurrency: Use threads or asynchronous I/O.
- Main Thread: Initialization, start other threads/tasks.
- Server Thread: Listens on the node's port, accepts incoming connections, spawns Peer Handler threads.
- Peer Handler Thread (per connection): Dedicated to reading messages from one peer. Handles incoming
transactionmessages (validate, add to pool, respondTrue/False), handles incomingvaluesrequests (responds immediately with current proposal). Detects disconnects (EOF) and marks peer as crashed. - Client/Connector Logic: Manages outgoing connections to peers listed in the file. Handles retries on startup if a peer isn't up yet. Used by the consensus thread to send requests.
- Consensus Thread: The core logic loop. Waits for a trigger (pool not empty / external request signal). Executes the propose-exchange-decide-commit steps. Needs to interact with client logic to send
valuesrequests and manage the 2-second timeouts.
- State Management: Maintain node state carefully:
- Current blockchain (list of blocks).
- Transaction pool (use a set or dict for efficiency, perhaps keyed by tx hash or
(sender, nonce)). - Peer status (addresses, socket objects, is_crashed flag).
- Confirmed nonce count per sender (dictionary
sender_pubkey -> nonce_count).
- Synchronization: Use locks/mutexes/semaphores to protect shared data structures accessed by multiple threads (e.g., transaction pool, blockchain, nonce counts, peer status list).
- Modularity: Separate concerns:
- Networking module (sending/receiving framed JSON messages).
- Validation module (transaction checks).
- Blockchain state module (managing the chain, nonce counts).
- Transaction Pool module.
- Consensus logic module.
- Networking Details:
- Implement the 2-byte length prefixing correctly (unsigned, big-endian).
- Read the length first, then read exactly that many bytes for the JSON.
- Use socket timeouts (
socket.settimeout()in Python or equivalent) only for the 2-second wait during the consensus exchange phase. Reset timeout to blocking/None afterwards for normal operation (receiving transactions). - Handle connection errors, EOF (peer disconnect), etc.
- Hashing & Signatures:
- Use the specified hashing script/logic (Appendix D) exactly, especially the
json.dumpsparameters (sort_keys=True, indent=2, separators=(',', ': ')). - Use the specified crypto library (e.g., PyNaCl) for Ed25519 signature verification. Ensure you verify against the correct data (
sender+message+nonce).
- Use the specified hashing script/logic (Appendix D) exactly, especially the
- Execution:
./Run.sh <Port-Server> <Node-List-File><Port-Server>: Port for this node to listen on.<Node-List-File>: Path to a text file listinghost:portof all peers (including potentially itself, though usually not needed for self-connection).
- Submission:
- All source code files.
Run.shscript.README.md(brief description, instructions).
- Testing: Test frequently on the Ed platform. Pay extreme attention to exact stdout formatting. Remove all debug prints before final submission unless they match the required output format.
- Library Restrictions: Double-check you are only using allowed libraries.
- Stdout Formatting: Any deviation from the specified JSON format (indentation, sorted keys, spacing, quotes) will likely fail tests. Use
json.dumps(obj, sort_keys=True, indent=2, separators=(',', ': '))for hashing and consider similar precise formatting for stdout. - Hashing: The
current_hashmust be calculated exactly as specified in Appendix D. The specificseparatorsargument is crucial and non-default. - Consensus Logic: Implement the decision rule (smallest hash, ignoring empty blocks if non-empty exist) correctly. Handle the 2-second timeout precisely. Manage the
crashedstatus correctly. - Nonce Management: Nonce validation uses the count from the blockchain, not the pool. Increment nonces only after a block is committed.
- Concurrency Issues: Ensure thread safety for shared resources (pool, chain, nonces). Deadlocks or race conditions can be hard to debug.
- Genesis Block: Initialize it correctly but do not print it to stdout.
- Network Framing: Correctly handle the 2-byte length prefix.
- Transaction Response: Remember to send a simple
TrueorFalse(or equivalent boolean serialization) back over the TCP connection after processing a received transaction message. This is separate from the JSON message protocol itself.