mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-08 23:18:15 -05:00
418 lines
11 KiB
Go
418 lines
11 KiB
Go
package sync
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
mockChain "github.com/OffchainLabs/prysm/v7/beacon-chain/blockchain/testing"
|
|
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
|
|
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
|
"github.com/OffchainLabs/prysm/v7/testing/assert"
|
|
"github.com/OffchainLabs/prysm/v7/testing/require"
|
|
"github.com/OffchainLabs/prysm/v7/testing/util"
|
|
logTest "github.com/sirupsen/logrus/hooks/test"
|
|
)
|
|
|
|
func TestProcessAttestationBucket(t *testing.T) {
|
|
t.Run("EmptyBucket", func(t *testing.T) {
|
|
hook := logTest.NewGlobal()
|
|
s := &Service{}
|
|
|
|
s.processAttestationBucket(context.Background(), nil)
|
|
|
|
emptyBucket := &attestationBucket{
|
|
attestations: []ethpb.Att{},
|
|
}
|
|
s.processAttestationBucket(context.Background(), emptyBucket)
|
|
|
|
require.Equal(t, 0, len(hook.Entries), "Should not log any messages for empty buckets")
|
|
})
|
|
|
|
t.Run("ForkchoiceFailure", func(t *testing.T) {
|
|
hook := logTest.NewGlobal()
|
|
chainService := &mockChain.ChainService{
|
|
NotFinalized: true, // This makes InForkchoice return false
|
|
}
|
|
|
|
s := &Service{
|
|
cfg: &config{
|
|
chain: chainService,
|
|
},
|
|
}
|
|
|
|
attData := ðpb.AttestationData{
|
|
BeaconBlockRoot: bytesutil.PadTo([]byte("blockroot"), 32),
|
|
}
|
|
|
|
bucket := &attestationBucket{
|
|
data: attData,
|
|
attestations: []ethpb.Att{util.NewAttestation()},
|
|
}
|
|
|
|
s.processAttestationBucket(context.Background(), bucket)
|
|
|
|
require.Equal(t, 1, len(hook.Entries))
|
|
assert.StringContains(t, "Failed forkchoice check for bucket", hook.LastEntry().Message)
|
|
require.NotNil(t, hook.LastEntry().Data["error"])
|
|
})
|
|
|
|
t.Run("CommitteeFailure", func(t *testing.T) {
|
|
hook := logTest.NewGlobal()
|
|
beaconState, err := util.NewBeaconState()
|
|
require.NoError(t, err)
|
|
require.NoError(t, beaconState.SetSlot(1))
|
|
|
|
chainService := &mockChain.ChainService{
|
|
State: beaconState,
|
|
ValidAttestation: true,
|
|
}
|
|
|
|
s := &Service{
|
|
cfg: &config{
|
|
chain: chainService,
|
|
},
|
|
}
|
|
|
|
attData := ðpb.AttestationData{
|
|
BeaconBlockRoot: bytesutil.PadTo([]byte("blockroot"), 32),
|
|
Target: ðpb.Checkpoint{
|
|
Epoch: 1,
|
|
Root: bytesutil.PadTo([]byte("blockroot"), 32),
|
|
},
|
|
CommitteeIndex: 999999,
|
|
}
|
|
|
|
att := util.NewAttestation()
|
|
att.Data = attData
|
|
|
|
bucket := &attestationBucket{
|
|
data: attData,
|
|
attestations: []ethpb.Att{att},
|
|
}
|
|
|
|
s.processAttestationBucket(context.Background(), bucket)
|
|
|
|
require.Equal(t, 1, len(hook.Entries))
|
|
assert.StringContains(t, "Failed to get committee from state", hook.LastEntry().Message)
|
|
})
|
|
|
|
t.Run("FFGConsistencyFailure", func(t *testing.T) {
|
|
hook := logTest.NewGlobal()
|
|
|
|
validators := make([]*ethpb.Validator, 64)
|
|
for i := range validators {
|
|
validators[i] = ðpb.Validator{
|
|
ExitEpoch: 1000000,
|
|
EffectiveBalance: 32000000000,
|
|
}
|
|
}
|
|
|
|
beaconState, err := util.NewBeaconState()
|
|
require.NoError(t, err)
|
|
require.NoError(t, beaconState.SetSlot(1))
|
|
require.NoError(t, beaconState.SetValidators(validators))
|
|
|
|
chainService := &mockChain.ChainService{
|
|
State: beaconState,
|
|
}
|
|
|
|
s := &Service{
|
|
cfg: &config{
|
|
chain: chainService,
|
|
},
|
|
}
|
|
|
|
attData := ðpb.AttestationData{
|
|
BeaconBlockRoot: bytesutil.PadTo([]byte("blockroot"), 32),
|
|
Target: ðpb.Checkpoint{
|
|
Epoch: 1,
|
|
Root: bytesutil.PadTo([]byte("different_target"), 32), // Different from BeaconBlockRoot to trigger FFG failure
|
|
},
|
|
}
|
|
|
|
att := util.NewAttestation()
|
|
att.Data = attData
|
|
|
|
bucket := &attestationBucket{
|
|
data: attData,
|
|
attestations: []ethpb.Att{att},
|
|
}
|
|
|
|
s.processAttestationBucket(context.Background(), bucket)
|
|
|
|
require.Equal(t, 1, len(hook.Entries))
|
|
assert.StringContains(t, "Failed FFG consistency check for bucket", hook.LastEntry().Message)
|
|
})
|
|
|
|
t.Run("ProcessingSuccess", func(t *testing.T) {
|
|
hook := logTest.NewGlobal()
|
|
validators := make([]*ethpb.Validator, 64)
|
|
for i := range validators {
|
|
validators[i] = ðpb.Validator{
|
|
ExitEpoch: 1000000,
|
|
EffectiveBalance: 32000000000,
|
|
}
|
|
}
|
|
|
|
beaconState, err := util.NewBeaconState()
|
|
require.NoError(t, err)
|
|
require.NoError(t, beaconState.SetSlot(1))
|
|
require.NoError(t, beaconState.SetValidators(validators))
|
|
|
|
chainService := &mockChain.ChainService{
|
|
State: beaconState,
|
|
ValidAttestation: true,
|
|
}
|
|
|
|
s := &Service{
|
|
cfg: &config{
|
|
chain: chainService,
|
|
},
|
|
}
|
|
|
|
// Test with Phase0 attestation
|
|
t.Run("Phase0_NoError", func(t *testing.T) {
|
|
hook.Reset() // Reset logs before test
|
|
phase0Att := util.NewAttestation()
|
|
phase0Att.Data.Slot = 1
|
|
phase0Att.Data.CommitteeIndex = 0
|
|
|
|
bucket := &attestationBucket{
|
|
data: phase0Att.GetData(),
|
|
attestations: []ethpb.Att{phase0Att},
|
|
}
|
|
|
|
s.processAttestationBucket(context.Background(), bucket)
|
|
})
|
|
|
|
// Test with SingleAttestation
|
|
t.Run("Electra_NoError", func(t *testing.T) {
|
|
hook.Reset() // Reset logs before test
|
|
attData := ðpb.AttestationData{
|
|
Slot: 1,
|
|
CommitteeIndex: 0,
|
|
BeaconBlockRoot: bytesutil.PadTo([]byte("blockroot"), 32),
|
|
Source: ðpb.Checkpoint{
|
|
Epoch: 0,
|
|
Root: bytesutil.PadTo([]byte("source"), 32),
|
|
},
|
|
Target: ðpb.Checkpoint{
|
|
Epoch: 1,
|
|
Root: bytesutil.PadTo([]byte("blockroot"), 32), // Same as BeaconBlockRoot for LMD/FFG consistency
|
|
},
|
|
}
|
|
|
|
singleAtt := ðpb.SingleAttestation{
|
|
CommitteeId: 0,
|
|
AttesterIndex: 0,
|
|
Data: attData,
|
|
Signature: make([]byte, 96),
|
|
}
|
|
|
|
bucket := &attestationBucket{
|
|
data: singleAtt.GetData(),
|
|
attestations: []ethpb.Att{singleAtt},
|
|
}
|
|
|
|
s.processAttestationBucket(context.Background(), bucket)
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestBucketAttestationsByData(t *testing.T) {
|
|
t.Run("EmptyInput", func(t *testing.T) {
|
|
hook := logTest.NewGlobal()
|
|
buckets := bucketAttestationsByData(nil)
|
|
require.Equal(t, 0, len(buckets))
|
|
require.Equal(t, 0, len(hook.Entries))
|
|
|
|
buckets = bucketAttestationsByData([]ethpb.Att{})
|
|
require.Equal(t, 0, len(buckets))
|
|
require.Equal(t, 0, len(hook.Entries))
|
|
})
|
|
|
|
t.Run("SingleAttestation", func(t *testing.T) {
|
|
hook := logTest.NewGlobal()
|
|
att := util.NewAttestation()
|
|
att.Data.Slot = 1
|
|
att.Data.CommitteeIndex = 0
|
|
|
|
buckets := bucketAttestationsByData([]ethpb.Att{att})
|
|
|
|
require.Equal(t, 1, len(buckets))
|
|
var bucket *attestationBucket
|
|
for _, b := range buckets {
|
|
bucket = b
|
|
break
|
|
}
|
|
require.NotNil(t, bucket)
|
|
require.Equal(t, 1, len(bucket.attestations))
|
|
require.Equal(t, att, bucket.attestations[0])
|
|
require.Equal(t, att.GetData(), bucket.data)
|
|
require.Equal(t, 0, len(hook.Entries))
|
|
})
|
|
|
|
t.Run("MultipleAttestationsSameData", func(t *testing.T) {
|
|
hook := logTest.NewGlobal()
|
|
|
|
att1 := util.NewAttestation()
|
|
att1.Data.Slot = 1
|
|
att1.Data.CommitteeIndex = 0
|
|
|
|
att2 := util.NewAttestation()
|
|
att2.Data = att1.Data // Same data
|
|
att2.Signature = make([]byte, 96) // Different signature
|
|
|
|
buckets := bucketAttestationsByData([]ethpb.Att{att1, att2})
|
|
|
|
require.Equal(t, 1, len(buckets), "Should have one bucket for same data")
|
|
var bucket *attestationBucket
|
|
for _, b := range buckets {
|
|
bucket = b
|
|
break
|
|
}
|
|
require.NotNil(t, bucket)
|
|
require.Equal(t, 2, len(bucket.attestations), "Should have both attestations in one bucket")
|
|
require.Equal(t, att1.GetData(), bucket.data)
|
|
require.Equal(t, 0, len(hook.Entries))
|
|
})
|
|
|
|
t.Run("MultipleAttestationsDifferentData", func(t *testing.T) {
|
|
hook := logTest.NewGlobal()
|
|
|
|
att1 := util.NewAttestation()
|
|
att1.Data.Slot = 1
|
|
att1.Data.CommitteeIndex = 0
|
|
|
|
att2 := util.NewAttestation()
|
|
att2.Data.Slot = 2 // Different slot
|
|
att2.Data.CommitteeIndex = 1
|
|
|
|
buckets := bucketAttestationsByData([]ethpb.Att{att1, att2})
|
|
|
|
require.Equal(t, 2, len(buckets), "Should have two buckets for different data")
|
|
bucketCount := 0
|
|
for _, bucket := range buckets {
|
|
require.Equal(t, 1, len(bucket.attestations), "Each bucket should have one attestation")
|
|
bucketCount++
|
|
}
|
|
require.Equal(t, 2, bucketCount, "Should have exactly two buckets")
|
|
require.Equal(t, 0, len(hook.Entries))
|
|
})
|
|
|
|
t.Run("MixedAttestationTypes", func(t *testing.T) {
|
|
hook := logTest.NewGlobal()
|
|
|
|
// Create Phase0 attestation
|
|
phase0Att := util.NewAttestation()
|
|
phase0Att.Data.Slot = 1
|
|
phase0Att.Data.CommitteeIndex = 0
|
|
|
|
electraAtt := ðpb.SingleAttestation{
|
|
CommitteeId: 0,
|
|
AttesterIndex: 1,
|
|
Data: phase0Att.Data, // Same data
|
|
Signature: make([]byte, 96),
|
|
}
|
|
|
|
buckets := bucketAttestationsByData([]ethpb.Att{phase0Att, electraAtt})
|
|
|
|
require.Equal(t, 1, len(buckets), "Should have one bucket for same data")
|
|
var bucket *attestationBucket
|
|
for _, b := range buckets {
|
|
bucket = b
|
|
break
|
|
}
|
|
require.NotNil(t, bucket)
|
|
require.Equal(t, 2, len(bucket.attestations), "Should have both attestations in one bucket")
|
|
require.Equal(t, phase0Att.GetData(), bucket.data)
|
|
require.Equal(t, 0, len(hook.Entries))
|
|
})
|
|
}
|
|
|
|
func TestBatchVerifyAttestationSignatures(t *testing.T) {
|
|
t.Run("EmptyInput", func(t *testing.T) {
|
|
s := &Service{}
|
|
|
|
beaconState, err := util.NewBeaconState()
|
|
require.NoError(t, err)
|
|
|
|
result := s.batchVerifyAttestationSignatures(context.Background(), []ethpb.Att{}, beaconState)
|
|
|
|
// Empty input should return empty output
|
|
require.Equal(t, 0, len(result))
|
|
})
|
|
|
|
t.Run("BatchVerificationWithState", func(t *testing.T) {
|
|
hook := logTest.NewGlobal()
|
|
validators := make([]*ethpb.Validator, 64)
|
|
for i := range validators {
|
|
validators[i] = ðpb.Validator{
|
|
ExitEpoch: 1000000,
|
|
EffectiveBalance: 32000000000,
|
|
}
|
|
}
|
|
|
|
beaconState, err := util.NewBeaconState()
|
|
require.NoError(t, err)
|
|
require.NoError(t, beaconState.SetSlot(1))
|
|
require.NoError(t, beaconState.SetValidators(validators))
|
|
|
|
s := &Service{}
|
|
|
|
att := util.NewAttestation()
|
|
att.Data.Slot = 1
|
|
attestations := []ethpb.Att{att}
|
|
|
|
result := s.batchVerifyAttestationSignatures(context.Background(), attestations, beaconState)
|
|
require.NotNil(t, result)
|
|
|
|
if len(result) == 0 && len(hook.Entries) > 0 {
|
|
_ = false // Check if fallback message is logged
|
|
for _, entry := range hook.Entries {
|
|
if entry.Message == "batch verification failed, using individual checks" {
|
|
_ = true // Found the fallback message
|
|
break
|
|
}
|
|
}
|
|
// It's OK if fallback message is logged - this means the function is working correctly
|
|
}
|
|
})
|
|
|
|
t.Run("BatchVerificationFailureFallbackToIndividual", func(t *testing.T) {
|
|
hook := logTest.NewGlobal()
|
|
beaconState, err := util.NewBeaconState()
|
|
require.NoError(t, err)
|
|
require.NoError(t, beaconState.SetSlot(1))
|
|
|
|
chainService := &mockChain.ChainService{
|
|
State: beaconState,
|
|
ValidAttestation: false, // This will cause verification to fail
|
|
}
|
|
|
|
s := &Service{
|
|
cfg: &config{
|
|
chain: chainService,
|
|
},
|
|
}
|
|
|
|
att := util.NewAttestation()
|
|
att.Data.Slot = 1
|
|
attestations := []ethpb.Att{att}
|
|
|
|
result := s.batchVerifyAttestationSignatures(context.Background(), attestations, beaconState)
|
|
|
|
require.Equal(t, 0, len(result))
|
|
|
|
require.NotEqual(t, 0, len(hook.Entries), "Should have log entries")
|
|
found := false
|
|
for _, entry := range hook.Entries {
|
|
if entry.Message == "batch verification failed, using individual checks" {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
require.Equal(t, true, found, "Should log fallback message")
|
|
})
|
|
}
|