Files
next.orly.dev/.claude/skills/distributed-systems/references/logical-clocks.md
mleku 3c17e975df Add foundational resources for elliptic curve operations and distributed systems
Added detailed pseudocode for elliptic curve algorithms covering modular arithmetic, point operations, scalar multiplication, and coordinate conversions. Also introduced a comprehensive knowledge base for distributed systems, including CAP theorem, consistency models, consensus protocols (e.g., Paxos, Raft, PBFT, Nakamoto), and fault-tolerant design principles.
2025-12-02 19:14:39 +00:00

14 KiB

Logical Clocks - Implementation Reference

Detailed implementations and algorithms for causality tracking.

Lamport Clock Implementation

Data Structure

type LamportClock struct {
    counter uint64
    mu      sync.Mutex
}

func NewLamportClock() *LamportClock {
    return &LamportClock{counter: 0}
}

Operations

// Tick increments clock for local event
func (c *LamportClock) Tick() uint64 {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.counter++
    return c.counter
}

// Send returns timestamp for outgoing message
func (c *LamportClock) Send() uint64 {
    return c.Tick()
}

// Receive updates clock based on incoming message timestamp
func (c *LamportClock) Receive(msgTime uint64) uint64 {
    c.mu.Lock()
    defer c.mu.Unlock()

    if msgTime > c.counter {
        c.counter = msgTime
    }
    c.counter++
    return c.counter
}

// Time returns current clock value without incrementing
func (c *LamportClock) Time() uint64 {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.counter
}

Usage Example

// Process A
clockA := NewLamportClock()
e1 := clockA.Tick()      // Event 1: time=1
msgTime := clockA.Send() // Send: time=2

// Process B
clockB := NewLamportClock()
e2 := clockB.Tick()           // Event 2: time=1
e3 := clockB.Receive(msgTime) // Receive: time=3 (max(1,2)+1)

Vector Clock Implementation

Data Structure

type VectorClock struct {
    clocks map[string]uint64  // processID -> logical time
    self   string             // this process's ID
    mu     sync.RWMutex
}

func NewVectorClock(processID string, allProcesses []string) *VectorClock {
    clocks := make(map[string]uint64)
    for _, p := range allProcesses {
        clocks[p] = 0
    }
    return &VectorClock{
        clocks: clocks,
        self:   processID,
    }
}

Operations

// Tick increments own clock
func (vc *VectorClock) Tick() map[string]uint64 {
    vc.mu.Lock()
    defer vc.mu.Unlock()

    vc.clocks[vc.self]++
    return vc.copy()
}

// Send returns copy of vector for message
func (vc *VectorClock) Send() map[string]uint64 {
    return vc.Tick()
}

// Receive merges incoming vector and increments
func (vc *VectorClock) Receive(incoming map[string]uint64) map[string]uint64 {
    vc.mu.Lock()
    defer vc.mu.Unlock()

    // Merge: take max of each component
    for pid, time := range incoming {
        if time > vc.clocks[pid] {
            vc.clocks[pid] = time
        }
    }

    // Increment own clock
    vc.clocks[vc.self]++
    return vc.copy()
}

// copy returns a copy of the vector
func (vc *VectorClock) copy() map[string]uint64 {
    result := make(map[string]uint64)
    for k, v := range vc.clocks {
        result[k] = v
    }
    return result
}

Comparison Functions

// Compare returns ordering relationship between two vectors
type Ordering int

const (
    Equal      Ordering = iota // V1 == V2
    HappenedBefore            // V1 < V2
    HappenedAfter             // V1 > V2
    Concurrent                // V1 || V2
)

func Compare(v1, v2 map[string]uint64) Ordering {
    less := false
    greater := false

    // Get all keys
    allKeys := make(map[string]bool)
    for k := range v1 {
        allKeys[k] = true
    }
    for k := range v2 {
        allKeys[k] = true
    }

    for k := range allKeys {
        t1 := v1[k] // 0 if not present
        t2 := v2[k]

        if t1 < t2 {
            less = true
        }
        if t1 > t2 {
            greater = true
        }
    }

    if !less && !greater {
        return Equal
    }
    if less && !greater {
        return HappenedBefore
    }
    if greater && !less {
        return HappenedAfter
    }
    return Concurrent
}

// IsConcurrent checks if two events are concurrent
func IsConcurrent(v1, v2 map[string]uint64) bool {
    return Compare(v1, v2) == Concurrent
}

// HappenedBefore checks if v1 -> v2 (v1 causally precedes v2)
func HappenedBefore(v1, v2 map[string]uint64) bool {
    return Compare(v1, v2) == HappenedBefore
}

Interval Tree Clock Implementation

Data Structures

// ID represents the identity tree
type ID struct {
    IsLeaf bool
    Value  int   // 0 or 1 for leaves
    Left   *ID   // nil for leaves
    Right  *ID
}

// Stamp represents the event tree
type Stamp struct {
    Base  int
    Left  *Stamp // nil for leaf stamps
    Right *Stamp
}

// ITC combines ID and Stamp
type ITC struct {
    ID    *ID
    Stamp *Stamp
}

ID Operations

// NewSeedID creates initial full ID (1)
func NewSeedID() *ID {
    return &ID{IsLeaf: true, Value: 1}
}

// Fork splits an ID into two
func (id *ID) Fork() (*ID, *ID) {
    if id.IsLeaf {
        if id.Value == 0 {
            // Cannot fork zero ID
            return &ID{IsLeaf: true, Value: 0},
                   &ID{IsLeaf: true, Value: 0}
        }
        // Split full ID into left and right halves
        return &ID{
                IsLeaf: false,
                Left:   &ID{IsLeaf: true, Value: 1},
                Right:  &ID{IsLeaf: true, Value: 0},
            },
            &ID{
                IsLeaf: false,
                Left:   &ID{IsLeaf: true, Value: 0},
                Right:  &ID{IsLeaf: true, Value: 1},
            }
    }

    // Fork from non-leaf: give half to each
    if id.Left.IsLeaf && id.Left.Value == 0 {
        // Left is zero, fork right
        newRight1, newRight2 := id.Right.Fork()
        return &ID{IsLeaf: false, Left: id.Left, Right: newRight1},
               &ID{IsLeaf: false, Left: &ID{IsLeaf: true, Value: 0}, Right: newRight2}
    }
    if id.Right.IsLeaf && id.Right.Value == 0 {
        // Right is zero, fork left
        newLeft1, newLeft2 := id.Left.Fork()
        return &ID{IsLeaf: false, Left: newLeft1, Right: id.Right},
               &ID{IsLeaf: false, Left: newLeft2, Right: &ID{IsLeaf: true, Value: 0}}
    }

    // Both have IDs, split
    return &ID{IsLeaf: false, Left: id.Left, Right: &ID{IsLeaf: true, Value: 0}},
           &ID{IsLeaf: false, Left: &ID{IsLeaf: true, Value: 0}, Right: id.Right}
}

// Join merges two IDs
func Join(id1, id2 *ID) *ID {
    if id1.IsLeaf && id1.Value == 0 {
        return id2
    }
    if id2.IsLeaf && id2.Value == 0 {
        return id1
    }
    if id1.IsLeaf && id2.IsLeaf && id1.Value == 1 && id2.Value == 1 {
        return &ID{IsLeaf: true, Value: 1}
    }

    // Normalize to non-leaf
    left1 := id1.Left
    right1 := id1.Right
    left2 := id2.Left
    right2 := id2.Right

    if id1.IsLeaf {
        left1 = id1
        right1 = id1
    }
    if id2.IsLeaf {
        left2 = id2
        right2 = id2
    }

    newLeft := Join(left1, left2)
    newRight := Join(right1, right2)

    return normalize(&ID{IsLeaf: false, Left: newLeft, Right: newRight})
}

func normalize(id *ID) *ID {
    if !id.IsLeaf {
        if id.Left.IsLeaf && id.Right.IsLeaf &&
           id.Left.Value == id.Right.Value {
            return &ID{IsLeaf: true, Value: id.Left.Value}
        }
    }
    return id
}

Stamp Operations

// NewStamp creates initial stamp (0)
func NewStamp() *Stamp {
    return &Stamp{Base: 0}
}

// Event increments the stamp for the given ID
func Event(id *ID, stamp *Stamp) *Stamp {
    if id.IsLeaf {
        if id.Value == 1 {
            return &Stamp{Base: stamp.Base + 1}
        }
        return stamp // Cannot increment with zero ID
    }

    // Non-leaf ID: fill where we have ID
    if id.Left.IsLeaf && id.Left.Value == 1 {
        // Have left ID, increment left
        newLeft := Event(&ID{IsLeaf: true, Value: 1}, getLeft(stamp))
        return normalizeStamp(&Stamp{
            Base:  stamp.Base,
            Left:  newLeft,
            Right: getRight(stamp),
        })
    }
    if id.Right.IsLeaf && id.Right.Value == 1 {
        newRight := Event(&ID{IsLeaf: true, Value: 1}, getRight(stamp))
        return normalizeStamp(&Stamp{
            Base:  stamp.Base,
            Left:  getLeft(stamp),
            Right: newRight,
        })
    }

    // Both non-zero, choose lower side
    leftMax := maxStamp(getLeft(stamp))
    rightMax := maxStamp(getRight(stamp))

    if leftMax <= rightMax {
        return normalizeStamp(&Stamp{
            Base:  stamp.Base,
            Left:  Event(id.Left, getLeft(stamp)),
            Right: getRight(stamp),
        })
    }
    return normalizeStamp(&Stamp{
        Base:  stamp.Base,
        Left:  getLeft(stamp),
        Right: Event(id.Right, getRight(stamp)),
    })
}

func getLeft(s *Stamp) *Stamp {
    if s.Left == nil {
        return &Stamp{Base: 0}
    }
    return s.Left
}

func getRight(s *Stamp) *Stamp {
    if s.Right == nil {
        return &Stamp{Base: 0}
    }
    return s.Right
}

func maxStamp(s *Stamp) int {
    if s.Left == nil && s.Right == nil {
        return s.Base
    }
    left := 0
    right := 0
    if s.Left != nil {
        left = maxStamp(s.Left)
    }
    if s.Right != nil {
        right = maxStamp(s.Right)
    }
    max := left
    if right > max {
        max = right
    }
    return s.Base + max
}

// JoinStamps merges two stamps
func JoinStamps(s1, s2 *Stamp) *Stamp {
    // Take max at each level
    base := s1.Base
    if s2.Base > base {
        base = s2.Base
    }

    // Adjust for base difference
    adj1 := s1.Base
    adj2 := s2.Base

    return normalizeStamp(&Stamp{
        Base:  base,
        Left:  joinStampsRecursive(s1.Left, s2.Left, adj1-base, adj2-base),
        Right: joinStampsRecursive(s1.Right, s2.Right, adj1-base, adj2-base),
    })
}

func normalizeStamp(s *Stamp) *Stamp {
    if s.Left == nil && s.Right == nil {
        return s
    }
    if s.Left != nil && s.Right != nil {
        if s.Left.Base > 0 && s.Right.Base > 0 {
            min := s.Left.Base
            if s.Right.Base < min {
                min = s.Right.Base
            }
            return &Stamp{
                Base:  s.Base + min,
                Left:  &Stamp{Base: s.Left.Base - min, Left: s.Left.Left, Right: s.Left.Right},
                Right: &Stamp{Base: s.Right.Base - min, Left: s.Right.Left, Right: s.Right.Right},
            }
        }
    }
    return s
}

Hybrid Logical Clock Implementation

type HLC struct {
    l  int64      // logical component (physical time)
    c  int64      // counter
    mu sync.Mutex
}

func NewHLC() *HLC {
    return &HLC{l: 0, c: 0}
}

type HLCTimestamp struct {
    L int64
    C int64
}

func (hlc *HLC) physicalTime() int64 {
    return time.Now().UnixNano()
}

// Now returns current HLC timestamp for local/send event
func (hlc *HLC) Now() HLCTimestamp {
    hlc.mu.Lock()
    defer hlc.mu.Unlock()

    pt := hlc.physicalTime()

    if pt > hlc.l {
        hlc.l = pt
        hlc.c = 0
    } else {
        hlc.c++
    }

    return HLCTimestamp{L: hlc.l, C: hlc.c}
}

// Update updates HLC based on received timestamp
func (hlc *HLC) Update(received HLCTimestamp) HLCTimestamp {
    hlc.mu.Lock()
    defer hlc.mu.Unlock()

    pt := hlc.physicalTime()

    if pt > hlc.l && pt > received.L {
        hlc.l = pt
        hlc.c = 0
    } else if received.L > hlc.l {
        hlc.l = received.L
        hlc.c = received.C + 1
    } else if hlc.l > received.L {
        hlc.c++
    } else { // hlc.l == received.L
        if received.C > hlc.c {
            hlc.c = received.C + 1
        } else {
            hlc.c++
        }
    }

    return HLCTimestamp{L: hlc.l, C: hlc.c}
}

// Compare compares two HLC timestamps
func (t1 HLCTimestamp) Compare(t2 HLCTimestamp) int {
    if t1.L < t2.L {
        return -1
    }
    if t1.L > t2.L {
        return 1
    }
    if t1.C < t2.C {
        return -1
    }
    if t1.C > t2.C {
        return 1
    }
    return 0
}

Causal Broadcast Implementation

type CausalBroadcast struct {
    vc       *VectorClock
    pending  []PendingMessage
    deliver  func(Message)
    mu       sync.Mutex
}

type PendingMessage struct {
    Msg       Message
    Timestamp map[string]uint64
}

func NewCausalBroadcast(processID string, processes []string, deliver func(Message)) *CausalBroadcast {
    return &CausalBroadcast{
        vc:      NewVectorClock(processID, processes),
        pending: make([]PendingMessage, 0),
        deliver: deliver,
    }
}

// Broadcast sends a message to all processes
func (cb *CausalBroadcast) Broadcast(msg Message) map[string]uint64 {
    cb.mu.Lock()
    defer cb.mu.Unlock()

    timestamp := cb.vc.Send()
    // Actual network broadcast would happen here
    return timestamp
}

// Receive handles an incoming message
func (cb *CausalBroadcast) Receive(msg Message, sender string, timestamp map[string]uint64) {
    cb.mu.Lock()
    defer cb.mu.Unlock()

    // Add to pending
    cb.pending = append(cb.pending, PendingMessage{Msg: msg, Timestamp: timestamp})

    // Try to deliver pending messages
    cb.tryDeliver()
}

func (cb *CausalBroadcast) tryDeliver() {
    changed := true
    for changed {
        changed = false

        for i, pending := range cb.pending {
            if cb.canDeliver(pending.Timestamp) {
                // Deliver message
                cb.vc.Receive(pending.Timestamp)
                cb.deliver(pending.Msg)

                // Remove from pending
                cb.pending = append(cb.pending[:i], cb.pending[i+1:]...)
                changed = true
                break
            }
        }
    }
}

func (cb *CausalBroadcast) canDeliver(msgVC map[string]uint64) bool {
    currentVC := cb.vc.clocks

    for pid, msgTime := range msgVC {
        if pid == cb.vc.self {
            // Must be next expected from sender
            if msgTime != currentVC[pid]+1 {
                return false
            }
        } else {
            // All other dependencies must be satisfied
            if msgTime > currentVC[pid] {
                return false
            }
        }
    }
    return true
}