Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement storage power evolution in simulations for honest participants #202

Merged
merged 1 commit into from
May 13, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Kubuxu re my comments on #188 , putting the pubkey here seems reasonable and may avoid the power table plumbing.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The power table plumbing is external to the sim, as F3 won't know for which participants it will be signing. Only the application itself knows that.


// 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 @@ -53,8 +53,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 @@ -143,15 +144,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