Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions consensus/random_polling.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package bft

import (
"crypto/rand"
"math/big"
"sync"
"time"
)

// Node represents a Fabric orderer node in the BiniBFT network.
type Node struct {
ID string
IsActive bool
LastPing time.Time
}

// BiniBFTRandomPolling manages the Random Polling consensus.
type BiniBFTRandomPolling struct {
nodes []*Node
currentLeader *Node
mu sync.Mutex
}

// NewBiniBFTRandomPolling initializes the Random Polling consensus.
func NewBiniBFTRandomPolling(nodes []*Node) *BiniBFTRandomPolling {
return &BiniBFTRandomPolling{
nodes: nodes,
}
}

// SelectLeader randomly selects an active node as leader.
func (b *BiniBFTRandomPolling) SelectLeader() (*Node, error) {
b.mu.Lock()
defer b.mu.Unlock()

// Filter active nodes (pinged within last 10 seconds).
activeNodes := []*Node{}
for _, node := range b.nodes {
if node.IsActive && time.Since(node.LastPing) < 10*time.Second {
activeNodes = append(activeNodes, node)
}
}

if len(activeNodes) == 0 {
return nil, errors.New("no active nodes available")
}

// Randomly select a leader.
index, err := rand.Int(rand.Reader, big.NewInt(int64(len(activeNodes))))
if err != nil {
return nil, err
}
b.currentLeader = activeNodes[index]
return b.currentLeader, nil
}

// Heartbeat updates node activity status.
func (b *BiniBFTRandomPolling) Heartbeat(nodeID string) {
b.mu.Lock()
defer b.mu.Unlock()
for _, node := range b.nodes {
if node.ID == nodeID {
node.LastPing = time.Now()
node.IsActive = true
break
}
}
}
46 changes: 46 additions & 0 deletions consensus/time_weighted_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package bft

import (
"testing"
"time"
)

func TestTimeWeightedLeaderSelection(t *testing.T) {
nodes := []*Node{
{ID: "node1", IsActive: true, LastPing: time.Now().Add(-5 * time.Second)},
{ID: "node2", IsActive: true, LastPing: time.Now().Add(-2 * time.Second)},
{ID: "node3", IsActive: false, LastPing: time.Now().Add(-20 * time.Second)},
}

bft := &BiniBFTTimeWeighted{
nodes: nodes,
weights: map[string]float64{
"node1": 0.6, // Older ping, lower weight
"node2": 0.9, // Recent ping, higher weight
"node3": 0.0, // Inactive, no weight
},
}

leader, err := bft.SelectLeader()
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if leader == nil || leader.ID != "node2" {
t.Errorf("expected leader node2, got %v", leader)
}
}

func TestTimeWeightedNoActiveNodes(t *testing.T) {
nodes := []*Node{
{ID: "node1", IsActive: false, LastPing: time.Now().Add(-30 * time.Second)},
}
bft := &BiniBFTTimeWeighted{
nodes: nodes,
weights: map[string]float64{"node1": 0.0},
}

_, err := bft.SelectLeader()
if err == nil {
t.Fatal("expected error for no active nodes, got nil")
}
}