mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2024-12-22 19:40:37 +00:00
5a66807989
* First take at updating everything to v5 * Patch gRPC gateway to use prysm v5 Fix patch * Update go ssz --------- Co-authored-by: Preston Van Loon <pvanloon@offchainlabs.com>
1176 lines
46 KiB
Go
1176 lines
46 KiB
Go
package sync
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/golang/snappy"
|
|
pubsub "github.com/libp2p/go-libp2p-pubsub"
|
|
pubsubpb "github.com/libp2p/go-libp2p-pubsub/pb"
|
|
"github.com/libp2p/go-libp2p/core/peer"
|
|
"github.com/prysmaticlabs/go-bitfield"
|
|
mockChain "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain/testing"
|
|
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/altair"
|
|
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/feed"
|
|
opfeed "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/feed/operation"
|
|
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/signing"
|
|
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/transition"
|
|
"github.com/prysmaticlabs/prysm/v5/beacon-chain/db"
|
|
testingdb "github.com/prysmaticlabs/prysm/v5/beacon-chain/db/testing"
|
|
doublylinkedtree "github.com/prysmaticlabs/prysm/v5/beacon-chain/forkchoice/doubly-linked-tree"
|
|
"github.com/prysmaticlabs/prysm/v5/beacon-chain/p2p"
|
|
"github.com/prysmaticlabs/prysm/v5/beacon-chain/p2p/encoder"
|
|
mockp2p "github.com/prysmaticlabs/prysm/v5/beacon-chain/p2p/testing"
|
|
p2ptypes "github.com/prysmaticlabs/prysm/v5/beacon-chain/p2p/types"
|
|
"github.com/prysmaticlabs/prysm/v5/beacon-chain/startup"
|
|
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
|
|
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stategen"
|
|
mockSync "github.com/prysmaticlabs/prysm/v5/beacon-chain/sync/initial-sync/testing"
|
|
"github.com/prysmaticlabs/prysm/v5/beacon-chain/verification"
|
|
"github.com/prysmaticlabs/prysm/v5/config/params"
|
|
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
|
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
|
"github.com/prysmaticlabs/prysm/v5/crypto/bls"
|
|
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
|
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
|
"github.com/prysmaticlabs/prysm/v5/testing/assert"
|
|
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
|
"github.com/prysmaticlabs/prysm/v5/testing/util"
|
|
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
|
)
|
|
|
|
func TestService_ValidateSyncContributionAndProof(t *testing.T) {
|
|
database := testingdb.SetupDB(t)
|
|
headRoot, keys := fillUpBlocksAndState(context.Background(), t, database)
|
|
defaultTopic := p2p.SyncContributionAndProofSubnetTopicFormat
|
|
defaultTopic = fmt.Sprintf(defaultTopic, []byte{0xAB, 0x00, 0xCC, 0x9E})
|
|
defaultTopic = defaultTopic + "/" + encoder.ProtocolSuffixSSZSnappy
|
|
chainService := &mockChain.ChainService{
|
|
Genesis: time.Now(),
|
|
ValidatorsRoot: [32]byte{'A'},
|
|
}
|
|
var emptySig [96]byte
|
|
type args struct {
|
|
pid peer.ID
|
|
msg *ethpb.SignedContributionAndProof
|
|
topic string
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
svcopts []Option
|
|
setupSvc func(s *Service, msg *ethpb.SignedContributionAndProof) (*Service, *startup.Clock)
|
|
clock *startup.Clock
|
|
args args
|
|
want pubsub.ValidationResult
|
|
}{
|
|
{
|
|
name: "Is syncing",
|
|
svcopts: []Option{
|
|
WithP2P(mockp2p.NewTestP2P(t)),
|
|
WithInitialSync(&mockSync.Sync{IsSyncing: true}),
|
|
WithChainService(chainService),
|
|
WithOperationNotifier(chainService.OperationNotifier()),
|
|
},
|
|
setupSvc: func(s *Service, msg *ethpb.SignedContributionAndProof) (*Service, *startup.Clock) {
|
|
s.cfg.stateGen = stategen.New(database, doublylinkedtree.New())
|
|
msg.Message.Contribution.BlockRoot = headRoot[:]
|
|
s.cfg.beaconDB = database
|
|
s.initCaches()
|
|
return s, startup.NewClock(time.Now(), [32]byte{})
|
|
},
|
|
args: args{
|
|
pid: "random",
|
|
topic: "junk",
|
|
msg: ðpb.SignedContributionAndProof{
|
|
Message: ðpb.ContributionAndProof{
|
|
AggregatorIndex: 1,
|
|
Contribution: ðpb.SyncCommitteeContribution{
|
|
Slot: 1,
|
|
SubcommitteeIndex: 1,
|
|
BlockRoot: params.BeaconConfig().ZeroHash[:],
|
|
AggregationBits: bitfield.NewBitvector128(),
|
|
Signature: emptySig[:],
|
|
},
|
|
SelectionProof: emptySig[:],
|
|
},
|
|
Signature: emptySig[:],
|
|
}},
|
|
want: pubsub.ValidationIgnore,
|
|
},
|
|
{
|
|
name: "Bad Topic",
|
|
svcopts: []Option{
|
|
WithP2P(mockp2p.NewTestP2P(t)),
|
|
WithInitialSync(&mockSync.Sync{IsSyncing: false}),
|
|
WithChainService(chainService),
|
|
WithOperationNotifier(chainService.OperationNotifier()),
|
|
},
|
|
setupSvc: func(s *Service, msg *ethpb.SignedContributionAndProof) (*Service, *startup.Clock) {
|
|
s.cfg.stateGen = stategen.New(database, doublylinkedtree.New())
|
|
msg.Message.Contribution.BlockRoot = headRoot[:]
|
|
s.cfg.beaconDB = database
|
|
s.initCaches()
|
|
return s, startup.NewClock(time.Now(), [32]byte{})
|
|
},
|
|
args: args{
|
|
pid: "random",
|
|
topic: "junk",
|
|
msg: ðpb.SignedContributionAndProof{
|
|
Message: ðpb.ContributionAndProof{
|
|
AggregatorIndex: 1,
|
|
Contribution: ðpb.SyncCommitteeContribution{
|
|
Slot: 1,
|
|
SubcommitteeIndex: 1,
|
|
BlockRoot: params.BeaconConfig().ZeroHash[:],
|
|
AggregationBits: bitfield.NewBitvector128(),
|
|
Signature: emptySig[:],
|
|
},
|
|
SelectionProof: emptySig[:],
|
|
},
|
|
Signature: emptySig[:],
|
|
}},
|
|
want: pubsub.ValidationReject,
|
|
},
|
|
{
|
|
name: "Future Slot Message",
|
|
svcopts: []Option{
|
|
WithP2P(mockp2p.NewTestP2P(t)),
|
|
WithInitialSync(&mockSync.Sync{IsSyncing: false}),
|
|
WithChainService(chainService),
|
|
WithOperationNotifier(chainService.OperationNotifier()),
|
|
},
|
|
setupSvc: func(s *Service, msg *ethpb.SignedContributionAndProof) (*Service, *startup.Clock) {
|
|
s.cfg.stateGen = stategen.New(database, doublylinkedtree.New())
|
|
s.cfg.beaconDB = database
|
|
s.initCaches()
|
|
return s, startup.NewClock(time.Now(), [32]byte{})
|
|
},
|
|
args: args{
|
|
pid: "random",
|
|
topic: defaultTopic,
|
|
msg: ðpb.SignedContributionAndProof{
|
|
Message: ðpb.ContributionAndProof{
|
|
AggregatorIndex: 1,
|
|
Contribution: ðpb.SyncCommitteeContribution{
|
|
Slot: 30,
|
|
SubcommitteeIndex: 1,
|
|
BlockRoot: params.BeaconConfig().ZeroHash[:],
|
|
AggregationBits: bitfield.NewBitvector128(),
|
|
Signature: emptySig[:],
|
|
},
|
|
SelectionProof: emptySig[:],
|
|
},
|
|
Signature: emptySig[:],
|
|
}},
|
|
want: pubsub.ValidationIgnore,
|
|
},
|
|
{
|
|
name: "Already Seen Message",
|
|
svcopts: []Option{
|
|
WithP2P(mockp2p.NewTestP2P(t)),
|
|
WithInitialSync(&mockSync.Sync{IsSyncing: false}),
|
|
WithChainService(chainService),
|
|
WithOperationNotifier(chainService.OperationNotifier()),
|
|
},
|
|
setupSvc: func(s *Service, msg *ethpb.SignedContributionAndProof) (*Service, *startup.Clock) {
|
|
s.cfg.stateGen = stategen.New(database, doublylinkedtree.New())
|
|
s.cfg.beaconDB = database
|
|
s.initCaches()
|
|
s.cfg.chain = &mockChain.ChainService{
|
|
Genesis: time.Now(),
|
|
}
|
|
msg.Message.Contribution.BlockRoot = headRoot[:]
|
|
msg.Message.Contribution.AggregationBits.SetBitAt(1, true)
|
|
|
|
s.setSyncContributionIndexSlotSeen(1, 1, 1)
|
|
gt := time.Now().Add(-time.Second * time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Duration(msg.Message.Contribution.Slot))
|
|
return s, startup.NewClock(gt, [32]byte{'A'})
|
|
},
|
|
args: args{
|
|
pid: "random",
|
|
topic: defaultTopic,
|
|
msg: ðpb.SignedContributionAndProof{
|
|
Message: ðpb.ContributionAndProof{
|
|
AggregatorIndex: 1,
|
|
Contribution: ðpb.SyncCommitteeContribution{
|
|
Slot: 1,
|
|
SubcommitteeIndex: 1,
|
|
BlockRoot: params.BeaconConfig().ZeroHash[:],
|
|
AggregationBits: bitfield.NewBitvector128(),
|
|
Signature: emptySig[:],
|
|
},
|
|
SelectionProof: emptySig[:],
|
|
},
|
|
Signature: emptySig[:],
|
|
}},
|
|
want: pubsub.ValidationIgnore,
|
|
},
|
|
{
|
|
name: "Invalid Subcommittee Index",
|
|
svcopts: []Option{
|
|
WithP2P(mockp2p.NewTestP2P(t)),
|
|
WithInitialSync(&mockSync.Sync{IsSyncing: false}),
|
|
WithChainService(chainService),
|
|
WithOperationNotifier(chainService.OperationNotifier()),
|
|
},
|
|
setupSvc: func(s *Service, msg *ethpb.SignedContributionAndProof) (*Service, *startup.Clock) {
|
|
s.cfg.stateGen = stategen.New(database, doublylinkedtree.New())
|
|
s.cfg.beaconDB = database
|
|
s.initCaches()
|
|
s.cfg.chain = &mockChain.ChainService{
|
|
Genesis: time.Now(),
|
|
}
|
|
msg.Message.Contribution.BlockRoot = headRoot[:]
|
|
msg.Message.Contribution.AggregationBits.SetBitAt(1, true)
|
|
msg.Message.Contribution.SubcommitteeIndex = 20
|
|
|
|
gt := time.Now().Add(-time.Second * time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Duration(msg.Message.Contribution.Slot))
|
|
return s, startup.NewClock(gt, [32]byte{'A'})
|
|
},
|
|
args: args{
|
|
pid: "random",
|
|
topic: defaultTopic,
|
|
msg: ðpb.SignedContributionAndProof{
|
|
Message: ðpb.ContributionAndProof{
|
|
AggregatorIndex: 1,
|
|
Contribution: ðpb.SyncCommitteeContribution{
|
|
Slot: 1,
|
|
SubcommitteeIndex: 1,
|
|
BlockRoot: params.BeaconConfig().ZeroHash[:],
|
|
AggregationBits: bitfield.NewBitvector128(),
|
|
Signature: emptySig[:],
|
|
},
|
|
SelectionProof: emptySig[:],
|
|
},
|
|
Signature: emptySig[:],
|
|
}},
|
|
want: pubsub.ValidationReject,
|
|
},
|
|
{
|
|
name: "Invalid Selection Proof",
|
|
svcopts: []Option{
|
|
WithP2P(mockp2p.NewTestP2P(t)),
|
|
WithInitialSync(&mockSync.Sync{IsSyncing: false}),
|
|
WithChainService(chainService),
|
|
WithOperationNotifier(chainService.OperationNotifier()),
|
|
},
|
|
setupSvc: func(s *Service, msg *ethpb.SignedContributionAndProof) (*Service, *startup.Clock) {
|
|
s.cfg.stateGen = stategen.New(database, doublylinkedtree.New())
|
|
s.cfg.beaconDB = database
|
|
s.initCaches()
|
|
s.cfg.chain = &mockChain.ChainService{
|
|
Genesis: time.Now(),
|
|
}
|
|
msg.Message.Contribution.BlockRoot = headRoot[:]
|
|
incorrectProof := [96]byte{0xBB}
|
|
msg.Message.SelectionProof = incorrectProof[:]
|
|
msg.Message.Contribution.AggregationBits.SetBitAt(1, true)
|
|
|
|
gt := time.Now().Add(-time.Second * time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Duration(msg.Message.Contribution.Slot))
|
|
return s, startup.NewClock(gt, [32]byte{'A'})
|
|
},
|
|
args: args{
|
|
pid: "random",
|
|
topic: defaultTopic,
|
|
msg: ðpb.SignedContributionAndProof{
|
|
Message: ðpb.ContributionAndProof{
|
|
AggregatorIndex: 1,
|
|
Contribution: ðpb.SyncCommitteeContribution{
|
|
Slot: 1,
|
|
SubcommitteeIndex: 1,
|
|
BlockRoot: params.BeaconConfig().ZeroHash[:],
|
|
AggregationBits: bitfield.NewBitvector128(),
|
|
Signature: emptySig[:],
|
|
},
|
|
SelectionProof: emptySig[:],
|
|
},
|
|
Signature: emptySig[:],
|
|
}},
|
|
want: pubsub.ValidationReject,
|
|
},
|
|
{
|
|
name: "Invalid Aggregator",
|
|
svcopts: []Option{
|
|
WithP2P(mockp2p.NewTestP2P(t)),
|
|
WithInitialSync(&mockSync.Sync{IsSyncing: false}),
|
|
WithChainService(chainService),
|
|
WithOperationNotifier(chainService.OperationNotifier()),
|
|
},
|
|
setupSvc: func(s *Service, msg *ethpb.SignedContributionAndProof) (*Service, *startup.Clock) {
|
|
s.cfg.stateGen = stategen.New(database, doublylinkedtree.New())
|
|
s.cfg.beaconDB = database
|
|
s.initCaches()
|
|
s.cfg.chain = &mockChain.ChainService{
|
|
Genesis: time.Now(),
|
|
}
|
|
msg.Message.Contribution.BlockRoot = headRoot[:]
|
|
hState, err := database.State(context.Background(), headRoot)
|
|
assert.NoError(t, err)
|
|
sc, err := hState.CurrentSyncCommittee()
|
|
assert.NoError(t, err)
|
|
for i := uint64(0); i < params.BeaconConfig().SyncCommitteeSubnetCount; i++ {
|
|
coms, err := altair.SyncSubCommitteePubkeys(sc, primitives.CommitteeIndex(i))
|
|
assert.NoError(t, err)
|
|
for _, p := range coms {
|
|
idx, ok := hState.ValidatorIndexByPubkey(bytesutil.ToBytes48(p))
|
|
assert.Equal(t, true, ok)
|
|
rt, err := syncSelectionProofSigningRoot(hState, slots.PrevSlot(hState.Slot()), primitives.CommitteeIndex(i))
|
|
assert.NoError(t, err)
|
|
sig := keys[idx].Sign(rt[:])
|
|
isAggregator, err := altair.IsSyncCommitteeAggregator(sig.Marshal())
|
|
require.NoError(t, err)
|
|
if !isAggregator {
|
|
msg.Message.AggregatorIndex = idx
|
|
break
|
|
}
|
|
}
|
|
}
|
|
msg.Message.Contribution.AggregationBits.SetBitAt(1, true)
|
|
gt := time.Now().Add(-time.Second * time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Duration(msg.Message.Contribution.Slot))
|
|
return s, startup.NewClock(gt, [32]byte{'A'})
|
|
},
|
|
args: args{
|
|
pid: "random",
|
|
topic: defaultTopic,
|
|
msg: ðpb.SignedContributionAndProof{
|
|
Message: ðpb.ContributionAndProof{
|
|
AggregatorIndex: 1,
|
|
Contribution: ðpb.SyncCommitteeContribution{
|
|
Slot: 1,
|
|
SubcommitteeIndex: 1,
|
|
BlockRoot: params.BeaconConfig().ZeroHash[:],
|
|
AggregationBits: bitfield.NewBitvector128(),
|
|
Signature: emptySig[:],
|
|
},
|
|
SelectionProof: emptySig[:],
|
|
},
|
|
Signature: emptySig[:],
|
|
}},
|
|
want: pubsub.ValidationReject,
|
|
},
|
|
{
|
|
name: "Failed Selection Proof Verification ",
|
|
svcopts: []Option{
|
|
WithP2P(mockp2p.NewTestP2P(t)),
|
|
WithInitialSync(&mockSync.Sync{IsSyncing: false}),
|
|
WithChainService(chainService),
|
|
WithOperationNotifier(chainService.OperationNotifier()),
|
|
},
|
|
setupSvc: func(s *Service, msg *ethpb.SignedContributionAndProof) (*Service, *startup.Clock) {
|
|
s.cfg.stateGen = stategen.New(database, doublylinkedtree.New())
|
|
s.cfg.beaconDB = database
|
|
msg.Message.Contribution.BlockRoot = headRoot[:]
|
|
hState, err := database.State(context.Background(), headRoot)
|
|
assert.NoError(t, err)
|
|
sc, err := hState.CurrentSyncCommittee()
|
|
assert.NoError(t, err)
|
|
for i := uint64(0); i < params.BeaconConfig().SyncCommitteeSubnetCount; i++ {
|
|
coms, err := altair.SyncSubCommitteePubkeys(sc, primitives.CommitteeIndex(i))
|
|
assert.NoError(t, err)
|
|
for _, p := range coms {
|
|
idx, ok := hState.ValidatorIndexByPubkey(bytesutil.ToBytes48(p))
|
|
assert.Equal(t, true, ok)
|
|
rt, err := syncSelectionProofSigningRoot(hState, slots.PrevSlot(hState.Slot()), primitives.CommitteeIndex(i))
|
|
assert.NoError(t, err)
|
|
sig := keys[idx].Sign(rt[:])
|
|
isAggregator, err := altair.IsSyncCommitteeAggregator(sig.Marshal())
|
|
require.NoError(t, err)
|
|
if !isAggregator {
|
|
msg.Message.AggregatorIndex = idx
|
|
break
|
|
}
|
|
}
|
|
}
|
|
subCommitteeSize := params.BeaconConfig().SyncCommitteeSize / params.BeaconConfig().SyncCommitteeSubnetCount
|
|
s.cfg.chain = &mockChain.ChainService{
|
|
Genesis: time.Now(),
|
|
SyncCommitteeIndices: []primitives.CommitteeIndex{primitives.CommitteeIndex(msg.Message.Contribution.SubcommitteeIndex * subCommitteeSize)},
|
|
}
|
|
msg.Message.Contribution.AggregationBits.SetBitAt(1, true)
|
|
|
|
s.initCaches()
|
|
gt := time.Now().Add(-time.Second * time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Duration(msg.Message.Contribution.Slot))
|
|
return s, startup.NewClock(gt, [32]byte{'A'})
|
|
},
|
|
args: args{
|
|
pid: "random",
|
|
topic: defaultTopic,
|
|
msg: ðpb.SignedContributionAndProof{
|
|
Message: ðpb.ContributionAndProof{
|
|
AggregatorIndex: 1,
|
|
Contribution: ðpb.SyncCommitteeContribution{
|
|
Slot: 1,
|
|
SubcommitteeIndex: 1,
|
|
BlockRoot: params.BeaconConfig().ZeroHash[:],
|
|
AggregationBits: bitfield.NewBitvector128(),
|
|
Signature: emptySig[:],
|
|
},
|
|
SelectionProof: emptySig[:],
|
|
},
|
|
Signature: emptySig[:],
|
|
}},
|
|
want: pubsub.ValidationReject,
|
|
},
|
|
{
|
|
name: "Invalid Proof Signature",
|
|
svcopts: []Option{
|
|
WithP2P(mockp2p.NewTestP2P(t)),
|
|
WithInitialSync(&mockSync.Sync{IsSyncing: false}),
|
|
WithChainService(chainService),
|
|
WithOperationNotifier(chainService.OperationNotifier()),
|
|
},
|
|
setupSvc: func(s *Service, msg *ethpb.SignedContributionAndProof) (*Service, *startup.Clock) {
|
|
s.cfg.stateGen = stategen.New(database, doublylinkedtree.New())
|
|
s.cfg.beaconDB = database
|
|
s.cfg.chain = chainService
|
|
msg.Message.Contribution.BlockRoot = headRoot[:]
|
|
hState, err := database.State(context.Background(), headRoot)
|
|
assert.NoError(t, err)
|
|
sc, err := hState.CurrentSyncCommittee()
|
|
assert.NoError(t, err)
|
|
var pubkey []byte
|
|
for i := uint64(0); i < params.BeaconConfig().SyncCommitteeSubnetCount; i++ {
|
|
coms, err := altair.SyncSubCommitteePubkeys(sc, primitives.CommitteeIndex(i))
|
|
assert.NoError(t, err)
|
|
for _, p := range coms {
|
|
idx, ok := hState.ValidatorIndexByPubkey(bytesutil.ToBytes48(p))
|
|
assert.Equal(t, true, ok)
|
|
rt, err := syncSelectionProofSigningRoot(hState, slots.PrevSlot(hState.Slot()), primitives.CommitteeIndex(i))
|
|
assert.NoError(t, err)
|
|
sig := keys[idx].Sign(rt[:])
|
|
isAggregator, err := altair.IsSyncCommitteeAggregator(sig.Marshal())
|
|
require.NoError(t, err)
|
|
if isAggregator {
|
|
infiniteSig := [96]byte{0xC0}
|
|
pubkey = keys[idx].PublicKey().Marshal()
|
|
msg.Message.AggregatorIndex = idx
|
|
msg.Message.SelectionProof = sig.Marshal()
|
|
msg.Message.Contribution.Slot = slots.PrevSlot(hState.Slot())
|
|
msg.Message.Contribution.SubcommitteeIndex = i
|
|
msg.Message.Contribution.Signature = infiniteSig[:]
|
|
msg.Message.Contribution.BlockRoot = headRoot[:]
|
|
msg.Message.Contribution.AggregationBits = bitfield.NewBitvector128()
|
|
msg.Message.Contribution.AggregationBits.SetBitAt(1, true)
|
|
msg.Signature = infiniteSig[:]
|
|
break
|
|
}
|
|
}
|
|
}
|
|
d, err := signing.Domain(hState.Fork(), slots.ToEpoch(slots.PrevSlot(hState.Slot())), params.BeaconConfig().DomainSyncCommitteeSelectionProof, hState.GenesisValidatorsRoot())
|
|
require.NoError(t, err)
|
|
subCommitteeSize := params.BeaconConfig().SyncCommitteeSize / params.BeaconConfig().SyncCommitteeSubnetCount
|
|
s.cfg.chain = &mockChain.ChainService{
|
|
SyncCommitteeIndices: []primitives.CommitteeIndex{primitives.CommitteeIndex(msg.Message.Contribution.SubcommitteeIndex * subCommitteeSize)},
|
|
PublicKey: bytesutil.ToBytes48(pubkey),
|
|
SyncSelectionProofDomain: d,
|
|
Genesis: time.Now(),
|
|
}
|
|
|
|
s.initCaches()
|
|
gt := time.Now().Add(-time.Second * time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Duration(msg.Message.Contribution.Slot))
|
|
return s, startup.NewClock(gt, [32]byte{'A'})
|
|
},
|
|
args: args{
|
|
pid: "random",
|
|
topic: defaultTopic,
|
|
msg: ðpb.SignedContributionAndProof{
|
|
Message: ðpb.ContributionAndProof{
|
|
AggregatorIndex: 1,
|
|
Contribution: ðpb.SyncCommitteeContribution{
|
|
Slot: 1,
|
|
SubcommitteeIndex: 1,
|
|
BlockRoot: params.BeaconConfig().ZeroHash[:],
|
|
AggregationBits: bitfield.NewBitvector128(),
|
|
Signature: emptySig[:],
|
|
},
|
|
SelectionProof: emptySig[:],
|
|
},
|
|
Signature: emptySig[:],
|
|
}},
|
|
want: pubsub.ValidationReject,
|
|
},
|
|
{
|
|
name: "Invalid Sync Aggregate",
|
|
svcopts: []Option{
|
|
WithP2P(mockp2p.NewTestP2P(t)),
|
|
WithInitialSync(&mockSync.Sync{IsSyncing: false}),
|
|
WithChainService(chainService),
|
|
WithOperationNotifier(chainService.OperationNotifier()),
|
|
},
|
|
setupSvc: func(s *Service, msg *ethpb.SignedContributionAndProof) (*Service, *startup.Clock) {
|
|
s.cfg.stateGen = stategen.New(database, doublylinkedtree.New())
|
|
s.cfg.beaconDB = database
|
|
msg.Message.Contribution.BlockRoot = headRoot[:]
|
|
hState, err := database.State(context.Background(), headRoot)
|
|
assert.NoError(t, err)
|
|
sc, err := hState.CurrentSyncCommittee()
|
|
assert.NoError(t, err)
|
|
for i := uint64(0); i < params.BeaconConfig().SyncCommitteeSubnetCount; i++ {
|
|
coms, err := altair.SyncSubCommitteePubkeys(sc, primitives.CommitteeIndex(i))
|
|
assert.NoError(t, err)
|
|
for _, p := range coms {
|
|
idx, ok := hState.ValidatorIndexByPubkey(bytesutil.ToBytes48(p))
|
|
assert.Equal(t, true, ok)
|
|
rt, err := syncSelectionProofSigningRoot(hState, slots.PrevSlot(hState.Slot()), primitives.CommitteeIndex(i))
|
|
assert.NoError(t, err)
|
|
sig := keys[idx].Sign(rt[:])
|
|
isAggregator, err := altair.IsSyncCommitteeAggregator(sig.Marshal())
|
|
require.NoError(t, err)
|
|
if isAggregator {
|
|
infiniteSig := [96]byte{0xC0}
|
|
junkRoot := [32]byte{'A'}
|
|
badSig := keys[idx].Sign(junkRoot[:])
|
|
msg.Message.AggregatorIndex = idx
|
|
msg.Message.SelectionProof = sig.Marshal()
|
|
msg.Message.Contribution.Slot = slots.PrevSlot(hState.Slot())
|
|
msg.Message.Contribution.SubcommitteeIndex = i
|
|
msg.Message.Contribution.Signature = badSig.Marshal()
|
|
msg.Message.Contribution.BlockRoot = headRoot[:]
|
|
msg.Message.Contribution.AggregationBits = bitfield.NewBitvector128()
|
|
msg.Message.Contribution.AggregationBits.SetBitAt(1, true)
|
|
msg.Signature = infiniteSig[:]
|
|
|
|
d, err := signing.Domain(hState.Fork(), slots.ToEpoch(slots.PrevSlot(hState.Slot())), params.BeaconConfig().DomainContributionAndProof, hState.GenesisValidatorsRoot())
|
|
assert.NoError(t, err)
|
|
sigRoot, err := signing.ComputeSigningRoot(msg.Message, d)
|
|
assert.NoError(t, err)
|
|
contrSig := keys[idx].Sign(sigRoot[:])
|
|
|
|
msg.Signature = contrSig.Marshal()
|
|
break
|
|
}
|
|
}
|
|
}
|
|
s.cfg.chain = &mockChain.ChainService{
|
|
SyncCommitteeIndices: []primitives.CommitteeIndex{1},
|
|
Genesis: time.Now(),
|
|
}
|
|
|
|
s.initCaches()
|
|
gt := time.Now().Add(-time.Second * time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Duration(msg.Message.Contribution.Slot))
|
|
return s, startup.NewClock(gt, [32]byte{'A'})
|
|
},
|
|
args: args{
|
|
pid: "random",
|
|
topic: defaultTopic,
|
|
msg: ðpb.SignedContributionAndProof{
|
|
Message: ðpb.ContributionAndProof{
|
|
AggregatorIndex: 1,
|
|
Contribution: ðpb.SyncCommitteeContribution{
|
|
Slot: 1,
|
|
SubcommitteeIndex: 1,
|
|
BlockRoot: params.BeaconConfig().ZeroHash[:],
|
|
AggregationBits: bitfield.NewBitvector128(),
|
|
Signature: emptySig[:],
|
|
},
|
|
SelectionProof: emptySig[:],
|
|
},
|
|
Signature: emptySig[:],
|
|
}},
|
|
want: pubsub.ValidationReject,
|
|
},
|
|
{
|
|
name: "Invalid Signed Sync Contribution And Proof - Zero Bits Set",
|
|
svcopts: []Option{
|
|
WithP2P(mockp2p.NewTestP2P(t)),
|
|
WithInitialSync(&mockSync.Sync{IsSyncing: false}),
|
|
WithChainService(chainService),
|
|
WithOperationNotifier(chainService.OperationNotifier()),
|
|
},
|
|
setupSvc: func(s *Service, msg *ethpb.SignedContributionAndProof) (*Service, *startup.Clock) {
|
|
s.cfg.stateGen = stategen.New(database, doublylinkedtree.New())
|
|
msg.Message.Contribution.BlockRoot = headRoot[:]
|
|
s.cfg.beaconDB = database
|
|
hState, err := database.State(context.Background(), headRoot)
|
|
assert.NoError(t, err)
|
|
sc, err := hState.CurrentSyncCommittee()
|
|
assert.NoError(t, err)
|
|
cd, err := signing.Domain(hState.Fork(), slots.ToEpoch(slots.PrevSlot(hState.Slot())), params.BeaconConfig().DomainContributionAndProof, hState.GenesisValidatorsRoot())
|
|
assert.NoError(t, err)
|
|
for i := uint64(0); i < params.BeaconConfig().SyncCommitteeSubnetCount; i++ {
|
|
coms, err := altair.SyncSubCommitteePubkeys(sc, primitives.CommitteeIndex(i))
|
|
assert.NoError(t, err)
|
|
for _, p := range coms {
|
|
idx, ok := hState.ValidatorIndexByPubkey(bytesutil.ToBytes48(p))
|
|
assert.Equal(t, true, ok)
|
|
rt, err := syncSelectionProofSigningRoot(hState, slots.PrevSlot(hState.Slot()), primitives.CommitteeIndex(i))
|
|
assert.NoError(t, err)
|
|
sig := keys[idx].Sign(rt[:])
|
|
isAggregator, err := altair.IsSyncCommitteeAggregator(sig.Marshal())
|
|
require.NoError(t, err)
|
|
if isAggregator {
|
|
infiniteSig := [96]byte{0xC0}
|
|
msg.Message.AggregatorIndex = idx
|
|
msg.Message.SelectionProof = sig.Marshal()
|
|
msg.Message.Contribution.Slot = slots.PrevSlot(hState.Slot())
|
|
msg.Message.Contribution.SubcommitteeIndex = i
|
|
msg.Message.Contribution.Signature = infiniteSig[:]
|
|
msg.Message.Contribution.BlockRoot = headRoot[:]
|
|
msg.Message.Contribution.AggregationBits = bitfield.NewBitvector128()
|
|
sigRoot, err := signing.ComputeSigningRoot(msg.Message, cd)
|
|
assert.NoError(t, err)
|
|
contrSig := keys[idx].Sign(sigRoot[:])
|
|
|
|
msg.Signature = contrSig.Marshal()
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
d, err := signing.Domain(hState.Fork(), slots.ToEpoch(slots.PrevSlot(hState.Slot())), params.BeaconConfig().DomainSyncCommitteeSelectionProof, hState.GenesisValidatorsRoot())
|
|
require.NoError(t, err)
|
|
subCommitteeSize := params.BeaconConfig().SyncCommitteeSize / params.BeaconConfig().SyncCommitteeSubnetCount
|
|
s.cfg.chain = &mockChain.ChainService{
|
|
SyncCommitteeIndices: []primitives.CommitteeIndex{primitives.CommitteeIndex(msg.Message.Contribution.SubcommitteeIndex * subCommitteeSize)},
|
|
PublicKey: bytesutil.ToBytes48(keys[msg.Message.AggregatorIndex].PublicKey().Marshal()),
|
|
SyncSelectionProofDomain: d,
|
|
SyncContributionProofDomain: cd,
|
|
SyncCommitteeDomain: make([]byte, 32),
|
|
Genesis: time.Now(),
|
|
}
|
|
s.initCaches()
|
|
gt := time.Now().Add(-time.Second * time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Duration(msg.Message.Contribution.Slot))
|
|
return s, startup.NewClock(gt, [32]byte{'A'})
|
|
},
|
|
args: args{
|
|
pid: "random",
|
|
topic: defaultTopic,
|
|
msg: ðpb.SignedContributionAndProof{
|
|
Message: ðpb.ContributionAndProof{
|
|
AggregatorIndex: 1,
|
|
Contribution: ðpb.SyncCommitteeContribution{
|
|
Slot: 1,
|
|
SubcommitteeIndex: 1,
|
|
BlockRoot: params.BeaconConfig().ZeroHash[:],
|
|
AggregationBits: bitfield.NewBitvector128(),
|
|
Signature: emptySig[:],
|
|
},
|
|
SelectionProof: emptySig[:],
|
|
},
|
|
Signature: emptySig[:],
|
|
}},
|
|
want: pubsub.ValidationReject,
|
|
},
|
|
{
|
|
name: "Valid Signed Sync Contribution And Proof - Single Bit Set",
|
|
svcopts: []Option{
|
|
WithP2P(mockp2p.NewTestP2P(t)),
|
|
WithInitialSync(&mockSync.Sync{IsSyncing: false}),
|
|
WithChainService(chainService),
|
|
WithOperationNotifier(chainService.OperationNotifier()),
|
|
},
|
|
setupSvc: func(s *Service, msg *ethpb.SignedContributionAndProof) (*Service, *startup.Clock) {
|
|
s.cfg.stateGen = stategen.New(database, doublylinkedtree.New())
|
|
msg.Message.Contribution.BlockRoot = headRoot[:]
|
|
s.cfg.beaconDB = database
|
|
hState, err := database.State(context.Background(), headRoot)
|
|
assert.NoError(t, err)
|
|
sc, err := hState.CurrentSyncCommittee()
|
|
assert.NoError(t, err)
|
|
cd, err := signing.Domain(hState.Fork(), slots.ToEpoch(slots.PrevSlot(hState.Slot())), params.BeaconConfig().DomainContributionAndProof, hState.GenesisValidatorsRoot())
|
|
assert.NoError(t, err)
|
|
d, err := signing.Domain(hState.Fork(), slots.ToEpoch(hState.Slot()), params.BeaconConfig().DomainSyncCommittee, hState.GenesisValidatorsRoot())
|
|
assert.NoError(t, err)
|
|
var pubkeys [][]byte
|
|
for i := uint64(0); i < params.BeaconConfig().SyncCommitteeSubnetCount; i++ {
|
|
coms, err := altair.SyncSubCommitteePubkeys(sc, primitives.CommitteeIndex(i))
|
|
pubkeys = coms
|
|
assert.NoError(t, err)
|
|
for _, p := range coms {
|
|
idx, ok := hState.ValidatorIndexByPubkey(bytesutil.ToBytes48(p))
|
|
assert.Equal(t, true, ok)
|
|
rt, err := syncSelectionProofSigningRoot(hState, slots.PrevSlot(hState.Slot()), primitives.CommitteeIndex(i))
|
|
assert.NoError(t, err)
|
|
sig := keys[idx].Sign(rt[:])
|
|
isAggregator, err := altair.IsSyncCommitteeAggregator(sig.Marshal())
|
|
require.NoError(t, err)
|
|
if isAggregator {
|
|
msg.Message.AggregatorIndex = idx
|
|
msg.Message.SelectionProof = sig.Marshal()
|
|
msg.Message.Contribution.Slot = slots.PrevSlot(hState.Slot())
|
|
msg.Message.Contribution.SubcommitteeIndex = i
|
|
msg.Message.Contribution.BlockRoot = headRoot[:]
|
|
msg.Message.Contribution.AggregationBits = bitfield.NewBitvector128()
|
|
// Only Sign for 1 validator.
|
|
rawBytes := p2ptypes.SSZBytes(headRoot[:])
|
|
sigRoot, err := signing.ComputeSigningRoot(&rawBytes, d)
|
|
assert.NoError(t, err)
|
|
valIdx, ok := hState.ValidatorIndexByPubkey(bytesutil.ToBytes48(coms[0]))
|
|
assert.Equal(t, true, ok)
|
|
sig = keys[valIdx].Sign(sigRoot[:])
|
|
msg.Message.Contribution.AggregationBits.SetBitAt(uint64(0), true)
|
|
msg.Message.Contribution.Signature = sig.Marshal()
|
|
|
|
sigRoot, err = signing.ComputeSigningRoot(msg.Message, cd)
|
|
assert.NoError(t, err)
|
|
contrSig := keys[idx].Sign(sigRoot[:])
|
|
msg.Signature = contrSig.Marshal()
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
pd, err := signing.Domain(hState.Fork(), slots.ToEpoch(slots.PrevSlot(hState.Slot())), params.BeaconConfig().DomainSyncCommitteeSelectionProof, hState.GenesisValidatorsRoot())
|
|
require.NoError(t, err)
|
|
subCommitteeSize := params.BeaconConfig().SyncCommitteeSize / params.BeaconConfig().SyncCommitteeSubnetCount
|
|
s.cfg.chain = &mockChain.ChainService{
|
|
SyncCommitteeIndices: []primitives.CommitteeIndex{primitives.CommitteeIndex(msg.Message.Contribution.SubcommitteeIndex * subCommitteeSize)},
|
|
PublicKey: bytesutil.ToBytes48(keys[msg.Message.AggregatorIndex].PublicKey().Marshal()),
|
|
SyncSelectionProofDomain: pd,
|
|
SyncContributionProofDomain: cd,
|
|
SyncCommitteeDomain: d,
|
|
SyncCommitteePubkeys: pubkeys,
|
|
Genesis: time.Now(),
|
|
}
|
|
s.initCaches()
|
|
gt := time.Now().Add(-time.Second * time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Duration(msg.Message.Contribution.Slot))
|
|
return s, startup.NewClock(gt, [32]byte{'A'})
|
|
},
|
|
args: args{
|
|
pid: "random",
|
|
topic: defaultTopic,
|
|
msg: ðpb.SignedContributionAndProof{
|
|
Message: ðpb.ContributionAndProof{
|
|
AggregatorIndex: 1,
|
|
Contribution: ðpb.SyncCommitteeContribution{
|
|
Slot: 1,
|
|
SubcommitteeIndex: 1,
|
|
BlockRoot: params.BeaconConfig().ZeroHash[:],
|
|
AggregationBits: bitfield.NewBitvector128(),
|
|
Signature: emptySig[:],
|
|
},
|
|
SelectionProof: emptySig[:],
|
|
},
|
|
Signature: emptySig[:],
|
|
}},
|
|
want: pubsub.ValidationAccept,
|
|
},
|
|
{
|
|
name: "Valid Signed Sync Contribution And Proof with Multiple Signatures",
|
|
svcopts: []Option{
|
|
WithP2P(mockp2p.NewTestP2P(t)),
|
|
WithInitialSync(&mockSync.Sync{IsSyncing: false}),
|
|
WithChainService(chainService),
|
|
WithOperationNotifier(chainService.OperationNotifier()),
|
|
},
|
|
setupSvc: func(s *Service, msg *ethpb.SignedContributionAndProof) (*Service, *startup.Clock) {
|
|
s.cfg.stateGen = stategen.New(database, doublylinkedtree.New())
|
|
msg.Message.Contribution.BlockRoot = headRoot[:]
|
|
s.cfg.beaconDB = database
|
|
hState, err := database.State(context.Background(), headRoot)
|
|
assert.NoError(t, err)
|
|
sc, err := hState.CurrentSyncCommittee()
|
|
assert.NoError(t, err)
|
|
cd, err := signing.Domain(hState.Fork(), slots.ToEpoch(slots.PrevSlot(hState.Slot())), params.BeaconConfig().DomainContributionAndProof, hState.GenesisValidatorsRoot())
|
|
assert.NoError(t, err)
|
|
d, err := signing.Domain(hState.Fork(), slots.ToEpoch(hState.Slot()), params.BeaconConfig().DomainSyncCommittee, hState.GenesisValidatorsRoot())
|
|
assert.NoError(t, err)
|
|
var pubkeys [][]byte
|
|
for i := uint64(0); i < params.BeaconConfig().SyncCommitteeSubnetCount; i++ {
|
|
coms, err := altair.SyncSubCommitteePubkeys(sc, primitives.CommitteeIndex(i))
|
|
pubkeys = coms
|
|
assert.NoError(t, err)
|
|
for _, p := range coms {
|
|
idx, ok := hState.ValidatorIndexByPubkey(bytesutil.ToBytes48(p))
|
|
assert.Equal(t, true, ok)
|
|
rt, err := syncSelectionProofSigningRoot(hState, slots.PrevSlot(hState.Slot()), primitives.CommitteeIndex(i))
|
|
assert.NoError(t, err)
|
|
sig := keys[idx].Sign(rt[:])
|
|
isAggregator, err := altair.IsSyncCommitteeAggregator(sig.Marshal())
|
|
require.NoError(t, err)
|
|
if isAggregator {
|
|
msg.Message.AggregatorIndex = idx
|
|
msg.Message.SelectionProof = sig.Marshal()
|
|
msg.Message.Contribution.Slot = slots.PrevSlot(hState.Slot())
|
|
msg.Message.Contribution.SubcommitteeIndex = i
|
|
msg.Message.Contribution.BlockRoot = headRoot[:]
|
|
msg.Message.Contribution.AggregationBits = bitfield.NewBitvector128()
|
|
rawBytes := p2ptypes.SSZBytes(headRoot[:])
|
|
sigRoot, err := signing.ComputeSigningRoot(&rawBytes, d)
|
|
assert.NoError(t, err)
|
|
var sigs []bls.Signature
|
|
for i, p2 := range coms {
|
|
idx, ok := hState.ValidatorIndexByPubkey(bytesutil.ToBytes48(p2))
|
|
assert.Equal(t, true, ok)
|
|
sig := keys[idx].Sign(sigRoot[:])
|
|
sigs = append(sigs, sig)
|
|
msg.Message.Contribution.AggregationBits.SetBitAt(uint64(i), true)
|
|
}
|
|
msg.Message.Contribution.Signature = bls.AggregateSignatures(sigs).Marshal()
|
|
sigRoot, err = signing.ComputeSigningRoot(msg.Message, cd)
|
|
assert.NoError(t, err)
|
|
contrSig := keys[idx].Sign(sigRoot[:])
|
|
msg.Signature = contrSig.Marshal()
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
pd, err := signing.Domain(hState.Fork(), slots.ToEpoch(slots.PrevSlot(hState.Slot())), params.BeaconConfig().DomainSyncCommitteeSelectionProof, hState.GenesisValidatorsRoot())
|
|
require.NoError(t, err)
|
|
subCommitteeSize := params.BeaconConfig().SyncCommitteeSize / params.BeaconConfig().SyncCommitteeSubnetCount
|
|
s.cfg.chain = &mockChain.ChainService{
|
|
SyncCommitteeIndices: []primitives.CommitteeIndex{primitives.CommitteeIndex(msg.Message.Contribution.SubcommitteeIndex * subCommitteeSize)},
|
|
PublicKey: bytesutil.ToBytes48(keys[msg.Message.AggregatorIndex].PublicKey().Marshal()),
|
|
SyncSelectionProofDomain: pd,
|
|
SyncContributionProofDomain: cd,
|
|
SyncCommitteeDomain: d,
|
|
SyncCommitteePubkeys: pubkeys,
|
|
Genesis: time.Now(),
|
|
}
|
|
gt := time.Now().Add(-time.Second * time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Duration(msg.Message.Contribution.Slot))
|
|
|
|
s.initCaches()
|
|
return s, startup.NewClock(gt, [32]byte{'A'})
|
|
},
|
|
args: args{
|
|
pid: "random",
|
|
topic: defaultTopic,
|
|
msg: ðpb.SignedContributionAndProof{
|
|
Message: ðpb.ContributionAndProof{
|
|
AggregatorIndex: 1,
|
|
Contribution: ðpb.SyncCommitteeContribution{
|
|
Slot: 1,
|
|
SubcommitteeIndex: 1,
|
|
BlockRoot: params.BeaconConfig().ZeroHash[:],
|
|
AggregationBits: bitfield.NewBitvector128(),
|
|
Signature: emptySig[:],
|
|
},
|
|
SelectionProof: emptySig[:],
|
|
},
|
|
Signature: emptySig[:],
|
|
}},
|
|
want: pubsub.ValidationAccept,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ctx := context.Background()
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
defer cancel()
|
|
cw := startup.NewClockSynchronizer()
|
|
svc := NewService(ctx, append([]Option{WithClockWaiter(cw), WithStateNotifier(chainService.StateNotifier())}, tt.svcopts...)...)
|
|
var clock *startup.Clock
|
|
svc, clock = tt.setupSvc(svc, tt.args.msg)
|
|
require.NoError(t, cw.SetClock(clock))
|
|
svc.verifierWaiter = verification.NewInitializerWaiter(cw, chainService.ForkChoiceStore, svc.cfg.stateGen)
|
|
|
|
go svc.Start()
|
|
marshalledObj, err := tt.args.msg.MarshalSSZ()
|
|
assert.NoError(t, err)
|
|
marshalledObj = snappy.Encode(nil, marshalledObj)
|
|
msg := &pubsub.Message{
|
|
Message: &pubsubpb.Message{
|
|
Data: marshalledObj,
|
|
Topic: &tt.args.topic,
|
|
},
|
|
ReceivedFrom: "",
|
|
ValidatorData: nil,
|
|
}
|
|
// a lot happens in the chain service after SetClock is called,
|
|
// give it a moment before calling internal methods that would typically
|
|
// only execute after waitFor
|
|
for i := 0; i < 10; i++ {
|
|
if !svc.chainIsStarted() {
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
}
|
|
require.Equal(t, true, svc.chainIsStarted())
|
|
if got, err := svc.validateSyncContributionAndProof(ctx, tt.args.pid, msg); got != tt.want {
|
|
_ = err
|
|
t.Errorf("validateSyncContributionAndProof() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateSyncContributionAndProof(t *testing.T) {
|
|
ctx := context.Background()
|
|
database := testingdb.SetupDB(t)
|
|
headRoot, keys := fillUpBlocksAndState(ctx, t, database)
|
|
defaultTopic := p2p.SyncContributionAndProofSubnetTopicFormat
|
|
defaultTopic = fmt.Sprintf(defaultTopic, []byte{0xAB, 0x00, 0xCC, 0x9E})
|
|
defaultTopic = defaultTopic + "/" + encoder.ProtocolSuffixSSZSnappy
|
|
var emptySig [96]byte
|
|
pid := peer.ID("random")
|
|
msg := ðpb.SignedContributionAndProof{
|
|
Message: ðpb.ContributionAndProof{
|
|
AggregatorIndex: 1,
|
|
Contribution: ðpb.SyncCommitteeContribution{
|
|
Slot: 0,
|
|
SubcommitteeIndex: 1,
|
|
BlockRoot: params.BeaconConfig().ZeroHash[:],
|
|
AggregationBits: bitfield.NewBitvector128(),
|
|
Signature: emptySig[:],
|
|
},
|
|
SelectionProof: emptySig[:],
|
|
},
|
|
Signature: emptySig[:],
|
|
}
|
|
chainService := &mockChain.ChainService{
|
|
Genesis: time.Now(),
|
|
ValidatorsRoot: [32]byte{'A'},
|
|
}
|
|
s := NewService(context.Background(),
|
|
WithP2P(mockp2p.NewTestP2P(t)),
|
|
WithInitialSync(&mockSync.Sync{IsSyncing: false}),
|
|
WithChainService(chainService),
|
|
WithOperationNotifier(chainService.OperationNotifier()),
|
|
)
|
|
s.cfg.clock = startup.NewClock(chainService.Genesis, chainService.ValidatorsRoot)
|
|
go s.verifierRoutine()
|
|
s.cfg.stateGen = stategen.New(database, doublylinkedtree.New())
|
|
msg.Message.Contribution.BlockRoot = headRoot[:]
|
|
s.cfg.beaconDB = database
|
|
hState, err := database.State(context.Background(), headRoot)
|
|
assert.NoError(t, err)
|
|
sc, err := hState.CurrentSyncCommittee()
|
|
assert.NoError(t, err)
|
|
cd, err := signing.Domain(hState.Fork(), slots.ToEpoch(slots.PrevSlot(hState.Slot())), params.BeaconConfig().DomainContributionAndProof, hState.GenesisValidatorsRoot())
|
|
assert.NoError(t, err)
|
|
d, err := signing.Domain(hState.Fork(), slots.ToEpoch(hState.Slot()), params.BeaconConfig().DomainSyncCommittee, hState.GenesisValidatorsRoot())
|
|
assert.NoError(t, err)
|
|
var pubkeys [][]byte
|
|
for i := uint64(0); i < params.BeaconConfig().SyncCommitteeSubnetCount; i++ {
|
|
coms, err := altair.SyncSubCommitteePubkeys(sc, primitives.CommitteeIndex(i))
|
|
pubkeys = coms
|
|
assert.NoError(t, err)
|
|
for _, p := range coms {
|
|
idx, ok := hState.ValidatorIndexByPubkey(bytesutil.ToBytes48(p))
|
|
assert.Equal(t, true, ok)
|
|
rt, err := syncSelectionProofSigningRoot(hState, slots.PrevSlot(hState.Slot()), primitives.CommitteeIndex(i))
|
|
assert.NoError(t, err)
|
|
sig := keys[idx].Sign(rt[:])
|
|
isAggregator, err := altair.IsSyncCommitteeAggregator(sig.Marshal())
|
|
require.NoError(t, err)
|
|
if isAggregator {
|
|
msg.Message.AggregatorIndex = idx
|
|
msg.Message.SelectionProof = sig.Marshal()
|
|
msg.Message.Contribution.Slot = slots.PrevSlot(hState.Slot())
|
|
msg.Message.Contribution.SubcommitteeIndex = i
|
|
msg.Message.Contribution.BlockRoot = headRoot[:]
|
|
msg.Message.Contribution.AggregationBits = bitfield.NewBitvector128()
|
|
// Only Sign for 1 validator.
|
|
rawBytes := p2ptypes.SSZBytes(headRoot[:])
|
|
sigRoot, err := signing.ComputeSigningRoot(&rawBytes, d)
|
|
assert.NoError(t, err)
|
|
valIdx, ok := hState.ValidatorIndexByPubkey(bytesutil.ToBytes48(coms[0]))
|
|
assert.Equal(t, true, ok)
|
|
sig = keys[valIdx].Sign(sigRoot[:])
|
|
msg.Message.Contribution.AggregationBits.SetBitAt(uint64(0), true)
|
|
msg.Message.Contribution.Signature = sig.Marshal()
|
|
|
|
sigRoot, err = signing.ComputeSigningRoot(msg.Message, cd)
|
|
assert.NoError(t, err)
|
|
contrSig := keys[idx].Sign(sigRoot[:])
|
|
msg.Signature = contrSig.Marshal()
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
pd, err := signing.Domain(hState.Fork(), slots.ToEpoch(slots.PrevSlot(hState.Slot())), params.BeaconConfig().DomainSyncCommitteeSelectionProof, hState.GenesisValidatorsRoot())
|
|
require.NoError(t, err)
|
|
subCommitteeSize := params.BeaconConfig().SyncCommitteeSize / params.BeaconConfig().SyncCommitteeSubnetCount
|
|
s.cfg.chain = &mockChain.ChainService{
|
|
ValidatorsRoot: [32]byte{'A'},
|
|
Genesis: time.Now().Add(-time.Second * time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Duration(msg.Message.Contribution.Slot)),
|
|
SyncCommitteeIndices: []primitives.CommitteeIndex{primitives.CommitteeIndex(msg.Message.Contribution.SubcommitteeIndex * subCommitteeSize)},
|
|
PublicKey: bytesutil.ToBytes48(keys[msg.Message.AggregatorIndex].PublicKey().Marshal()),
|
|
SyncSelectionProofDomain: pd,
|
|
SyncContributionProofDomain: cd,
|
|
SyncCommitteeDomain: d,
|
|
SyncCommitteePubkeys: pubkeys,
|
|
}
|
|
s.cfg.clock = startup.NewClock(s.cfg.chain.GenesisTime(), s.cfg.chain.GenesisValidatorsRoot())
|
|
s.initCaches()
|
|
|
|
marshalledObj, err := msg.MarshalSSZ()
|
|
assert.NoError(t, err)
|
|
marshalledObj = snappy.Encode(nil, marshalledObj)
|
|
pubsubMsg := &pubsub.Message{
|
|
Message: &pubsubpb.Message{
|
|
Data: marshalledObj,
|
|
Topic: &defaultTopic,
|
|
},
|
|
ReceivedFrom: "",
|
|
ValidatorData: nil,
|
|
}
|
|
|
|
// Subscribe to operation notifications.
|
|
opChannel := make(chan *feed.Event, 1)
|
|
opSub := s.cfg.operationNotifier.OperationFeed().Subscribe(opChannel)
|
|
defer opSub.Unsubscribe()
|
|
|
|
_, err = s.validateSyncContributionAndProof(ctx, pid, pubsubMsg)
|
|
require.NoError(t, err)
|
|
|
|
// Ensure the state notification was broadcast.
|
|
notificationFound := false
|
|
for !notificationFound {
|
|
select {
|
|
case event := <-opChannel:
|
|
if event.Type == opfeed.SyncCommitteeContributionReceived {
|
|
notificationFound = true
|
|
_, ok := event.Data.(*opfeed.SyncCommitteeContributionReceivedData)
|
|
assert.Equal(t, true, ok, "Entity is not of type *opfeed.SyncCommitteeContributionReceivedData")
|
|
}
|
|
case <-opSub.Err():
|
|
t.Error("Subscription to state notifier failed")
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func fillUpBlocksAndState(ctx context.Context, t *testing.T, beaconDB db.Database) ([32]byte, []bls.SecretKey) {
|
|
gs, keys := util.DeterministicGenesisStateAltair(t, 64)
|
|
sCom, err := altair.NextSyncCommittee(ctx, gs)
|
|
assert.NoError(t, err)
|
|
assert.NoError(t, gs.SetCurrentSyncCommittee(sCom))
|
|
assert.NoError(t, beaconDB.SaveGenesisData(context.Background(), gs))
|
|
|
|
testState := gs.Copy()
|
|
var hRoot [32]byte
|
|
for i := primitives.Slot(1); i <= params.BeaconConfig().SlotsPerEpoch; i++ {
|
|
blk, err := util.GenerateFullBlockAltair(testState, keys, util.DefaultBlockGenConfig(), i)
|
|
require.NoError(t, err)
|
|
r, err := blk.Block.HashTreeRoot()
|
|
require.NoError(t, err)
|
|
wsb, err := blocks.NewSignedBeaconBlock(blk)
|
|
require.NoError(t, err)
|
|
_, testState, err = transition.ExecuteStateTransitionNoVerifyAnySig(ctx, testState, wsb)
|
|
assert.NoError(t, err)
|
|
assert.NoError(t, beaconDB.SaveBlock(ctx, wsb))
|
|
assert.NoError(t, beaconDB.SaveStateSummary(ctx, ðpb.StateSummary{Slot: i, Root: r[:]}))
|
|
assert.NoError(t, beaconDB.SaveState(ctx, testState, r))
|
|
require.NoError(t, beaconDB.SaveHeadBlockRoot(ctx, r))
|
|
hRoot = r
|
|
}
|
|
return hRoot, keys
|
|
}
|
|
|
|
func syncSelectionProofSigningRoot(st state.BeaconState, slot primitives.Slot, comIdx primitives.CommitteeIndex) ([32]byte, error) {
|
|
dom, err := signing.Domain(st.Fork(), slots.ToEpoch(slot), params.BeaconConfig().DomainSyncCommitteeSelectionProof, st.GenesisValidatorsRoot())
|
|
if err != nil {
|
|
return [32]byte{}, err
|
|
}
|
|
selectionData := ðpb.SyncAggregatorSelectionData{Slot: slot, SubcommitteeIndex: uint64(comIdx)}
|
|
return signing.ComputeSigningRoot(selectionData, dom)
|
|
}
|
|
|
|
func TestService_setSyncContributionIndexSlotSeen(t *testing.T) {
|
|
s := NewService(context.Background(), WithP2P(mockp2p.NewTestP2P(t)))
|
|
s.initCaches()
|
|
|
|
// Empty cache
|
|
b0 := bitfield.NewBitvector128()
|
|
b0.SetBitAt(0, true)
|
|
has, err := s.hasSeenSyncContributionBits(ðpb.SyncCommitteeContribution{
|
|
AggregationBits: b0,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, false, has)
|
|
|
|
// Cache with entries but same key
|
|
require.NoError(t, s.setSyncContributionBits(ðpb.SyncCommitteeContribution{
|
|
AggregationBits: b0,
|
|
}))
|
|
has, err = s.hasSeenSyncContributionBits(ðpb.SyncCommitteeContribution{
|
|
AggregationBits: b0,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, true, has)
|
|
b1 := bitfield.NewBitvector128()
|
|
b1.SetBitAt(1, true)
|
|
has, err = s.hasSeenSyncContributionBits(ðpb.SyncCommitteeContribution{
|
|
AggregationBits: b1,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, false, has)
|
|
b2 := bitfield.NewBitvector128()
|
|
b2.SetBitAt(1, true)
|
|
b2.SetBitAt(2, true)
|
|
has, err = s.hasSeenSyncContributionBits(ðpb.SyncCommitteeContribution{
|
|
AggregationBits: b2,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, false, has)
|
|
b2.SetBitAt(0, true)
|
|
has, err = s.hasSeenSyncContributionBits(ðpb.SyncCommitteeContribution{
|
|
AggregationBits: b2,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, true, has)
|
|
|
|
// Make sure set doesn't contain existing overlaps
|
|
require.Equal(t, 1, s.syncContributionBitsOverlapCache.Len())
|
|
|
|
// Cache with entries but different key
|
|
has, err = s.hasSeenSyncContributionBits(ðpb.SyncCommitteeContribution{
|
|
Slot: 1,
|
|
SubcommitteeIndex: 2,
|
|
BlockRoot: []byte{'A'},
|
|
AggregationBits: b0,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, false, has)
|
|
require.NoError(t, s.setSyncContributionBits(ðpb.SyncCommitteeContribution{
|
|
Slot: 1,
|
|
SubcommitteeIndex: 2,
|
|
BlockRoot: []byte{'A'},
|
|
AggregationBits: b2,
|
|
}))
|
|
has, err = s.hasSeenSyncContributionBits(ðpb.SyncCommitteeContribution{
|
|
Slot: 1,
|
|
SubcommitteeIndex: 2,
|
|
BlockRoot: []byte{'A'},
|
|
AggregationBits: b0,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, true, has)
|
|
has, err = s.hasSeenSyncContributionBits(ðpb.SyncCommitteeContribution{
|
|
Slot: 1,
|
|
SubcommitteeIndex: 2,
|
|
BlockRoot: []byte{'A'},
|
|
AggregationBits: b1,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, true, has)
|
|
has, err = s.hasSeenSyncContributionBits(ðpb.SyncCommitteeContribution{
|
|
Slot: 1,
|
|
SubcommitteeIndex: 2,
|
|
BlockRoot: []byte{'A'},
|
|
AggregationBits: b2,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, true, has)
|
|
|
|
// Check invariant with the keys
|
|
has, err = s.hasSeenSyncContributionBits(ðpb.SyncCommitteeContribution{
|
|
Slot: 2,
|
|
SubcommitteeIndex: 2,
|
|
BlockRoot: []byte{'A'},
|
|
AggregationBits: b0,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, false, has)
|
|
has, err = s.hasSeenSyncContributionBits(ðpb.SyncCommitteeContribution{
|
|
Slot: 1,
|
|
SubcommitteeIndex: 2,
|
|
BlockRoot: []byte{'B'},
|
|
AggregationBits: b0,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, false, has)
|
|
has, err = s.hasSeenSyncContributionBits(ðpb.SyncCommitteeContribution{
|
|
Slot: 1,
|
|
SubcommitteeIndex: 3,
|
|
BlockRoot: []byte{'A'},
|
|
AggregationBits: b0,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, false, has)
|
|
}
|