Skip to content
Merged
Show file tree
Hide file tree
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
8 changes: 1 addition & 7 deletions internal/orchestration/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,12 +211,6 @@ func (w *Worker) createReplica(opts *orchestrationpb.ReplicaOpts) (*replica.Repl
base,
)

var timeoutRules synchronizer.TimeoutRuler
if depsCore.RuntimeCfg().HasAggregateQC() {
timeoutRules = synchronizer.NewAggregate(depsCore.RuntimeCfg(), depsSecure.Authority())
} else {
timeoutRules = synchronizer.NewSimple(depsCore.RuntimeCfg(), depsSecure.Authority())
}
consensusRules, viewStates, leaderRotation, comm, viewDuration, err := initConsensusModules(depsCore, depsSecure, sender, opts)
if err != nil {
return nil, err
Expand All @@ -230,7 +224,7 @@ func (w *Worker) createReplica(opts *orchestrationpb.ReplicaOpts) (*replica.Repl
leaderRotation,
consensusRules,
viewDuration,
timeoutRules,
synchronizer.NewTimeoutRuler(depsCore.RuntimeCfg(), depsSecure.Authority()),
opts.GetBatchSize(),
replicaOpts...,
)
Expand Down
5 changes: 3 additions & 2 deletions protocol/rules/fasthotstuff.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,21 @@ import (
const NameFastHotStuff = "fasthotstuff"

// FastHotStuff is an implementation of the Fast-HotStuff protocol.
// See the paper for details: https://arxiv.org/abs/2010.11454
type FastHotStuff struct {
logger logging.Logger
config *core.RuntimeConfig
blockchain *blockchain.Blockchain
}

// NewFastHotStuff returns a new instance of the Fast-HotStuff consensus ruleset.
// NewFastHotStuff returns a new instance of the FastHotStuff consensus ruleset.
func NewFastHotStuff(
logger logging.Logger,
config *core.RuntimeConfig,
blockchain *blockchain.Blockchain,
) *FastHotStuff {
if !config.HasAggregateQC() {
panic("aggregate qc must be enabled for fasthotstuff")
panic(NameFastHotStuff + " requires aggregated quorum certificates")
}
return &FastHotStuff{
logger: logger,
Expand Down
18 changes: 8 additions & 10 deletions protocol/synchronizer/synchronizer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"cuelang.org/go/pkg/time"
"github.com/relab/hotstuff"

"github.com/relab/hotstuff/core"
"github.com/relab/hotstuff/internal/proto/clientpb"
"github.com/relab/hotstuff/internal/testutil"
"github.com/relab/hotstuff/protocol"
Expand Down Expand Up @@ -63,7 +64,7 @@ func wireUpSynchronizer(
essentials.Authority(),
leaderRotation,
NewFixedDuration(1000*time.Nanosecond),
NewSimple(essentials.RuntimeCfg(), essentials.Authority()),
NewTimeoutRuler(essentials.RuntimeCfg(), essentials.Authority()),
depsConsensus.Proposer(),
depsConsensus.Voter(),
viewStates,
Expand Down Expand Up @@ -221,15 +222,12 @@ func TestAdvanceView(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
set, viewStates, synchronizer, block := prepareSynchronizer(t)
signers := set.Signers()
essentials := set[0]
// this is a workaround since we want to test both timeout rules in the same test
var opts []core.RuntimeOption
if tt.tr == A {
synchronizer.timeoutRules = NewAggregate(essentials.RuntimeCfg(), essentials.Authority())
} else {
synchronizer.timeoutRules = NewSimple(essentials.RuntimeCfg(), essentials.Authority())
opts = append(opts, core.WithAggregateQC())
}
set, viewStates, synchronizer, block := prepareSynchronizer(t, opts...)
signers := set.Signers()

syncInfo := hotstuff.NewSyncInfo()
if tt.qc {
Expand All @@ -256,8 +254,8 @@ func TestAdvanceView(t *testing.T) {
}
}

func prepareSynchronizer(t *testing.T) (testutil.EssentialsSet, *protocol.ViewStates, *Synchronizer, *hotstuff.Block) {
set := testutil.NewEssentialsSet(t, 4, crypto.NameECDSA)
func prepareSynchronizer(t *testing.T, opts ...core.RuntimeOption) (testutil.EssentialsSet, *protocol.ViewStates, *Synchronizer, *hotstuff.Block) {
set := testutil.NewEssentialsSet(t, 4, crypto.NameECDSA, opts...)
subject := set[0]
viewStates, err := protocol.NewViewStates(
subject.Blockchain(),
Expand Down
4 changes: 2 additions & 2 deletions protocol/synchronizer/timeoutrule_aggregate.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ type Aggregate struct {
auth *cert.Authority
}

// NewAggregate returns an aggregate timeout rule instance.
func NewAggregate(
// newAggregate returns an aggregate timeout rule instance.
func newAggregate(
config *core.RuntimeConfig,
auth *cert.Authority,
) *Aggregate {
Expand Down
4 changes: 2 additions & 2 deletions protocol/synchronizer/timeoutrule_simple.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ type Simple struct {
auth *cert.Authority
}

// NewSimple returns a simple timeout rule instance.
func NewSimple(
// newSimple returns a simple timeout rule instance.
func newSimple(
config *core.RuntimeConfig,
auth *cert.Authority,
) *Simple {
Expand Down
14 changes: 13 additions & 1 deletion protocol/synchronizer/timeoutrules.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
package synchronizer

import "github.com/relab/hotstuff"
import (
"github.com/relab/hotstuff"
"github.com/relab/hotstuff/core"
"github.com/relab/hotstuff/security/cert"
)

// NewTimeoutRuler returns a TimeoutRuler based on the configuration.
func NewTimeoutRuler(cfg *core.RuntimeConfig, auth *cert.Authority) TimeoutRuler {
if cfg.HasAggregateQC() {
return newAggregate(cfg, auth)
}
return newSimple(cfg, auth)
}

type TimeoutRuler interface {
LocalTimeoutRule(hotstuff.View, hotstuff.SyncInfo) (*hotstuff.TimeoutMsg, error)
Expand Down
6 changes: 1 addition & 5 deletions server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,18 +310,14 @@ func createServers(t *testing.T, td testData) []replicaDeps {
sender,
),
)
timeoutRuler := synchronizer.NewSimple(
depsCore.RuntimeCfg(),
depsSecurity.Authority(),
)
synchronizer := synchronizer.New(
depsCore.EventLoop(),
depsCore.Logger(),
depsCore.RuntimeCfg(),
depsSecurity.Authority(),
leaderRotation,
synchronizer.NewFixedDuration(100*time.Millisecond),
timeoutRuler,
synchronizer.NewTimeoutRuler(depsCore.RuntimeCfg(), depsSecurity.Authority()),
depsConsensus.Proposer(),
depsConsensus.Voter(),
states,
Expand Down
25 changes: 13 additions & 12 deletions twins/fhsbug_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"strings"
"testing"

"github.com/relab/hotstuff/core"
"github.com/relab/hotstuff/core/logging"
"github.com/relab/hotstuff/twins"
)
Expand All @@ -22,57 +23,57 @@ const fhsBugScenario = `
[
{
"leader": 1,
"partitions": [ [1, 2, 3, 4], [] ],
"partitions": [ [{"ReplicaID": 1}, {"ReplicaID": 2}, {"ReplicaID": 3}, {"ReplicaID": 4}], [] ],
"comment": "view 1"
},
{
"leader": 1,
"partitions": [ [1, 2, 3, 4], [] ],
"partitions": [ [{"ReplicaID": 1}, {"ReplicaID": 2}, {"ReplicaID": 3}, {"ReplicaID": 4}], [] ],
"comment": "view 2"
},
{
"leader": 1,
"partitions": [ [1, 2, 3, 4], [] ],
"partitions": [ [{"ReplicaID": 1}, {"ReplicaID": 2}, {"ReplicaID": 3}, {"ReplicaID": 4}], [] ],
"comment": "view 3"
},
{
"leader": 1,
"partitions": [ [1, 2, 3, 4], [] ],
"partitions": [ [{"ReplicaID": 1}, {"ReplicaID": 2}, {"ReplicaID": 3}, {"ReplicaID": 4}], [] ],
"comment": "view 4. replicas time out in this view"
},
{
"leader": 2,
"partitions": [ [1, 3, 4], [2] ],
"partitions": [ [{"ReplicaID": 1}, {"ReplicaID": 3}, {"ReplicaID": 4}], [{"ReplicaID": 2}] ],
"comment": "view 5."
},
{
"leader": 1,
"partitions": [ [1, 3, 4], [2] ],
"partitions": [ [{"ReplicaID": 1}, {"ReplicaID": 3}, {"ReplicaID": 4}], [{"ReplicaID": 2}] ],
"comment": "view 6"
},
{
"leader": 3,
"partitions": [ [1, 2, 4], [3] ],
"partitions": [ [{"ReplicaID": 1}, {"ReplicaID": 2}, {"ReplicaID": 4}], [{"ReplicaID": 3}] ],
"comment": "view 7"
},
{
"leader": 2,
"partitions": [ [1, 2, 4], [3] ],
"partitions": [ [{"ReplicaID": 1}, {"ReplicaID": 2}, {"ReplicaID": 4}], [{"ReplicaID": 3}] ],
"comment": "view 8"
},
{
"leader": 2,
"partitions": [ [1, 3, 4], [2] ],
"partitions": [ [{"ReplicaID": 1}, {"ReplicaID": 3}, {"ReplicaID": 4}], [{"ReplicaID": 2}] ],
"comment": "view 9"
},
{
"leader": 3,
"partitions": [ [1, 3, 4], [2] ],
"partitions": [ [{"ReplicaID": 1}, {"ReplicaID": 3}, {"ReplicaID": 4}], [{"ReplicaID": 2}] ],
"comment": "view 10"
},
{
"leader": 3,
"partitions": [ [1, 3, 4], [2] ],
"partitions": [ [{"ReplicaID": 1}, {"ReplicaID": 3}, {"ReplicaID": 4}], [{"ReplicaID": 2}] ],
"comment": "view 11"
}
]
Expand Down Expand Up @@ -101,7 +102,7 @@ func TestFHSBug(t *testing.T) {

settings := src.Settings()

res, err := twins.ExecuteScenario(scenario, settings.NumNodes, settings.NumTwins, 100, vulnerableModule)
res, err := twins.ExecuteScenario(scenario, settings.NumNodes, settings.NumTwins, 100, vulnerableModule, core.WithAggregateQC())
if err != nil {
t.Fatalf("failed to execute scenario: %v", err)
}
Expand Down
7 changes: 3 additions & 4 deletions twins/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"strings"

"github.com/relab/hotstuff"
"github.com/relab/hotstuff/core"
"github.com/relab/hotstuff/core/logging"
"github.com/relab/hotstuff/security/crypto/keygen"
)
Expand Down Expand Up @@ -115,7 +116,7 @@ func NewPartitionedNetwork(views []View, dropTypes ...any) *Network {

// createNodesAndTwins creates nodes and their twins in the network.
// Twins receive the same private key.
func (n *Network) createNodesAndTwins(nodes []NodeID, consensusName string) error {
func (n *Network) createNodesAndTwins(nodes []NodeID, consensusName string, opts ...core.RuntimeOption) error {
for _, nodeID := range nodes {
var privKey *ecdsa.PrivateKey
var err error
Expand All @@ -129,7 +130,7 @@ func (n *Network) createNodesAndTwins(nodes []NodeID, consensusName string) erro
// reuse the private key of the first replica
privKey = twins[0].config.PrivateKey().(*ecdsa.PrivateKey)
}
node, err := newNode(n, nodeID, consensusName, privKey)
node, err := newNode(n, nodeID, consensusName, privKey, opts...)
if err != nil {
return fmt.Errorf("failed to create node %v: %w", nodeID, err)
}
Expand Down Expand Up @@ -200,13 +201,11 @@ func (n *Network) tick() {
for node.eventLoop.Tick(context.Background()) { //revive:disable-line:empty-block
}
}

}

// shouldDrop decides if the sender should drop the message, based on the current view of the sender and the
// partitions configured for that view.
func (n *Network) shouldDrop(sender, receiver NodeID, message any, view hotstuff.View) bool {

// Index into viewPartitions.
i := int(view) - 1

Expand Down
40 changes: 12 additions & 28 deletions twins/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import (
"github.com/relab/hotstuff/protocol/comm"
"github.com/relab/hotstuff/protocol/consensus"
"github.com/relab/hotstuff/protocol/leaderrotation"
"github.com/relab/hotstuff/protocol/rules"
"github.com/relab/hotstuff/protocol/synchronizer"
"github.com/relab/hotstuff/protocol/votingmachine"
"github.com/relab/hotstuff/security/blockchain"
Expand Down Expand Up @@ -43,11 +42,12 @@ type node struct {
log strings.Builder
}

func newNode(n *Network, nodeID NodeID, consensusName string, pk *ecdsa.PrivateKey) (*node, error) {
func newNode(n *Network, nodeID NodeID, consensusName string, pk *ecdsa.PrivateKey, opts ...core.RuntimeOption) (*node, error) {
cryptoName := crypto.NameECDSA
allOpts := append([]core.RuntimeOption{core.WithSyncVerification(), core.WithCache(100)}, opts...)
node := &node{
id: nodeID,
config: core.NewRuntimeConfig(nodeID.ReplicaID, pk, core.WithSyncVerification(), core.WithCache(100)),
config: core.NewRuntimeConfig(nodeID.ReplicaID, pk, allOpts...),
commandCache: clientpb.NewCommandCache(1),
}
node.logger = logging.NewWithDest(&n.log, fmt.Sprintf("r%dn%d", nodeID.ReplicaID, nodeID.TwinID))
Expand All @@ -68,22 +68,14 @@ func newNode(n *Network, nodeID NodeID, consensusName string, pk *ecdsa.PrivateK
base,
)
node.blockchain = depsSecurity.Blockchain()
var consensusRules consensus.Ruleset
if consensusName == nameVulnerableFHS {
consensusRules = NewVulnFHS(
node.logger,
node.blockchain,
rules.NewFastHotStuff(
node.logger,
node.config,
node.blockchain,
),
)
} else {
consensusRules, err = rules.New(node.logger, node.config, node.blockchain, consensusName)
if err != nil {
return nil, err
}
consensusRules, err := newTwinsConsensusRules(
node.logger,
node.config,
node.blockchain,
consensusName,
)
if err != nil {
return nil, err
}
node.viewStates, err = protocol.NewViewStates(node.blockchain, depsSecurity.Authority())
if err != nil {
Expand Down Expand Up @@ -124,22 +116,14 @@ func newNode(n *Network, nodeID NodeID, consensusName string, pk *ecdsa.PrivateK
node.commandCache,
committer,
)
var timeoutRules synchronizer.TimeoutRuler
if consensusName == rules.NameFastHotStuff || consensusName == nameVulnerableFHS {
// Use aggregated quorum certificates.
// This must be true for Fast-HotStuff: https://arxiv.org/abs/2010.11454
timeoutRules = synchronizer.NewAggregate(node.config, depsSecurity.Authority())
} else {
timeoutRules = synchronizer.NewSimple(node.config, depsSecurity.Authority())
}
node.synchronizer = synchronizer.New(
node.eventLoop,
node.logger,
node.config,
depsSecurity.Authority(),
node.leaderRotation,
synchronizer.NewFixedDuration(500*time.Millisecond),
timeoutRules,
synchronizer.NewTimeoutRuler(node.config, depsSecurity.Authority()),
node.proposer,
node.voter,
node.viewStates,
Expand Down
6 changes: 4 additions & 2 deletions twins/scenario.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"strconv"
"strings"

"github.com/relab/hotstuff/core"

"github.com/relab/hotstuff"
"github.com/relab/hotstuff/internal/proto/clientpb"
"github.com/relab/hotstuff/protocol/leaderrotation"
Expand Down Expand Up @@ -46,7 +48,7 @@ type ScenarioResult struct {
}

// ExecuteScenario executes a twins scenario.
func ExecuteScenario(scenario Scenario, numNodes, numTwins uint8, numTicks int, consensusName string) (result ScenarioResult, err error) {
func ExecuteScenario(scenario Scenario, numNodes, numTwins uint8, numTicks int, consensusName string, opts ...core.RuntimeOption) (result ScenarioResult, err error) {
// Network simulator that blocks proposals, votes, and fetch requests between nodes that are in different partitions.
// Timeout and NewView messages are permitted.
network := NewPartitionedNetwork(scenario,
Expand All @@ -60,7 +62,7 @@ func ExecuteScenario(scenario Scenario, numNodes, numTwins uint8, numTicks int,
nodes, twins := assignNodeIDs(numNodes, numTwins)
nodes = append(nodes, twins...)

err = network.createNodesAndTwins(nodes, consensusName)
err = network.createNodesAndTwins(nodes, consensusName, opts...)
if err != nil {
return ScenarioResult{}, err
}
Expand Down
Loading