-
Notifications
You must be signed in to change notification settings - Fork 79
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
testutil/validatormock: implement (#187)
To test signature aggregation, we need to create signatures. This add a validator client mock that performs attestations, including signatures as per spec. This will be used to test the SigAgg component. category: testing ticket: #184
- Loading branch information
1 parent
0629cfe
commit 518a573
Showing
4 changed files
with
278 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
[ | ||
{ | ||
"aggregation_bits": "0x0101", | ||
"data": { | ||
"slot": "32", | ||
"index": "0", | ||
"beacon_block_root": "0x0000000000000000000000000000000000000000000000000000000000000000", | ||
"source": { | ||
"epoch": "0", | ||
"root": "0x0000000000000000000000000000000000000000000000000000000000000000" | ||
}, | ||
"target": { | ||
"epoch": "0", | ||
"root": "0x0000000000000000000000000000000000000000000000000000000000000000" | ||
} | ||
}, | ||
"signature": "0x914cff835a769156ba43ad50b931083c2dadd94e8359ce394bc7a3e06424d0214922ddf15f81640530b9c25c0bc0d490000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" | ||
}, | ||
{ | ||
"aggregation_bits": "0x0101", | ||
"data": { | ||
"slot": "32", | ||
"index": "0", | ||
"beacon_block_root": "0x0000000000000000000000000000000000000000000000000000000000000000", | ||
"source": { | ||
"epoch": "0", | ||
"root": "0x0000000000000000000000000000000000000000000000000000000000000000" | ||
}, | ||
"target": { | ||
"epoch": "0", | ||
"root": "0x0000000000000000000000000000000000000000000000000000000000000000" | ||
} | ||
}, | ||
"signature": "0x8dae41352b69f2b3a1c0b05330c1bf65f03730c520273028864b11fcb94d8ce8f26d64f979a0ee3025467f45fd2241ea000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" | ||
}, | ||
{ | ||
"aggregation_bits": "0x0101", | ||
"data": { | ||
"slot": "32", | ||
"index": "0", | ||
"beacon_block_root": "0x0000000000000000000000000000000000000000000000000000000000000000", | ||
"source": { | ||
"epoch": "0", | ||
"root": "0x0000000000000000000000000000000000000000000000000000000000000000" | ||
}, | ||
"target": { | ||
"epoch": "0", | ||
"root": "0x0000000000000000000000000000000000000000000000000000000000000000" | ||
} | ||
}, | ||
"signature": "0x8ee91545183c8c2db86633626f5074fd8ef93c4c9b7a2879ad1768f600c5b5906c3af20d47de42c3b032956fa8db1a76000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" | ||
} | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
[ | ||
{ | ||
"aggregation_bits": "0x0101", | ||
"data": { | ||
"slot": "32", | ||
"index": "0", | ||
"beacon_block_root": "0x0000000000000000000000000000000000000000000000000000000000000000", | ||
"source": { | ||
"epoch": "0", | ||
"root": "0x0000000000000000000000000000000000000000000000000000000000000000" | ||
}, | ||
"target": { | ||
"epoch": "0", | ||
"root": "0x0000000000000000000000000000000000000000000000000000000000000000" | ||
} | ||
}, | ||
"signature": "0x914cff835a769156ba43ad50b931083c2dadd94e8359ce394bc7a3e06424d0214922ddf15f81640530b9c25c0bc0d490000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" | ||
} | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
// Package validatormock provides mock validator client functionality. | ||
package validatormock | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
eth2client "github.com/attestantio/go-eth2-client" | ||
eth2p0 "github.com/attestantio/go-eth2-client/spec/phase0" | ||
"github.com/prysmaticlabs/go-bitfield" | ||
|
||
"github.com/obolnetwork/charon/app/errors" | ||
) | ||
|
||
// Eth2AttProvider defines the eth2 beacon api providers required to perform attestations. | ||
type Eth2AttProvider interface { | ||
eth2client.AttestationDataProvider | ||
eth2client.AttesterDutiesProvider | ||
eth2client.AttestationsSubmitter | ||
eth2client.SlotsPerEpochProvider | ||
eth2client.ValidatorsProvider | ||
eth2client.SpecProvider | ||
eth2client.DomainProvider | ||
} | ||
|
||
// SignFunc abstract signing done by the validator client. | ||
type SignFunc func(context.Context, eth2p0.BLSPubKey, eth2p0.SigningData) (eth2p0.BLSSignature, error) | ||
|
||
// Attest performs attestation duties for the provided slot and pubkeys (validators). | ||
func Attest(ctx context.Context, eth2Cl Eth2AttProvider, signFunc SignFunc, | ||
slot eth2p0.Slot, pubkeys []eth2p0.BLSPubKey, | ||
) error { | ||
slotsPerEpoch, err := eth2Cl.SlotsPerEpoch(ctx) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
epoch := eth2p0.Epoch(uint64(slot) / slotsPerEpoch) | ||
|
||
domain, err := getDomain(ctx, eth2Cl, "DOMAIN_BEACON_ATTESTER", epoch) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
valMap, err := eth2Cl.ValidatorsByPubKey(ctx, fmt.Sprint(slot), pubkeys) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
var indexes []eth2p0.ValidatorIndex | ||
for index, val := range valMap { | ||
if !val.Status.IsActive() { | ||
continue | ||
} | ||
indexes = append(indexes, index) | ||
} | ||
|
||
duties, err := eth2Cl.AttesterDuties(ctx, epoch, indexes) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
var atts []*eth2p0.Attestation | ||
for _, duty := range duties { | ||
if duty.Slot != slot { | ||
continue | ||
} | ||
|
||
data, err := eth2Cl.AttestationData(ctx, duty.Slot, duty.CommitteeIndex) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
root, err := data.HashTreeRoot() | ||
if err != nil { | ||
return errors.Wrap(err, "hash attestation") | ||
} | ||
|
||
sig, err := signFunc(ctx, duty.PubKey, eth2p0.SigningData{ | ||
ObjectRoot: root, | ||
Domain: domain, | ||
}) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
aggBits := bitfield.NewBitlist(duty.CommitteeLength) | ||
aggBits.SetBitAt(duty.ValidatorCommitteeIndex, true) | ||
|
||
atts = append(atts, ð2p0.Attestation{ | ||
AggregationBits: aggBits, | ||
Data: data, | ||
Signature: sig, | ||
}) | ||
} | ||
|
||
return eth2Cl.SubmitAttestations(ctx, atts) | ||
} | ||
|
||
// eth2DomainProvider is the subset of eth2 beacon api provider required to get a signing domain. | ||
type eth2DomainProvider interface { | ||
eth2client.SpecProvider | ||
eth2client.DomainProvider | ||
} | ||
|
||
// getDomain returns the beacon domain for the provided type. | ||
// Types are defined in eth2 spec, see "specs/[phase0|altair]/beacon-chain.md#domain-types" | ||
// at https://github.com/ethereum/consensus-specs. | ||
func getDomain(ctx context.Context, eth2Cl eth2DomainProvider, typ string, epoch eth2p0.Epoch) (eth2p0.Domain, error) { | ||
spec, err := eth2Cl.Spec(ctx) | ||
if err != nil { | ||
return eth2p0.Domain{}, err | ||
} | ||
|
||
domainType, ok := spec[typ] | ||
if !ok { | ||
return eth2p0.Domain{}, errors.New("domain type not found") | ||
} | ||
|
||
domainTyped, ok := domainType.(eth2p0.DomainType) | ||
if !ok { | ||
return eth2p0.Domain{}, errors.New("invalid domain type") | ||
} | ||
|
||
return eth2Cl.Domain(ctx, domainTyped, epoch) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package validatormock_test | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"testing" | ||
|
||
eth2p0 "github.com/attestantio/go-eth2-client/spec/phase0" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/obolnetwork/charon/testutil" | ||
"github.com/obolnetwork/charon/testutil/beaconmock" | ||
"github.com/obolnetwork/charon/testutil/validatormock" | ||
) | ||
|
||
//go:generate go test -run=TestAttest -update -clean | ||
|
||
func TestAttest(t *testing.T) { | ||
tests := []struct { | ||
DutyFactor int | ||
Expect int | ||
}{ | ||
{ | ||
DutyFactor: 0, // All validators in first slot of epoch | ||
Expect: 3, | ||
}, | ||
{ | ||
DutyFactor: 1, // Validators spread over 1st, 2nd, 3rd slots of epoch | ||
Expect: 1, | ||
}, | ||
} | ||
for _, test := range tests { | ||
t.Run(fmt.Sprint(test.DutyFactor), func(t *testing.T) { | ||
ctx := context.Background() | ||
|
||
// Configure beacon mock | ||
static, err := beaconmock.NewStaticProvider(ctx) | ||
require.NoError(t, err) | ||
|
||
valSet := beaconmock.ValidatorSetA | ||
beaconMock := beaconmock.New( | ||
beaconmock.WithStaticProvider(static), | ||
beaconmock.WithValidatorSet(valSet), | ||
beaconmock.WithDeterministicDuties(test.DutyFactor), | ||
) | ||
require.NoError(t, err) | ||
|
||
// Callback to collect attestations | ||
var atts []*eth2p0.Attestation | ||
beaconMock.SubmitAttestationsFunc = func(_ context.Context, attestations []*eth2p0.Attestation) error { | ||
atts = attestations | ||
return nil | ||
} | ||
|
||
// Signature stub function | ||
signFunc := func(ctx context.Context, key eth2p0.BLSPubKey, _ eth2p0.SigningData) (eth2p0.BLSSignature, error) { | ||
var sig eth2p0.BLSSignature | ||
copy(sig[:], key[:]) | ||
|
||
return sig, nil | ||
} | ||
|
||
// Get first slot in epoch 1 | ||
slotsPerEpoch, err := static.SlotsPerEpoch(ctx) | ||
require.NoError(t, err) | ||
|
||
// Call attest function | ||
err = validatormock.Attest(ctx, | ||
beaconMock, signFunc, | ||
eth2p0.Slot(slotsPerEpoch), | ||
valSet.ETH2PubKeys(), | ||
) | ||
require.NoError(t, err) | ||
|
||
// Assert length and expected attestations | ||
require.Len(t, atts, test.Expect) | ||
testutil.RequireGoldenJSON(t, atts) | ||
}) | ||
} | ||
} |