Skip to content

Commit

Permalink
Samlaf/bls aggregation service (#24)
Browse files Browse the repository at this point in the history
* wip

* finished first working implementation of bls aggregation service
tested and working with inc-sq, but still probably needs good brutal review and some more tests (multiquorum for eg)

* fix linting issues

* fix wrong comment

* add comments describing interfaces and functions

* updated names of channels

* added test for 2 quorums

* add test that processes 2 tasks concurrently

* fix comment typo: AvsRegistryService -> AvsRegistryServiceChainCaller

* added TODO comment to write error test cases for avsregistry_chaincaller

* Deleted outdated TaskDict struct

* updated errors in blsagg to address madhur's comments
  • Loading branch information
samlaf authored Oct 17, 2023
1 parent 5afbe24 commit 8a60549
Show file tree
Hide file tree
Showing 14 changed files with 1,206 additions and 55 deletions.
4 changes: 2 additions & 2 deletions crypto/bls/aggregation.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ func (a *StdSignatureAggregator) AggregateSignatures(
aggSigs[ind] = &Signature{sig.Deserialize(sig.Serialize())}
aggPubKeys[ind] = op.PubkeyG2.Deserialize(op.PubkeyG2.Serialize())
} else {
aggSigs[ind].Add(sig.G1Point)
aggSigs[ind].Add(sig)
aggPubKeys[ind].Add(op.PubkeyG2)
}

Expand Down Expand Up @@ -203,7 +203,7 @@ func (a *StdSignatureAggregator) AggregateSignatures(

// Aggregate the aggregated signatures. We reuse the first aggregated signature as the accumulator
for i := 1; i < len(aggSigs); i++ {
aggSigs[0].Add(aggSigs[i].G1Point)
aggSigs[0].Add(aggSigs[i])
}

// Aggregate the aggregated public keys. We reuse the first aggregated public key as the accumulator
Expand Down
29 changes: 25 additions & 4 deletions crypto/bls/attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,20 @@ func NewG1Point(x, y *big.Int) *G1Point {
}
}

func NewZeroG1Point() *G1Point {
return NewG1Point(big.NewInt(0), big.NewInt(0))
}

// Add another G1 point to this one
func (p *G1Point) Add(p2 *G1Point) {
func (p *G1Point) Add(p2 *G1Point) *G1Point {
p.G1Affine.Add(p.G1Affine, p2.G1Affine)
return p
}

// Sub another G1 point from this one
func (p *G1Point) Sub(p2 *G1Point) {
func (p *G1Point) Sub(p2 *G1Point) *G1Point {
p.G1Affine.Sub(p.G1Affine, p2.G1Affine)
return p
}

// VerifyEquivalence verifies G1Point is equivalent the G2Point
Expand Down Expand Up @@ -90,14 +96,20 @@ func NewG2Point(X, Y [2]*big.Int) *G2Point {
}
}

func NewZeroG2Point() *G2Point {
return NewG2Point([2]*big.Int{big.NewInt(0), big.NewInt(0)}, [2]*big.Int{big.NewInt(0), big.NewInt(0)})
}

// Add another G2 point to this one
func (p *G2Point) Add(p2 *G2Point) {
func (p *G2Point) Add(p2 *G2Point) *G2Point {
p.G2Affine.Add(p.G2Affine, p2.G2Affine)
return p
}

// Sub another G2 point from this one
func (p *G2Point) Sub(p2 *G2Point) {
func (p *G2Point) Sub(p2 *G2Point) *G2Point {
p.G2Affine.Sub(p.G2Affine, p2.G2Affine)
return p
}

func (p *G2Point) Serialize() []byte {
Expand All @@ -112,6 +124,15 @@ type Signature struct {
*G1Point `json:"g1_point"`
}

func NewZeroSignature() *Signature {
return &Signature{NewZeroG1Point()}
}

func (s *Signature) Add(otherS *Signature) *Signature {
s.G1Point.Add(otherS.G1Point)
return s
}

// Verify a message against a public key
func (s *Signature) Verify(pubkey *G2Point, message [32]byte) (bool, error) {
ok, err := bn254utils.VerifySig(s.G1Affine, pubkey.G2Affine, message)
Expand Down
11 changes: 8 additions & 3 deletions services/avsregistry/avsregistry.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package avsregistry
import (
"context"

"github.com/Layr-Labs/eigensdk-go/crypto/bls"
blsoperatorstateretrievar "github.com/Layr-Labs/eigensdk-go/contracts/bindings/BLSOperatorStateRetriever"
"github.com/Layr-Labs/eigensdk-go/types"
)

Expand All @@ -12,6 +12,11 @@ import (
type AvsRegistryService interface {
// GetOperatorsAvsState returns the state of an avs wrt to a list of quorums at a certain block.
// The state includes the operatorId, pubkey, and staking amount in each quorum.
GetOperatorsAvsStateAtBlock(ctx context.Context, quorumNumbers []types.QuorumNum, blockNumber types.BlockNumber) (map[types.OperatorId]types.OperatorAvsState, map[types.QuorumNum]*bls.G1Point, error)
GetOperatorPubkeys(ctx context.Context, operatorId types.OperatorId) (types.OperatorPubkeys, error)
GetOperatorsAvsStateAtBlock(ctx context.Context, quorumNumbers []types.QuorumNum, blockNumber types.BlockNum) (map[types.OperatorId]types.OperatorAvsState, error)
// GetQuorumsAvsStateAtBlock returns the aggregated data for a list of quorums at a certain block.
// The aggregated data includes the aggregated pubkey and total stake in each quorum.
// This information is derivable from the Operators Avs State (returned from GetOperatorsAvsStateAtBlock), but this function is provided for convenience.
GetQuorumsAvsStateAtBlock(ctx context.Context, quorumNumbers []types.QuorumNum, blockNumber types.BlockNum) (map[types.QuorumNum]types.QuorumAvsState, error)
// GetCheckSignaturesIndices returns the registry indices of the nonsigner operators specified by nonSignerOperatorIds who were registered at referenceBlockNumber.
GetCheckSignaturesIndices(ctx context.Context, referenceBlockNumber types.BlockNum, quorumNumbers []types.QuorumNum, nonSignerOperatorIds []types.OperatorId) (blsoperatorstateretrievar.BLSOperatorStateRetrieverCheckSignaturesIndices, error)
}
60 changes: 39 additions & 21 deletions services/avsregistry/avsregistry_chaincaller.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ import (
"github.com/Layr-Labs/eigensdk-go/types"
)

// AvsRegistryServiceChainCaller is a wrapper around AvsRegistryReader that transforms the data into
// nicer golang types that are easier to work with
type AvsRegistryServiceChainCaller struct {
avsregistry.AvsRegistryReader
elReader elcontracts.ELReader
avsRegistryReader avsregistry.AvsRegistryReader
pubkeyCompendiumService pcservice.PubkeyCompendiumService
logger logging.Logger
}
Expand All @@ -24,19 +26,19 @@ var _ AvsRegistryService = (*AvsRegistryServiceChainCaller)(nil)
func NewAvsRegistryServiceChainCaller(avsRegistryReader avsregistry.AvsRegistryReader, elReader elcontracts.ELReader, pubkeyCompendiumService pcservice.PubkeyCompendiumService, logger logging.Logger) *AvsRegistryServiceChainCaller {
return &AvsRegistryServiceChainCaller{
elReader: elReader,
avsRegistryReader: avsRegistryReader,
AvsRegistryReader: avsRegistryReader,
pubkeyCompendiumService: pubkeyCompendiumService,
logger: logger,
}
}

func (ar *AvsRegistryServiceChainCaller) GetOperatorsAvsStateAtBlock(ctx context.Context, quorumNumbers []types.QuorumNum, blockNumber types.BlockNumber) (map[types.OperatorId]types.OperatorAvsState, map[types.QuorumNum]*bls.G1Point, error) {
func (ar *AvsRegistryServiceChainCaller) GetOperatorsAvsStateAtBlock(ctx context.Context, quorumNumbers []types.QuorumNum, blockNumber types.BlockNum) (map[types.OperatorId]types.OperatorAvsState, error) {
operatorsAvsState := make(map[types.OperatorId]types.OperatorAvsState)
// Get operator state for each quorum by querying BLSOperatorStateRetriever (this call is why this service implementation is called ChainCaller)
operatorsStakesInQuorums, err := ar.avsRegistryReader.GetOperatorsStakeInQuorumsAtBlock(ctx, quorumNumbers, blockNumber)
operatorsStakesInQuorums, err := ar.AvsRegistryReader.GetOperatorsStakeInQuorumsAtBlock(ctx, quorumNumbers, blockNumber)
if err != nil {
ar.logger.Error("Failed to get operator state", "err", err, "service", "AvsRegistryServiceChainCaller")
return nil, nil, err
return nil, err
}
numquorums := len(quorumNumbers)
if len(operatorsStakesInQuorums) != numquorums {
Expand All @@ -45,10 +47,10 @@ func (ar *AvsRegistryServiceChainCaller) GetOperatorsAvsStateAtBlock(ctx context

for quorumIdx, quorumNum := range quorumNumbers {
for _, operator := range operatorsStakesInQuorums[quorumIdx] {
pubkeys, err := ar.GetOperatorPubkeys(ctx, operator.OperatorId)
pubkeys, err := ar.getOperatorPubkeys(ctx, operator.OperatorId)
if err != nil {
ar.logger.Error("Failed find pubkeys for operator while building operatorsAvsState", "err", err, "service", "AvsRegistryServiceChainCaller")
return nil, nil, err
return nil, err
}
if operatorAvsState, ok := operatorsAvsState[operator.OperatorId]; ok {
operatorAvsState.StakePerQuorum[quorumNum] = operator.Stake
Expand All @@ -66,28 +68,44 @@ func (ar *AvsRegistryServiceChainCaller) GetOperatorsAvsStateAtBlock(ctx context
}
}

aggG1PubkeyPerQuorum := make(map[types.QuorumNum]*bls.G1Point)
return operatorsAvsState, nil
}

func (ar *AvsRegistryServiceChainCaller) GetQuorumsAvsStateAtBlock(ctx context.Context, quorumNumbers []types.QuorumNum, blockNumber types.BlockNum) (map[types.QuorumNum]types.QuorumAvsState, error) {
operatorsAvsState, err := ar.GetOperatorsAvsStateAtBlock(ctx, quorumNumbers, blockNumber)
if err != nil {
ar.logger.Error("Failed to get operator state", "err", err, "service", "AvsRegistryServiceChainCaller")
return nil, err
}
quorumsAvsState := make(map[types.QuorumNum]types.QuorumAvsState)
for _, quorumNum := range quorumNumbers {
aggG1Pubkey := bls.NewG1Point(big.NewInt(0), big.NewInt(0))
aggPubkeyG1 := bls.NewG1Point(big.NewInt(0), big.NewInt(0))
totalStake := big.NewInt(0)
for _, operator := range operatorsAvsState {
// only include operators that have a stake in this quorum
if operator.StakePerQuorum != nil {
aggG1Pubkey.Add(operator.Pubkeys.G1Pubkey)
if stake, ok := operator.StakePerQuorum[quorumNum]; ok {
aggPubkeyG1.Add(operator.Pubkeys.G1Pubkey)
totalStake.Add(totalStake, stake)
}
}
aggG1PubkeyPerQuorum[quorumNum] = aggG1Pubkey
quorumsAvsState[quorumNum] = types.QuorumAvsState{
QuorumNumber: quorumNum,
AggPubkeyG1: aggPubkeyG1,
TotalStake: totalStake,
BlockNumber: blockNumber,
}
}
return operatorsAvsState, aggG1PubkeyPerQuorum, nil
return quorumsAvsState, nil
}

func (ar *AvsRegistryServiceChainCaller) GetOperatorPubkeys(ctx context.Context, operatorId types.OperatorId) (types.OperatorPubkeys, error) {
// TODO(samlaf): This is a temporary hack until we implement GetOperatorAddr on the BLSpubkeyregistry contract (shortly)
// we need operatorId -> operatorAddr so that we can query the pubkeyCompendiumService
// this inverse mapping (only operatorAddr->operatorId is stored in registryCoordinator) is not stored,
// but we know that the current implementation uses the hash of the G1 pubkey as the operatorId,
// and the pubkeycompendium contract stores the mapping G1pubkeyHash -> operatorAddr
// When the above PR is merged, we should change this to instead call GetOperatorAddressFromOperatorId on the avsRegistryReader
// and not hardcode the definition of the operatorId here
// getOperatorPubkeys is a temporary hack until we implement GetOperatorAddr on the BLSpubkeyregistry contract
// TODO(samlaf): we need operatorId -> operatorAddr so that we can query the pubkeyCompendiumService
// this inverse mapping (only operatorAddr->operatorId is stored in registryCoordinator) is not stored,
// but we know that the current implementation uses the hash of the G1 pubkey as the operatorId,
// and the pubkeycompendium contract stores the mapping G1pubkeyHash -> operatorAddr
// When the above PR is merged, we should change this to instead call GetOperatorAddressFromOperatorId on the avsRegistryReader
// and not hardcode the definition of the operatorId here
func (ar *AvsRegistryServiceChainCaller) getOperatorPubkeys(ctx context.Context, operatorId types.OperatorId) (types.OperatorPubkeys, error) {
operatorAddr, err := ar.elReader.GetOperatorAddressFromPubkeyHash(ctx, operatorId)
if err != nil {
ar.logger.Error("Failed to get operator address from pubkey hash", "err", err, "service", "AvsRegistryServiceChainCaller")
Expand Down
89 changes: 78 additions & 11 deletions services/avsregistry/avsregistry_chaincaller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type testOperator struct {
pubkeys types.OperatorPubkeys
}

func TestAvsRegistryServiceChainCaller_GetOperatorPubkeys(t *testing.T) {
func TestAvsRegistryServiceChainCaller_getOperatorPubkeys(t *testing.T) {
logger := logging.NewNoopLogger()
testOperator := testOperator{
operatorAddr: common.HexToAddress("0x1"),
Expand All @@ -33,6 +33,7 @@ func TestAvsRegistryServiceChainCaller_GetOperatorPubkeys(t *testing.T) {
},
}

// TODO(samlaf): add error test cases
var tests = []struct {
name string
mocksInitializationFunc func(*chainiomocks.MockAvsRegistryReader, *chainiomocks.MockELReader, *servicemocks.MockPubkeyCompendiumService)
Expand Down Expand Up @@ -67,7 +68,7 @@ func TestAvsRegistryServiceChainCaller_GetOperatorPubkeys(t *testing.T) {
service := NewAvsRegistryServiceChainCaller(mockAvsRegistryReader, mockElReader, mockPubkeyCompendium, logger)

// Call the GetOperatorPubkeys method with the test operator address
gotOperatorPubkeys, gotErr := service.GetOperatorPubkeys(context.Background(), tt.queryOperatorId)
gotOperatorPubkeys, gotErr := service.getOperatorPubkeys(context.Background(), tt.queryOperatorId)
if tt.wantErr != gotErr {
t.Fatalf("GetOperatorPubkeys returned wrong error. Got: %v, want: %v.", gotErr, tt.wantErr)
}
Expand All @@ -93,15 +94,14 @@ func TestAvsRegistryServiceChainCaller_GetOperatorsAvsState(t *testing.T) {
name string
mocksInitializationFunc func(*chainiomocks.MockAvsRegistryReader, *chainiomocks.MockELReader, *servicemocks.MockPubkeyCompendiumService)
queryQuorumNumbers []types.QuorumNum
queryBlockNum types.BlockNumber
queryBlockNum types.BlockNum
wantErr error
wantOperatorsAvsStateDict map[types.OperatorId]types.OperatorAvsState
wantAggG1PubkeyPerQuorum map[types.QuorumNum]*bls.G1Point
}{
{
name: "should return operatorsAvsState",
mocksInitializationFunc: func(mockAvsRegistryReader *chainiomocks.MockAvsRegistryReader, mockElReader *chainiomocks.MockELReader, mockPubkeyCompendiumService *servicemocks.MockPubkeyCompendiumService) {
mockAvsRegistryReader.EXPECT().GetOperatorsStakeInQuorumsAtBlock(context.Background(), []types.QuorumNum{1}, types.BlockNumber(1)).Return([][]blsoperatorstateretrievar.BLSOperatorStateRetrieverOperator{
mockAvsRegistryReader.EXPECT().GetOperatorsStakeInQuorumsAtBlock(context.Background(), []types.QuorumNum{1}, types.BlockNum(1)).Return([][]blsoperatorstateretrievar.BLSOperatorStateRetrieverOperator{
{
{
OperatorId: testOperator.operatorId,
Expand All @@ -123,9 +123,6 @@ func TestAvsRegistryServiceChainCaller_GetOperatorsAvsState(t *testing.T) {
BlockNumber: 1,
},
},
wantAggG1PubkeyPerQuorum: map[types.QuorumNum]*bls.G1Point{
1: bls.NewG1Point(big.NewInt(1), big.NewInt(1)),
},
},
}

Expand All @@ -144,15 +141,85 @@ func TestAvsRegistryServiceChainCaller_GetOperatorsAvsState(t *testing.T) {
service := NewAvsRegistryServiceChainCaller(mockAvsRegistryReader, mockElReader, mockPubkeyCompendium, logger)

// Call the GetOperatorPubkeys method with the test operator address
gotOperatorsAvsStateDict, aggG1PubkeyPerQuorum, gotErr := service.GetOperatorsAvsStateAtBlock(context.Background(), tt.queryQuorumNumbers, tt.queryBlockNum)
gotOperatorsAvsStateDict, gotErr := service.GetOperatorsAvsStateAtBlock(context.Background(), tt.queryQuorumNumbers, tt.queryBlockNum)
if tt.wantErr != gotErr {
t.Fatalf("GetOperatorsAvsState returned wrong error. Got: %v, want: %v.", gotErr, tt.wantErr)
}
if tt.wantErr == nil && !reflect.DeepEqual(tt.wantOperatorsAvsStateDict, gotOperatorsAvsStateDict) {
t.Fatalf("GetOperatorsAvsState returned wrong operatorsAvsStateDict. Got: %v, want: %v.", gotOperatorsAvsStateDict, tt.wantOperatorsAvsStateDict)
}
if tt.wantErr == nil && !reflect.DeepEqual(tt.wantAggG1PubkeyPerQuorum, aggG1PubkeyPerQuorum) {
t.Fatalf("GetOperatorsAvsState returned wrong aggG1PubkeyPerQuorum. Got: %v, want: %v.", aggG1PubkeyPerQuorum, tt.wantAggG1PubkeyPerQuorum)
})
}
}

func TestAvsRegistryServiceChainCaller_GetQuorumsAvsState(t *testing.T) {
logger := logging.NewNoopLogger()
testOperator := testOperator{
operatorAddr: common.HexToAddress("0x1"),
operatorId: types.OperatorId{1},
pubkeys: types.OperatorPubkeys{
G1Pubkey: bls.NewG1Point(big.NewInt(1), big.NewInt(1)),
G2Pubkey: bls.NewG2Point([2]*big.Int{big.NewInt(1), big.NewInt(1)}, [2]*big.Int{big.NewInt(1), big.NewInt(1)}),
},
}

var tests = []struct {
name string
mocksInitializationFunc func(*chainiomocks.MockAvsRegistryReader, *chainiomocks.MockELReader, *servicemocks.MockPubkeyCompendiumService)
queryQuorumNumbers []types.QuorumNum
queryBlockNum types.BlockNum
wantErr error
wantQuorumsAvsStateDict map[types.QuorumNum]types.QuorumAvsState
}{
{
name: "should return operatorsAvsState",
mocksInitializationFunc: func(mockAvsRegistryReader *chainiomocks.MockAvsRegistryReader, mockElReader *chainiomocks.MockELReader, mockPubkeyCompendiumService *servicemocks.MockPubkeyCompendiumService) {
mockAvsRegistryReader.EXPECT().GetOperatorsStakeInQuorumsAtBlock(context.Background(), []types.QuorumNum{1}, types.BlockNum(1)).Return([][]blsoperatorstateretrievar.BLSOperatorStateRetrieverOperator{
{
{
OperatorId: testOperator.operatorId,
Stake: big.NewInt(123),
},
},
}, nil)
mockElReader.EXPECT().GetOperatorAddressFromPubkeyHash(context.Background(), testOperator.operatorId).Return(testOperator.operatorAddr, nil)
mockPubkeyCompendiumService.EXPECT().GetOperatorPubkeys(context.Background(), testOperator.operatorAddr).Return(testOperator.pubkeys, true)
},
queryQuorumNumbers: []types.QuorumNum{1},
queryBlockNum: 1,
wantErr: nil,
wantQuorumsAvsStateDict: map[types.QuorumNum]types.QuorumAvsState{
1: types.QuorumAvsState{
QuorumNumber: types.QuorumNum(1),
TotalStake: big.NewInt(123),
AggPubkeyG1: bls.NewG1Point(big.NewInt(1), big.NewInt(1)),
BlockNumber: 1,
},
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create mocks
mockCtrl := gomock.NewController(t)
mockAvsRegistryReader := chainiomocks.NewMockAvsRegistryReader(mockCtrl)
mockElReader := chainiomocks.NewMockELReader(mockCtrl)
mockPubkeyCompendium := servicemocks.NewMockPubkeyCompendiumService(mockCtrl)

if tt.mocksInitializationFunc != nil {
tt.mocksInitializationFunc(mockAvsRegistryReader, mockElReader, mockPubkeyCompendium)
}
// Create a new instance of the avsregistry service
service := NewAvsRegistryServiceChainCaller(mockAvsRegistryReader, mockElReader, mockPubkeyCompendium, logger)

// Call the GetOperatorPubkeys method with the test operator address
aggG1PubkeyPerQuorum, gotErr := service.GetQuorumsAvsStateAtBlock(context.Background(), tt.queryQuorumNumbers, tt.queryBlockNum)
if tt.wantErr != gotErr {
t.Fatalf("GetOperatorsAvsState returned wrong error. Got: %v, want: %v.", gotErr, tt.wantErr)
}
if tt.wantErr == nil && !reflect.DeepEqual(tt.wantQuorumsAvsStateDict, aggG1PubkeyPerQuorum) {
t.Fatalf("GetOperatorsAvsState returned wrong aggG1PubkeyPerQuorum. Got: %v, want: %v.", aggG1PubkeyPerQuorum, tt.wantQuorumsAvsStateDict)
}
})
}
Expand Down
Loading

0 comments on commit 8a60549

Please sign in to comment.