Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix bls aggregation for multiple quorums #394

Open
wants to merge 12 commits into
base: dev
Choose a base branch
from
21 changes: 16 additions & 5 deletions services/bls_aggregation/blsagg.go
Original file line number Diff line number Diff line change
Expand Up @@ -387,12 +387,22 @@ func (a *BlsAggregatorService) singleTaskAggregatorGoroutineFunc(
// after verifying signature we aggregate its sig and pubkey, and update the signed stake amount
if !ok {
// first operator to sign on this digest
signersApkG2 := bls.NewZeroG2Point()
signersAggSigG1 := bls.NewZeroSignature()
// for each quorum the operator has stake in, the signature is aggregated
// see
// https://github.com/Layr-Labs/eigenlayer-middleware/blob/7d49b5181b09198ed275783453aa082bb3766990/src/BLSSignatureChecker.sol#L161-L168
for range operatorsAvsStateDict[signedTaskResponseDigest.OperatorId].StakePerQuorum {
signersApkG2 = signersApkG2.Add(
operatorsAvsStateDict[signedTaskResponseDigest.OperatorId].OperatorInfo.Pubkeys.G2Pubkey,
)
signersAggSigG1 = signersAggSigG1.Add(signedTaskResponseDigest.BlsSignature)
}
digestAggregatedOperators = aggregatedOperators{
// we've already verified that the operator is part of the task's quorum, so we don't need checks
// here
signersApkG2: bls.NewZeroG2Point().
Add(operatorsAvsStateDict[signedTaskResponseDigest.OperatorId].OperatorInfo.Pubkeys.G2Pubkey),
signersAggSigG1: signedTaskResponseDigest.BlsSignature,
signersApkG2: signersApkG2,
signersAggSigG1: signersAggSigG1,
signersOperatorIdsSet: map[types.OperatorId]bool{signedTaskResponseDigest.OperatorId: true},
signersTotalStakePerQuorum: cloneStakePerQuorumMap(
operatorsAvsStateDict[signedTaskResponseDigest.OperatorId].StakePerQuorum,
Expand All @@ -403,10 +413,11 @@ func (a *BlsAggregatorService) singleTaskAggregatorGoroutineFunc(
"taskIndex", taskIndex,
"taskResponseDigest", taskResponseDigest)

digestAggregatedOperators.signersAggSigG1.Add(signedTaskResponseDigest.BlsSignature)
digestAggregatedOperators.signersApkG2.Add(operatorsAvsStateDict[signedTaskResponseDigest.OperatorId].OperatorInfo.Pubkeys.G2Pubkey)
digestAggregatedOperators.signersOperatorIdsSet[signedTaskResponseDigest.OperatorId] = true
// for each quorum the operator has stake in, the signature is aggregated
for quorumNum, stake := range operatorsAvsStateDict[signedTaskResponseDigest.OperatorId].StakePerQuorum {
digestAggregatedOperators.signersAggSigG1.Add(signedTaskResponseDigest.BlsSignature)
digestAggregatedOperators.signersApkG2.Add(operatorsAvsStateDict[signedTaskResponseDigest.OperatorId].OperatorInfo.Pubkeys.G2Pubkey)
if _, ok := digestAggregatedOperators.signersTotalStakePerQuorum[quorumNum]; !ok {
// if we haven't seen this quorum before, initialize its signed stake to 0
// possible if previous operators who sent us signatures were not part of this quorum
Expand Down
174 changes: 154 additions & 20 deletions services/bls_aggregation/blsagg_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (

"github.com/Layr-Labs/eigensdk-go/chainio/clients"
"github.com/Layr-Labs/eigensdk-go/chainio/utils"
avssm "github.com/Layr-Labs/eigensdk-go/contracts/bindings/MockAvsServiceManager"
regcoord "github.com/Layr-Labs/eigensdk-go/contracts/bindings/RegistryCoordinator"
"github.com/Layr-Labs/eigensdk-go/crypto/bls"
"github.com/Layr-Labs/eigensdk-go/logging"
"github.com/Layr-Labs/eigensdk-go/services/avsregistry"
Expand All @@ -23,8 +25,6 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require"

avssm "github.com/Layr-Labs/eigensdk-go/contracts/bindings/MockAvsServiceManager"
)

// TestBlsAgg is a suite of test that tests the main aggregation logic of the aggregation service
Expand Down Expand Up @@ -62,7 +62,7 @@ func TestBlsAgg(t *testing.T) {
t.Run("1 quorum 1 operator 1 correct signature", func(t *testing.T) {
testOperator1 := types.TestOperator{
OperatorId: types.OperatorId{1},
StakePerQuorum: map[types.QuorumNum]types.StakeAmount{0: big.NewInt(100), 1: big.NewInt(200)},
StakePerQuorum: map[types.QuorumNum]types.StakeAmount{0: big.NewInt(100)},
BlsKeypair: newBlsKeyPairPanics("0x1"),
}
blockNum := uint32(1)
Expand Down Expand Up @@ -115,17 +115,17 @@ func TestBlsAgg(t *testing.T) {
t.Run("1 quorum 3 operator 3 correct signatures", func(t *testing.T) {
testOperator1 := types.TestOperator{
OperatorId: types.OperatorId{1},
StakePerQuorum: map[types.QuorumNum]types.StakeAmount{0: big.NewInt(100), 1: big.NewInt(200)},
StakePerQuorum: map[types.QuorumNum]types.StakeAmount{0: big.NewInt(100)},
BlsKeypair: newBlsKeyPairPanics("0x1"),
}
testOperator2 := types.TestOperator{
OperatorId: types.OperatorId{2},
StakePerQuorum: map[types.QuorumNum]types.StakeAmount{0: big.NewInt(100), 1: big.NewInt(200)},
StakePerQuorum: map[types.QuorumNum]types.StakeAmount{0: big.NewInt(100)},
BlsKeypair: newBlsKeyPairPanics("0x2"),
}
testOperator3 := types.TestOperator{
OperatorId: types.OperatorId{3},
StakePerQuorum: map[types.QuorumNum]types.StakeAmount{0: big.NewInt(300), 1: big.NewInt(100)},
StakePerQuorum: map[types.QuorumNum]types.StakeAmount{0: big.NewInt(300)},
BlsKeypair: newBlsKeyPairPanics("0x3"),
}
blockNum := uint32(1)
Expand Down Expand Up @@ -255,6 +255,11 @@ func TestBlsAgg(t *testing.T) {
)
require.Nil(t, err)

op1G2Key := testOperator1.BlsKeypair.GetPubKeyG2()
op2G2Key := testOperator2.BlsKeypair.GetPubKeyG2()
op1Signature := testOperator1.BlsKeypair.SignMessage(taskResponseDigest)
op2Signature := testOperator2.BlsKeypair.SignMessage(taskResponseDigest)

wantAggregationServiceResponse := BlsAggregationServiceResponse{
Err: nil,
TaskIndex: taskIndex,
Expand All @@ -269,9 +274,9 @@ func TestBlsAgg(t *testing.T) {
Add(testOperator1.BlsKeypair.GetPubKeyG1()).
Add(testOperator2.BlsKeypair.GetPubKeyG1()),
},
SignersApkG2: testOperator1.BlsKeypair.GetPubKeyG2().Add(testOperator2.BlsKeypair.GetPubKeyG2()),
SignersAggSigG1: testOperator1.BlsKeypair.SignMessage(taskResponseDigest).
Add(testOperator2.BlsKeypair.SignMessage(taskResponseDigest)),
SignersApkG2: op1G2Key.Add(op1G2Key).Add(op2G2Key).Add(op2G2Key),
SignersAggSigG1: op1Signature.Add(op1Signature).Add(op2Signature).Add(op2Signature),
// each key is added twice because both operators stake on two quorums
}
gotAggregationServiceResponse := <-blsAggServ.aggregatedResponsesC
require.EqualValues(t, wantAggregationServiceResponse, gotAggregationServiceResponse)
Expand Down Expand Up @@ -377,8 +382,13 @@ func TestBlsAgg(t *testing.T) {
Add(testOperator2.BlsKeypair.GetPubKeyG1()),
},
SignersApkG2: bls.NewZeroG2Point().
Add(testOperator1.BlsKeypair.GetPubKeyG2().Add(testOperator2.BlsKeypair.GetPubKeyG2())),
Add(testOperator1.BlsKeypair.GetPubKeyG2()).
Add(testOperator1.BlsKeypair.GetPubKeyG2()).
Add(testOperator2.BlsKeypair.GetPubKeyG2()).
Add(testOperator2.BlsKeypair.GetPubKeyG2()),
SignersAggSigG1: testOperator1.BlsKeypair.SignMessage(task1ResponseDigest).
Add(testOperator1.BlsKeypair.SignMessage(task1ResponseDigest)).
Add(testOperator2.BlsKeypair.SignMessage(task1ResponseDigest)).
Add(testOperator2.BlsKeypair.SignMessage(task1ResponseDigest)),
}
wantAggregationServiceResponseTask2 := BlsAggregationServiceResponse{
Expand All @@ -395,8 +405,13 @@ func TestBlsAgg(t *testing.T) {
Add(testOperator1.BlsKeypair.GetPubKeyG1()).
Add(testOperator2.BlsKeypair.GetPubKeyG1()),
},
SignersApkG2: testOperator1.BlsKeypair.GetPubKeyG2().Add(testOperator2.BlsKeypair.GetPubKeyG2()),
SignersApkG2: testOperator1.BlsKeypair.GetPubKeyG2().
Add(testOperator1.BlsKeypair.GetPubKeyG2()).
Add(testOperator2.BlsKeypair.GetPubKeyG2()).
Add(testOperator2.BlsKeypair.GetPubKeyG2()),
SignersAggSigG1: testOperator1.BlsKeypair.SignMessage(task2ResponseDigest).
Add(testOperator1.BlsKeypair.SignMessage(task2ResponseDigest)).
Add(testOperator2.BlsKeypair.SignMessage(task2ResponseDigest)).
Add(testOperator2.BlsKeypair.SignMessage(task2ResponseDigest)),
}

Expand Down Expand Up @@ -541,12 +556,12 @@ func TestBlsAgg(t *testing.T) {
t.Run("1 quorum 2 operator 1 correct signature quorumThreshold 50% - verified", func(t *testing.T) {
testOperator1 := types.TestOperator{
OperatorId: types.OperatorId{1},
StakePerQuorum: map[types.QuorumNum]types.StakeAmount{0: big.NewInt(100), 1: big.NewInt(200)},
StakePerQuorum: map[types.QuorumNum]types.StakeAmount{0: big.NewInt(100)},
BlsKeypair: newBlsKeyPairPanics("0x1"),
}
testOperator2 := types.TestOperator{
OperatorId: types.OperatorId{2},
StakePerQuorum: map[types.QuorumNum]types.StakeAmount{0: big.NewInt(100), 1: big.NewInt(200)},
StakePerQuorum: map[types.QuorumNum]types.StakeAmount{0: big.NewInt(100)},
BlsKeypair: newBlsKeyPairPanics("0x2"),
}
taskIndex := types.TaskIndex(0)
Expand Down Expand Up @@ -600,12 +615,12 @@ func TestBlsAgg(t *testing.T) {
t.Run("1 quorum 2 operator 1 correct signature quorumThreshold 60% - task expired", func(t *testing.T) {
testOperator1 := types.TestOperator{
OperatorId: types.OperatorId{1},
StakePerQuorum: map[types.QuorumNum]types.StakeAmount{0: big.NewInt(100), 1: big.NewInt(200)},
StakePerQuorum: map[types.QuorumNum]types.StakeAmount{0: big.NewInt(100)},
BlsKeypair: newBlsKeyPairPanics("0x1"),
}
testOperator2 := types.TestOperator{
OperatorId: types.OperatorId{2},
StakePerQuorum: map[types.QuorumNum]types.StakeAmount{0: big.NewInt(100), 1: big.NewInt(200)},
StakePerQuorum: map[types.QuorumNum]types.StakeAmount{0: big.NewInt(100)},
BlsKeypair: newBlsKeyPairPanics("0x2"),
}
blockNum := uint32(1)
Expand Down Expand Up @@ -1013,12 +1028,12 @@ func TestBlsAgg(t *testing.T) {
func(t *testing.T) {
testOperator1 := types.TestOperator{
OperatorId: types.OperatorId{1},
StakePerQuorum: map[types.QuorumNum]types.StakeAmount{0: big.NewInt(100), 1: big.NewInt(200)},
StakePerQuorum: map[types.QuorumNum]types.StakeAmount{0: big.NewInt(100)},
BlsKeypair: newBlsKeyPairPanics("0x1"),
}
testOperator2 := types.TestOperator{
OperatorId: types.OperatorId{2},
StakePerQuorum: map[types.QuorumNum]types.StakeAmount{0: big.NewInt(100), 1: big.NewInt(200)},
StakePerQuorum: map[types.QuorumNum]types.StakeAmount{0: big.NewInt(100)},
BlsKeypair: newBlsKeyPairPanics("0x2"),
}
blockNum := uint32(1)
Expand Down Expand Up @@ -1213,6 +1228,7 @@ func TestIntegrationBlsAgg(t *testing.T) {
anvilWsEndpoint, err := anvilC.Endpoint(context.Background(), "ws")
require.NoError(t, err)
contractAddrs := testutils.GetContractAddressesFromContractRegistry(anvilHttpEndpoint)

t.Run("1 quorums 1 operator", func(t *testing.T) {
// read input from JSON if available, otherwise use default values
var defaultInput = struct {
Expand Down Expand Up @@ -1240,7 +1256,7 @@ func TestIntegrationBlsAgg(t *testing.T) {
logger := logging.NewTextSLogger(os.Stdout, &logging.SLoggerOptions{Level: slog.LevelDebug})
avsClients, err := clients.BuildAll(clients.BuildAllConfig{
EthHttpUrl: anvilHttpEndpoint,
EthWsUrl: anvilWsEndpoint, // not used so doesn't matter that we pass an http url
EthWsUrl: anvilWsEndpoint,
RegistryCoordinatorAddr: contractAddrs.RegistryCoordinator.String(),
OperatorStateRetrieverAddr: contractAddrs.OperatorStateRetriever.String(),
AvsName: "avs",
Expand Down Expand Up @@ -1319,8 +1335,126 @@ func TestIntegrationBlsAgg(t *testing.T) {
require.NoError(t, err)
})

t.Run("2 quorums 1 operator", func(t *testing.T) {
// TODO: Implement this test
t.Run("2 quorums 1 operator staking on both", func(t *testing.T) {
// define operator ecdsa and bls private keys
ecdsaPrivKey, err := crypto.HexToECDSA("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80")
require.NoError(t, err)
blsPrivKeyHex := "0x1"
blsKeyPair := newBlsKeyPairPanics(blsPrivKeyHex)
operatorId := types.OperatorIdFromG1Pubkey(blsKeyPair.GetPubKeyG1())

// create avs clients to interact with contracts deployed on anvil
ethHttpClient, err := ethclient.Dial(anvilHttpEndpoint)
require.NoError(t, err)
logger := logging.NewTextSLogger(os.Stdout, &logging.SLoggerOptions{Level: slog.LevelDebug})
avsClients, err := clients.BuildAll(clients.BuildAllConfig{
EthHttpUrl: anvilHttpEndpoint,
EthWsUrl: anvilWsEndpoint,
RegistryCoordinatorAddr: contractAddrs.RegistryCoordinator.String(),
OperatorStateRetrieverAddr: contractAddrs.OperatorStateRetriever.String(),
AvsName: "avs",
PromMetricsIpPortAddress: "localhost:9090",
}, ecdsaPrivKey, logger)
require.NoError(t, err)
avsWriter := avsClients.AvsRegistryChainWriter
avsServiceManager, err := avssm.NewContractMockAvsServiceManager(contractAddrs.ServiceManager, ethHttpClient)
require.NoError(t, err)

// create aggregation service
operatorsInfoService := operatorsinfo.NewOperatorsInfoServiceInMemory(
context.TODO(),
avsClients.AvsRegistryChainSubscriber,
avsClients.AvsRegistryChainReader,
nil,
operatorsinfo.Opts{},
logger,
)
avsRegistryService := avsregistry.NewAvsRegistryServiceChainCaller(
avsClients.AvsRegistryChainReader,
operatorsInfoService,
logger,
)
blsAggServ := NewBlsAggregatorService(avsRegistryService, hashFunction, logger)

// create quorum
registryCoordinator, _ := regcoord.NewContractRegistryCoordinator(
contractAddrs.RegistryCoordinator,
ethHttpClient,
)
operatorSetParam := regcoord.IRegistryCoordinatorOperatorSetParam{
MaxOperatorCount: 10,
KickBIPsOfOperatorStake: 1,
KickBIPsOfTotalStake: 1,
}
strategyParam := []regcoord.IStakeRegistryStrategyParams{
{
Strategy: contractAddrs.Erc20MockStrategy,
Multiplier: big.NewInt(1),
},
}
noSendTxOpts, err := avsClients.TxManager.GetNoSendTxOpts()
require.NoError(t, err)
tx, err := registryCoordinator.CreateQuorum(noSendTxOpts, operatorSetParam, big.NewInt(0), strategyParam)
require.NoError(t, err)
_, err = avsClients.TxManager.Send(context.TODO(), tx, true)
require.NoError(t, err)

tx, err = registryCoordinator.CreateQuorum(noSendTxOpts, operatorSetParam, big.NewInt(0), strategyParam)
require.NoError(t, err)
_, err = avsClients.TxManager.Send(context.TODO(), tx, true)
require.NoError(t, err)

// register operator
quorumNumbers := types.QuorumNums{1, 2}
quorumThresholdPercentages := []types.QuorumThresholdPercentage{100, 100}

_, err = avsWriter.RegisterOperator(
context.Background(),
ecdsaPrivKey,
blsKeyPair,
quorumNumbers,
"socket",
true,
)
require.NoError(t, err)

// create the task related parameters: RBN, quorumThresholdPercentages, taskIndex and taskResponse
curBlockNum, err := ethHttpClient.BlockNumber(context.Background())
require.NoError(t, err)
referenceBlockNumber := uint32(curBlockNum)
// need to advance chain by 1 block because of the check in signatureChecker where RBN must be < current block
// number
testutils.AdvanceChainByNBlocksExecInContainer(context.TODO(), 1, anvilC)
taskIndex := types.TaskIndex(0)
taskResponse := mockTaskResponse{123} // Initialize with appropriate data

// initialize the task
err = blsAggServ.InitializeNewTask(
taskIndex,
uint32(referenceBlockNumber),
quorumNumbers,
quorumThresholdPercentages,
tasksTimeToExpiry,
)
require.Nil(t, err)

// compute the signature and send it to the aggregation service
taskResponseDigest, err := hashFunction(taskResponse)
require.Nil(t, err)
blsSig := blsKeyPair.SignMessage(taskResponseDigest)
err = blsAggServ.ProcessNewSignature(context.Background(), taskIndex, taskResponse, blsSig, operatorId)
require.Nil(t, err)

// wait for the response from the aggregation service and check the signature
blsAggServiceResp := <-blsAggServ.aggregatedResponsesC
_, _, err = avsServiceManager.CheckSignatures(
&bind.CallOpts{},
taskResponseDigest,
quorumNumbers.UnderlyingType(),
uint32(referenceBlockNumber),
blsAggServiceResp.toNonSignerStakesAndSignature(),
)
require.NoError(t, err)
})
}

Expand Down