Files
bft-crdt-experiment/examples/orderbook_crdt.rs

538 lines
17 KiB
Rust
Raw Normal View History

use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::collections::{BTreeMap, HashMap, HashSet};
/// Unique identifier for an order
#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
pub struct OrderId(pub String);
/// Public key representing a trader
#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
pub struct TraderId(pub String);
/// Side of the order (buy or sell)
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
pub enum Side {
Buy,
Sell,
}
/// Status of an order
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
pub enum OrderStatus {
Open,
PartiallyFilled,
Filled,
Cancelled,
}
/// An order in the order book
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Order {
pub id: OrderId,
pub trader: TraderId,
pub side: Side,
pub price: u128,
pub original_amount: u128,
pub remaining_amount: u128,
pub timestamp: u64,
pub status: OrderStatus,
pub signature: Vec<u8>, // Signature from the trader
}
/// A trade execution
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Execution {
pub id: String,
pub buy_order_id: OrderId,
pub sell_order_id: OrderId,
pub price: u128,
pub amount: u128,
pub timestamp: u64,
pub executor: TraderId, // Who executed the trade (could be either party)
}
/// A cancellation request
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Cancellation {
pub order_id: OrderId,
pub timestamp: u64,
pub signature: Vec<u8>, // Must be signed by original trader
}
/// CRDT-based decentralized order book
#[derive(Debug, Clone)]
pub struct OrderBookCRDT {
/// All orders ever placed
orders: HashMap<OrderId, Order>,
/// All executions
executions: HashMap<String, Execution>,
/// Cancellation tombstones
cancellations: HashMap<OrderId, Cancellation>,
/// Index for fast price-level lookups
buy_levels: BTreeMap<u128, HashSet<OrderId>>,
sell_levels: BTreeMap<u128, HashSet<OrderId>>,
}
impl OrderBookCRDT {
pub fn new() -> Self {
Self {
orders: HashMap::new(),
executions: HashMap::new(),
cancellations: HashMap::new(),
buy_levels: BTreeMap::new(),
sell_levels: BTreeMap::new(),
}
}
/// Add a new order to the book
pub fn add_order(&mut self, order: Order) -> Result<(), String> {
// Verify signature (simplified - real implementation would verify cryptographically)
if order.signature.is_empty() {
return Err("Order must be signed".to_string());
}
// Check if order was previously cancelled
if self.cancellations.contains_key(&order.id) {
return Err("Order was previously cancelled".to_string());
}
// Add to orders map
let order_id = order.id.clone();
let price = order.price;
let side = order.side;
self.orders.insert(order_id.clone(), order);
// Update price level index
match side {
Side::Buy => {
self.buy_levels
.entry(price)
.or_insert_with(HashSet::new)
.insert(order_id);
}
Side::Sell => {
self.sell_levels
.entry(price)
.or_insert_with(HashSet::new)
.insert(order_id);
}
}
Ok(())
}
/// Execute a trade between two orders
pub fn execute_trade(&mut self, execution: Execution) -> Result<(), String> {
// Verify both orders exist and are not cancelled
let buy_order = self
.orders
.get(&execution.buy_order_id)
.ok_or("Buy order not found")?;
let sell_order = self
.orders
.get(&execution.sell_order_id)
.ok_or("Sell order not found")?;
if self.cancellations.contains_key(&execution.buy_order_id) {
return Err("Buy order is cancelled".to_string());
}
if self.cancellations.contains_key(&execution.sell_order_id) {
return Err("Sell order is cancelled".to_string());
}
// Verify price matching
if buy_order.price < sell_order.price {
return Err("Price mismatch - buy price less than sell price".to_string());
}
// Check for duplicate execution
if self.executions.contains_key(&execution.id) {
return Ok(()); // Idempotent - already processed
}
// Add execution
self.executions
.insert(execution.id.clone(), execution.clone());
// Update order states
self.update_order_after_execution(&execution.buy_order_id, execution.amount);
self.update_order_after_execution(&execution.sell_order_id, execution.amount);
Ok(())
}
/// Cancel an order
pub fn cancel_order(&mut self, cancellation: Cancellation) -> Result<(), String> {
// Verify the order exists
let order = self
.orders
.get(&cancellation.order_id)
.ok_or("Order not found")?;
// Verify signature matches order owner (simplified)
if cancellation.signature.is_empty() {
return Err("Cancellation must be signed".to_string());
}
// Add cancellation tombstone
self.cancellations
.insert(cancellation.order_id.clone(), cancellation);
// Remove from price level indices
if let Some(order) = self.orders.get_mut(&cancellation.order_id) {
order.status = OrderStatus::Cancelled;
match order.side {
Side::Buy => {
if let Some(level) = self.buy_levels.get_mut(&order.price) {
level.remove(&order.id);
}
}
Side::Sell => {
if let Some(level) = self.sell_levels.get_mut(&order.price) {
level.remove(&order.id);
}
}
}
}
Ok(())
}
/// Merge another OrderBookCRDT into this one
pub fn merge(&mut self, other: &OrderBookCRDT) {
// Merge orders
for (id, order) in &other.orders {
if !self.orders.contains_key(id) {
let _ = self.add_order(order.clone());
}
}
// Merge executions
for (id, execution) in &other.executions {
if !self.executions.contains_key(id) {
let _ = self.execute_trade(execution.clone());
}
}
// Merge cancellations (cancellations always win)
for (id, cancellation) in &other.cancellations {
if !self.cancellations.contains_key(id) {
let _ = self.cancel_order(cancellation.clone());
}
}
}
/// Get the current order book view
pub fn get_book_view(&self) -> OrderBookView {
let mut bids = Vec::new();
let mut asks = Vec::new();
// Collect buy orders (descending price)
for (price, order_ids) in self.buy_levels.iter().rev() {
let mut level_amount = 0u128;
for order_id in order_ids {
if let Some(order) = self.orders.get(order_id) {
if order.status == OrderStatus::Open
|| order.status == OrderStatus::PartiallyFilled
{
level_amount += self.get_remaining_amount(order_id);
}
}
}
if level_amount > 0 {
bids.push(PriceLevel {
price: *price,
amount: level_amount,
});
}
}
// Collect sell orders (ascending price)
for (price, order_ids) in &self.sell_levels {
let mut level_amount = 0u128;
for order_id in order_ids {
if let Some(order) = self.orders.get(order_id) {
if order.status == OrderStatus::Open
|| order.status == OrderStatus::PartiallyFilled
{
level_amount += self.get_remaining_amount(order_id);
}
}
}
if level_amount > 0 {
asks.push(PriceLevel {
price: *price,
amount: level_amount,
});
}
}
OrderBookView { bids, asks }
}
/// Get remaining amount for an order after all executions
fn get_remaining_amount(&self, order_id: &OrderId) -> u128 {
let order = match self.orders.get(order_id) {
Some(o) => o,
None => return 0,
};
let mut filled_amount = 0u128;
for execution in self.executions.values() {
if &execution.buy_order_id == order_id || &execution.sell_order_id == order_id {
filled_amount += execution.amount;
}
}
order.original_amount.saturating_sub(filled_amount)
}
/// Update order status after execution
fn update_order_after_execution(&mut self, order_id: &OrderId, amount: u128) {
if let Some(order) = self.orders.get_mut(order_id) {
let remaining = self.get_remaining_amount(order_id);
order.remaining_amount = remaining;
if remaining == 0 {
order.status = OrderStatus::Filled;
// Remove from price level index
match order.side {
Side::Buy => {
if let Some(level) = self.buy_levels.get_mut(&order.price) {
level.remove(order_id);
}
}
Side::Sell => {
if let Some(level) = self.sell_levels.get_mut(&order.price) {
level.remove(order_id);
}
}
}
} else if remaining < order.original_amount {
order.status = OrderStatus::PartiallyFilled;
}
}
}
/// Find matching orders for a new order (for market making)
pub fn find_matches(&self, order: &Order) -> Vec<&Order> {
let mut matches = Vec::new();
match order.side {
Side::Buy => {
// Look for sell orders at or below buy price
for (price, order_ids) in &self.sell_levels {
if *price > order.price {
break; // Prices too high
}
for order_id in order_ids {
if let Some(sell_order) = self.orders.get(order_id) {
if !self.cancellations.contains_key(order_id)
&& self.get_remaining_amount(order_id) > 0
{
matches.push(sell_order);
}
}
}
}
}
Side::Sell => {
// Look for buy orders at or above sell price
for (price, order_ids) in self.buy_levels.iter().rev() {
if *price < order.price {
break; // Prices too low
}
for order_id in order_ids {
if let Some(buy_order) = self.orders.get(order_id) {
if !self.cancellations.contains_key(order_id)
&& self.get_remaining_amount(order_id) > 0
{
matches.push(buy_order);
}
}
}
}
}
}
matches
}
}
/// A view of the order book at a point in time
#[derive(Debug, Clone)]
pub struct OrderBookView {
pub bids: Vec<PriceLevel>,
pub asks: Vec<PriceLevel>,
}
#[derive(Debug, Clone)]
pub struct PriceLevel {
pub price: u128,
pub amount: u128,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_order_book_operations() {
let mut book1 = OrderBookCRDT::new();
let mut book2 = OrderBookCRDT::new();
// Add buy order to book1
let buy_order = Order {
id: OrderId("buy1".to_string()),
trader: TraderId("alice".to_string()),
side: Side::Buy,
price: 100,
original_amount: 50,
remaining_amount: 50,
timestamp: 1000,
status: OrderStatus::Open,
signature: vec![1, 2, 3],
};
book1.add_order(buy_order).unwrap();
// Add sell order to book2
let sell_order = Order {
id: OrderId("sell1".to_string()),
trader: TraderId("bob".to_string()),
side: Side::Sell,
price: 95,
original_amount: 30,
remaining_amount: 30,
timestamp: 1001,
status: OrderStatus::Open,
signature: vec![4, 5, 6],
};
book2.add_order(sell_order).unwrap();
// Merge books
book1.merge(&book2);
book2.merge(&book1);
// Both books should have both orders
assert_eq!(book1.orders.len(), 2);
assert_eq!(book2.orders.len(), 2);
// Execute trade on book1
let execution = Execution {
id: "exec1".to_string(),
buy_order_id: OrderId("buy1".to_string()),
sell_order_id: OrderId("sell1".to_string()),
price: 95,
amount: 30,
timestamp: 1002,
executor: TraderId("alice".to_string()),
};
book1.execute_trade(execution).unwrap();
// Merge again
book2.merge(&book1);
// Both should have the execution
assert_eq!(book1.executions.len(), 1);
assert_eq!(book2.executions.len(), 1);
// Check remaining amounts
assert_eq!(book1.get_remaining_amount(&OrderId("buy1".to_string())), 20);
assert_eq!(book1.get_remaining_amount(&OrderId("sell1".to_string())), 0);
}
#[test]
fn test_order_cancellation() {
let mut book = OrderBookCRDT::new();
// Add order
let order = Order {
id: OrderId("order1".to_string()),
trader: TraderId("alice".to_string()),
side: Side::Buy,
price: 100,
original_amount: 50,
remaining_amount: 50,
timestamp: 1000,
status: OrderStatus::Open,
signature: vec![1, 2, 3],
};
book.add_order(order).unwrap();
// Cancel order
let cancellation = Cancellation {
order_id: OrderId("order1".to_string()),
timestamp: 1001,
signature: vec![1, 2, 3],
};
book.cancel_order(cancellation).unwrap();
// Verify order is cancelled
let order = book.orders.get(&OrderId("order1".to_string())).unwrap();
assert_eq!(order.status, OrderStatus::Cancelled);
// Try to execute against cancelled order
let execution = Execution {
id: "exec1".to_string(),
buy_order_id: OrderId("order1".to_string()),
sell_order_id: OrderId("sell1".to_string()),
price: 100,
amount: 10,
timestamp: 1002,
executor: TraderId("bob".to_string()),
};
assert!(book.execute_trade(execution).is_err());
}
#[test]
fn test_order_book_view() {
let mut book = OrderBookCRDT::new();
// Add multiple orders at different price levels
for i in 0..3 {
let buy_order = Order {
id: OrderId(format!("buy{}", i)),
trader: TraderId("alice".to_string()),
side: Side::Buy,
price: 100 - i as u128,
original_amount: 10 * (i + 1) as u128,
remaining_amount: 10 * (i + 1) as u128,
timestamp: 1000 + i,
status: OrderStatus::Open,
signature: vec![1, 2, 3],
};
book.add_order(buy_order).unwrap();
let sell_order = Order {
id: OrderId(format!("sell{}", i)),
trader: TraderId("bob".to_string()),
side: Side::Sell,
price: 105 + i as u128,
original_amount: 10 * (i + 1) as u128,
remaining_amount: 10 * (i + 1) as u128,
timestamp: 2000 + i,
status: OrderStatus::Open,
signature: vec![4, 5, 6],
};
book.add_order(sell_order).unwrap();
}
let view = book.get_book_view();
// Check bid side (should be sorted descending)
assert_eq!(view.bids.len(), 3);
assert_eq!(view.bids[0].price, 100);
assert_eq!(view.bids[1].price, 99);
assert_eq!(view.bids[2].price, 98);
// Check ask side (should be sorted ascending)
assert_eq!(view.asks.len(), 3);
assert_eq!(view.asks[0].price, 105);
assert_eq!(view.asks[1].price, 106);
assert_eq!(view.asks[2].price, 107);
}
}