Skip to content

Commit

Permalink
Add self-equivocation filter
Browse files Browse the repository at this point in the history
Signed-off-by: Jakub Sztandera <[email protected]>
  • Loading branch information
Kubuxu committed Sep 20, 2024
1 parent 6f7d7d9 commit 6df23f1
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 8 deletions.
59 changes: 59 additions & 0 deletions equivocation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package f3

import (
"bytes"
"sync"

"github.com/filecoin-project/go-f3/gpbft"
)

// zero value is valid
type equivocationFilter struct {
lk sync.Mutex
currentInstance uint64
// sentMessages map unique message slot to its signature
sentMessages map[equivocationKey][]byte
}

type equivocationKey struct {
Sender gpbft.ActorID
Round uint64
Phase gpbft.Phase
}

func (ef *equivocationFilter) formKey(m gpbft.GMessage) equivocationKey {
return equivocationKey{
Sender: m.Sender,
Round: m.Vote.Round,
Phase: m.Vote.Phase,
}
}

func (ef *equivocationFilter) Process(m *gpbft.GMessage) bool {
ef.lk.Lock()
defer ef.lk.Unlock()

if m.Vote.Instance < ef.currentInstance {
// disallow past instances
log.Debugw("disallowing broadcast for past message", "sender", m.Sender, "instance",
m.Vote.Instance, "currentInstance", ef.currentInstance)
return false
}
// lazy initialization or moved onto new instance
if ef.sentMessages == nil || m.Vote.Instance > ef.currentInstance {
// bump to next instance
ef.currentInstance = m.Vote.Instance
ef.sentMessages = make(map[equivocationKey][]byte)
}

key := ef.formKey(*m)
sig, ok := ef.sentMessages[key]
if ok {
log.Debugw("disallowing broadcast due to signature mismatch", "sender", m.Sender,
"instance", m.Vote.Instance, "round", m.Vote.Round, "phase", m.Vote.Phase)
return bytes.Equal(sig, m.Signature)
}

ef.sentMessages[key] = m.Signature
return true
}
69 changes: 69 additions & 0 deletions equivocation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package f3

import (
"testing"

"github.com/filecoin-project/go-f3/gpbft"
"github.com/stretchr/testify/assert"
)

func TestEquivactionFilter_Process(t *testing.T) {
ef := &equivocationFilter{}

// Test case 1: First message should be processed
msg1 := &gpbft.GMessage{
Sender: gpbft.ActorID(1),
Vote: gpbft.Payload{Instance: 1, Round: 1, Phase: gpbft.Phase(1)},
Signature: []byte("signature1"),
}
assert.True(t, ef.Process(msg1), "First message should be processed")

// Test case 2: Duplicate message with same signature should be processed
msg2 := &gpbft.GMessage{
Sender: gpbft.ActorID(1),
Vote: gpbft.Payload{Instance: 1, Round: 1, Phase: gpbft.Phase(1)},
Signature: []byte("signature1"),
}
assert.True(t, ef.Process(msg2), "Duplicate message with same signature should be processed")

// Test case 3 Message with same key but different signature should not be processed
msg3 := &gpbft.GMessage{
Sender: gpbft.ActorID(1),
Vote: gpbft.Payload{Instance: 1, Round: 1, Phase: gpbft.Phase(1)},
Signature: []byte("signature2"),
}
assert.False(t, ef.Process(msg3), "Message with same key but different signature should not be processed")

// Test case 4: Message with new instance should be processed
msg4 := &gpbft.GMessage{
Sender: gpbft.ActorID(1),
Vote: gpbft.Payload{Instance: 2, Round: 1, Phase: gpbft.Phase(1)},
Signature: []byte("signature3"),
}
assert.True(t, ef.Process(msg4), "Message with new instance should be processed")

// Test case 5: Message with past instance should not be processed
msg5 := &gpbft.GMessage{
Sender: gpbft.ActorID(1),
Vote: gpbft.Payload{Instance: 1, Round: 1, Phase: gpbft.Phase(1)},
Signature: []byte("signature4"),
}
assert.False(t, ef.Process(msg5), "Message with past instance should not be processed")
}

func TestEquivactionFilter_formKey(t *testing.T) {
ef := &equivocationFilter{}

msg := gpbft.GMessage{
Sender: gpbft.ActorID(1),
Vote: gpbft.Payload{Round: 1, Phase: gpbft.Phase(1)},
}

expectedKey := equivocationKey{
Sender: gpbft.ActorID(1),
Round: 1,
Phase: gpbft.Phase(1),
}

assert.Equal(t, expectedKey, ef.formKey(msg), "Keys should match")
}
21 changes: 13 additions & 8 deletions f3.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,15 @@ type F3 struct {
cancelCtx context.CancelFunc
errgrp *errgroup.Group

mu sync.Mutex
cs *certstore.Store
wal *writeaheadlog.WriteAheadLog[walEntry, *walEntry]
manifest *manifest.Manifest
runner *gpbftRunner
ps *powerstore.Store
certsub *certexpoll.Subscriber
certserv *certexchange.Server
mu sync.Mutex
cs *certstore.Store
wal *writeaheadlog.WriteAheadLog[walEntry, *walEntry]
equivFilter equivocationFilter
manifest *manifest.Manifest
runner *gpbftRunner
ps *powerstore.Store
certsub *certexpoll.Subscriber
certserv *certexchange.Server
}

// New creates and setups f3 with libp2p
Expand Down Expand Up @@ -103,6 +104,9 @@ func (m *F3) Broadcast(ctx context.Context, signatureBuilder *gpbft.SignatureBui
log.Error("attempted to broadcast message while F3 wasn't running")
return
}
if !m.equivFilter.Process(msg) {
return
}
err := wal.Append(walEntry{*msg})
if err != nil {
log.Error("appending to WAL: %+v", err)
Expand Down Expand Up @@ -280,6 +284,7 @@ func (m *F3) reconfigure(ctx context.Context, manif *manifest.Manifest) (_err er
// If we have a new manifest, reconfigure.
if m.manifest == nil || m.manifest.NetworkName != manif.NetworkName {
m.cs = nil
m.equivFilter = equivocationFilter{} // clear out filter
}

// Pause if explicitly paused.
Expand Down

0 comments on commit 6df23f1

Please sign in to comment.