Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions aggsender/aggsender.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ func New(
l1InfoTreeSyncer,
l2Syncer,
rollupDataQuerier,
committeeQuerier,
)
if err != nil {
return nil, fmt.Errorf("error creating flow manager: %w", err)
Expand Down
13 changes: 11 additions & 2 deletions aggsender/aggsender_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ func TestAggSenderStart(t *testing.T) {
epochNotifierMock := mocks.NewEpochNotifier(t)
bridgeL2SyncerMock := mocks.NewL2BridgeSyncer(t)
rollupQuerierMock := mocks.NewRollupDataQuerier(t)
committeQuerierMock := mocks.NewMultisigQuerier(t)
ch := make(chan aggsendertypes.EpochEvent)
epochNotifierMock.EXPECT().Subscribe("aggsender").Return(ch)
epochNotifierMock.EXPECT().GetEpochStatus().Return(aggsendertypes.EpochStatus{}).Once()
Expand All @@ -84,6 +85,9 @@ func TestAggSenderStart(t *testing.T) {
aggLayerMock.EXPECT().GetLatestPendingCertificateHeader(mock.Anything, mock.Anything).Return(nil, nil)
aggLayerMock.EXPECT().GetLatestSettledCertificateHeader(mock.Anything, mock.Anything).Return(nil, nil)
rollupQuerierMock.EXPECT().GetRollupChainID().Return(uint64(1234), nil)
committee, err := aggsendertypes.NewMultisigCommittee([]*aggsendertypes.SignerInfo{aggsendertypes.NewSignerInfo("", common.Address{})}, 1)
require.NoError(t, err)
committeQuerierMock.EXPECT().GetMultisigCommittee(mock.Anything, mock.Anything).Return(committee, nil).Once()

ctx := t.Context()
aggSender, err := New(
Expand All @@ -104,7 +108,7 @@ func TestAggSenderStart(t *testing.T) {
nil, // l1 client
nil, // l2 client
rollupQuerierMock,
nil, // committee querier
committeQuerierMock,
)
require.NoError(t, err)
require.NotNil(t, aggSender)
Expand Down Expand Up @@ -570,8 +574,13 @@ func TestGetValidators(t *testing.T) {
func TestNewAggSender(t *testing.T) {
mockBridgeSyncer := mocks.NewL2BridgeSyncer(t)
mockRollupQuerier := mocks.NewRollupDataQuerier(t)
mockCommitteeQuerier := mocks.NewMultisigQuerier(t)
mockBridgeSyncer.EXPECT().OriginNetwork().Return(uint32(1)).Times(2)
mockRollupQuerier.EXPECT().GetRollupChainID().Return(uint64(1234), nil)
committee, err := aggsendertypes.NewMultisigCommittee([]*aggsendertypes.SignerInfo{aggsendertypes.NewSignerInfo("", common.Address{})}, 1)
require.NoError(t, err)
mockCommitteeQuerier.EXPECT().GetMultisigCommittee(mock.Anything, mock.Anything).Return(committee, nil).Once()

sut, err := New(context.TODO(), log.WithFields("module", "ut"), config.Config{
AggsenderPrivateKey: signertypes.SignerConfig{
Method: signertypes.MethodNone,
Expand All @@ -582,7 +591,7 @@ func TestNewAggSender(t *testing.T) {
nil, // l1 client
nil, // l2 client
mockRollupQuerier,
nil, // committee querier
mockCommitteeQuerier,
)
require.NoError(t, err)
require.NotNil(t, sut)
Expand Down
2 changes: 2 additions & 0 deletions aggsender/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ type Config struct {
ValidatorClient *grpc.ClientConfig `mapstructure:"ValidatorClient"`
// RetriesToBuildAndSendCertificate is the configuration for the retries to build and send a certificate
RetriesToBuildAndSendCertificate common.RetryPolicyGenericConfig `mapstructure:"RetriesToBuildAndSendCertificate"`
// RequireCommitteeMembershipCheck indicates whether to check if the signer is part of the committee
RequireCommitteeMembershipCheck bool `mapstructure:"RequireCommitteeMembershipCheck"`
}

func (c Config) CheckCertConfigBriefString() string {
Expand Down
38 changes: 35 additions & 3 deletions aggsender/flows/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package flows
import (
"context"
"fmt"
"math/big"
"time"

"github.com/agglayer/aggkit/aggsender/aggchainproofclient"
Expand Down Expand Up @@ -34,14 +35,18 @@ func NewFlow(
l1InfoTreeSyncer types.L1InfoTreeSyncer,
l2Syncer types.L2BridgeSyncer,
rollupDataQuerier types.RollupDataQuerier,
committeeQuerier types.MultisigQuerier,
) (types.AggsenderFlow, error) {
switch types.AggsenderMode(cfg.Mode) {
case types.PessimisticProofMode:
commonFlowComponents, err := CreateCommonFlowComponents(
ctx, logger, storage, l1Client, l1InfoTreeSyncer, l2Syncer, rollupDataQuerier, 0, false,
ctx, logger, storage, l1Client, l1InfoTreeSyncer, l2Syncer,
rollupDataQuerier, committeeQuerier,
0, false,
cfg.MaxCertSize, cfg.RollupCreationBlockL1,
cfg.DelayBetweenRetries.Duration, cfg.AggsenderPrivateKey,
true,
cfg.RequireCommitteeMembershipCheck,
)
if err != nil {
return nil, fmt.Errorf("failed to create common flow components: %w", err)
Expand Down Expand Up @@ -81,11 +86,13 @@ func NewFlow(
}

commonFlowComponents, err := CreateCommonFlowComponents(
ctx, logger, storage, l1Client, l1InfoTreeSyncer, l2Syncer, rollupDataQuerier,
ctx, logger, storage, l1Client, l1InfoTreeSyncer, l2Syncer,
rollupDataQuerier, committeeQuerier,
aggchainFEPQuerier.StartL2Block(), cfg.RequireNoFEPBlockGap,
cfg.MaxCertSize, cfg.RollupCreationBlockL1,
cfg.DelayBetweenRetries.Duration, cfg.AggsenderPrivateKey,
true,
cfg.RequireCommitteeMembershipCheck,
)
if err != nil {
return nil, fmt.Errorf("failed to create common flow components: %w", err)
Expand Down Expand Up @@ -139,20 +146,23 @@ func CreateCommonFlowComponents(
l1InfoTreeSyncer types.L1InfoTreeSyncer,
l2Syncer types.L2BridgeSyncer,
rollupDataQuerier types.RollupDataQuerier,
committeeQuerier types.MultisigQuerier,
startL2Block uint64,
requireNoFEPBlockGap bool,
maxCertSize uint,
rollupCreationBlockL1 uint64,
delayBetweenRetries time.Duration,
signerCfg signertypes.SignerConfig,
fullClaimsRequired bool,
requireCommitteeMembershipCheck bool,
) (*CommonFlowComponents, error) {
l2ChainID, err := rollupDataQuerier.GetRollupChainID()
if err != nil {
return nil, fmt.Errorf("error getting rollup chain id: %w", err)
}

signer, err := initializeSigner(ctx, signerCfg, logger, l2ChainID)
signer, err := initializeSigner(ctx, signerCfg, logger, l2ChainID,
committeeQuerier, requireCommitteeMembershipCheck)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -180,6 +190,8 @@ func initializeSigner(
signerCfg signertypes.SignerConfig,
logger *log.Logger,
l2ChainID uint64,
committeeQuerier types.MultisigQuerier,
requireCommitteeMembershipCheck bool,
) (signertypes.Signer, error) {
signer, err := signer.NewSigner(ctx, l2ChainID, signerCfg, aggkitcommon.AGGSENDER, logger)
if err != nil {
Expand All @@ -190,5 +202,25 @@ func initializeSigner(
return nil, fmt.Errorf("error signer.Initialize. Err: %w", err)
}

multisigCommittee, err := committeeQuerier.GetMultisigCommittee(ctx, big.NewInt(int64(aggkittypes.Latest)))
if err != nil {
if requireCommitteeMembershipCheck {
return nil, fmt.Errorf("error getting multisig committee: %w", err)
}

logger.Warnf("error getting multisig committee: %v", err)
return signer, nil
}

if !multisigCommittee.IsMember(signer.PublicAddress()) {
if requireCommitteeMembershipCheck {
return nil, fmt.Errorf("signer address %s is not part of the multisig committee: %s",
signer.PublicAddress(), multisigCommittee.String())
}

logger.Warnf("signer address %s is not part of the multisig committee: %s",
signer.PublicAddress(), multisigCommittee.String())
}

return signer, nil
}
86 changes: 86 additions & 0 deletions aggsender/flows/factory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package flows

import (
"context"
"errors"
"testing"
"time"

Expand All @@ -14,6 +15,7 @@ import (
"github.com/agglayer/aggkit/log"
typesmocks "github.com/agglayer/aggkit/types/mocks"
signertypes "github.com/agglayer/go_signer/signer/types"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
Expand All @@ -26,6 +28,7 @@ func TestNewFlow(t *testing.T) {
testCases := []struct {
name string
cfg config.Config
mockFn func(*mocks.MultisigQuerier)
expectedError string
}{
{
Expand All @@ -36,6 +39,82 @@ func TestNewFlow(t *testing.T) {
MaxCertSize: 100,
AggkitProverClient: aggkitgrpc.DefaultConfig(),
},
mockFn: func(mockCommittee *mocks.MultisigQuerier) {
committee, err := types.NewMultisigCommittee([]*types.SignerInfo{types.NewSignerInfo("", common.Address{})}, 1)
require.NoError(t, err)

mockCommittee.EXPECT().GetMultisigCommittee(mock.Anything, mock.Anything).Return(committee, nil).Maybe()
},
},
{
name: "error getting multisig committee when RequireCommitteeMembershipCheck is true",
cfg: config.Config{
Mode: string(types.PessimisticProofMode),
AggsenderPrivateKey: signertypes.SignerConfig{Method: signertypes.MethodNone},
MaxCertSize: 100,
AggkitProverClient: aggkitgrpc.DefaultConfig(),
RequireCommitteeMembershipCheck: true,
},
mockFn: func(mockCommittee *mocks.MultisigQuerier) {
mockCommittee.EXPECT().GetMultisigCommittee(mock.Anything, mock.Anything).Return(nil, errors.New("test error")).Maybe()
},
expectedError: "error getting multisig committee: test error",
},
{
name: "error getting multisig committee when RequireCommitteeMembershipCheck is false",
cfg: config.Config{
Mode: string(types.PessimisticProofMode),
AggsenderPrivateKey: signertypes.SignerConfig{Method: signertypes.MethodNone},
MaxCertSize: 100,
AggkitProverClient: aggkitgrpc.DefaultConfig(),
RequireCommitteeMembershipCheck: false,
},
mockFn: func(mockCommittee *mocks.MultisigQuerier) {
mockCommittee.EXPECT().GetMultisigCommittee(mock.Anything, mock.Anything).Return(nil, errors.New("test error")).Maybe()
},
},
{
name: "committee membership check disabled with PessimisticProofMode",
cfg: config.Config{
Mode: string(types.PessimisticProofMode),
AggsenderPrivateKey: signertypes.SignerConfig{Method: signertypes.MethodNone},
MaxCertSize: 100,
AggkitProverClient: aggkitgrpc.DefaultConfig(),
RequireCommitteeMembershipCheck: false,
},
mockFn: func(mockCommittee *mocks.MultisigQuerier) {
signers := []*types.SignerInfo{
types.NewSignerInfo("http://signer2", common.HexToAddress("0x2222222222222222222222222222222222222222")),
types.NewSignerInfo("http://signer3", common.HexToAddress("0x3333333333333333333333333333333333333333")),
types.NewSignerInfo("http://signer4", common.HexToAddress("0x4444444444444444444444444444444444")),
}

committee, err := types.NewMultisigCommittee(signers, 2)
require.NoError(t, err)
mockCommittee.EXPECT().GetMultisigCommittee(mock.Anything, mock.Anything).Return(committee, nil).Maybe()
},
},
{
name: "not member of committee",
cfg: config.Config{
Mode: string(types.PessimisticProofMode),
AggsenderPrivateKey: signertypes.SignerConfig{Method: signertypes.MethodNone},
MaxCertSize: 100,
AggkitProverClient: aggkitgrpc.DefaultConfig(),
RequireCommitteeMembershipCheck: true,
},
mockFn: func(mockCommittee *mocks.MultisigQuerier) {
signers := []*types.SignerInfo{
types.NewSignerInfo("http://signer2", common.HexToAddress("0x2222222222222222222222222222222222222222")),
types.NewSignerInfo("http://signer3", common.HexToAddress("0x3333333333333333333333333333333333333333")),
types.NewSignerInfo("http://signer4", common.HexToAddress("0x4444444444444444444444444444444444444444")),
}

committee, err := types.NewMultisigCommittee(signers, 2)
require.NoError(t, err)
mockCommittee.EXPECT().GetMultisigCommittee(mock.Anything, mock.Anything).Return(committee, nil).Maybe()
},
expectedError: "signer address 0x0000000000000000000000000000000000000000 is not part of the multisig committee",
},
{
name: "error creating signer in PessimisticProofMode",
Expand Down Expand Up @@ -84,6 +163,7 @@ func TestNewFlow(t *testing.T) {
mockL1InfoTreeSyncer := mocks.NewL1InfoTreeSyncer(t)
mockL2BridgeSyncer := mocks.NewL2BridgeSyncer(t)
mockRollupDataQuerier := mocks.NewRollupDataQuerier(t)
mockCommitteeQuerier := mocks.NewMultisigQuerier(t)

mockL2BridgeSyncer.EXPECT().OriginNetwork().Return(1).Maybe()
mockLogger := log.WithFields("test", "NewFlow")
Expand All @@ -93,6 +173,11 @@ func TestNewFlow(t *testing.T) {
mockL2Client.EXPECT().CallContract(mock.Anything, mock.Anything, mock.Anything).Return([]byte{1, 2, 3}, nil).Maybe()
mockL2Client.EXPECT().CodeAt(mock.Anything, mock.Anything, mock.Anything).Return([]byte{1, 2, 3}, nil).Maybe()
mockRollupDataQuerier.EXPECT().GetRollupChainID().Return(uint64(1234), nil).Maybe()

if tc.mockFn != nil {
tc.mockFn(mockCommitteeQuerier)
}

flow, err := NewFlow(
ctx,
tc.cfg,
Expand All @@ -103,6 +188,7 @@ func TestNewFlow(t *testing.T) {
mockL1InfoTreeSyncer,
mockL2BridgeSyncer,
mockRollupDataQuerier,
mockCommitteeQuerier,
)

if tc.expectedError != "" {
Expand Down
19 changes: 19 additions & 0 deletions aggsender/types/multisig_committee.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,22 @@ func (m *MultisigCommittee) Signers() []SignerInfo {
func (m *MultisigCommittee) Size() int {
return len(m.signers)
}

// IsMember checks if the given address is part of the committee
func (m *MultisigCommittee) IsMember(address common.Address) bool {
_, exists := m.signersSet[address]
return exists
}

// String returns a string representation of the committee
func (m *MultisigCommittee) String() string {
s := "[Committee: "
for i, signer := range m.signers {
s += signer.Address.Hex()
if i < len(m.signers)-1 {
s += ", "
}
}
s += fmt.Sprintf(" Threshold: %d]", m.threshold)
return s
}
61 changes: 61 additions & 0 deletions aggsender/types/multisig_committee_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,64 @@ func TestMultisigCommittee_Signers(t *testing.T) {
cpySigners[0].Address = common.HexToAddress("0x4")
require.NotEqual(t, signers, cpySigners)
}

func TestMultisigCommittee_IsMember(t *testing.T) {
s1 := NewSignerInfo("http://localhost:7001", common.HexToAddress("0x1"))
s2 := NewSignerInfo("http://localhost:7002", common.HexToAddress("0x2"))

mc, err := NewMultisigCommittee([]*SignerInfo{s1}, 1)
require.NoError(t, err)

// existing member
require.True(t, mc.IsMember(s1.Address))

// non-member
require.False(t, mc.IsMember(s2.Address))

// add new signer and verify membership
require.NoError(t, mc.AddSigner(s2))
require.True(t, mc.IsMember(s2.Address))

// zero address should not be a member
require.False(t, mc.IsMember(common.Address{}))
}

func TestMultisigCommittee_String(t *testing.T) {
s1 := NewSignerInfo("http://localhost:7001", common.HexToAddress("0x1"))
s2 := NewSignerInfo("http://localhost:7002", common.HexToAddress("0x2"))
s3 := NewSignerInfo("http://localhost:7003", common.HexToAddress("0x3"))

tests := []struct {
name string
members []*SignerInfo
threshold uint32
expected string
}{
{
name: "single signer",
members: []*SignerInfo{s1},
threshold: 1,
expected: "[Committee: " + s1.Address.Hex() + " Threshold: 1]",
},
{
name: "two signers",
members: []*SignerInfo{s1, s2},
threshold: 2,
expected: "[Committee: " + s1.Address.Hex() + ", " + s2.Address.Hex() + " Threshold: 2]",
},
{
name: "three signers, threshold less than size",
members: []*SignerInfo{s1, s2, s3},
threshold: 2,
expected: "[Committee: " + s1.Address.Hex() + ", " + s2.Address.Hex() + ", " + s3.Address.Hex() + " Threshold: 2]",
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
mc, err := NewMultisigCommittee(tc.members, tc.threshold)
require.NoError(t, err)
require.Equal(t, tc.expected, mc.String())
})
}
}
Loading
Loading