diff --git a/sim/adversary/decide.go b/sim/adversary/decide.go index de2ae986..a723350c 100644 --- a/sim/adversary/decide.go +++ b/sim/adversary/decide.go @@ -10,25 +10,39 @@ import ( var _ Receiver = (*ImmediateDecide)(nil) +type ImmediateDecideOption func(*ImmediateDecide) + +func ImmediateDecideWithNthParticipant(n uint64) ImmediateDecideOption { + return func(i *ImmediateDecide) { + i.additionalParticipant = &n + } +} + // / An "adversary" that immediately sends a DECIDE message, justified by its own COMMIT. type ImmediateDecide struct { id gpbft.ActorID host Host value gpbft.ECChain + + additionalParticipant *uint64 } -func NewImmediateDecide(id gpbft.ActorID, host Host, value gpbft.ECChain) *ImmediateDecide { - return &ImmediateDecide{ +func NewImmediateDecide(id gpbft.ActorID, host Host, value gpbft.ECChain, opts ...ImmediateDecideOption) *ImmediateDecide { + i := &ImmediateDecide{ id: id, host: host, value: value, } + for _, opt := range opts { + opt(i) + } + return i } -func NewImmediateDecideGenerator(value gpbft.ECChain, power *gpbft.StoragePower) Generator { +func NewImmediateDecideGenerator(value gpbft.ECChain, power *gpbft.StoragePower, opts ...ImmediateDecideOption) Generator { return func(id gpbft.ActorID, host Host) *Adversary { return &Adversary{ - Receiver: NewImmediateDecide(id, host, value), + Receiver: NewImmediateDecide(id, host, value, opts...), Power: power, } } @@ -55,15 +69,39 @@ func (i *ImmediateDecide) StartInstanceAt(instance uint64, _when time.Time) erro SupplementalData: *supplementalData, } sigPayload := i.host.MarshalPayloadForSigning(i.host.NetworkName(), &justificationPayload) - _, pubkey := powertable.Get(i.id) - sig, err := i.host.Sign(context.Background(), pubkey, sigPayload) - if err != nil { + signers := bitfield.New() + + signers.Set(uint64(powertable.Lookup[i.id])) + + if i.additionalParticipant != nil { + signers.Set(*i.additionalParticipant) + } + + var ( + pubkeys []gpbft.PubKey + sigs [][]byte + ) + + if err := signers.ForEach(func(j uint64) error { + pubkey := gpbft.PubKey("fake pubkey") + sig := []byte("fake sig") + if j < uint64(len(powertable.Entries)) { + pubkey = powertable.Entries[j].PubKey + var err error + sig, err = i.host.Sign(context.Background(), pubkey, sigPayload) + if err != nil { + return err + } + } + + pubkeys = append(pubkeys, pubkey) + sigs = append(sigs, sig) + return nil + }); err != nil { panic(err) } - signers := bitfield.New() - signers.Set(uint64(powertable.Lookup[i.id])) - aggregatedSig, err := i.host.Aggregate([]gpbft.PubKey{pubkey}, [][]byte{sig}) + aggregatedSig, err := i.host.Aggregate(pubkeys, sigs) if err != nil { panic(err) } diff --git a/test/decide_test.go b/test/decide_test.go index 3fdfd55c..c3491d96 100644 --- a/test/decide_test.go +++ b/test/decide_test.go @@ -47,3 +47,49 @@ func FuzzImmediateDecideAdversary(f *testing.F) { require.Equal(t, adversaryValue.Head(), decision.Head(), "honest node did not decide the right value") }) } + +func TestIllegalCommittee_OutOfRange(t *testing.T) { + const seed = 98562314 + t.Parallel() + rng := rand.New(rand.NewSource(int64(seed))) + tsg := sim.NewTipSetGenerator(tipSetGeneratorSeed) + baseChain := generateECChain(t, tsg) + adversaryValue := baseChain.Extend(tsg.Sample()) + sm, err := sim.NewSimulation( + asyncOptions(rng.Int(), + sim.AddHonestParticipants( + 1, + sim.NewUniformECChainGenerator(rng.Uint64(), 1, 5), + uniformOneStoragePower), + sim.WithBaseChain(&baseChain), + // Add the adversary to the simulation with 3/4 of total power. + sim.WithAdversary(adversary.NewImmediateDecideGenerator(adversaryValue, gpbft.NewStoragePower(3), adversary.ImmediateDecideWithNthParticipant(100))), + )...) + require.NoError(t, err) + + err = sm.Run(1, maxRounds) + require.ErrorContains(t, err, "invalid signer index") +} + +func TestIllegalCommittee_NoPower(t *testing.T) { + const seed = 98562314 + t.Parallel() + rng := rand.New(rand.NewSource(int64(seed))) + tsg := sim.NewTipSetGenerator(tipSetGeneratorSeed) + baseChain := generateECChain(t, tsg) + adversaryValue := baseChain.Extend(tsg.Sample()) + sm, err := sim.NewSimulation( + asyncOptions(rng.Int(), + sim.AddHonestParticipants( + 1, + sim.NewUniformECChainGenerator(rng.Uint64(), 1, 5), + sim.UniformStoragePower(gpbft.NewStoragePower(1))), + sim.WithBaseChain(&baseChain), + // Add the adversary to the simulation with 3/4 of total power. + sim.WithAdversary(adversary.NewImmediateDecideGenerator(adversaryValue, gpbft.NewStoragePower(0xffff), adversary.ImmediateDecideWithNthParticipant(1))), + )...) + require.NoError(t, err) + + err = sm.Run(1, maxRounds) + require.ErrorContains(t, err, "signer with ID 0 has no power") +}