Skip to content

Commit

Permalink
Implement storage power evolution in simulations for honest participants
Browse files Browse the repository at this point in the history
Implement the ability to evolve the storage power across honest
participants over the course of a simulation. The changes introduce
primitives to allow doing this and reflect the changes across the tests
using a uniformed fixed storage power generator.

The adversary storage power evolution is not yet implemented and left to
future work in favour of reducing the number of lines changed.

Add two tests that exercise increase and decrease of storage power over
sync and async message passing:

1. Sudden increase of storage power results in the corresponding group
of nodes to dominate consensus as expected.

2. Gradual decrease of storage power results in loss of dominance among
the corresponding group.

Fixes #114
  • Loading branch information
masih committed May 10, 2024
1 parent 52f910b commit e9599cb
Show file tree
Hide file tree
Showing 14 changed files with 292 additions and 73 deletions.
5 changes: 4 additions & 1 deletion sim/cmd/f3sim/f3sim.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ func main() {
sim.WithLatencyModel(latencyModel),
sim.WithECEpochDuration(30 * time.Second),
sim.WithECStabilisationDelay(0),
sim.AddHonestParticipants(*participantCount, sim.NewUniformECChainGenerator(uint64(seed), 1, 10)),
sim.AddHonestParticipants(
*participantCount,
sim.NewUniformECChainGenerator(uint64(seed), 1, 10),
sim.UniformStoragePower(gpbft.NewStoragePower(1))),
sim.WithTraceLevel(*traceLevel),
sim.WithGpbftOptions(
gpbft.WithDelta(*delta),
Expand Down
11 changes: 11 additions & 0 deletions sim/ecchain_gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,14 @@ func (u *AggregateECChainGenerator) GenerateECChain(instance uint64, base gpbft.
}
return chain
}

var _ ECChainGenerator = (*BaseECChainGenerator)(nil)

// BaseECChainGenerator always return the given base as the EC chain for all participants and instances.
type BaseECChainGenerator struct{}

func NewBaseECChainGenerator() *BaseECChainGenerator { return &BaseECChainGenerator{} }

func (u *BaseECChainGenerator) GenerateECChain(_ uint64, base gpbft.TipSet, _ gpbft.ActorID) gpbft.ECChain {
return gpbft.ECChain{base}
}
23 changes: 20 additions & 3 deletions sim/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,29 @@ type simHost struct {
gpbft.Verifier
gpbft.Clock

id gpbft.ActorID
sim *Simulation
id gpbft.ActorID
sim *Simulation
pubkey gpbft.PubKey

// The simulation package always starts at instance zero.
// TODO: https://github.com/filecoin-project/go-f3/issues/195
instance uint64
ecChain gpbft.ECChain
ecg ECChainGenerator
spg StoragePowerGenerator
}

func newHost(id gpbft.ActorID, sim *Simulation, ecg ECChainGenerator) *simHost {
func newHost(id gpbft.ActorID, sim *Simulation, ecg ECChainGenerator, spg StoragePowerGenerator) *simHost {
pubKey, _ := sim.signingBacked.GenerateKey()
return &simHost{
Network: sim.network,
Verifier: sim.signingBacked,
Signer: sim.signingBacked,
sim: sim,
id: id,
ecg: ecg,
spg: spg,
pubkey: pubKey,
ecChain: *sim.baseChain,
}
}
Expand Down Expand Up @@ -65,3 +70,15 @@ func (v *simHost) ReceiveDecision(decision *gpbft.Justification) time.Time {
func (v *simHost) BroadcastSynchronous(sender gpbft.ActorID, msg gpbft.GMessage) {
v.sim.network.BroadcastSynchronous(sender, msg)
}

func (v *simHost) StoragePower() *gpbft.StoragePower {
return v.spg(v.instance, v.id)
}

func (v *simHost) PublicKey() gpbft.PubKey {
return v.pubkey
}

func (v *simHost) ID() gpbft.ActorID {
return v.id
}
12 changes: 7 additions & 5 deletions sim/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,9 @@ type options struct {
}

type participantArchetype struct {
count int
ecChainGenerator ECChainGenerator
count int
ecChainGenerator ECChainGenerator
storagePowerGenerator StoragePowerGenerator
}

func newOptions(o ...Option) (*options, error) {
Expand Down Expand Up @@ -148,15 +149,16 @@ func WithBaseChain(base *gpbft.ECChain) Option {
}
}

func AddHonestParticipants(count int, generator ECChainGenerator) Option {
func AddHonestParticipants(count int, ecg ECChainGenerator, spg StoragePowerGenerator) Option {
return func(o *options) error {
if count <= 0 {
return fmt.Errorf("honest participant count must be larger than zero; got: %d", count)
}
o.honestParticipantArchetypes = append(o.honestParticipantArchetypes,
participantArchetype{
count: count,
ecChainGenerator: generator,
count: count,
ecChainGenerator: ecg,
storagePowerGenerator: spg,
})
return nil
}
Expand Down
53 changes: 20 additions & 33 deletions sim/sim.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type Simulation struct {
*options
network *Network
ec *simEC
hosts []*simHost
participants []*gpbft.Participant
adversary *adversary.Adversary
}
Expand All @@ -33,11 +34,10 @@ func (s *Simulation) Run(instanceCount uint64, maxRounds uint64) error {
if err := s.initParticipants(); err != nil {
return err
}
pt, err := s.initPowerTable()
pt, err := s.getPowerTable()
if err != nil {
return err
}

currentInstance := s.ec.BeginInstance(*s.baseChain, pt, s.beacon)
s.startParticipants()

Expand Down Expand Up @@ -65,13 +65,14 @@ func (s *Simulation) Run(instanceCount uint64, maxRounds uint64) error {
return fmt.Errorf("concensus was not reached at instance %d", currentInstance.Instance)
}

pt, err := s.getPowerTable()
if err != nil {
return err
}

// Instantiate the next instance even if it goes beyond finalInstance.
// The last incomplete instance is used for testing assertions.
currentInstance = s.ec.BeginInstance(*decidedChain,
// Copy the previous instance power table.
// The simulator doesn't have any facility to evolve the power table.
// See https://github.com/filecoin-project/go-f3/issues/114.
currentInstance.PowerTable.Copy(),
currentInstance = s.ec.BeginInstance(*decidedChain, pt,
[]byte(fmt.Sprintf("beacon %d", currentInstance.Instance+1)),
)

Expand Down Expand Up @@ -105,34 +106,20 @@ func (s *Simulation) startParticipants() {
}
}

func (s *Simulation) initPowerTable() (*gpbft.PowerTable, error) {
func (s *Simulation) getPowerTable() (*gpbft.PowerTable, error) {
pEntries := make([]gpbft.PowerEntry, 0, len(s.participants))
// Set chains for first instance
for _, p := range s.participants {
pubKey, _ := s.signingBacked.GenerateKey()
for _, h := range s.hosts {
pEntries = append(pEntries, gpbft.PowerEntry{
ID: p.ID(),
// TODO: support varying power distribution across participants.
// See: https://github.com/filecoin-project/go-f3/issues/114.
Power: gpbft.NewStoragePower(1),
PubKey: pubKey,
ID: h.ID(),
Power: h.StoragePower(),
PubKey: h.PublicKey(),
})
}
pt := gpbft.NewPowerTable()
if err := pt.Add(pEntries...); err != nil {
return nil, fmt.Errorf("failed to set up power table at first instance: %w", err)
}
if s.adversary != nil {
aPubKey, _ := s.signingBacked.GenerateKey()
err := pt.Add(gpbft.PowerEntry{
ID: s.adversary.ID(),
Power: s.adversary.Power,
PubKey: aPubKey,
})
if err != nil {
return nil, fmt.Errorf("failed to set up adversary power entry at first instance: %w", err)
}
}
return pt, nil
}

Expand All @@ -141,28 +128,28 @@ func (s *Simulation) initParticipants() error {
var nextID gpbft.ActorID
for _, archetype := range s.honestParticipantArchetypes {
for i := 0; i < archetype.count; i++ {
host := newHost(nextID, s, archetype.ecChainGenerator)
host := newHost(nextID, s, archetype.ecChainGenerator, archetype.storagePowerGenerator)
participant, err := gpbft.NewParticipant(nextID, host, pOpts...)
if err != nil {
return fmt.Errorf("failed to instnatiate participant: %w", err)
}
s.participants = append(s.participants, participant)
s.hosts = append(s.hosts, host)
s.network.AddParticipant(participant)
nextID++
}
}

// TODO: expand the simulation to accommodate more than one adversary for
// a more realistic simulation.
// Limit adversaryCount to exactly one for now to reduce LOC up for review.
// Future PRs will expand this to support a group of adversaries.
// There is at most one adversary but with arbitrary power.
if s.adversaryGenerator != nil && s.adversaryCount == 1 {
host := newHost(nextID, s, NewFixedECChainGenerator(*s.baseChain), nil)
// Adversary implementations currently ignore the canonical chain.
// Set to a fixed ec chain generator and expand later for possibility
// of implementing adversaries that adapt based on ec chain.
// TODO: expand adversary archetypes.
host := newHost(nextID, s, NewFixedECChainGenerator(*s.baseChain))
s.adversary = s.adversaryGenerator(nextID, host)
// Adversary power does not evolve.
host.spg = UniformStoragePower(s.adversary.Power)
s.hosts = append(s.hosts, host)
s.network.AddParticipant(s.adversary)
}
return nil
Expand Down
11 changes: 11 additions & 0 deletions sim/storage_power_gen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package sim

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

type StoragePowerGenerator func(instance uint64, id gpbft.ActorID) *gpbft.StoragePower

func UniformStoragePower(power *gpbft.StoragePower) StoragePowerGenerator {
return func(uint64, gpbft.ActorID) *gpbft.StoragePower {
return power
}
}
5 changes: 4 additions & 1 deletion test/absent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ func TestAbsent(t *testing.T) {
repeatInParallel(t, 1, func(t *testing.T, repetition int) {
// Total network size of 3 + 1, where the adversary has 1/4 of power.
sm, err := sim.NewSimulation(asyncOptions(t, repetition,
sim.AddHonestParticipants(3, sim.NewUniformECChainGenerator(tipSetGeneratorSeed, 1, 10)),
sim.AddHonestParticipants(
3,
sim.NewUniformECChainGenerator(tipSetGeneratorSeed, 1, 10),
uniformOneStoragePower),
sim.WithAdversary(adversary.NewAbsentGenerator(oneStoragePower)),
)...)
require.NoError(t, err)
Expand Down
3 changes: 2 additions & 1 deletion test/constants_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ const (
)

var (
oneStoragePower = gpbft.NewStoragePower(1)
oneStoragePower = gpbft.NewStoragePower(1)
uniformOneStoragePower = sim.UniformStoragePower(oneStoragePower)

// testGpbftOptions is configuration constants used across most tests.
// These values are not intended to reflect real-world conditions.
Expand Down
5 changes: 4 additions & 1 deletion test/decide_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ func TestImmediateDecide(t *testing.T) {
baseChain := generateECChain(t, tsg)
adversaryValue := baseChain.Extend(tsg.Sample())
sm, err := sim.NewSimulation(asyncOptions(t, 1413,
sim.AddHonestParticipants(1, sim.NewUniformECChainGenerator(tipSetGeneratorSeed, 1, 10)),
sim.AddHonestParticipants(
1,
sim.NewUniformECChainGenerator(tipSetGeneratorSeed, 1, 10),
uniformOneStoragePower),
sim.WithBaseChain(&baseChain),
// Add the adversary to the simulation with 3/4 of total power.
sim.WithAdversary(adversary.NewImmediateDecideGenerator(adversaryValue, gpbft.NewStoragePower(3))),
Expand Down
43 changes: 23 additions & 20 deletions test/honest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func TestHonest_ChainAgreement(t *testing.T) {
targetChain := baseChain.Extend(tsg.Sample())
test.options = append(test.options,
sim.WithBaseChain(&baseChain),
sim.AddHonestParticipants(test.participantCount, sim.NewFixedECChainGenerator(targetChain)))
sim.AddHonestParticipants(test.participantCount, sim.NewFixedECChainGenerator(targetChain), uniformOneStoragePower))
sm, err := sim.NewSimulation(test.options...)
require.NoError(t, err)

Expand Down Expand Up @@ -97,8 +97,8 @@ func TestHonest_ChainDisagreement(t *testing.T) {
anotherChain := baseChain.Extend(tsg.Sample())
test.options = append(test.options,
sim.WithBaseChain(&baseChain),
sim.AddHonestParticipants(1, sim.NewFixedECChainGenerator(oneChain)),
sim.AddHonestParticipants(1, sim.NewFixedECChainGenerator(anotherChain)),
sim.AddHonestParticipants(1, sim.NewFixedECChainGenerator(oneChain), uniformOneStoragePower),
sim.AddHonestParticipants(1, sim.NewFixedECChainGenerator(anotherChain), uniformOneStoragePower),
)
sm, err := sim.NewSimulation(test.options...)
require.NoError(t, err)
Expand All @@ -116,7 +116,7 @@ func TestSync_AgreementWithRepetition(t *testing.T) {
someChain := baseChain.Extend(tsg.Sample())
sm, err := sim.NewSimulation(syncOptions(
sim.WithBaseChain(&baseChain),
sim.AddHonestParticipants(honestCount, sim.NewFixedECChainGenerator(someChain)),
sim.AddHonestParticipants(honestCount, sim.NewFixedECChainGenerator(someChain), uniformOneStoragePower),
)...)
require.NoError(t, err)

Expand All @@ -142,7 +142,10 @@ func TestAsyncAgreement(t *testing.T) {
someChain := baseChain.Extend(tsg.Sample())
sm, err := sim.NewSimulation(asyncOptions(t, repetition,
sim.WithBaseChain(&baseChain),
sim.AddHonestParticipants(honestCount, sim.NewFixedECChainGenerator(someChain)),
sim.AddHonestParticipants(
honestCount,
sim.NewFixedECChainGenerator(someChain),
uniformOneStoragePower),
)...)
require.NoError(t, err)
require.NoErrorf(t, sm.Run(1, maxRounds), "%s", sm.Describe())
Expand All @@ -163,8 +166,8 @@ func TestSyncHalves(t *testing.T) {
anotherChain := baseChain.Extend(tsg.Sample())
sm, err := sim.NewSimulation(syncOptions(
sim.WithBaseChain(&baseChain),
sim.AddHonestParticipants(honestCount/2, sim.NewFixedECChainGenerator(oneChain)),
sim.AddHonestParticipants(honestCount/2, sim.NewFixedECChainGenerator(anotherChain)),
sim.AddHonestParticipants(honestCount/2, sim.NewFixedECChainGenerator(oneChain), uniformOneStoragePower),
sim.AddHonestParticipants(honestCount/2, sim.NewFixedECChainGenerator(anotherChain), uniformOneStoragePower),
)...)
require.NoError(t, err)
require.NoErrorf(t, sm.Run(1, maxRounds), "%s", sm.Describe())
Expand All @@ -188,8 +191,8 @@ func TestSyncHalvesBLS(t *testing.T) {
sm, err := sim.NewSimulation(syncOptions(
sim.WithSigningBackend(signing.NewBLSBackend()),
sim.WithBaseChain(&baseChain),
sim.AddHonestParticipants(honestCount/2, sim.NewFixedECChainGenerator(oneChain)),
sim.AddHonestParticipants(honestCount/2, sim.NewFixedECChainGenerator(anotherChain)),
sim.AddHonestParticipants(honestCount/2, sim.NewFixedECChainGenerator(oneChain), uniformOneStoragePower),
sim.AddHonestParticipants(honestCount/2, sim.NewFixedECChainGenerator(anotherChain), uniformOneStoragePower),
)...)
require.NoError(t, err)
require.NoErrorf(t, sm.Run(1, maxRounds), "%s", sm.Describe())
Expand All @@ -214,8 +217,8 @@ func TestAsyncHalves(t *testing.T) {
anotherChain := baseChain.Extend(tsg.Sample())
sm, err := sim.NewSimulation(asyncOptions(t, repetition,
sim.WithBaseChain(&baseChain),
sim.AddHonestParticipants(honestCount/2, sim.NewFixedECChainGenerator(oneChain)),
sim.AddHonestParticipants(honestCount/2, sim.NewFixedECChainGenerator(anotherChain)),
sim.AddHonestParticipants(honestCount/2, sim.NewFixedECChainGenerator(oneChain), uniformOneStoragePower),
sim.AddHonestParticipants(honestCount/2, sim.NewFixedECChainGenerator(anotherChain), uniformOneStoragePower),
)...)
require.NoError(t, err)
require.NoErrorf(t, sm.Run(1, maxRounds), "%s", sm.Describe())
Expand All @@ -239,8 +242,8 @@ func TestRequireStrongQuorumToProgress(t *testing.T) {
anotherChain := baseChain.Extend(tsg.Sample())
sm, err := sim.NewSimulation(asyncOptions(t, repetition,
sim.WithBaseChain(&baseChain),
sim.AddHonestParticipants(10, sim.NewFixedECChainGenerator(oneChain)),
sim.AddHonestParticipants(20, sim.NewFixedECChainGenerator(anotherChain)),
sim.AddHonestParticipants(10, sim.NewFixedECChainGenerator(oneChain), uniformOneStoragePower),
sim.AddHonestParticipants(20, sim.NewFixedECChainGenerator(anotherChain), uniformOneStoragePower),
)...)
require.NoError(t, err)
require.NoErrorf(t, sm.Run(1, maxRounds), "%s", sm.Describe())
Expand All @@ -261,10 +264,10 @@ func TestHonest_FixedLongestCommonPrefix(t *testing.T) {
abf := commonPrefix.Extend(tsg.Sample())
sm, err := sim.NewSimulation(syncOptions(
sim.WithBaseChain(&baseChain),
sim.AddHonestParticipants(1, sim.NewFixedECChainGenerator(abc)),
sim.AddHonestParticipants(1, sim.NewFixedECChainGenerator(abd)),
sim.AddHonestParticipants(1, sim.NewFixedECChainGenerator(abe)),
sim.AddHonestParticipants(1, sim.NewFixedECChainGenerator(abf)),
sim.AddHonestParticipants(1, sim.NewFixedECChainGenerator(abc), uniformOneStoragePower),
sim.AddHonestParticipants(1, sim.NewFixedECChainGenerator(abd), uniformOneStoragePower),
sim.AddHonestParticipants(1, sim.NewFixedECChainGenerator(abe), uniformOneStoragePower),
sim.AddHonestParticipants(1, sim.NewFixedECChainGenerator(abf), uniformOneStoragePower),
)...)
require.NoError(t, err)
require.NoErrorf(t, sm.Run(1, maxRounds), "%s", sm.Describe())
Expand All @@ -282,15 +285,15 @@ func TestHonest_MajorityCommonPrefix(t *testing.T) {
sim.AddHonestParticipants(20, sim.NewAppendingECChainGenerator(
majorityCommonPrefixGenerator,
sim.NewRandomECChainGenerator(23002354, 1, 8),
)),
), uniformOneStoragePower),
sim.AddHonestParticipants(5, sim.NewAppendingECChainGenerator(
sim.NewUniformECChainGenerator(84154, 1, 20),
sim.NewRandomECChainGenerator(8965741, 5, 8),
)),
), uniformOneStoragePower),
sim.AddHonestParticipants(1, sim.NewAppendingECChainGenerator(
sim.NewUniformECChainGenerator(2158, 10, 20),
sim.NewRandomECChainGenerator(154787878, 2, 8),
)),
), uniformOneStoragePower),
)...)
require.NoError(t, err)
require.NoErrorf(t, sm.Run(instanceCount, maxRounds), "%s", sm.Describe())
Expand Down
Loading

0 comments on commit e9599cb

Please sign in to comment.