diff --git a/agglayer/agglayer_client_cache.go b/agglayer/agglayer_client_cache.go index c0fcd5296..ce3515207 100644 --- a/agglayer/agglayer_client_cache.go +++ b/agglayer/agglayer_client_cache.go @@ -104,9 +104,10 @@ func (c *AgglayerClientCache) GetCertificateHeader( } // SendCertificate sends a certificate to the Agglayer client. (no cache) -func (c *AgglayerClientCache) SendCertificate(ctx context.Context, - certificate *agglayertypes.Certificate, validatorSignature []byte) (common.Hash, error) { - return c.agglayerClient.SendCertificate(ctx, certificate, validatorSignature) +func (c *AgglayerClientCache) SendCertificate( + ctx context.Context, + certificate *agglayertypes.Certificate) (common.Hash, error) { + return c.agglayerClient.SendCertificate(ctx, certificate) } // GetEpochConfiguration retrieves the current epoch configuration from the Agglayer client. (no cache) @@ -126,8 +127,8 @@ func (c *AgglayerClientCache) GetLatestPendingCertificateHeader(ctx context.Cont return c.agglayerClient.GetLatestPendingCertificateHeader(ctx, networkID) } -// GetNetworkStatus retrieves the network status for a given network ID from the Agglayer client. (no cache) -func (c *AgglayerClientCache) GetNetworkStatus( - ctx context.Context, networkID uint32) (agglayertypes.NetworkStatus, error) { - return c.agglayerClient.GetNetworkStatus(ctx, networkID) +// GetNetworkState retrieves the network state for a given network ID from the Agglayer client. (no cache) +func (c *AgglayerClientCache) GetNetworkState( + ctx context.Context, networkID uint32) (agglayertypes.NetworkState, error) { + return c.agglayerClient.GetNetworkState(ctx, networkID) } diff --git a/agglayer/client.go b/agglayer/client.go index af1b33d25..266418971 100644 --- a/agglayer/client.go +++ b/agglayer/client.go @@ -28,9 +28,9 @@ type AggLayerClientCertificateIDQuerier interface { // AgglayerClientInterface is the interface that defines the methods that the AggLayerClient will implement type AgglayerClientInterface interface { - SendCertificate(ctx context.Context, certificate *types.Certificate, validatorSignature []byte) (common.Hash, error) + SendCertificate(ctx context.Context, certificate *types.Certificate) (common.Hash, error) GetCertificateHeader(ctx context.Context, certificateHash common.Hash) (*types.CertificateHeader, error) - GetNetworkStatus(ctx context.Context, networkID uint32) (types.NetworkStatus, error) + GetNetworkState(ctx context.Context, networkID uint32) (types.NetworkState, error) AggLayerClientGetEpochConfiguration AggLayerClientRecoveryQuerier AggLayerClientCertificateIDQuerier diff --git a/agglayer/grpc/agglayer_grpc_client.go b/agglayer/grpc/agglayer_grpc_client.go index 601986c0c..653ce85c7 100644 --- a/agglayer/grpc/agglayer_grpc_client.go +++ b/agglayer/grpc/agglayer_grpc_client.go @@ -64,8 +64,7 @@ func (a *AgglayerGRPCClient) GetEpochConfiguration(ctx context.Context) (*types. // SendCertificate sends a certificate to the AggLayer // It returns the certificate ID func (a *AgglayerGRPCClient) SendCertificate(ctx context.Context, - certificate *types.Certificate, - validatorSignature []byte) (common.Hash, error) { + certificate *types.Certificate) (common.Hash, error) { protoCert, err := ConvertCertToProtoCertificate(certificate) if err != nil { return common.Hash{}, err @@ -147,23 +146,23 @@ func (a *AgglayerGRPCClient) GetCertificateHeader( return convertProtoCertificateHeader(response.CertificateHeader), nil } -func (a *AgglayerGRPCClient) GetNetworkStatus(ctx context.Context, networkID uint32) (types.NetworkStatus, error) { - status, err := a.networkStateService.GetNetworkStatus(ctx, &v1.GetNetworkStatusRequest{ +func (a *AgglayerGRPCClient) GetNetworkState(ctx context.Context, networkID uint32) (types.NetworkState, error) { + status, err := a.networkStateService.GetNetworkState(ctx, &v1.GetNetworkStateRequest{ NetworkId: networkID, }) if err != nil { - return types.NetworkStatus{}, fmt.Errorf("failed to get network status: %w", + return types.NetworkState{}, fmt.Errorf("failed to get network status: %w", aggkitgrpc.RepackGRPCErrorWithDetails(err)) } - if !status.HasNetworkStatus() { - return types.NetworkStatus{}, errors.New("network status is not available") + if !status.HasNetworkState() { + return types.NetworkState{}, errors.New("network state is not available") } - return convertProtoNetworkStatus(status.NetworkStatus) + return convertProtoNetworkState(status.NetworkState) } -func convertProtoNetworkStatus(status *v1nodetypes.NetworkStatus) (types.NetworkStatus, error) { +func convertProtoNetworkState(status *v1nodetypes.NetworkState) (types.NetworkState, error) { var settledCertID *common.Hash if status.SettledCertificateId != nil { certID := common.BytesToHash(status.SettledCertificateId.Value.Value) @@ -190,18 +189,14 @@ func convertProtoNetworkStatus(status *v1nodetypes.NetworkStatus) (types.Network } } - latestPendingStatus := types.Pending - if status.LatestPendingStatus != "" { - if err := latestPendingStatus.UnmarshalJSON([]byte(status.LatestPendingStatus)); err != nil { - return types.NetworkStatus{}, - fmt.Errorf("failed to unmarshal latest pending status %q from NetworkStatus struct: %w", - status.LatestPendingStatus, err) - } + var latestPendingError string + if status.LatestPendingError != nil { + latestPendingError = string(status.LatestPendingError.Message) } - return types.NetworkStatus{ - Status: status.NetworkStatus, - NetworkType: status.NetworkType, + return types.NetworkState{ + Status: status.NetworkStatus.String(), + NetworkType: status.NetworkType.String(), NetworkID: status.NetworkId, SettledCertificateID: settledCertID, SettledHeight: status.SettledHeight, @@ -210,12 +205,25 @@ func convertProtoNetworkStatus(status *v1nodetypes.NetworkStatus) (types.Network SettledLETLeafCount: status.SettledLetLeafCount, SettledImportedBridgeExit: settledClaim, LatestPendingHeight: status.LatestPendingHeight, - LatestPendingStatus: latestPendingStatus, - LatestPendingError: status.LatestPendingError, + LatestPendingStatus: convertProtoCertStatus(status.LatestPendingStatus), + LatestPendingError: latestPendingError, LatestEpochWithSettlement: status.LatestEpochWithSettlement, }, nil } +func convertProtoCertStatus(status *v1nodetypes.CertificateStatus) *types.CertificateStatus { + if status == nil || status == v1nodetypes.CertificateStatus_CERTIFICATE_STATUS_UNSPECIFIED.Enum() { + return nil + } + + // we do not have unspecified type of cert status which is a grpc proto standard to be on value 0, + // so if it is not unspecified, just subtract 1 from the status value to get our types + statusInt := int(*status) - 1 + typesStatus := types.CertificateStatus(statusInt) + + return &typesStatus +} + // ConvertCertToProtoCertificate converts a types.Certificate to a grpc v1nodetypes.Certificate func ConvertCertToProtoCertificate( certificate *types.Certificate, @@ -274,22 +282,7 @@ func convertAggchainData(aggchainData types.AggchainData) (*v1types.AggchainData case *types.AggchainDataProof: return &v1types.AggchainData{ Data: &v1types.AggchainData_Generic{ - Generic: &v1types.AggchainProof{ - Proof: &v1types.AggchainProof_Sp1Stark{ - Sp1Stark: &v1types.SP1StarkProof{ - Version: ad.Version, - Proof: ad.Proof, - Vkey: ad.Vkey, - }, - }, - AggchainParams: &v1types.FixedBytes32{ - Value: ad.AggchainParams.Bytes(), - }, - Context: ad.Context, - Signature: &v1types.FixedBytes65{ - Value: ad.Signature, - }, - }, + Generic: convertAggchainDataProofToProto(ad), }, }, nil case *types.AggchainDataSignature: @@ -300,11 +293,76 @@ func convertAggchainData(aggchainData types.AggchainData) (*v1types.AggchainData }, }, }, nil + case *types.AggchainDataMultisigWithProof: + return &v1types.AggchainData{ + Data: &v1types.AggchainData_MultisigAndAggchainProof{ + MultisigAndAggchainProof: &v1types.AggchainProofWithMultisig{ + Multisig: convertMultisigToProtoMultisig(ad.Multisig), + AggchainProof: convertAggchainDataProofToProto(ad.AggchainProof), + }, + }, + }, nil + case *types.AggchainDataMultisig: + return &v1types.AggchainData{ + Data: &v1types.AggchainData_Multisig{ + Multisig: convertMultisigToProtoMultisig(ad.Multisig), + }, + }, nil default: return nil, errUnknownAggchainData } } +// convertAggchainDataProofToProto converts an aggchain data proof to proto aggchain proof +func convertAggchainDataProofToProto(proof *types.AggchainDataProof) *v1types.AggchainProof { + if proof == nil { + return nil + } + + return &v1types.AggchainProof{ + Proof: &v1types.AggchainProof_Sp1Stark{ + Sp1Stark: &v1types.SP1StarkProof{ + Version: proof.Version, + Proof: proof.Proof, + Vkey: proof.Vkey, + }, + }, + AggchainParams: &v1types.FixedBytes32{ + Value: proof.AggchainParams.Bytes(), + }, + Context: proof.Context, + Signature: &v1types.FixedBytes65{ + Value: proof.Signature, + }, + } +} + +// convertMultisigToProtoMultisig converts a multisig to a proto multisig +func convertMultisigToProtoMultisig(multisig *types.Multisig) *v1types.Multisig { + if multisig == nil { + return nil + } + + protoEntries := make([]*v1types.ECDSAMultisig_ECDSAMultisigEntry, 0, len(multisig.Signatures)) + + for _, entry := range multisig.Signatures { + protoEntries = append(protoEntries, &v1types.ECDSAMultisig_ECDSAMultisigEntry{ + Signature: &v1types.FixedBytes65{ + Value: entry.Signature, + }, + Index: entry.Index, + }) + } + + return &v1types.Multisig{ + Data: &v1types.Multisig_Ecdsa{ + Ecdsa: &v1types.ECDSAMultisig{ + Signatures: protoEntries, + }, + }, + } +} + // convertProtoCertificateHeader converts a proto certificate header to a types certificate header func convertProtoCertificateHeader(response *v1nodetypes.CertificateHeader) *types.CertificateHeader { if response == nil { diff --git a/agglayer/grpc/agglayer_grpc_client_test.go b/agglayer/grpc/agglayer_grpc_client_test.go index db30b8bc2..2cc707d63 100644 --- a/agglayer/grpc/agglayer_grpc_client_test.go +++ b/agglayer/grpc/agglayer_grpc_client_test.go @@ -15,6 +15,7 @@ import ( v1types "buf.build/gen/go/agglayer/interop/protocolbuffers/go/agglayer/interop/types/v1" "github.com/agglayer/aggkit/agglayer/mocks" "github.com/agglayer/aggkit/agglayer/types" + aggkitcommon "github.com/agglayer/aggkit/common" configtypes "github.com/agglayer/aggkit/config/types" aggkitgrpc "github.com/agglayer/aggkit/grpc" "github.com/ethereum/go-ethereum/common" @@ -40,7 +41,7 @@ func TestAgglayerGRPCCLientExploratory(t *testing.T) { NetworkID: 1, Height: 0, AggchainData: &types.AggchainDataSignature{ - Signature: make([]byte, 65), + Signature: make([]byte, aggkitcommon.SignatureSize), }, } // 100000 iteration produces a transaction of 12800188 bytes @@ -58,7 +59,7 @@ func TestAgglayerGRPCCLientExploratory(t *testing.T) { }) } - id, err := client.SendCertificate(t.Context(), certificate, nil) + id, err := client.SendCertificate(t.Context(), certificate) require.NoError(t, err) t.Log("Certificate ID:", id.Hex()) } @@ -335,95 +336,122 @@ func TestSendCertificate(t *testing.T) { ctx := context.Background() - t.Run("returns error when AggchainData not defined", func(t *testing.T) { - t.Parallel() - - client := &AgglayerGRPCClient{ - cfg: aggkitgrpc.DefaultConfig(), - } - - certificate := &types.Certificate{} - - _, err := client.SendCertificate(ctx, certificate, nil) - require.ErrorIs(t, err, errUndefinedAggchainData) - }) - - t.Run("returns error from submission service", func(t *testing.T) { - t.Parallel() - - submissionServiceMock := mocks.NewCertificateSubmissionServiceClient(t) - client := &AgglayerGRPCClient{ - submissionService: submissionServiceMock, - cfg: aggkitgrpc.DefaultConfig(), - } - - certificate := &types.Certificate{ - AggchainData: &types.AggchainDataSignature{ - Signature: []byte{0x01}, + testCases := []struct { + name string + certificate *types.Certificate + mockFn func(*mocks.CertificateSubmissionServiceClient) + expectedCertID common.Hash + expectedError string + }{ + { + name: "returns error when AggchainData not defined", + certificate: &types.Certificate{}, + expectedError: "undefined aggchain data", + }, + { + name: "returns error from submission service", + certificate: &types.Certificate{ + AggchainData: &types.AggchainDataSignature{ + Signature: []byte{0x01}, + }, }, - } - - submissionServiceMock.EXPECT().SubmitCertificate(mock.Anything, mock.Anything).Return(nil, errors.New("test error")) - - _, err := client.SendCertificate(ctx, certificate, nil) - require.ErrorContains(t, err, "test error") - }) - - t.Run("has validator signature", func(t *testing.T) { - t.Parallel() - - signature := crypto.Keccak256Hash([]byte("test signature")) - submissionServiceMock := mocks.NewCertificateSubmissionServiceClient(t) - client := &AgglayerGRPCClient{ - submissionService: submissionServiceMock, - cfg: aggkitgrpc.DefaultConfig(), - } - - certificate := &types.Certificate{ - Height: 100, - NetworkID: 1, - AggchainData: &types.AggchainDataSignature{ - Signature: []byte{0x01}, // regular aggsender signature + mockFn: func(submissionServiceMock *mocks.CertificateSubmissionServiceClient) { + submissionServiceMock.EXPECT().SubmitCertificate(mock.Anything, mock.Anything).Return(nil, errors.New("test error")) }, - } - - submissionServiceMock.EXPECT().SubmitCertificate(mock.Anything, mock.Anything).Return(&node.SubmitCertificateResponse{ - CertificateId: &v1nodetypes.CertificateId{ - Value: &v1types.FixedBytes32{ - Value: common.HexToHash("0x010203").Bytes(), + expectedError: "test error", + }, + { + name: "returns certificate ID on success", + certificate: exampleTestAgglayerCert, + mockFn: func(submissionServiceMock *mocks.CertificateSubmissionServiceClient) { + expectedResponse := &node.SubmitCertificateResponse{ + CertificateId: &v1nodetypes.CertificateId{ + Value: &v1types.FixedBytes32{ + Value: common.HexToHash("0x010203").Bytes(), + }, + }, + } + submissionServiceMock.EXPECT().SubmitCertificate(mock.Anything, mock.Anything).Return(expectedResponse, nil) + }, + expectedCertID: common.HexToHash("0x010203"), + }, + { + name: "submit certificate with multisig", + certificate: &types.Certificate{ + AggchainData: &types.AggchainDataMultisig{ + Multisig: &types.Multisig{ + Signatures: []types.ECDSAMultisigEntry{ + {Signature: []byte{0x01}, Index: 0}, + {Signature: []byte{0x02}, Index: 1}, + }, + }, }, }, - }, nil) - - _, err := client.SendCertificate(ctx, certificate, signature.Bytes()) - require.NoError(t, err) - }) - - t.Run("returns certificate ID on success", func(t *testing.T) { - t.Parallel() - - submissionServiceMock := mocks.NewCertificateSubmissionServiceClient(t) - client := &AgglayerGRPCClient{ - submissionService: submissionServiceMock, - cfg: aggkitgrpc.DefaultConfig(), - } - - certificate := exampleTestAgglayerCert - - expectedResponse := &node.SubmitCertificateResponse{ - CertificateId: &v1nodetypes.CertificateId{ - Value: &v1types.FixedBytes32{ - Value: common.HexToHash("0x010203").Bytes(), + mockFn: func(submissionServiceMock *mocks.CertificateSubmissionServiceClient) { + expectedResponse := &node.SubmitCertificateResponse{ + CertificateId: &v1nodetypes.CertificateId{ + Value: &v1types.FixedBytes32{ + Value: common.HexToHash("0x010203").Bytes(), + }, + }, + } + submissionServiceMock.EXPECT().SubmitCertificate(mock.Anything, mock.Anything).Return(expectedResponse, nil) + }, + expectedCertID: common.HexToHash("0x010203"), + }, + { + name: "certificate with proof and multisig", + certificate: &types.Certificate{ + AggchainData: &types.AggchainDataMultisigWithProof{ + Multisig: &types.Multisig{ + Signatures: []types.ECDSAMultisigEntry{ + {Signature: []byte{0x01}, Index: 0}, + {Signature: []byte{0x02}, Index: 1}, + }, + }, + AggchainProof: &types.AggchainDataProof{ + Proof: []byte{0x01}, + }, }, }, - } + mockFn: func(submissionServiceMock *mocks.CertificateSubmissionServiceClient) { + expectedResponse := &node.SubmitCertificateResponse{ + CertificateId: &v1nodetypes.CertificateId{ + Value: &v1types.FixedBytes32{ + Value: common.HexToHash("0x010203").Bytes(), + }, + }, + } + submissionServiceMock.EXPECT().SubmitCertificate(mock.Anything, mock.Anything).Return(expectedResponse, nil) + }, + expectedCertID: common.HexToHash("0x010203"), + }, + } - submissionServiceMock.EXPECT().SubmitCertificate(mock.Anything, mock.Anything).Return(expectedResponse, nil) + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() - resp, err := client.SendCertificate(ctx, certificate, nil) - require.NoError(t, err) - require.Equal(t, expectedResponse.CertificateId.Value.Value, resp.Bytes()) - }) + submissionServiceMock := mocks.NewCertificateSubmissionServiceClient(t) + client := &AgglayerGRPCClient{ + submissionService: submissionServiceMock, + cfg: aggkitgrpc.DefaultConfig(), + } + + if tc.mockFn != nil { + tc.mockFn(submissionServiceMock) + } + + resp, err := client.SendCertificate(ctx, tc.certificate) + if tc.expectedError != "" { + require.ErrorContains(t, err, tc.expectedError) + } else { + require.NoError(t, err) + require.Equal(t, tc.expectedCertID.Bytes(), resp.Bytes()) + } + }) + } } func TestLeafTypeToProto(t *testing.T) { @@ -565,13 +593,13 @@ func TestGetNetworkStatus(t *testing.T) { cfg: aggkitgrpc.DefaultConfig(), } - networkStateServiceMock.EXPECT().GetNetworkStatus(mock.Anything, mock.Anything).Return(nil, errors.New("test error")) + networkStateServiceMock.EXPECT().GetNetworkState(mock.Anything, mock.Anything).Return(nil, errors.New("test error")) - _, err := client.GetNetworkStatus(ctx, networkID) + _, err := client.GetNetworkState(ctx, networkID) require.ErrorContains(t, err, "test error") }) - t.Run("returns error when network status not available", func(t *testing.T) { + t.Run("returns error when network state not available", func(t *testing.T) { t.Parallel() networkStateServiceMock := mocks.NewNodeStateServiceClient(t) @@ -581,10 +609,10 @@ func TestGetNetworkStatus(t *testing.T) { } // empty response -> HasNetworkStatus() == false - networkStateServiceMock.EXPECT().GetNetworkStatus(mock.Anything, mock.Anything).Return(&node.GetNetworkStatusResponse{}, nil) + networkStateServiceMock.EXPECT().GetNetworkState(mock.Anything, mock.Anything).Return(&node.GetNetworkStateResponse{}, nil) - _, err := client.GetNetworkStatus(ctx, networkID) - require.ErrorContains(t, err, "network status is not available") + _, err := client.GetNetworkState(ctx, networkID) + require.ErrorContains(t, err, "network state is not available") }) t.Run("returns response on success", func(t *testing.T) { @@ -597,41 +625,45 @@ func TestGetNetworkStatus(t *testing.T) { } settledClaimIdx := big.NewInt(84) - - expectedProto := &v1nodetypes.NetworkStatus{ - NetworkStatus: "ok", - NetworkType: "test", + settledHeight := uint64(55) + settledLeafCount := uint64(100) + latestPendingHeight := uint64(99) + latestEpochWithSettlement := uint64(3) + + expectedProto := &v1nodetypes.NetworkState{ + NetworkStatus: v1nodetypes.NetworkStatus_NETWORK_STATUS_ACTIVE, + NetworkType: v1nodetypes.NetworkType_NETWORK_TYPE_ECDSA, NetworkId: networkID, SettledCertificateId: &v1nodetypes.CertificateId{Value: &v1types.FixedBytes32{ Value: common.HexToHash("0x010203").Bytes(), }}, - SettledHeight: 55, + SettledHeight: &settledHeight, SettledPpRoot: &v1types.FixedBytes32{Value: common.HexToHash("0x010204").Bytes()}, SettledLer: &v1types.FixedBytes32{Value: common.HexToHash("0x010205").Bytes()}, - SettledLetLeafCount: 100, + SettledLetLeafCount: &settledLeafCount, SettledClaim: &v1nodetypes.SettledClaim{ GlobalIndex: &v1types.FixedBytes32{Value: common.BigToHash(settledClaimIdx).Bytes()}, BridgeExitHash: &v1types.FixedBytes32{Value: common.HexToHash("0x010206").Bytes()}, }, - LatestPendingHeight: 99, - LatestPendingStatus: "Settled", - LatestPendingError: "some error", - LatestEpochWithSettlement: 3, + LatestPendingHeight: &latestPendingHeight, + LatestPendingStatus: v1nodetypes.CertificateStatus_CERTIFICATE_STATUS_SETTLED.Enum(), + LatestPendingError: &v1nodetypes.CertificateStatusError{Message: []byte("some error")}, + LatestEpochWithSettlement: &latestEpochWithSettlement, } - networkStateServiceMock.EXPECT().GetNetworkStatus(mock.Anything, mock.Anything).Return(&node.GetNetworkStatusResponse{ - NetworkStatus: expectedProto, + networkStateServiceMock.EXPECT().GetNetworkState(mock.Anything, mock.Anything).Return(&node.GetNetworkStateResponse{ + NetworkState: expectedProto, }, nil) - resp, err := client.GetNetworkStatus(ctx, networkID) + resp, err := client.GetNetworkState(ctx, networkID) require.NoError(t, err) - require.Equal(t, expectedProto.NetworkStatus, resp.Status) - require.Equal(t, expectedProto.NetworkType, resp.NetworkType) + require.Equal(t, expectedProto.NetworkStatus.String(), resp.Status) + require.Equal(t, expectedProto.NetworkType.String(), resp.NetworkType) require.Equal(t, expectedProto.NetworkId, resp.NetworkID) require.Equal(t, expectedProto.SettledHeight, resp.SettledHeight) require.Equal(t, expectedProto.LatestPendingHeight, resp.LatestPendingHeight) - require.Equal(t, expectedProto.LatestPendingError, resp.LatestPendingError) + require.Equal(t, string(expectedProto.LatestPendingError.Message), resp.LatestPendingError) require.Equal(t, expectedProto.LatestEpochWithSettlement, resp.LatestEpochWithSettlement) require.Equal(t, expectedProto.SettledLetLeafCount, resp.SettledLETLeafCount) @@ -648,7 +680,8 @@ func TestGetNetworkStatus(t *testing.T) { require.Equal(t, expectedClaim, resp.SettledImportedBridgeExit) // LatestPendingStatus defaults to types.Pending when proto field is empty - require.Equal(t, types.Settled, resp.LatestPendingStatus) + expected := convertProtoCertStatus(expectedProto.LatestPendingStatus) + require.Equal(t, expected, resp.LatestPendingStatus) }) } diff --git a/agglayer/grpc/conversion_to_agglayer.go b/agglayer/grpc/conversion_to_agglayer.go index bc112f52e..a8eca2275 100644 --- a/agglayer/grpc/conversion_to_agglayer.go +++ b/agglayer/grpc/conversion_to_agglayer.go @@ -309,32 +309,96 @@ func grpcAggchainDataToAgglayer( Signature: ad.Signature.Value, }, nil case *v1types.AggchainData_Generic: - sp1Proof, ok := ad.Generic.Proof.(*v1types.AggchainProof_Sp1Stark) - if !ok { - return nil, fmt.Errorf("grpcAggchainDataToAgglayer. expected Sp1Stark proof, got: %T", ad.Generic.Proof) + return grpcAggchainProofToAgglayer(ad.Generic) + case *v1types.AggchainData_Multisig: + multisig, err := grpcMultisigToAgglayer(ad.Multisig) + if err != nil { + return nil, fmt.Errorf("grpcAggchainDataToAgglayer. failed to convert multisig: %w", err) } - - if sp1Proof.Sp1Stark == nil { - return nil, fmt.Errorf("grpcAggchainDataToAgglayer. aggchain data has nil Sp1Stark proof. %w", ErrNilCertificate) + return &agglayertypes.AggchainDataMultisig{ + Multisig: multisig, + }, nil + case *v1types.AggchainData_MultisigAndAggchainProof: + if ad.MultisigAndAggchainProof == nil { + return nil, fmt.Errorf("grpcAggchainDataToAgglayer. aggchain data has nil MultisigAndAggchainProof. %w", + ErrNilCertificate) } - if ad.Generic.AggchainParams == nil { - return nil, fmt.Errorf("grpcAggchainDataToAgglayer. aggchain data has nil AggchainParams. %w", ErrNilCertificate) + aggchainProof, err := grpcAggchainProofToAgglayer(ad.MultisigAndAggchainProof.AggchainProof) + if err != nil { + return nil, fmt.Errorf("grpcAggchainDataToAgglayer. failed to convert aggchain proof: %w", err) } - if ad.Generic.Signature == nil { - return nil, fmt.Errorf("grpcAggchainDataToAgglayer. aggchain data has nil Signature. %w", ErrNilCertificate) + multisig, err := grpcMultisigToAgglayer(ad.MultisigAndAggchainProof.Multisig) + if err != nil { + return nil, fmt.Errorf("grpcAggchainDataToAgglayer. failed to convert multisig: %w", err) } - return &agglayertypes.AggchainDataProof{ - Proof: sp1Proof.Sp1Stark.Proof, - Version: sp1Proof.Sp1Stark.Version, - Vkey: sp1Proof.Sp1Stark.Vkey, - AggchainParams: common.BytesToHash(ad.Generic.AggchainParams.Value), - Context: ad.Generic.Context, - Signature: ad.Generic.Signature.Value, + return &agglayertypes.AggchainDataMultisigWithProof{ + AggchainProof: aggchainProof, + Multisig: multisig, }, nil default: return nil, fmt.Errorf("grpcAggchainDataToAgglayer. unknown aggchain data type: %T", aggchainData) } } + +func grpcMultisigToAgglayer(multisig *v1types.Multisig) (*agglayertypes.Multisig, error) { + if multisig == nil { + return nil, fmt.Errorf("grpcMultisigToAgglayer. multisig is nil. %w", ErrNilCertificate) + } + + multisigEcdsa, ok := multisig.Data.(*v1types.Multisig_Ecdsa) + if !ok { + return nil, fmt.Errorf("grpcMultisigToAgglayer. expected Ecdsa multisig, got: %T", multisig.Data) + } + + if multisigEcdsa.Ecdsa == nil { + return nil, fmt.Errorf("grpcMultisigToAgglayer. multisig Ecdsa is nil. %w", ErrNilCertificate) + } + + multisigAgglayer := &agglayertypes.Multisig{ + Signatures: make([]agglayertypes.ECDSAMultisigEntry, len(multisigEcdsa.Ecdsa.Signatures)), + } + + for i, sig := range multisigEcdsa.Ecdsa.Signatures { + multisigAgglayer.Signatures[i] = agglayertypes.ECDSAMultisigEntry{ + Signature: sig.Signature.Value, + Index: sig.Index, + } + } + + return multisigAgglayer, nil +} + +func grpcAggchainProofToAgglayer(aggchainProof *v1types.AggchainProof) (*agglayertypes.AggchainDataProof, error) { + if aggchainProof == nil { + return nil, fmt.Errorf("grpcAggchainProofToAgglayer. aggchain proof is nil. %w", ErrNilCertificate) + } + + sp1Proof, ok := aggchainProof.Proof.(*v1types.AggchainProof_Sp1Stark) + if !ok { + return nil, fmt.Errorf("grpcAggchainDataToAgglayer. expected Sp1Stark proof, got: %T", aggchainProof.Proof) + } + + if sp1Proof.Sp1Stark == nil { + return nil, fmt.Errorf("grpcAggchainDataToAgglayer. aggchain data has nil Sp1Stark proof. %w", ErrNilCertificate) + } + + if aggchainProof.AggchainParams == nil { + return nil, fmt.Errorf("grpcAggchainDataToAgglayer. aggchain data has nil AggchainParams. %w", ErrNilCertificate) + } + + if aggchainProof.Signature == nil { + return nil, fmt.Errorf("grpcAggchainDataToAgglayer. aggchain data has nil Signature. %w", ErrNilCertificate) + } + + return &agglayertypes.AggchainDataProof{ + Proof: sp1Proof.Sp1Stark.Proof, + Version: sp1Proof.Sp1Stark.Version, + Vkey: sp1Proof.Sp1Stark.Vkey, + AggchainParams: common.BytesToHash(aggchainProof.AggchainParams.Value), + Context: aggchainProof.Context, + Signature: aggchainProof.Signature.Value, + }, nil +} diff --git a/agglayer/grpc/conversion_to_agglayer_test.go b/agglayer/grpc/conversion_to_agglayer_test.go index 210fe15f1..74f9f9208 100644 --- a/agglayer/grpc/conversion_to_agglayer_test.go +++ b/agglayer/grpc/conversion_to_agglayer_test.go @@ -297,6 +297,116 @@ func TestConvertProtoCertToAgglayer(t *testing.T) { require.Nil(t, result) require.ErrorContains(t, err, "aggchain data has nil Signature") }) + + t.Run("aggchain data multisig", func(t *testing.T) { + // multisig nil + protoCert, err := ConvertCertToProtoCertificate(exampleTestAgglayerCert) + require.NoError(t, err) + protoCert.AggchainData = &v1types.AggchainData{ + Data: &v1types.AggchainData_Multisig{}, + } + + result, err := ConvertProtoCertToAgglayer(protoCert) + require.Nil(t, result) + require.ErrorContains(t, err, "multisig is nil") + + // multisig not ecdsa + protoCert.AggchainData = &v1types.AggchainData{ + Data: &v1types.AggchainData_Multisig{ + Multisig: &v1types.Multisig{}, + }, + } + + result, err = ConvertProtoCertToAgglayer(protoCert) + require.Nil(t, result) + require.ErrorContains(t, err, "expected Ecdsa multisig") + + // mutlisig ecdsa nil + protoCert.AggchainData = &v1types.AggchainData{ + Data: &v1types.AggchainData_Multisig{ + Multisig: &v1types.Multisig{ + Data: &v1types.Multisig_Ecdsa{}, + }, + }, + } + + result, err = ConvertProtoCertToAgglayer(protoCert) + require.Nil(t, result) + require.ErrorContains(t, err, "multisig Ecdsa is nil") + + // ok conversion + protoCert.AggchainData = &v1types.AggchainData{ + Data: &v1types.AggchainData_Multisig{ + Multisig: &v1types.Multisig{ + Data: &v1types.Multisig_Ecdsa{ + Ecdsa: &v1types.ECDSAMultisig{ + Signatures: []*v1types.ECDSAMultisig_ECDSAMultisigEntry{ + {Index: 0, Signature: &v1types.FixedBytes65{Value: make([]byte, aggkitcommon.SignatureSize)}}, + {Index: 1, Signature: &v1types.FixedBytes65{Value: make([]byte, aggkitcommon.SignatureSize)}}, + }, + }, + }, + }, + }, + } + + result, err = ConvertProtoCertToAgglayer(protoCert) + require.NoError(t, err) + require.NotNil(t, result.AggchainData) + + multisig, ok := result.AggchainData.(*agglayertypes.AggchainDataMultisig) + require.True(t, ok) + require.NotNil(t, multisig) + require.Len(t, multisig.Multisig.Signatures, 2) + }) + + t.Run("aggchain data multisig and aggchain proof", func(t *testing.T) { + // multisig and aggchain proof nil + protoCert, err := ConvertCertToProtoCertificate(exampleTestAgglayerCert) + require.NoError(t, err) + + protoCert.AggchainData = &v1types.AggchainData{ + Data: &v1types.AggchainData_MultisigAndAggchainProof{}, + } + + result, err := ConvertProtoCertToAgglayer(protoCert) + require.Nil(t, result) + require.ErrorContains(t, err, "aggchain data has nil MultisigAndAggchainProof") + + // ok conversion + protoCert.AggchainData = &v1types.AggchainData{ + Data: &v1types.AggchainData_MultisigAndAggchainProof{ + MultisigAndAggchainProof: &v1types.AggchainProofWithMultisig{ + Multisig: &v1types.Multisig{ + Data: &v1types.Multisig_Ecdsa{ + Ecdsa: &v1types.ECDSAMultisig{ + Signatures: []*v1types.ECDSAMultisig_ECDSAMultisigEntry{ + {Index: 0, Signature: &v1types.FixedBytes65{Value: make([]byte, aggkitcommon.SignatureSize)}}, + }, + }, + }, + }, + AggchainProof: &v1types.AggchainProof{ + Proof: &v1types.AggchainProof_Sp1Stark{ + Sp1Stark: &v1types.SP1StarkProof{ + Proof: []byte{0x01, 0x02, 0x03}, + Version: "1.0", + Vkey: []byte{0x01, 0x02, 0x03}, + }, + }, + AggchainParams: &v1types.FixedBytes32{ + Value: common.HexToHash("0x0102030405060708090a0b0c0d0e0f1011121314151617181920212223242526").Bytes(), + }, + Signature: &v1types.FixedBytes65{Value: make([]byte, aggkitcommon.SignatureSize)}, + }, + }, + }, + } + + result, err = ConvertProtoCertToAgglayer(protoCert) + require.NoError(t, err) + require.NotNil(t, result) + }) } func TestGrpcBridgeExitToAgglayer(t *testing.T) { @@ -417,146 +527,3 @@ func TestGrpcL1LeafToAgglayer(t *testing.T) { require.ErrorIs(t, err, ErrNilCertificate) }) } - -/* -func TestGrpcMerkleProofToAgglayer(t *testing.T) { - t.Run("nil proof", func(t *testing.T) { - result, err := grpcMerkleProofToAgglayer(nil) - require.Error(t, err) - require.Nil(t, result) - require.ErrorIs(t, err, ErrNilCertificate) - }) - - t.Run("nil root", func(t *testing.T) { - siblings := make([]*v1nodetypes.Hash, treetypes.DefaultHeight) - for i := range siblings { - siblings[i] = &v1nodetypes.Hash{Value: make([]byte, 32)} - } - - proof := &v1types.MerkleProof{ - Root: nil, - Siblings: siblings, - } - - result, err := grpcMerkleProofToAgglayer(proof) - require.Error(t, err) - require.Nil(t, result) - require.ErrorIs(t, err, ErrNilCertificate) - }) - - t.Run("invalid number of siblings", func(t *testing.T) { - proof := &v1types.MerkleProof{ - Root: &v1nodetypes.Hash{Value: make([]byte, 32)}, - Siblings: []*v1nodetypes.Hash{{Value: make([]byte, 32)}}, // Wrong number - } - - result, err := grpcMerkleProofToAgglayer(proof) - require.Error(t, err) - require.Nil(t, result) - require.Contains(t, err.Error(), "invalid number of siblings") - }) - - t.Run("successful conversion", func(t *testing.T) { - root := make([]byte, 32) - root[0] = 0x01 - - siblings := make([]*v1nodetypes.Hash, treetypes.DefaultHeight) - for i := range siblings { - sibling := make([]byte, 32) - sibling[0] = byte(i + 1) - siblings[i] = &v1nodetypes.Hash{Value: sibling} - } - - proof := &v1types.MerkleProof{ - Root: &v1nodetypes.Hash{Value: root}, - Siblings: siblings, - } - - result, err := grpcMerkleProofToAgglayer(proof) - require.NoError(t, err) - require.NotNil(t, result) - require.Equal(t, common.BytesToHash(root), result.Root) - require.Len(t, result.Proof, treetypes.DefaultHeight) - }) -} - - - -func TestGrpcL1LeafToAgglayer(t *testing.T) { - t.Run("nil l1 leaf", func(t *testing.T) { - result, err := grpcL1LeafToAgglayer(nil) - require.Error(t, err) - require.Nil(t, result) - require.ErrorIs(t, err, ErrNilCertificate) - }) - - t.Run("nil Rer", func(t *testing.T) { - l1Leaf := &v1types.L1InfoTreeLeafWithContext{ - L1InfoTreeIndex: 1, - Rer: nil, - Mer: &v1nodetypes.Hash{Value: make([]byte, 32)}, - } - - result, err := grpcL1LeafToAgglayer(l1Leaf) - require.Error(t, err) - require.Nil(t, result) - require.ErrorIs(t, err, ErrNilCertificate) - }) - - t.Run("successful conversion with inner", func(t *testing.T) { - rer := make([]byte, 32) - mer := make([]byte, 32) - globalExitRoot := make([]byte, 32) - blockHash := make([]byte, 32) - - rer[0] = 0x01 - mer[0] = 0x02 - globalExitRoot[0] = 0x03 - blockHash[0] = 0x04 - - l1Leaf := &v1types.L1InfoTreeLeafWithContext{ - L1InfoTreeIndex: 1, - Rer: &v1nodetypes.Hash{Value: rer}, - Mer: &v1nodetypes.Hash{Value: mer}, - Inner: &v1types.L1InfoTreeLeafInner{ - GlobalExitRoot: &v1nodetypes.Hash{Value: globalExitRoot}, - BlockHash: &v1nodetypes.Hash{Value: blockHash}, - Timestamp: 1234567890, - }, - } - - result, err := grpcL1LeafToAgglayer(l1Leaf) - require.NoError(t, err) - require.NotNil(t, result) - require.Equal(t, uint32(1), result.L1InfoTreeIndex) - require.Equal(t, common.BytesToHash(rer), result.RollupExitRoot) - require.Equal(t, common.BytesToHash(mer), result.MainnetExitRoot) - require.NotNil(t, result.Inner) - require.Equal(t, common.BytesToHash(globalExitRoot), result.Inner.GlobalExitRoot) - require.Equal(t, common.BytesToHash(blockHash), result.Inner.BlockHash) - require.Equal(t, uint64(1234567890), result.Inner.Timestamp) - }) - - t.Run("successful conversion without inner", func(t *testing.T) { - rer := make([]byte, 32) - mer := make([]byte, 32) - rer[0] = 0x01 - mer[0] = 0x02 - - l1Leaf := &v1types.L1InfoTreeLeafWithContext{ - L1InfoTreeIndex: 1, - Rer: &v1nodetypes.Hash{Value: rer}, - Mer: &v1nodetypes.Hash{Value: mer}, - Inner: nil, - } - - result, err := grpcL1LeafToAgglayer(l1Leaf) - require.NoError(t, err) - require.NotNil(t, result) - require.Equal(t, uint32(1), result.L1InfoTreeIndex) - require.Equal(t, common.BytesToHash(rer), result.RollupExitRoot) - require.Equal(t, common.BytesToHash(mer), result.MainnetExitRoot) - require.Nil(t, result.Inner) - }) -} -*/ diff --git a/agglayer/mocks/mock_agglayer_client.go b/agglayer/mocks/mock_agglayer_client.go index cb0d0e5f4..641c21c21 100644 --- a/agglayer/mocks/mock_agglayer_client.go +++ b/agglayer/mocks/mock_agglayer_client.go @@ -260,23 +260,23 @@ func (_c *AgglayerClientMock_GetLatestSettledCertificateHeader_Call) RunAndRetur return _c } -// GetNetworkStatus provides a mock function with given fields: ctx, networkID -func (_m *AgglayerClientMock) GetNetworkStatus(ctx context.Context, networkID uint32) (types.NetworkStatus, error) { +// GetNetworkState provides a mock function with given fields: ctx, networkID +func (_m *AgglayerClientMock) GetNetworkState(ctx context.Context, networkID uint32) (types.NetworkState, error) { ret := _m.Called(ctx, networkID) if len(ret) == 0 { - panic("no return value specified for GetNetworkStatus") + panic("no return value specified for GetNetworkState") } - var r0 types.NetworkStatus + var r0 types.NetworkState var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) (types.NetworkStatus, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, uint32) (types.NetworkState, error)); ok { return rf(ctx, networkID) } - if rf, ok := ret.Get(0).(func(context.Context, uint32) types.NetworkStatus); ok { + if rf, ok := ret.Get(0).(func(context.Context, uint32) types.NetworkState); ok { r0 = rf(ctx, networkID) } else { - r0 = ret.Get(0).(types.NetworkStatus) + r0 = ret.Get(0).(types.NetworkState) } if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { @@ -288,38 +288,38 @@ func (_m *AgglayerClientMock) GetNetworkStatus(ctx context.Context, networkID ui return r0, r1 } -// AgglayerClientMock_GetNetworkStatus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetNetworkStatus' -type AgglayerClientMock_GetNetworkStatus_Call struct { +// AgglayerClientMock_GetNetworkState_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetNetworkState' +type AgglayerClientMock_GetNetworkState_Call struct { *mock.Call } -// GetNetworkStatus is a helper method to define mock.On call +// GetNetworkState is a helper method to define mock.On call // - ctx context.Context // - networkID uint32 -func (_e *AgglayerClientMock_Expecter) GetNetworkStatus(ctx interface{}, networkID interface{}) *AgglayerClientMock_GetNetworkStatus_Call { - return &AgglayerClientMock_GetNetworkStatus_Call{Call: _e.mock.On("GetNetworkStatus", ctx, networkID)} +func (_e *AgglayerClientMock_Expecter) GetNetworkState(ctx interface{}, networkID interface{}) *AgglayerClientMock_GetNetworkState_Call { + return &AgglayerClientMock_GetNetworkState_Call{Call: _e.mock.On("GetNetworkState", ctx, networkID)} } -func (_c *AgglayerClientMock_GetNetworkStatus_Call) Run(run func(ctx context.Context, networkID uint32)) *AgglayerClientMock_GetNetworkStatus_Call { +func (_c *AgglayerClientMock_GetNetworkState_Call) Run(run func(ctx context.Context, networkID uint32)) *AgglayerClientMock_GetNetworkState_Call { _c.Call.Run(func(args mock.Arguments) { run(args[0].(context.Context), args[1].(uint32)) }) return _c } -func (_c *AgglayerClientMock_GetNetworkStatus_Call) Return(_a0 types.NetworkStatus, _a1 error) *AgglayerClientMock_GetNetworkStatus_Call { +func (_c *AgglayerClientMock_GetNetworkState_Call) Return(_a0 types.NetworkState, _a1 error) *AgglayerClientMock_GetNetworkState_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *AgglayerClientMock_GetNetworkStatus_Call) RunAndReturn(run func(context.Context, uint32) (types.NetworkStatus, error)) *AgglayerClientMock_GetNetworkStatus_Call { +func (_c *AgglayerClientMock_GetNetworkState_Call) RunAndReturn(run func(context.Context, uint32) (types.NetworkState, error)) *AgglayerClientMock_GetNetworkState_Call { _c.Call.Return(run) return _c } -// SendCertificate provides a mock function with given fields: ctx, certificate, validatorSignature -func (_m *AgglayerClientMock) SendCertificate(ctx context.Context, certificate *types.Certificate, validatorSignature []byte) (common.Hash, error) { - ret := _m.Called(ctx, certificate, validatorSignature) +// SendCertificate provides a mock function with given fields: ctx, certificate +func (_m *AgglayerClientMock) SendCertificate(ctx context.Context, certificate *types.Certificate) (common.Hash, error) { + ret := _m.Called(ctx, certificate) if len(ret) == 0 { panic("no return value specified for SendCertificate") @@ -327,19 +327,19 @@ func (_m *AgglayerClientMock) SendCertificate(ctx context.Context, certificate * var r0 common.Hash var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *types.Certificate, []byte) (common.Hash, error)); ok { - return rf(ctx, certificate, validatorSignature) + if rf, ok := ret.Get(0).(func(context.Context, *types.Certificate) (common.Hash, error)); ok { + return rf(ctx, certificate) } - if rf, ok := ret.Get(0).(func(context.Context, *types.Certificate, []byte) common.Hash); ok { - r0 = rf(ctx, certificate, validatorSignature) + if rf, ok := ret.Get(0).(func(context.Context, *types.Certificate) common.Hash); ok { + r0 = rf(ctx, certificate) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(common.Hash) } } - if rf, ok := ret.Get(1).(func(context.Context, *types.Certificate, []byte) error); ok { - r1 = rf(ctx, certificate, validatorSignature) + if rf, ok := ret.Get(1).(func(context.Context, *types.Certificate) error); ok { + r1 = rf(ctx, certificate) } else { r1 = ret.Error(1) } @@ -355,14 +355,13 @@ type AgglayerClientMock_SendCertificate_Call struct { // SendCertificate is a helper method to define mock.On call // - ctx context.Context // - certificate *types.Certificate -// - validatorSignature []byte -func (_e *AgglayerClientMock_Expecter) SendCertificate(ctx interface{}, certificate interface{}, validatorSignature interface{}) *AgglayerClientMock_SendCertificate_Call { - return &AgglayerClientMock_SendCertificate_Call{Call: _e.mock.On("SendCertificate", ctx, certificate, validatorSignature)} +func (_e *AgglayerClientMock_Expecter) SendCertificate(ctx interface{}, certificate interface{}) *AgglayerClientMock_SendCertificate_Call { + return &AgglayerClientMock_SendCertificate_Call{Call: _e.mock.On("SendCertificate", ctx, certificate)} } -func (_c *AgglayerClientMock_SendCertificate_Call) Run(run func(ctx context.Context, certificate *types.Certificate, validatorSignature []byte)) *AgglayerClientMock_SendCertificate_Call { +func (_c *AgglayerClientMock_SendCertificate_Call) Run(run func(ctx context.Context, certificate *types.Certificate)) *AgglayerClientMock_SendCertificate_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*types.Certificate), args[2].([]byte)) + run(args[0].(context.Context), args[1].(*types.Certificate)) }) return _c } @@ -372,7 +371,7 @@ func (_c *AgglayerClientMock_SendCertificate_Call) Return(_a0 common.Hash, _a1 e return _c } -func (_c *AgglayerClientMock_SendCertificate_Call) RunAndReturn(run func(context.Context, *types.Certificate, []byte) (common.Hash, error)) *AgglayerClientMock_SendCertificate_Call { +func (_c *AgglayerClientMock_SendCertificate_Call) RunAndReturn(run func(context.Context, *types.Certificate) (common.Hash, error)) *AgglayerClientMock_SendCertificate_Call { _c.Call.Return(run) return _c } diff --git a/agglayer/mocks/mock_node_state_service_client.go b/agglayer/mocks/mock_node_state_service_client.go index ec75d4433..001c5bf4a 100644 --- a/agglayer/mocks/mock_node_state_service_client.go +++ b/agglayer/mocks/mock_node_state_service_client.go @@ -173,8 +173,8 @@ func (_c *NodeStateServiceClient_GetLatestCertificateHeader_Call) RunAndReturn(r return _c } -// GetNetworkStatus provides a mock function with given fields: ctx, in, opts -func (_m *NodeStateServiceClient) GetNetworkStatus(ctx context.Context, in *nodev1.GetNetworkStatusRequest, opts ...grpc.CallOption) (*nodev1.GetNetworkStatusResponse, error) { +// GetNetworkState provides a mock function with given fields: ctx, in, opts +func (_m *NodeStateServiceClient) GetNetworkState(ctx context.Context, in *nodev1.GetNetworkStateRequest, opts ...grpc.CallOption) (*nodev1.GetNetworkStateResponse, error) { _va := make([]interface{}, len(opts)) for _i := range opts { _va[_i] = opts[_i] @@ -185,23 +185,23 @@ func (_m *NodeStateServiceClient) GetNetworkStatus(ctx context.Context, in *node ret := _m.Called(_ca...) if len(ret) == 0 { - panic("no return value specified for GetNetworkStatus") + panic("no return value specified for GetNetworkState") } - var r0 *nodev1.GetNetworkStatusResponse + var r0 *nodev1.GetNetworkStateResponse var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *nodev1.GetNetworkStatusRequest, ...grpc.CallOption) (*nodev1.GetNetworkStatusResponse, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, *nodev1.GetNetworkStateRequest, ...grpc.CallOption) (*nodev1.GetNetworkStateResponse, error)); ok { return rf(ctx, in, opts...) } - if rf, ok := ret.Get(0).(func(context.Context, *nodev1.GetNetworkStatusRequest, ...grpc.CallOption) *nodev1.GetNetworkStatusResponse); ok { + if rf, ok := ret.Get(0).(func(context.Context, *nodev1.GetNetworkStateRequest, ...grpc.CallOption) *nodev1.GetNetworkStateResponse); ok { r0 = rf(ctx, in, opts...) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(*nodev1.GetNetworkStatusResponse) + r0 = ret.Get(0).(*nodev1.GetNetworkStateResponse) } } - if rf, ok := ret.Get(1).(func(context.Context, *nodev1.GetNetworkStatusRequest, ...grpc.CallOption) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *nodev1.GetNetworkStateRequest, ...grpc.CallOption) error); ok { r1 = rf(ctx, in, opts...) } else { r1 = ret.Error(1) @@ -210,21 +210,21 @@ func (_m *NodeStateServiceClient) GetNetworkStatus(ctx context.Context, in *node return r0, r1 } -// NodeStateServiceClient_GetNetworkStatus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetNetworkStatus' -type NodeStateServiceClient_GetNetworkStatus_Call struct { +// NodeStateServiceClient_GetNetworkState_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetNetworkState' +type NodeStateServiceClient_GetNetworkState_Call struct { *mock.Call } -// GetNetworkStatus is a helper method to define mock.On call +// GetNetworkState is a helper method to define mock.On call // - ctx context.Context -// - in *nodev1.GetNetworkStatusRequest +// - in *nodev1.GetNetworkStateRequest // - opts ...grpc.CallOption -func (_e *NodeStateServiceClient_Expecter) GetNetworkStatus(ctx interface{}, in interface{}, opts ...interface{}) *NodeStateServiceClient_GetNetworkStatus_Call { - return &NodeStateServiceClient_GetNetworkStatus_Call{Call: _e.mock.On("GetNetworkStatus", +func (_e *NodeStateServiceClient_Expecter) GetNetworkState(ctx interface{}, in interface{}, opts ...interface{}) *NodeStateServiceClient_GetNetworkState_Call { + return &NodeStateServiceClient_GetNetworkState_Call{Call: _e.mock.On("GetNetworkState", append([]interface{}{ctx, in}, opts...)...)} } -func (_c *NodeStateServiceClient_GetNetworkStatus_Call) Run(run func(ctx context.Context, in *nodev1.GetNetworkStatusRequest, opts ...grpc.CallOption)) *NodeStateServiceClient_GetNetworkStatus_Call { +func (_c *NodeStateServiceClient_GetNetworkState_Call) Run(run func(ctx context.Context, in *nodev1.GetNetworkStateRequest, opts ...grpc.CallOption)) *NodeStateServiceClient_GetNetworkState_Call { _c.Call.Run(func(args mock.Arguments) { variadicArgs := make([]grpc.CallOption, len(args)-2) for i, a := range args[2:] { @@ -232,17 +232,17 @@ func (_c *NodeStateServiceClient_GetNetworkStatus_Call) Run(run func(ctx context variadicArgs[i] = a.(grpc.CallOption) } } - run(args[0].(context.Context), args[1].(*nodev1.GetNetworkStatusRequest), variadicArgs...) + run(args[0].(context.Context), args[1].(*nodev1.GetNetworkStateRequest), variadicArgs...) }) return _c } -func (_c *NodeStateServiceClient_GetNetworkStatus_Call) Return(_a0 *nodev1.GetNetworkStatusResponse, _a1 error) *NodeStateServiceClient_GetNetworkStatus_Call { +func (_c *NodeStateServiceClient_GetNetworkState_Call) Return(_a0 *nodev1.GetNetworkStateResponse, _a1 error) *NodeStateServiceClient_GetNetworkState_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *NodeStateServiceClient_GetNetworkStatus_Call) RunAndReturn(run func(context.Context, *nodev1.GetNetworkStatusRequest, ...grpc.CallOption) (*nodev1.GetNetworkStatusResponse, error)) *NodeStateServiceClient_GetNetworkStatus_Call { +func (_c *NodeStateServiceClient_GetNetworkState_Call) RunAndReturn(run func(context.Context, *nodev1.GetNetworkStateRequest, ...grpc.CallOption) (*nodev1.GetNetworkStateResponse, error)) *NodeStateServiceClient_GetNetworkState_Call { _c.Call.Return(run) return _c } diff --git a/agglayer/types/types.go b/agglayer/types/types.go index 68307755b..b2484567d 100644 --- a/agglayer/types/types.go +++ b/agglayer/types/types.go @@ -156,10 +156,14 @@ func (a *AggchainDataSelector) UnmarshalJSON(data []byte) error { return err } var ok bool - if _, ok = obj["proof"]; ok { + if _, ok = obj["aggchain_data_proof"]; ok { a.obj = &AggchainDataProof{} - } else if _, ok = obj["signature"]; ok { + } else if _, ok = obj["aggchain_data_signature"]; ok { a.obj = &AggchainDataSignature{} + } else if _, ok = obj["aggchain_data_multisig_with_proof"]; ok { + a.obj = &AggchainDataMultisigWithProof{} + } else if _, ok = obj["aggchain_data_multisig"]; ok { + a.obj = &AggchainDataMultisig{} } else { return errors.New("invalid aggchain_data type") } @@ -177,21 +181,30 @@ type AggchainDataSignature struct { // MarshalJSON is the implementation of the json.Marshaler interface func (a *AggchainDataSignature) MarshalJSON() ([]byte, error) { return json.Marshal(&struct { - Signature string `json:"signature"` + AggchainDataSignature struct { + Signature string `json:"signature"` + } `json:"aggchain_data_signature"` }{ - Signature: common.Bytes2Hex(a.Signature), + AggchainDataSignature: struct { + Signature string `json:"signature"` + }{ + Signature: common.Bytes2Hex(a.Signature), + }, }) } // UnmarshalJSON is the implementation of the json.Unmarshaler interface func (a *AggchainDataSignature) UnmarshalJSON(data []byte) error { aux := &struct { - Signature string `json:"signature"` + AggchainDataSignature struct { + Signature string `json:"signature"` + } `json:"aggchain_data_signature"` }{} if err := json.Unmarshal(data, &aux); err != nil { return err } - a.Signature = common.Hex2Bytes(aux.Signature) + + a.Signature = common.Hex2Bytes(aux.AggchainDataSignature.Signature) return nil } @@ -209,46 +222,174 @@ type AggchainDataProof struct { // MarshalJSON is the implementation of the json.Marshaler interface func (a *AggchainDataProof) MarshalJSON() ([]byte, error) { return json.Marshal(&struct { - Proof string `json:"proof"` - AggchainParams string `json:"aggchain_params"` - Context map[string][]byte `json:"context"` - Version string `json:"version"` - VKey string `json:"vkey"` - Signature string `json:"signature"` + AggchainDataProof struct { + Proof string `json:"proof"` + AggchainParams string `json:"aggchain_params"` + Context map[string][]byte `json:"context"` + Version string `json:"version"` + VKey string `json:"vkey"` + Signature string `json:"signature"` + } `json:"aggchain_data_proof"` }{ - Proof: common.Bytes2Hex(a.Proof), - AggchainParams: a.AggchainParams.String(), - Context: a.Context, - Version: a.Version, - VKey: common.Bytes2Hex(a.Vkey), - Signature: common.Bytes2Hex(a.Signature), + AggchainDataProof: struct { + Proof string `json:"proof"` + AggchainParams string `json:"aggchain_params"` + Context map[string][]byte `json:"context"` + Version string `json:"version"` + VKey string `json:"vkey"` + Signature string `json:"signature"` + }{ + Proof: common.Bytes2Hex(a.Proof), + AggchainParams: a.AggchainParams.String(), + Context: a.Context, + Version: a.Version, + VKey: common.Bytes2Hex(a.Vkey), + Signature: common.Bytes2Hex(a.Signature), + }, }) } // UnmarshalJSON is the implementation of the json.Unmarshaler interface func (a *AggchainDataProof) UnmarshalJSON(data []byte) error { aux := &struct { - Proof string `json:"proof"` - AggchainParams string `json:"aggchain_params"` - Context map[string][]byte `json:"context"` - Version string `json:"version"` - VKey string `json:"vkey"` - Signature string `json:"signature"` + AggchainDataProof struct { + Proof string `json:"proof"` + AggchainParams string `json:"aggchain_params"` + Context map[string][]byte `json:"context"` + Version string `json:"version"` + VKey string `json:"vkey"` + Signature string `json:"signature"` + } `json:"aggchain_data_proof"` }{} if err := json.Unmarshal(data, &aux); err != nil { return err } - a.Proof = common.Hex2Bytes(aux.Proof) - a.AggchainParams = common.HexToHash(aux.AggchainParams) - a.Context = aux.Context - a.Version = aux.Version - a.Vkey = common.Hex2Bytes(aux.VKey) - a.Signature = common.Hex2Bytes(aux.Signature) + a.Proof = common.Hex2Bytes(aux.AggchainDataProof.Proof) + a.AggchainParams = common.HexToHash(aux.AggchainDataProof.AggchainParams) + a.Context = aux.AggchainDataProof.Context + a.Version = aux.AggchainDataProof.Version + a.Vkey = common.Hex2Bytes(aux.AggchainDataProof.VKey) + a.Signature = common.Hex2Bytes(aux.AggchainDataProof.Signature) return nil } +// AggchainDataMultisig is the data structure that will hold the multisig information +// for PP networks +type AggchainDataMultisig struct { + Multisig *Multisig `json:"multisig"` +} + +// MarshalJSON is the implementation of the json.Marshaler interface +func (a *AggchainDataMultisig) MarshalJSON() ([]byte, error) { + return json.Marshal(&struct { + AggchainDataMultisig struct { + Multisig *Multisig `json:"multisig"` + } `json:"aggchain_data_multisig"` + }{ + AggchainDataMultisig: struct { + Multisig *Multisig `json:"multisig"` + }{ + Multisig: a.Multisig, + }, + }) +} + +// UnmarshalJSON is the implementation of the json.Unmarshaler interface +func (a *AggchainDataMultisig) UnmarshalJSON(data []byte) error { + aux := &struct { + AggchainDataMultisig struct { + Multisig *Multisig `json:"multisig"` + } `json:"aggchain_data_multisig"` + }{} + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + a.Multisig = aux.AggchainDataMultisig.Multisig + return nil +} + +// AggchainDataMultisigWithProof is the data structure that will hold the +// multisig and aggchain proof information for FEP networks +type AggchainDataMultisigWithProof struct { + Multisig *Multisig `json:"multisig"` + AggchainProof *AggchainDataProof `json:"aggchain_proof"` +} + +// MarshalJSON is the implementation of the json.Marshaler interface +func (a *AggchainDataMultisigWithProof) MarshalJSON() ([]byte, error) { + return json.Marshal(&struct { + AggchainDataMultisigWithProof struct { + Multisig *Multisig `json:"multisig"` + AggchainProof *AggchainDataProof `json:"aggchain_proof"` + } `json:"aggchain_data_multisig_with_proof"` + }{ + AggchainDataMultisigWithProof: struct { + Multisig *Multisig `json:"multisig"` + AggchainProof *AggchainDataProof `json:"aggchain_proof"` + }{ + Multisig: a.Multisig, + AggchainProof: a.AggchainProof, + }, + }) +} + +// UnmarshalJSON is the implementation of the json.Unmarshaler interface +func (a *AggchainDataMultisigWithProof) UnmarshalJSON(data []byte) error { + aux := &struct { + AggchainDataMultisigWithProof struct { + Multisig *Multisig `json:"multisig"` + AggchainProof *AggchainDataProof `json:"aggchain_proof"` + } `json:"aggchain_data_multisig_with_proof"` + }{} + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + a.Multisig = aux.AggchainDataMultisigWithProof.Multisig + a.AggchainProof = aux.AggchainDataMultisigWithProof.AggchainProof + return nil +} + +// ECDSAMultisigEntry is the data structure that will hold the information +// about a single signature in the multisig +type ECDSAMultisigEntry struct { + Index uint32 `json:"index"` + Signature []byte `json:"signature"` +} + +// MarshalJSON is the implementation of the json.Marshaler interface +func (e *ECDSAMultisigEntry) MarshalJSON() ([]byte, error) { + return json.Marshal(&struct { + Index uint32 `json:"index"` + Signature string `json:"signature"` + }{ + Index: e.Index, + Signature: common.Bytes2Hex(e.Signature), + }) +} + +// UnmarshalJSON is the implementation of the json.Unmarshaler interface +func (e *ECDSAMultisigEntry) UnmarshalJSON(data []byte) error { + aux := &struct { + Index uint32 `json:"index"` + Signature string `json:"signature"` + }{} + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + + e.Index = aux.Index + e.Signature = common.Hex2Bytes(aux.Signature) + + return nil +} + +// Multisig is the data structure that will hold the multisig information +type Multisig struct { + Signatures []ECDSAMultisigEntry `json:"signatures"` +} + // Certificate is the data structure that will be sent to the agglayer type Certificate struct { NetworkID uint32 `json:"network_id"` @@ -335,7 +476,6 @@ func (c *Certificate) Brief() string { // CertificateID returns a certificateID that identifies the certificate // next fields are not included: CustomChainData, AggchainData, L1InfoTreeLeafCount -// TODO: probably about to be changed for phase III func (c *Certificate) CertificateID() common.Hash { bridgeExitsHashes := make([][]byte, len(c.BridgeExits)) for i, bridgeExit := range c.BridgeExits { @@ -351,7 +491,7 @@ func (c *Certificate) CertificateID() common.Hash { importedBridgeExitsPart := crypto.Keccak256(importedBridgeExitsHashes...) return crypto.Keccak256Hash( - aggkitcommon.Uint32ToBytes(c.NetworkID), + aggkitcommon.Uint32ToBigEndianBytes(c.NetworkID), aggkitcommon.Uint64ToBigEndianBytes(c.Height), c.PrevLocalExitRoot.Bytes(), c.NewLocalExitRoot.Bytes(), @@ -1339,8 +1479,8 @@ func (c ClockConfiguration) String() string { return fmt.Sprintf("EpochDuration: %d, GenesisBlock: %d", c.EpochDuration, c.GenesisBlock) } -// NetworkStatus represents the status of the network returned by the interop_getNetworkStatus RPC call -type NetworkStatus struct { +// NetworkState represents the state of the network returned by the interop_getNetworkStatus RPC call +type NetworkState struct { // Status is the current status of the network (e.g., "active", "syncing", "error") Status string `json:"network_status"` // NetworkType is the aggchain type of network @@ -1348,7 +1488,7 @@ type NetworkStatus struct { // NetworkID is the unique identifier of the network NetworkID uint32 `json:"network_id"` // SettledHeight is the height of the latest settled certificate - SettledHeight uint64 `json:"settled_height"` + SettledHeight *uint64 `json:"settled_height"` // SettledCertificateID is the ID of the latest settled certificate SettledCertificateID *common.Hash `json:"settled_certificate_id"` // SettledPPRoot pessimistic proof root of the latest settled certificate @@ -1356,17 +1496,17 @@ type NetworkStatus struct { // SettledLER is the local exit root of the latest settled certificate SettledLER *common.Hash `json:"settled_ler"` // SettledLETLeafCount is the leaf count of the latest settled local exit tree - SettledLETLeafCount uint64 `json:"settled_let_leaf_count"` + SettledLETLeafCount *uint64 `json:"settled_let_leaf_count"` // SettledImportedBridgeExit is the information about the latest settled claim SettledImportedBridgeExit *SettledImportedBridgeExit `json:"settled_claim,omitempty"` // LatestPendingHeight is the height of the latest pending certificate - LatestPendingHeight uint64 `json:"latest_pending_height"` + LatestPendingHeight *uint64 `json:"latest_pending_height"` // LatestPendingCertificateID is the status of the latest pending certificate (e.g., "Proven", "Pending", "InError") - LatestPendingStatus CertificateStatus `json:"latest_pending_status"` + LatestPendingStatus *CertificateStatus `json:"latest_pending_status"` // LatestPendingError is the error message of the latest pending certificate, if any LatestPendingError string `json:"latest_pending_error"` // LatestEpochWithSettlement is the epoch number of the latest settlement - LatestEpochWithSettlement uint64 `json:"latest_epoch_with_settlement"` + LatestEpochWithSettlement *uint64 `json:"latest_epoch_with_settlement"` } // SettledImportedBridgeExit represents the information about a settled claim diff --git a/agglayer/types/types_test.go b/agglayer/types/types_test.go index 90383793a..b165d83ca 100644 --- a/agglayer/types/types_test.go +++ b/agglayer/types/types_test.go @@ -27,7 +27,7 @@ import ( const ( expectedSignedCertificateEmptyMetadataJSON = `{"network_id":1,"height":1,"prev_local_exit_root":"0x0000000000000000000000000000000000000000000000000000000000000000","new_local_exit_root":"0x0000000000000000000000000000000000000000000000000000000000000000","bridge_exits":[{"leaf_type":"Transfer","token_info":null,"dest_network":0,"dest_address":"0x0000000000000000000000000000000000000000","amount":"1","metadata":null}],"imported_bridge_exits":[{"bridge_exit":{"leaf_type":"Transfer","token_info":null,"dest_network":0,"dest_address":"0x0000000000000000000000000000000000000000","amount":"1","metadata":null},"claim_data":null,"global_index":{"mainnet_flag":false,"rollup_index":1,"leaf_index":1}}],"metadata":"0x0000000000000000000000000000000000000000000000000000000000000000"}` expectedSignedCertificateMetadataJSON = `{"network_id":1,"height":1,"prev_local_exit_root":"0x0000000000000000000000000000000000000000000000000000000000000000","new_local_exit_root":"0x0000000000000000000000000000000000000000000000000000000000000000","bridge_exits":[{"leaf_type":"Transfer","token_info":null,"dest_network":0,"dest_address":"0x0000000000000000000000000000000000000000","amount":"1","metadata":"010203"}],"imported_bridge_exits":[{"bridge_exit":{"leaf_type":"Transfer","token_info":null,"dest_network":0,"dest_address":"0x0000000000000000000000000000000000000000","amount":"1","metadata":null},"claim_data":null,"global_index":{"mainnet_flag":false,"rollup_index":1,"leaf_index":1}}],"metadata":"0x0000000000000000000000000000000000000000000000000000000000000000"}` - fullCertificateJSON = `{"network_id":1,"height":0,"prev_local_exit_root":"0x27ae5ba08d7291c96c8cbddcc148bf48a6d68c7974b94356f53754ef6171d757","new_local_exit_root":"0x79011be874bf6f229d8473eb251aa932210bc3ab843a316492d5bc0e4b9e945b","bridge_exits":[{"leaf_type":"Transfer","token_info":{"origin_network":0,"origin_token_address":"0x0000000000000000000000000000000000000000"},"dest_network":0,"dest_address":"0xbece3a31343c6019cde0d5a4df2af8df17ebcb0f","amount":"10000005400000000","metadata":null}],"imported_bridge_exits":[{"bridge_exit":{"leaf_type":"Transfer","token_info":{"origin_network":0,"origin_token_address":"0x0000000000000000000000000000000000000000"},"dest_network":1,"dest_address":"0xbece3a31343c6019cde0d5a4df2af8df17ebcb0f","amount":"20000005400000000","metadata":null},"claim_data":{"Mainnet":{"l1_leaf":{"l1_info_tree_index":3,"rer":"0x0000000000000000000000000000000000000000000000000000000000000000","mer":"0x34c7e5206c4c793171805029b5a3a5c6f2d3e5344731cd69912142dc083768bf","inner":{"global_exit_root":"0xefb4efc883a8d7ab7c414684a4f44fac0f522d5eef9144dbad85a6b7756d770d","block_hash":"0x9cec78aa63185df6ae5e4a021dfd1d00913f35de3600564765ed07db6f5f7cec","timestamp":1741872827}},"proof_ger_l1root":{"root":"0xbace23ebc8bf61447ae0787134d7520a069ca8bacd96b4897767c02420656483","proof":{"siblings":["0xf1eba9d07291b35fd0b5c7c59efd235e666f614ae998e8d64431067e34f51d2b","0xfe17fd8b2fc79ce1c10f24a9692c085bdb49ba249629b4f4afc0ee9e5a547754","0x2ae60d22c940f1eac62674981b21adb092537dad03b80917f9f373853a7b12e7","0x21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85","0xe58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a19344","0x0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d","0x887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968","0xffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f83","0x9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af","0xcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0","0xf9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5","0xf8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf892","0x3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c","0xc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb","0x5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc","0xda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2","0x2733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981f","0xe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a","0x5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0","0xb46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0","0xc65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2","0xf4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd9","0x5a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e377","0x4df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652","0xcdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef","0x0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d","0xb8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0","0x838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e","0x662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e","0x388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea322","0x93237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d735","0x8448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a9"]}},"proof_leaf_mer":{"root":"0x34c7e5206c4c793171805029b5a3a5c6f2d3e5344731cd69912142dc083768bf","proof":{"siblings":["0x7e5dddb55a966fa6ccd6d470bb326a4fcef563567d6897c45b7ed885de710757","0x4b274df9344e005bfd46536d791100a85234bef4fab0348d1b2ffc0e7a709d33","0xb4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d30","0x21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85","0xe58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a19344","0x0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d","0x887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968","0xffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f83","0x9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af","0xcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0","0xf9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5","0xf8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf892","0x3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c","0xc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb","0x5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc","0xda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2","0x2733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981f","0xe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a","0x5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0","0xb46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0","0xc65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2","0xf4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd9","0x5a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e377","0x4df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652","0xcdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef","0x0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d","0xb8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0","0x838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e","0x662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e","0x388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea322","0x93237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d735","0x8448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a9"]}}}},"global_index":{"mainnet_flag":true,"rollup_index":0,"leaf_index":2}},{"bridge_exit":{"leaf_type":"Transfer","token_info":{"origin_network":0,"origin_token_address":"0x0000000000000000000000000000000000000000"},"dest_network":1,"dest_address":"0xbece3a31343c6019cde0d5a4df2af8df17ebcb0f","amount":"1234567","metadata":null},"claim_data":{"Rollup":{"l1_leaf":{"l1_info_tree_index":4,"rer":"0x33267c0646fee979e59af1cd62f9e46cd0917f62aba82658e1a92a50e1d7b4d1","mer":"0x34c7e5206c4c793171805029b5a3a5c6f2d3e5344731cd69912142dc083768bf","inner":{"global_exit_root":"0x6df4684b75569ffa9c0d352d1293c5d98950ecc1ea34226194842d10b14f47d0","block_hash":"0x61d1de4c3c0732397c009a6913fd51a1607334d6d959030cfb994f5c4708b7e9","timestamp":1741872911}},"proof_ger_l1root":{"root":"0xbace23ebc8bf61447ae0787134d7520a069ca8bacd96b4897767c02420656483","proof":{"siblings":["0x0000000000000000000000000000000000000000000000000000000000000000","0xad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5","0x8646aa878bf9bdec51df10f340b0b35126c0b06fc69c8448a1f3d75c649480c6","0x21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85","0xe58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a19344","0x0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d","0x887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968","0xffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f83","0x9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af","0xcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0","0xf9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5","0xf8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf892","0x3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c","0xc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb","0x5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc","0xda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2","0x2733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981f","0xe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a","0x5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0","0xb46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0","0xc65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2","0xf4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd9","0x5a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e377","0x4df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652","0xcdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef","0x0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d","0xb8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0","0x838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e","0x662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e","0x388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea322","0x93237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d735","0x8448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a9"]}},"proof_leaf_ler":{"root":"0x156ab7795d0bb31ed548c43f90e71b8f06f71e5776a5ba444f3f3cb0935b4647","proof":{"siblings":["0x0000000000000000000000000000000000000000000000000000000000000000","0xad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5","0xb4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d30","0x21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85","0xe58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a19344","0x0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d","0x887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968","0xffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f83","0x9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af","0xcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0","0xf9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5","0xf8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf892","0x3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c","0xc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb","0x5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc","0xda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2","0x2733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981f","0xe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a","0x5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0","0xb46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0","0xc65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2","0xf4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd9","0x5a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e377","0x4df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652","0xcdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef","0x0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d","0xb8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0","0x838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e","0x662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e","0x388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea322","0x93237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d735","0x8448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a9"]}},"proof_ler_rer":{"root":"0x33267c0646fee979e59af1cd62f9e46cd0917f62aba82658e1a92a50e1d7b4d1","proof":{"siblings":["0x0000000000000000000000000000000000000000000000000000000000000000","0xad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5","0xb4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d30","0x21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85","0xe58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a19344","0x0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d","0x887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968","0xffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f83","0x9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af","0xcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0","0xf9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5","0xf8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf892","0x3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c","0xc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb","0x5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc","0xda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2","0x2733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981f","0xe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a","0x5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0","0xb46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0","0xc65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2","0xf4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd9","0x5a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e377","0x4df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652","0xcdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef","0x0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d","0xb8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0","0x838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e","0x662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e","0x388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea322","0x93237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d735","0x8448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a9"]}}}},"global_index":{"mainnet_flag":false,"rollup_index":1,"leaf_index":0}}],"metadata":"0x0100000000000000010000007667d2df67000000000000000000000000000000","aggchain_data":{"signature":"59e46153ce34143f3b9cc3786f2473d8aa80ba578f07102d59ad01cbf221f81b4a9d68f0edc5635df585c134486b465bd588cd7f76a4d7154ba81e299f8f7ed601"}}` + fullCertificateJSON = `{"network_id":1,"height":0,"prev_local_exit_root":"0x27ae5ba08d7291c96c8cbddcc148bf48a6d68c7974b94356f53754ef6171d757","new_local_exit_root":"0x79011be874bf6f229d8473eb251aa932210bc3ab843a316492d5bc0e4b9e945b","bridge_exits":[{"leaf_type":"Transfer","token_info":{"origin_network":0,"origin_token_address":"0x0000000000000000000000000000000000000000"},"dest_network":0,"dest_address":"0xbece3a31343c6019cde0d5a4df2af8df17ebcb0f","amount":"10000005400000000","metadata":null}],"imported_bridge_exits":[{"bridge_exit":{"leaf_type":"Transfer","token_info":{"origin_network":0,"origin_token_address":"0x0000000000000000000000000000000000000000"},"dest_network":1,"dest_address":"0xbece3a31343c6019cde0d5a4df2af8df17ebcb0f","amount":"20000005400000000","metadata":null},"claim_data":{"Mainnet":{"l1_leaf":{"l1_info_tree_index":3,"rer":"0x0000000000000000000000000000000000000000000000000000000000000000","mer":"0x34c7e5206c4c793171805029b5a3a5c6f2d3e5344731cd69912142dc083768bf","inner":{"global_exit_root":"0xefb4efc883a8d7ab7c414684a4f44fac0f522d5eef9144dbad85a6b7756d770d","block_hash":"0x9cec78aa63185df6ae5e4a021dfd1d00913f35de3600564765ed07db6f5f7cec","timestamp":1741872827}},"proof_ger_l1root":{"root":"0xbace23ebc8bf61447ae0787134d7520a069ca8bacd96b4897767c02420656483","proof":{"siblings":["0xf1eba9d07291b35fd0b5c7c59efd235e666f614ae998e8d64431067e34f51d2b","0xfe17fd8b2fc79ce1c10f24a9692c085bdb49ba249629b4f4afc0ee9e5a547754","0x2ae60d22c940f1eac62674981b21adb092537dad03b80917f9f373853a7b12e7","0x21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85","0xe58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a19344","0x0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d","0x887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968","0xffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f83","0x9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af","0xcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0","0xf9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5","0xf8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf892","0x3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c","0xc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb","0x5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc","0xda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2","0x2733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981f","0xe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a","0x5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0","0xb46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0","0xc65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2","0xf4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd9","0x5a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e377","0x4df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652","0xcdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef","0x0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d","0xb8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0","0x838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e","0x662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e","0x388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea322","0x93237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d735","0x8448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a9"]}},"proof_leaf_mer":{"root":"0x34c7e5206c4c793171805029b5a3a5c6f2d3e5344731cd69912142dc083768bf","proof":{"siblings":["0x7e5dddb55a966fa6ccd6d470bb326a4fcef563567d6897c45b7ed885de710757","0x4b274df9344e005bfd46536d791100a85234bef4fab0348d1b2ffc0e7a709d33","0xb4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d30","0x21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85","0xe58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a19344","0x0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d","0x887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968","0xffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f83","0x9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af","0xcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0","0xf9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5","0xf8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf892","0x3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c","0xc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb","0x5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc","0xda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2","0x2733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981f","0xe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a","0x5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0","0xb46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0","0xc65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2","0xf4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd9","0x5a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e377","0x4df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652","0xcdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef","0x0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d","0xb8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0","0x838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e","0x662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e","0x388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea322","0x93237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d735","0x8448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a9"]}}}},"global_index":{"mainnet_flag":true,"rollup_index":0,"leaf_index":2}},{"bridge_exit":{"leaf_type":"Transfer","token_info":{"origin_network":0,"origin_token_address":"0x0000000000000000000000000000000000000000"},"dest_network":1,"dest_address":"0xbece3a31343c6019cde0d5a4df2af8df17ebcb0f","amount":"1234567","metadata":null},"claim_data":{"Rollup":{"l1_leaf":{"l1_info_tree_index":4,"rer":"0x33267c0646fee979e59af1cd62f9e46cd0917f62aba82658e1a92a50e1d7b4d1","mer":"0x34c7e5206c4c793171805029b5a3a5c6f2d3e5344731cd69912142dc083768bf","inner":{"global_exit_root":"0x6df4684b75569ffa9c0d352d1293c5d98950ecc1ea34226194842d10b14f47d0","block_hash":"0x61d1de4c3c0732397c009a6913fd51a1607334d6d959030cfb994f5c4708b7e9","timestamp":1741872911}},"proof_ger_l1root":{"root":"0xbace23ebc8bf61447ae0787134d7520a069ca8bacd96b4897767c02420656483","proof":{"siblings":["0x0000000000000000000000000000000000000000000000000000000000000000","0xad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5","0x8646aa878bf9bdec51df10f340b0b35126c0b06fc69c8448a1f3d75c649480c6","0x21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85","0xe58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a19344","0x0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d","0x887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968","0xffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f83","0x9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af","0xcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0","0xf9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5","0xf8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf892","0x3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c","0xc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb","0x5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc","0xda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2","0x2733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981f","0xe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a","0x5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0","0xb46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0","0xc65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2","0xf4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd9","0x5a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e377","0x4df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652","0xcdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef","0x0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d","0xb8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0","0x838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e","0x662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e","0x388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea322","0x93237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d735","0x8448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a9"]}},"proof_leaf_ler":{"root":"0x156ab7795d0bb31ed548c43f90e71b8f06f71e5776a5ba444f3f3cb0935b4647","proof":{"siblings":["0x0000000000000000000000000000000000000000000000000000000000000000","0xad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5","0xb4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d30","0x21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85","0xe58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a19344","0x0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d","0x887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968","0xffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f83","0x9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af","0xcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0","0xf9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5","0xf8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf892","0x3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c","0xc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb","0x5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc","0xda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2","0x2733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981f","0xe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a","0x5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0","0xb46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0","0xc65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2","0xf4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd9","0x5a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e377","0x4df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652","0xcdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef","0x0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d","0xb8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0","0x838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e","0x662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e","0x388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea322","0x93237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d735","0x8448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a9"]}},"proof_ler_rer":{"root":"0x33267c0646fee979e59af1cd62f9e46cd0917f62aba82658e1a92a50e1d7b4d1","proof":{"siblings":["0x0000000000000000000000000000000000000000000000000000000000000000","0xad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5","0xb4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d30","0x21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85","0xe58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a19344","0x0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d","0x887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968","0xffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f83","0x9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af","0xcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0","0xf9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5","0xf8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf892","0x3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c","0xc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb","0x5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc","0xda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2","0x2733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981f","0xe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a","0x5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0","0xb46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0","0xc65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2","0xf4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd9","0x5a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e377","0x4df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652","0xcdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef","0x0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d","0xb8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0","0x838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e","0x662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e","0x388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea322","0x93237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d735","0x8448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a9"]}}}},"global_index":{"mainnet_flag":false,"rollup_index":1,"leaf_index":0}}],"metadata":"0x0100000000000000010000007667d2df67000000000000000000000000000000","aggchain_data":{"aggchain_data_signature":{"signature":"59e46153ce34143f3b9cc3786f2473d8aa80ba578f07102d59ad01cbf221f81b4a9d68f0edc5635df585c134486b465bd588cd7f76a4d7154ba81e299f8f7ed601"}}}` ) func TestBridgeExit_Hash(t *testing.T) { @@ -269,6 +269,43 @@ func TestMarshalJSON(t *testing.T) { require.Equal(t, "0xe1a594db4275e6e5ab302057e48955c7faf53a8910497590a742b3da89046320", cert.ImportedBridgeExits[0].Hash().String()) require.Equal(t, "0xcc9e20b86e9984d9f68b0252f224cb4bc774981c320ef375fb63706220f5af4d", cert.ImportedBridgeExits[1].Hash().String()) }) + + t.Run("MarshalJSON with aggchain data", func(t *testing.T) { + t.Parallel() + + certificate := &Certificate{ + NetworkID: 1, + Height: 100, + PrevLocalExitRoot: common.HexToHash("0xabc"), + NewLocalExitRoot: common.HexToHash("0xdef"), + Metadata: aggkitcommon.ZeroHash, + L1InfoTreeLeafCount: 10, + CustomChainData: []byte{0x01, 0x02, 0x03}, + // AggchainData: &AggchainDataMultisigWithProof{ + // Multisig: &Multisig{ + // Signatures: []ECDSAMultisigEntry{ + // {Index: 0, Signature: make([]byte, aggkitcommon.SignatureSize)}, + // {Index: 1, Signature: make([]byte, aggkitcommon.SignatureSize)}, + // }, + // }, + // AggchainProof: &AggchainDataProof{ + // Proof: []byte("proof"), + // Version: "1.0", + // Vkey: []byte("vkey"), + // AggchainParams: common.HexToHash("0x123"), + // Signature: make([]byte, aggkitcommon.SignatureSize), + // }, + // }, + AggchainData: &AggchainDataSignature{ + Signature: make([]byte, aggkitcommon.SignatureSize), + }, + } + + raw, err := json.Marshal(certificate) + require.NoError(t, err) + + fmt.Println(string(raw)) + }) } func TestGlobalIndex_UnmarshalFromMap(t *testing.T) { @@ -1139,12 +1176,12 @@ func TestCertificate_ID(t *testing.T) { require.Equal(t, "cert{height:2, networkID:1}", cert.ID()) } -func TestAggchainDataSignature_MarshalJUnmarshalJSON(t *testing.T) { +func TestAggchainDataSignature_MarshalUnmarshalJSON(t *testing.T) { signature := &AggchainDataSignature{ Signature: common.FromHex("0x1234567890abcdef"), } - expectedJSON := `{"signature":"1234567890abcdef"}` + expectedJSON := `{"aggchain_data_signature":{"signature":"1234567890abcdef"}}` jsonData, err := signature.MarshalJSON() require.NoError(t, err) @@ -1173,7 +1210,7 @@ func TestAggchainDataProof_MarshalUnmarshalJSON(t *testing.T) { Vkey: common.FromHex("0x123456"), Signature: []byte{0x01, 0x02, 0x03}, }, - expected: `{"aggchain_params":"0x0000000000000000000000000000000000000000000000000000000000abcdef", "context":{}, "proof":"123456", "signature":"010203", "version":"0.1", "vkey":"123456"}`, + expected: `{"aggchain_data_proof":{"proof":"123456","aggchain_params":"0x0000000000000000000000000000000000000000000000000000000000abcdef","context":{},"version":"0.1","vkey":"123456","signature":"010203"}}`, }, { name: "Empty AggchainDataProof, Context and Signature", @@ -1185,7 +1222,66 @@ func TestAggchainDataProof_MarshalUnmarshalJSON(t *testing.T) { Version: "", Vkey: []byte{}, }, - expected: `{"proof":"","aggchain_params":"0x0000000000000000000000000000000000000000000000000000000000000000","context":{},"version":"","vkey":"","signature":""}`, + expected: `{"aggchain_data_proof":{"proof":"","aggchain_params":"0x0000000000000000000000000000000000000000000000000000000000000000","context":{},"version":"","vkey":"","signature":""}}`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + result, err := tt.input.MarshalJSON() + require.NoError(t, err) + require.JSONEq(t, tt.expected, string(result)) + + var unmarshalled AggchainDataProof + require.NoError(t, unmarshalled.UnmarshalJSON(result)) + require.Equal(t, *tt.input, unmarshalled) + }) + } +} + +func TestAggchainDataMultisig_MarshalUnmarshalJSON(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + input *AggchainDataMultisig + expected string + }{ + { + name: "Valid AggchainDataMultisig with signatures", + input: &AggchainDataMultisig{ + Multisig: &Multisig{ + Signatures: []ECDSAMultisigEntry{ + { + Index: 0, + Signature: []byte{0x01, 0x02, 0x03}, + }, + { + Index: 1, + Signature: []byte{0x04, 0x05, 0x06}, + }, + }, + }, + }, + expected: `{"aggchain_data_multisig":{"multisig":{"signatures":[{"index":0,"signature":"010203"},{"index":1,"signature":"040506"}]}}}`, + }, + { + name: "Empty AggchainDataMultisig", + input: &AggchainDataMultisig{ + Multisig: &Multisig{ + Signatures: []ECDSAMultisigEntry{}, + }, + }, + expected: `{"aggchain_data_multisig":{"multisig":{"signatures":[]}}}`, + }, + { + name: "Nil Multisig", + input: &AggchainDataMultisig{ + Multisig: nil, + }, + expected: `{"aggchain_data_multisig":{"multisig":null}}`, }, } @@ -1199,7 +1295,110 @@ func TestAggchainDataProof_MarshalUnmarshalJSON(t *testing.T) { require.NoError(t, err) require.JSONEq(t, tt.expected, string(result)) - var unmarshalled AggchainDataProof + var unmarshalled AggchainDataMultisig + require.NoError(t, unmarshalled.UnmarshalJSON(result)) + require.Equal(t, *tt.input, unmarshalled) + }) + } +} + +func TestAggchainDataMultisigWithProof_MarshalUnmarshalJSON(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + input *AggchainDataMultisigWithProof + expected string + }{ + { + name: "Valid AggchainDataMultisigWithProof with all fields", + input: &AggchainDataMultisigWithProof{ + Multisig: &Multisig{ + Signatures: []ECDSAMultisigEntry{ + { + Index: 0, + Signature: []byte{0x01, 0x02, 0x03}, + }, + }, + }, + AggchainProof: &AggchainDataProof{ + Proof: []byte{0xaa, 0xbb, 0xcc}, + AggchainParams: common.HexToHash("0x123456"), + Context: map[string][]byte{"key": {0xdd, 0xee}}, + Version: "1.0", + Vkey: []byte{0xff}, + Signature: []byte{0x11, 0x22}, + }, + }, + expected: `{"aggchain_data_multisig_with_proof":{"multisig":{"signatures":[{"index":0,"signature":"010203"}]},"aggchain_proof":{"aggchain_data_proof":{"proof":"aabbcc","aggchain_params":"0x0000000000000000000000000000000000000000000000000000000000123456","context":{"key":"3e4="},"version":"1.0","vkey":"ff","signature":"1122"}}}}`, + }, + { + name: "Empty multisig with proof", + input: &AggchainDataMultisigWithProof{ + Multisig: &Multisig{ + Signatures: []ECDSAMultisigEntry{}, + }, + AggchainProof: &AggchainDataProof{ + Proof: []byte{}, + AggchainParams: common.Hash{}, + Context: map[string][]byte{}, + Version: "", + Vkey: []byte{}, + Signature: []byte{}, + }, + }, + expected: `{"aggchain_data_multisig_with_proof":{"multisig":{"signatures":[]},"aggchain_proof":{"aggchain_data_proof":{"proof":"","aggchain_params":"0x0000000000000000000000000000000000000000000000000000000000000000","context":{},"version":"","vkey":"","signature":""}}}}`, + }, + { + name: "Nil Multisig with valid proof", + input: &AggchainDataMultisigWithProof{ + Multisig: nil, + AggchainProof: &AggchainDataProof{ + Proof: []byte{0x99}, + Version: "2.0", + Vkey: []byte{0xff}, + Signature: []byte{0x01, 0x02}, + }, + }, + expected: `{"aggchain_data_multisig_with_proof":{"multisig":null,"aggchain_proof":{"aggchain_data_proof":{"proof":"99","aggchain_params":"0x0000000000000000000000000000000000000000000000000000000000000000","context":null,"version":"2.0","vkey":"ff","signature":"0102"}}}}`, + }, + { + name: "Valid multisig with nil proof", + input: &AggchainDataMultisigWithProof{ + Multisig: &Multisig{ + Signatures: []ECDSAMultisigEntry{ + { + Index: 2, + Signature: []byte{0xaa}, + }, + }, + }, + AggchainProof: nil, + }, + expected: `{"aggchain_data_multisig_with_proof":{"multisig":{"signatures":[{"index":2,"signature":"aa"}]},"aggchain_proof":null}}`, + }, + { + name: "Both nil", + input: &AggchainDataMultisigWithProof{ + Multisig: nil, + AggchainProof: nil, + }, + expected: `{"aggchain_data_multisig_with_proof":{"multisig":null,"aggchain_proof":null}}`, + }, + } + + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + result, err := tt.input.MarshalJSON() + require.NoError(t, err) + fmt.Println(string(result)) + require.JSONEq(t, tt.expected, string(result)) + + var unmarshalled AggchainDataMultisigWithProof require.NoError(t, unmarshalled.UnmarshalJSON(result)) require.Equal(t, *tt.input, unmarshalled) }) diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index 76cb774bb..4499a110c 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -337,31 +337,29 @@ func (a *AggSender) sendCertificate(ctx context.Context) (*agglayertypes.Certifi return nil, fmt.Errorf("error building certificate: %w", err) } - var ( - validators []types.CertificateValidateAndSigner - signaturesThreshold uint32 - ) - + var multisig *agglayertypes.Multisig if a.localValidator != nil { - validators = append(validators, a.localValidator) - signaturesThreshold = 1 + if _, err := a.localValidator.ValidateAndSignCertificate(ctx, certificate, certificateParams.ToBlock); err != nil { + // TODO - just log the failure of local validation for now + a.log.Warnf("local validation of certificate failed: %v. Cert: %s", err, certificate.Brief()) + } } else { - validators, signaturesThreshold, err = a.getValidators(ctx) + validators, signaturesThreshold, err := a.getValidators(ctx) if err != nil { return nil, fmt.Errorf("failed to get validators: %w", err) } + + multisig, err = a.pollValidatorCommittee(ctx, validators, signaturesThreshold, + certificate, certificateParams.ToBlock) + if err != nil { + return nil, fmt.Errorf("error polling validator committee: %w", err) + } } - multisig, err := a.pollValidators(ctx, - validators, signaturesThreshold, - certificate, certificateParams.ToBlock) - if err != nil { - // TODO - agglayer has not yet implemented the endpoints needed to validate a certificate - // so lets just log the error and continue. This will be changed when the agglayer is ready - // a.saveNonAcceptedCert(ctx, certificate, certificateParams.CreatedAt, err) - // return nil, fmt.Errorf("certificate validation failed: %w", err) - a.log.Warnf("certificate validation failed: %w. Cert: %s", err, certificate.Brief()) + if err := a.flow.UpdateAggchainData(certificate, multisig); err != nil { + return nil, fmt.Errorf("error updating agchain data with multisig: %w", err) } + a.log.Infof("certificate ready to be sent to AggLayer: %s start: %s, end: %s", certificate.Brief(), startEpochStatus.String(), a.epochNotifier.GetEpochStatus().String()) metrics.CertificateBuildTime(time.Since(start).Seconds()) @@ -377,12 +375,7 @@ func (a *AggSender) sendCertificate(ctx context.Context) (*agglayertypes.Certifi time.Sleep(*rateLimitSleepTime) } - // TODO: Update once agglayer endpoint changes to accept the multisig - var signature []byte - if len(multisig) > 0 { - signature = multisig[0] - } - certificateHash, err := a.aggLayerClient.SendCertificate(ctx, certificate, signature) + certificateHash, err := a.aggLayerClient.SendCertificate(ctx, certificate) if err != nil { a.saveNonAcceptedCert(ctx, certificate, certificateParams.CreatedAt, err) @@ -433,14 +426,14 @@ func (a *AggSender) sendCertificate(ctx context.Context) (*agglayertypes.Certifi return certificate, nil } -// pollValidators committee signers to validate the certificate and gather signatures -func (a *AggSender) pollValidators( +// pollValidatorCommittee invokes validator committee members to validate and sign the certificate +func (a *AggSender) pollValidatorCommittee( ctx context.Context, validators []types.CertificateValidateAndSigner, signaturesThreshold uint32, certificate *agglayertypes.Certificate, lastL2BlockInCert uint64, -) ([][]byte, error) { +) (*agglayertypes.Multisig, error) { if len(validators) == 0 { a.log.Warnf("skipping certificate validation, because there are no validators configured") return nil, nil @@ -473,23 +466,9 @@ func (a *AggSender) pollValidators( default: } - status, err := v.HealthCheck(ctx) - if err != nil { - a.log.Warnf("error checking validator health (URL=%s): %v", v.URL(), err) - resultsCh <- signResult{err: err, validator: v} - return - } - - if !status.IsHealthy() { - a.log.Warnf("validator (URL=%s) is not healthy: %s, skipping it...", v.URL(), status.String()) - return // skip unhealthy validator - } - - a.log.Infof("validator health check (URL=%s): %s", v.URL(), status.String()) - sig, err := v.ValidateAndSignCertificate(ctx, certificate, lastL2BlockInCert) if err != nil { - a.log.Errorf("validator %v failed to validate the certificate: %v", v, err) + a.log.Errorf("validator %s failed to validate the certificate: %v", v.String(), err) resultsCh <- signResult{err: err, validator: v} return } @@ -504,8 +483,10 @@ func (a *AggSender) pollValidators( metrics.ValidateTime(time.Since(start).Seconds()) }() - signatures := make([][]byte, 0, len(validators)) - var errs []error + multisig := &agglayertypes.Multisig{ + Signatures: make([]agglayertypes.ECDSAMultisigEntry, 0, len(validators)), + } + errs := make([]error, 0) for res := range resultsCh { if res.err != nil { @@ -526,26 +507,27 @@ func (a *AggSender) pollValidators( continue } - signatures = append(signatures, res.signature) - if uint32(len(signatures)) >= signaturesThreshold { + multisig.Signatures = append(multisig.Signatures, agglayertypes.ECDSAMultisigEntry{ + Index: res.validator.Index(), + Signature: res.signature, + }) + + if uint32(len(multisig.Signatures)) >= signaturesThreshold { cancel() // signal other goroutines to stop early } } - if uint32(len(signatures)) < signaturesThreshold { + if uint32(len(multisig.Signatures)) < signaturesThreshold { metrics.MultiSigThresholdNotReached() - if len(errs) > 0 { - return signatures, errors.Join(errs...) - } - - return signatures, fmt.Errorf("threshold not reached: %d/%d", len(signatures), signaturesThreshold) + return nil, fmt.Errorf("threshold not reached: %d/%d. Errors: %w", + len(multisig.Signatures), signaturesThreshold, errors.Join(errs...)) } a.log.Infof("certificate validation passed with %d/%d signatures: %s", - len(signatures), signaturesThreshold, certificate.Brief()) + len(multisig.Signatures), signaturesThreshold, certificate.Brief()) - return signatures, nil + return multisig, nil } // getValidators retrieves the actual multisig committee and creates a set of the validators @@ -557,9 +539,9 @@ func (a *AggSender) getValidators(ctx context.Context) ([]types.CertificateValid } validators := make([]types.CertificateValidateAndSigner, 0, committee.Size()) - for _, signer := range committee.Signers() { + for i, signer := range committee.Signers() { clientCfg := a.cfg.ValidatorClient.WithURL(signer.URL) - validator, err := validator.NewRemoteValidator(&clientCfg, a.storage, signer.Address) + validator, err := validator.NewRemoteValidator(&clientCfg, a.storage, signer.Address, uint32(i)) if err != nil { return nil, 0, fmt.Errorf("failed to create a remote validator for committee signer (Address=%s, URL=%s): %w", signer.Address, signer.URL, err) diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go index 9536a77f3..67c894e75 100644 --- a/aggsender/aggsender_test.go +++ b/aggsender/aggsender_test.go @@ -212,8 +212,6 @@ func TestSendCertificate_NoClaims(t *testing.T) { logger := log.WithFields("aggsender-test", "no claims test") signer := signer.NewLocalSignFromPrivateKey("ut", log.WithFields("aggsender", 1), privateKey, 0) mockValidator := mocks.NewCertificateValidateAndSigner(t) - mockValidator.EXPECT().HealthCheck(mock.Anything).Return(&aggsendertypes.HealthCheckResponse{Status: aggsendertypes.HealthCheckStatusOK}, nil) - mockValidator.EXPECT().URL().Return("http://localhost") mockValidator.EXPECT(). ValidateAndSignCertificate(mock.Anything, mock.Anything, mock.Anything). Return(make([]byte, aggkitcommon.SignatureSize), nil).Once() @@ -258,7 +256,7 @@ func TestSendCertificate_NoClaims(t *testing.T) { mockL1Querier.EXPECT().GetLatestFinalizedL1InfoRoot(ctx).Return(&treetypes.Root{}, nil, nil).Once() mockL2BridgeQuerier.EXPECT().GetExitRootByIndex(mock.Anything, uint32(1)).Return(common.Hash{}, nil).Once() mockL2BridgeQuerier.EXPECT().OriginNetwork().Return(uint32(1)).Once() - mockAggLayerClient.EXPECT().SendCertificate(mock.Anything, mock.Anything, mock.Anything).Return(common.Hash{}, nil).Once() + mockAggLayerClient.EXPECT().SendCertificate(mock.Anything, mock.Anything).Return(common.Hash{}, nil).Once() mockEpochNotifier.EXPECT().GetEpochStatus().Return(aggsendertypes.EpochStatus{}) signedCertificate, err := aggSender.sendCertificate(ctx) require.NoError(t, err) @@ -319,20 +317,19 @@ func TestSendCertificate(t *testing.T) { mockFlow.EXPECT().GetCertificateBuildParams(mock.Anything).Return(&aggsendertypes.CertificateBuildParams{ Bridges: []bridgesync.Bridge{{}}, }, nil).Once() - mockFlow.EXPECT().BuildCertificate(mock.Anything, mock.Anything).Return(&agglayertypes.Certificate{ + cert := &agglayertypes.Certificate{ NetworkID: 1, Height: 0, NewLocalExitRoot: common.HexToHash("0x1"), BridgeExits: []*agglayertypes.BridgeExit{{}}, - }, nil).Once() - mockAgglayerClient.EXPECT().SendCertificate(mock.Anything, mock.Anything, mock.Anything).Return(common.Hash{}, errors.New("some error")).Once() + } + mockFlow.EXPECT().BuildCertificate(mock.Anything, mock.Anything).Return(cert, nil).Once() + mockFlow.EXPECT().UpdateAggchainData(cert, mock.Anything).Return(nil).Once() + mockAgglayerClient.EXPECT().SendCertificate(mock.Anything, mock.Anything).Return(common.Hash{}, errors.New("some error")).Once() mockStorage.EXPECT().SaveNonAcceptedCertificate(mock.Anything, mock.Anything).Return(nil).Once() }, mockValidatorFn: func() *mocks.CertificateValidateAndSigner { mockValidator := mocks.NewCertificateValidateAndSigner(t) - mockValidator.EXPECT().HealthCheck(mock.Anything).Return(&aggsendertypes. - HealthCheckResponse{Status: aggsendertypes.HealthCheckStatusOK}, nil) - mockValidator.EXPECT().URL().Return("http://localhost") mockValidator.EXPECT(). ValidateAndSignCertificate(mock.Anything, mock.Anything, mock.Anything). Return(make([]byte, aggkitcommon.SignatureSize), nil).Once() @@ -348,20 +345,19 @@ func TestSendCertificate(t *testing.T) { mockFlow.EXPECT().GetCertificateBuildParams(mock.Anything).Return(&aggsendertypes.CertificateBuildParams{ Bridges: []bridgesync.Bridge{{}}, }, nil).Once() - mockFlow.EXPECT().BuildCertificate(mock.Anything, mock.Anything).Return(&agglayertypes.Certificate{ + cert := &agglayertypes.Certificate{ NetworkID: 11, Height: 0, NewLocalExitRoot: common.HexToHash("0x11"), BridgeExits: []*agglayertypes.BridgeExit{{}}, - }, nil).Once() - mockAgglayerClient.EXPECT().SendCertificate(mock.Anything, mock.Anything, mock.Anything).Return(common.HexToHash("0x22"), nil).Once() + } + mockFlow.EXPECT().BuildCertificate(mock.Anything, mock.Anything).Return(cert, nil).Once() + mockFlow.EXPECT().UpdateAggchainData(cert, mock.Anything).Return(nil).Once() + mockAgglayerClient.EXPECT().SendCertificate(mock.Anything, mock.Anything).Return(common.HexToHash("0x22"), nil).Once() mockStorage.EXPECT().SaveLastSentCertificate(mock.Anything, mock.Anything).Return(errors.New("some error")).Once() }, mockValidatorFn: func() *mocks.CertificateValidateAndSigner { mockValidator := mocks.NewCertificateValidateAndSigner(t) - mockValidator.EXPECT().HealthCheck(mock.Anything).Return(&aggsendertypes. - HealthCheckResponse{Status: aggsendertypes.HealthCheckStatusOK}, nil) - mockValidator.EXPECT().URL().Return("http://localhost") mockValidator.EXPECT(). ValidateAndSignCertificate(mock.Anything, mock.Anything, mock.Anything). Return(make([]byte, aggkitcommon.SignatureSize), nil).Once() @@ -369,37 +365,33 @@ func TestSendCertificate(t *testing.T) { }, expectedError: "error saving last sent certificate", }, - { - name: "error getting validator signature", - mockFn: func(mockStorage *mocks.AggSenderStorage, - mockFlow *mocks.AggsenderFlow, - mockAgglayerClient *agglayermocks.AgglayerClientMock) { - mockFlow.EXPECT().GetCertificateBuildParams(mock.Anything).Return(&aggsendertypes.CertificateBuildParams{ - Bridges: []bridgesync.Bridge{{}}, - }, nil).Once() - mockFlow.EXPECT().BuildCertificate(mock.Anything, mock.Anything).Return(&agglayertypes.Certificate{ - NetworkID: 1, - Height: 0, - NewLocalExitRoot: common.HexToHash("0x1"), - BridgeExits: []*agglayertypes.BridgeExit{{}}, - }, nil).Once() - // mockStorage.EXPECT().SaveNonAcceptedCertificate(mock.Anything, mock.Anything).Return(nil).Once() - mockAgglayerClient.EXPECT().SendCertificate(mock.Anything, mock.Anything, mock.Anything).Return(common.HexToHash("0x22"), nil).Once() - mockStorage.EXPECT().SaveLastSentCertificate(mock.Anything, mock.Anything).Return(nil).Once() - }, - mockValidatorFn: func() *mocks.CertificateValidateAndSigner { - mockValidator := mocks.NewCertificateValidateAndSigner(t) - mockValidator.EXPECT().HealthCheck(mock.Anything).Return(&aggsendertypes.HealthCheckResponse{Status: aggsendertypes.HealthCheckStatusOK}, nil) - mockValidator.EXPECT().URL().Return("http://localhost") - mockValidator.EXPECT().String().Return("local validator") - mockValidator.EXPECT().Address().Return(common.HexToAddress("0x1")) - mockValidator.EXPECT(). - ValidateAndSignCertificate(mock.Anything, mock.Anything, mock.Anything). - Return(nil, errors.New("some error")).Once() - return mockValidator - }, - // expectedError: "certificate validation failed: some error", // TODO - this will be fixed when the agglayer is ready - }, + // TODO - this will be fixed when the agglayer is ready + //{ + // name: "error getting validator signature", + // mockFn: func(mockStorage *mocks.AggSenderStorage, + // mockFlow *mocks.AggsenderFlow, + // mockAgglayerClient *agglayermocks.AgglayerClientMock) { + // mockFlow.EXPECT().GetCertificateBuildParams(mock.Anything).Return(&aggsendertypes.CertificateBuildParams{ + // Bridges: []bridgesync.Bridge{{}}, + // }, nil).Once() + // mockFlow.EXPECT().BuildCertificate(mock.Anything, mock.Anything).Return(&agglayertypes.Certificate{ + // NetworkID: 1, + // Height: 0, + // NewLocalExitRoot: common.HexToHash("0x1"), + // BridgeExits: []*agglayertypes.BridgeExit{{}}, + // }, nil).Once() + // mockAgglayerClient.EXPECT().SendCertificate(mock.Anything, mock.Anything).Return(common.HexToHash("0x22"), nil).Once() + // mockStorage.EXPECT().SaveLastSentCertificate(mock.Anything, mock.Anything).Return(nil).Once() + // }, + // mockValidatorFn: func() *mocks.CertificateValidateAndSigner { + // mockValidator := mocks.NewCertificateValidateAndSigner(t) + // mockValidator.EXPECT(). + // ValidateAndSignCertificate(mock.Anything, mock.Anything, mock.Anything). + // Return(nil, errors.New("some error")).Once() + // return mockValidator + // }, + // expectedError: "certificate validation failed: some error", + // }, { name: "successful validation and sending of a certificate", mockFn: func(mockStorage *mocks.AggSenderStorage, @@ -408,21 +400,20 @@ func TestSendCertificate(t *testing.T) { mockFlow.EXPECT().GetCertificateBuildParams(mock.Anything).Return(&aggsendertypes.CertificateBuildParams{ Bridges: []bridgesync.Bridge{{}}, }, nil).Once() - mockFlow.EXPECT().BuildCertificate(mock.Anything, mock.Anything).Return(&agglayertypes.Certificate{ + cert := &agglayertypes.Certificate{ NetworkID: 11, Height: 0, NewLocalExitRoot: common.HexToHash("0x11"), BridgeExits: []*agglayertypes.BridgeExit{{}}, - }, nil).Once() - mockAgglayerClient.EXPECT().SendCertificate(mock.Anything, mock.Anything, mock.Anything). + } + mockFlow.EXPECT().BuildCertificate(mock.Anything, mock.Anything).Return(cert, nil).Once() + mockFlow.EXPECT().UpdateAggchainData(cert, mock.Anything).Return(nil).Once() + mockAgglayerClient.EXPECT().SendCertificate(mock.Anything, mock.Anything). Return(common.HexToHash("0x22"), nil).Once() mockStorage.EXPECT().SaveLastSentCertificate(mock.Anything, mock.Anything).Return(nil).Once() }, mockValidatorFn: func() *mocks.CertificateValidateAndSigner { mockValidator := mocks.NewCertificateValidateAndSigner(t) - mockValidator.EXPECT().HealthCheck(mock.Anything).Return(&aggsendertypes. - HealthCheckResponse{Status: aggsendertypes.HealthCheckStatusOK}, nil) - mockValidator.EXPECT().URL().Return("http://localhost") mockValidator.EXPECT().ValidateAndSignCertificate(mock.Anything, mock.Anything, mock.Anything). Return(make([]byte, aggkitcommon.SignatureSize), nil).Once() return mockValidator @@ -436,20 +427,19 @@ func TestSendCertificate(t *testing.T) { mockFlow.EXPECT().GetCertificateBuildParams(mock.Anything).Return(&aggsendertypes.CertificateBuildParams{ Bridges: []bridgesync.Bridge{{}}, }, nil).Once() - mockFlow.EXPECT().BuildCertificate(mock.Anything, mock.Anything).Return(&agglayertypes.Certificate{ + cert := &agglayertypes.Certificate{ NetworkID: 11, Height: 0, NewLocalExitRoot: common.HexToHash("0x11"), BridgeExits: []*agglayertypes.BridgeExit{{}}, - }, nil).Once() - mockAgglayerClient.EXPECT().SendCertificate(mock.Anything, mock.Anything, mock.Anything).Return(common.HexToHash("0x22"), nil).Once() + } + mockFlow.EXPECT().BuildCertificate(mock.Anything, mock.Anything).Return(cert, nil).Once() + mockFlow.EXPECT().UpdateAggchainData(cert, mock.Anything).Return(nil).Once() + mockAgglayerClient.EXPECT().SendCertificate(mock.Anything, mock.Anything).Return(common.HexToHash("0x22"), nil).Once() mockStorage.EXPECT().SaveLastSentCertificate(mock.Anything, mock.Anything).Return(nil).Once() }, mockValidatorFn: func() *mocks.CertificateValidateAndSigner { mockValidator := mocks.NewCertificateValidateAndSigner(t) - mockValidator.EXPECT().HealthCheck(mock.Anything).Return(&aggsendertypes. - HealthCheckResponse{Status: aggsendertypes.HealthCheckStatusOK}, nil) - mockValidator.EXPECT().URL().Return("http://localhost") mockValidator.EXPECT(). ValidateAndSignCertificate(mock.Anything, mock.Anything, mock.Anything). Return(make([]byte, aggkitcommon.SignatureSize), nil).Once() @@ -527,8 +517,8 @@ func TestGetValidators(t *testing.T) { t.Helper() validators := make([]aggsendertypes.CertificateValidateAndSigner, 0, len(signers)) - for _, signer := range signers { - validator, err := validator.NewRemoteValidator(&grpc.ClientConfig{URL: signer.URL}, nil, signer.Address) + for i, signer := range signers { + validator, err := validator.NewRemoteValidator(&grpc.ClientConfig{URL: signer.URL}, nil, signer.Address, uint32(i)) require.NoError(t, err) validators = append(validators, validator) } @@ -780,6 +770,117 @@ func TestSendCertificates(t *testing.T) { } } +func TestPollValidators(t *testing.T) { + t.Parallel() + + certificate := &agglayertypes.Certificate{ + NetworkID: 1, + Height: 1, + } + + tests := []struct { + name string + setupMocks func() ([]aggsendertypes.CertificateValidateAndSigner, uint32) + expectedMinSigs int + expectErrSubstring string + }{ + { + name: "no validators configured", + expectedMinSigs: 0, + }, + { + name: "single healthy validator returns valid signature", + setupMocks: func() ([]aggsendertypes.CertificateValidateAndSigner, uint32) { + mockValidator := mocks.NewCertificateValidateAndSigner(t) + mockValidator.EXPECT().Index().Return(uint32(1)) + mockValidator.EXPECT(). + ValidateAndSignCertificate(mock.Anything, mock.Anything, mock.Anything). + Return(make([]byte, aggkitcommon.SignatureSize), nil). + Once() + return []aggsendertypes.CertificateValidateAndSigner{mockValidator}, 1 + }, + expectedMinSigs: 1, + }, + { + name: "multiple validators reach threshold", + setupMocks: func() ([]aggsendertypes.CertificateValidateAndSigner, uint32) { + v1 := mocks.NewCertificateValidateAndSigner(t) + v2 := mocks.NewCertificateValidateAndSigner(t) + v3 := mocks.NewCertificateValidateAndSigner(t) + + for i, v := range [](*mocks.CertificateValidateAndSigner){v1, v2, v3} { + v.EXPECT().Index().Return(uint32(i)) + v.EXPECT(). + ValidateAndSignCertificate(mock.Anything, mock.Anything, mock.Anything). + Return(make([]byte, aggkitcommon.SignatureSize), nil). + Times(1) + } + + validators := []aggsendertypes.CertificateValidateAndSigner{v1, v2, v3} + return validators, 2 + }, + expectedMinSigs: 2, + }, + { + name: "threshold not reached", + setupMocks: func() ([]aggsendertypes.CertificateValidateAndSigner, uint32) { + v1 := mocks.NewCertificateValidateAndSigner(t) + v2 := mocks.NewCertificateValidateAndSigner(t) + v3 := mocks.NewCertificateValidateAndSigner(t) + + for i, v := range [](*mocks.CertificateValidateAndSigner){v1, v2, v3} { + v.EXPECT().String().Return(fmt.Sprintf("validator-%d", i)) + v.EXPECT().Address().Return(common.HexToAddress(fmt.Sprintf("0x%d", i+1))) + v.EXPECT(). + ValidateAndSignCertificate(mock.Anything, mock.Anything, mock.Anything). + Return(nil, errors.New("validation failed")). + Times(1) + } + + validators := []aggsendertypes.CertificateValidateAndSigner{v1, v2, v3} + return validators, 2 + }, + expectErrSubstring: "threshold not reached", + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + var ( + validators []aggsendertypes.CertificateValidateAndSigner + threshold uint32 + ) + + if tc.setupMocks != nil { + validators, threshold = tc.setupMocks() + } + + agg := &AggSender{ + log: log.WithFields("test", "pollValidators"), + } + + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + multiSig, err := agg.pollValidatorCommittee(ctx, validators, threshold, certificate, 0) + + if tc.expectErrSubstring != "" { + require.ErrorContains(t, err, tc.expectErrSubstring) + } else if len(validators) == 0 { + require.Nil(t, multiSig) + require.NoError(t, err) + } else { + require.NoError(t, err) + require.NotNil(t, multiSig) + require.GreaterOrEqual(t, len(multiSig.Signatures), tc.expectedMinSigs) + } + }) + } +} + type testDataFlags = int const ( diff --git a/aggsender/flows/flow_aggchain_prover.go b/aggsender/flows/flow_aggchain_prover.go index d3972117d..13036b2ea 100644 --- a/aggsender/flows/flow_aggchain_prover.go +++ b/aggsender/flows/flow_aggchain_prover.go @@ -333,6 +333,36 @@ func (a *AggchainProverFlow) BuildCertificate(ctx context.Context, return signedCert, nil } +// UpdateAggchainData updates the AggchainData field in certificate with the multisig if needed +func (a *AggchainProverFlow) UpdateAggchainData( + cert *agglayertypes.Certificate, + multisig *agglayertypes.Multisig, +) error { + if multisig == nil { + // multisig not turned on, we don't need to update the certificate + return nil + } + + proof, ok := cert.AggchainData.(*agglayertypes.AggchainDataProof) + if !ok { + proofWithMultisig, ok := cert.AggchainData.(*agglayertypes.AggchainDataMultisigWithProof) + if !ok { + return errors.New("aggchainProverFlow - aggchain data field not " + + "AggchainDataProof nor AggchainDataMultisigWithProof") + } + + proof = proofWithMultisig.AggchainProof + } + + // update the agchain data with multisig + cert.AggchainData = &agglayertypes.AggchainDataMultisigWithProof{ + Multisig: multisig, + AggchainProof: proof, + } + + return nil +} + // adjustBlockRange adjusts the block range of the certificate to match the range returned by the aggchain prover func adjustBlockRange(buildParams *types.CertificateBuildParams, requestedToBlock, aggchainProverToBlock uint64) (*types.CertificateBuildParams, error) { diff --git a/aggsender/flows/flow_aggchain_prover_test.go b/aggsender/flows/flow_aggchain_prover_test.go index 16204983c..6ec16bc68 100644 --- a/aggsender/flows/flow_aggchain_prover_test.go +++ b/aggsender/flows/flow_aggchain_prover_test.go @@ -939,3 +939,109 @@ func Test_AggchainProverFlow_GenerateBuildParams(t *testing.T) { }) } } + +func Test_AggchainProverFlow_UpdateAggchainData(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + certificate *agglayertypes.Certificate + multisig *agglayertypes.Multisig + expectedError string + expectedCertificate *agglayertypes.Certificate + }{ + { + name: "multisig nil - returns original certificate unchanged", + certificate: &agglayertypes.Certificate{ + AggchainData: &agglayertypes.AggchainDataProof{ + Proof: []byte("orig-proof"), + }, + }, + multisig: nil, + expectedCertificate: &agglayertypes.Certificate{ + AggchainData: &agglayertypes.AggchainDataProof{ + Proof: []byte("orig-proof"), + }, + }, + }, + { + name: "aggchain data not AggchainDataProof - returns error", + certificate: &agglayertypes.Certificate{ + AggchainData: &agglayertypes.AggchainDataSignature{}, // wrong type + }, + multisig: &agglayertypes.Multisig{}, + expectedError: "aggchainProverFlow - aggchain data field not AggchainDataProof", + }, + { + name: "successful update - wraps proof with multisig", + certificate: &agglayertypes.Certificate{ + AggchainData: &agglayertypes.AggchainDataProof{ + Proof: []byte("some-proof"), + Version: "0.1", + Vkey: []byte("vkey"), + AggchainParams: common.HexToHash("0x2"), + Context: map[string][]byte{"k": []byte("v")}, + }, + }, + multisig: &agglayertypes.Multisig{}, + expectedCertificate: &agglayertypes.Certificate{ + AggchainData: &agglayertypes.AggchainDataMultisigWithProof{ + Multisig: &agglayertypes.Multisig{}, + AggchainProof: &agglayertypes.AggchainDataProof{ + Proof: []byte("some-proof"), + Version: "0.1", + Vkey: []byte("vkey"), + AggchainParams: common.HexToHash("0x2"), + Context: map[string][]byte{"k": []byte("v")}, + }, + }, + }, + }, + { + name: "successful update - with aggchain data proof with multisig", + certificate: &agglayertypes.Certificate{ + AggchainData: &agglayertypes.AggchainDataMultisigWithProof{ + AggchainProof: &agglayertypes.AggchainDataProof{ + Proof: []byte("some-proof"), + Version: "0.1", + Vkey: []byte("vkey"), + AggchainParams: common.HexToHash("0x2"), + Context: map[string][]byte{"k": []byte("v")}, + }, + }, + }, + multisig: &agglayertypes.Multisig{}, + expectedCertificate: &agglayertypes.Certificate{ + AggchainData: &agglayertypes.AggchainDataMultisigWithProof{ + Multisig: &agglayertypes.Multisig{}, + AggchainProof: &agglayertypes.AggchainDataProof{ + Proof: []byte("some-proof"), + Version: "0.1", + Vkey: []byte("vkey"), + AggchainParams: common.HexToHash("0x2"), + Context: map[string][]byte{"k": []byte("v")}, + }, + }, + }, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + logger := log.WithFields("flowManager", "Test_AggchainProverFlow_UpdateAggchainData") + flow := &AggchainProverFlow{ + log: logger, + } + + err := flow.UpdateAggchainData(tc.certificate, tc.multisig) + if tc.expectedError != "" { + require.ErrorContains(t, err, tc.expectedError) + } else { + require.NoError(t, err) + require.Equal(t, tc.expectedCertificate, tc.certificate) + } + }) + } +} diff --git a/aggsender/flows/flow_pp.go b/aggsender/flows/flow_pp.go index 545c51b67..b922536b6 100644 --- a/aggsender/flows/flow_pp.go +++ b/aggsender/flows/flow_pp.go @@ -130,6 +130,23 @@ func (p *PPFlow) BuildCertificate(ctx context.Context, return signedCert, nil } +// UpdateAggchainData updates the AggchainData field in certificate with the multisig if needed +func (p *PPFlow) UpdateAggchainData( + cert *agglayertypes.Certificate, + multisig *agglayertypes.Multisig) error { + if multisig == nil { + // multisig not turned on, we don't need to update the certificate + return nil + } + + // update the aggchain data with multisig + cert.AggchainData = &agglayertypes.AggchainDataMultisig{ + Multisig: multisig, + } + + return nil +} + // signCertificate signs a certificate with the aggsender key func (p *PPFlow) signCertificate(ctx context.Context, certificate *agglayertypes.Certificate) (*agglayertypes.Certificate, error) { diff --git a/aggsender/flows/flow_pp_test.go b/aggsender/flows/flow_pp_test.go index 8f15a3549..ecb3f1f4c 100644 --- a/aggsender/flows/flow_pp_test.go +++ b/aggsender/flows/flow_pp_test.go @@ -712,3 +712,51 @@ func Test_PPFlow_ValidateCertificate(t *testing.T) { }) } } + +func Test_PPFlow_UpdateAggchainData(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + certificate *agglayertypes.Certificate + multisig *agglayertypes.Multisig + expectedCertificate *agglayertypes.Certificate + }{ + { + name: "multisig nil - leaves certificate unchanged", + certificate: &agglayertypes.Certificate{ + AggchainData: &agglayertypes.AggchainDataSignature{ + Signature: []byte("orig_sig"), + }, + }, + multisig: nil, + expectedCertificate: &agglayertypes.Certificate{ + AggchainData: &agglayertypes.AggchainDataSignature{ + Signature: []byte("orig_sig"), + }, + }, + }, + { + name: "multisig provided - replaces AggchainData with multisig wrapper", + certificate: &agglayertypes.Certificate{}, + multisig: &agglayertypes.Multisig{}, + expectedCertificate: &agglayertypes.Certificate{ + AggchainData: &agglayertypes.AggchainDataMultisig{ + Multisig: &agglayertypes.Multisig{}, + }, + }, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + sut := &PPFlow{} + + err := sut.UpdateAggchainData(tc.certificate, tc.multisig) + require.NoError(t, err) + require.Equal(t, tc.expectedCertificate, tc.certificate) + }) + } +} diff --git a/aggsender/mocks/mock_aggsender_flow.go b/aggsender/mocks/mock_aggsender_flow.go index 61417950b..b3bee2290 100644 --- a/aggsender/mocks/mock_aggsender_flow.go +++ b/aggsender/mocks/mock_aggsender_flow.go @@ -247,6 +247,53 @@ func (_c *AggsenderFlow_GetCertificateBuildParams_Call) RunAndReturn(run func(co return _c } +// UpdateAggchainData provides a mock function with given fields: cert, multisig +func (_m *AggsenderFlow) UpdateAggchainData(cert *agglayertypes.Certificate, multisig *agglayertypes.Multisig) error { + ret := _m.Called(cert, multisig) + + if len(ret) == 0 { + panic("no return value specified for UpdateAggchainData") + } + + var r0 error + if rf, ok := ret.Get(0).(func(*agglayertypes.Certificate, *agglayertypes.Multisig) error); ok { + r0 = rf(cert, multisig) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// AggsenderFlow_UpdateAggchainData_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateAggchainData' +type AggsenderFlow_UpdateAggchainData_Call struct { + *mock.Call +} + +// UpdateAggchainData is a helper method to define mock.On call +// - cert *agglayertypes.Certificate +// - multisig *agglayertypes.Multisig +func (_e *AggsenderFlow_Expecter) UpdateAggchainData(cert interface{}, multisig interface{}) *AggsenderFlow_UpdateAggchainData_Call { + return &AggsenderFlow_UpdateAggchainData_Call{Call: _e.mock.On("UpdateAggchainData", cert, multisig)} +} + +func (_c *AggsenderFlow_UpdateAggchainData_Call) Run(run func(cert *agglayertypes.Certificate, multisig *agglayertypes.Multisig)) *AggsenderFlow_UpdateAggchainData_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(*agglayertypes.Certificate), args[1].(*agglayertypes.Multisig)) + }) + return _c +} + +func (_c *AggsenderFlow_UpdateAggchainData_Call) Return(_a0 error) *AggsenderFlow_UpdateAggchainData_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *AggsenderFlow_UpdateAggchainData_Call) RunAndReturn(run func(*agglayertypes.Certificate, *agglayertypes.Multisig) error) *AggsenderFlow_UpdateAggchainData_Call { + _c.Call.Return(run) + return _c +} + // ValidateCertificate provides a mock function with given fields: ctx, cert func (_m *AggsenderFlow) ValidateCertificate(ctx context.Context, cert *agglayertypes.Certificate) error { ret := _m.Called(ctx, cert) diff --git a/aggsender/mocks/mock_certificate_validate_and_signer.go b/aggsender/mocks/mock_certificate_validate_and_signer.go index b7e72f019..cd65e1de6 100644 --- a/aggsender/mocks/mock_certificate_validate_and_signer.go +++ b/aggsender/mocks/mock_certificate_validate_and_signer.go @@ -131,6 +131,51 @@ func (_c *CertificateValidateAndSigner_HealthCheck_Call) RunAndReturn(run func(c return _c } +// Index provides a mock function with no fields +func (_m *CertificateValidateAndSigner) Index() uint32 { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Index") + } + + var r0 uint32 + if rf, ok := ret.Get(0).(func() uint32); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(uint32) + } + + return r0 +} + +// CertificateValidateAndSigner_Index_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Index' +type CertificateValidateAndSigner_Index_Call struct { + *mock.Call +} + +// Index is a helper method to define mock.On call +func (_e *CertificateValidateAndSigner_Expecter) Index() *CertificateValidateAndSigner_Index_Call { + return &CertificateValidateAndSigner_Index_Call{Call: _e.mock.On("Index")} +} + +func (_c *CertificateValidateAndSigner_Index_Call) Run(run func()) *CertificateValidateAndSigner_Index_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *CertificateValidateAndSigner_Index_Call) Return(_a0 uint32) *CertificateValidateAndSigner_Index_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *CertificateValidateAndSigner_Index_Call) RunAndReturn(run func() uint32) *CertificateValidateAndSigner_Index_Call { + _c.Call.Return(run) + return _c +} + // String provides a mock function with no fields func (_m *CertificateValidateAndSigner) String() string { ret := _m.Called() diff --git a/aggsender/query/certificate_query.go b/aggsender/query/certificate_query.go index 2f44f6197..45935a621 100644 --- a/aggsender/query/certificate_query.go +++ b/aggsender/query/certificate_query.go @@ -78,12 +78,12 @@ func (c *certificateQuerier) GetLastSettledCertificateToBlock( } // 2. Get the latest settled imported bridge exit block number - networkStatus, err := c.agglayerClient.GetNetworkStatus(ctx, cert.NetworkID) + networkState, err := c.agglayerClient.GetNetworkState(ctx, cert.NetworkID) if err != nil { return 0, fmt.Errorf("failed to get latest settled imported bridge exit from agglayer: %w", err) } - settledIBE := networkStatus.SettledImportedBridgeExit + settledIBE := networkState.SettledImportedBridgeExit if settledIBE != nil { lastImportedBridgeExitBlock, err = c.getBlockNumFromGlobalIndex(ctx, settledIBE.GlobalIndex, settledIBE.BridgeExitHash) diff --git a/aggsender/query/certificate_query_test.go b/aggsender/query/certificate_query_test.go index 7e0e75f2d..bfa9bd4c1 100644 --- a/aggsender/query/certificate_query_test.go +++ b/aggsender/query/certificate_query_test.go @@ -47,13 +47,13 @@ func TestGetLastSettledCertificateToBlock(t *testing.T) { BlockNum: uint64(100), }, nil) - networkStatus := agglayertypes.NetworkStatus{ + networkStatus := agglayertypes.NetworkState{ SettledImportedBridgeExit: &agglayertypes.SettledImportedBridgeExit{ BridgeExitHash: common.HexToHash("0xe3e297278c7df4ae4f235be10155ac62c53b08e2a14ed09b7dd6b688952ee883"), GlobalIndex: bridgesync.GenerateGlobalIndex(true, 0, 1), }, } - agglayerClient.EXPECT().GetNetworkStatus(ctx, uint32(0)).Return(networkStatus, nil) + agglayerClient.EXPECT().GetNetworkState(ctx, uint32(0)).Return(networkStatus, nil) bridgeSyncer.EXPECT().GetClaimsByGlobalIndex(ctx, networkStatus.SettledImportedBridgeExit.GlobalIndex).Return([]bridgesync.Claim{ { @@ -73,13 +73,13 @@ func TestGetLastSettledCertificateToBlock(t *testing.T) { NewLocalExitRoot: types.EmptyLER, }, mockFn: func(aggchainQuerier *mocks.AggchainFEPRollupQuerier, agglayerClient *agglayermocks.AgglayerClientMock, bridgeSyncer *mocks.L2BridgeSyncer) { - networkStatus := agglayertypes.NetworkStatus{ + networkStatus := agglayertypes.NetworkState{ SettledImportedBridgeExit: &agglayertypes.SettledImportedBridgeExit{ GlobalIndex: bridgesync.GenerateGlobalIndex(true, 0, 1), BridgeExitHash: common.HexToHash("0xe3e297278c7df4ae4f235be10155ac62c53b08e2a14ed09b7dd6b688952ee883"), }, } - agglayerClient.EXPECT().GetNetworkStatus(ctx, uint32(0)).Return(networkStatus, nil) + agglayerClient.EXPECT().GetNetworkState(ctx, uint32(0)).Return(networkStatus, nil) bridgeSyncer.EXPECT().GetClaimsByGlobalIndex(ctx, networkStatus.SettledImportedBridgeExit.GlobalIndex).Return([]bridgesync.Claim{ { BlockNum: 50, @@ -100,8 +100,8 @@ func TestGetLastSettledCertificateToBlock(t *testing.T) { bridgeSyncer.EXPECT().GetExitRootByHash(ctx, common.HexToHash("0x456")).Return(&treetypes.Root{ BlockNum: uint64(300), }, nil) - networkStatus := agglayertypes.NetworkStatus{} - agglayerClient.EXPECT().GetNetworkStatus(ctx, uint32(0)).Return(networkStatus, nil) + networkStatus := agglayertypes.NetworkState{} + agglayerClient.EXPECT().GetNetworkState(ctx, uint32(0)).Return(networkStatus, nil) aggchainQuerier.EXPECT().GetLastSettledL2Block().Return(uint64(250), nil) }, expectedBlock: 300, // max of 300, 0, 250 @@ -124,7 +124,7 @@ func TestGetLastSettledCertificateToBlock(t *testing.T) { NewLocalExitRoot: types.EmptyLER, }, mockFn: func(aggchainQuerier *mocks.AggchainFEPRollupQuerier, agglayerClient *agglayermocks.AgglayerClientMock, bridgeSyncer *mocks.L2BridgeSyncer) { - agglayerClient.EXPECT().GetNetworkStatus(ctx, uint32(0)).Return(agglayertypes.NetworkStatus{}, errors.New("agglayer error")) + agglayerClient.EXPECT().GetNetworkState(ctx, uint32(0)).Return(agglayertypes.NetworkState{}, errors.New("agglayer error")) }, expectedErr: "failed to get latest settled imported bridge exit from agglayer", }, @@ -135,13 +135,13 @@ func TestGetLastSettledCertificateToBlock(t *testing.T) { NewLocalExitRoot: types.EmptyLER, }, mockFn: func(aggchainQuerier *mocks.AggchainFEPRollupQuerier, agglayerClient *agglayermocks.AgglayerClientMock, bridgeSyncer *mocks.L2BridgeSyncer) { - networkStatus := agglayertypes.NetworkStatus{ + networkStatus := agglayertypes.NetworkState{ SettledImportedBridgeExit: &agglayertypes.SettledImportedBridgeExit{ GlobalIndex: bridgesync.GenerateGlobalIndex(true, 0, 1), BridgeExitHash: common.HexToHash("0xe3e297278c7df4ae4f235be10155ac62c53b08e2a14ed09b7dd6b688952ee883"), }, } - agglayerClient.EXPECT().GetNetworkStatus(ctx, uint32(0)).Return(networkStatus, nil) + agglayerClient.EXPECT().GetNetworkState(ctx, uint32(0)).Return(networkStatus, nil) bridgeSyncer.EXPECT().GetClaimsByGlobalIndex(ctx, networkStatus.SettledImportedBridgeExit.GlobalIndex).Return(nil, errors.New("claim not found")) }, expectedErr: "failed to get claim by global index", @@ -153,7 +153,7 @@ func TestGetLastSettledCertificateToBlock(t *testing.T) { NewLocalExitRoot: types.EmptyLER, }, mockFn: func(aggchainQuerier *mocks.AggchainFEPRollupQuerier, agglayerClient *agglayermocks.AgglayerClientMock, bridgeSyncer *mocks.L2BridgeSyncer) { - agglayerClient.EXPECT().GetNetworkStatus(ctx, uint32(0)).Return(agglayertypes.NetworkStatus{}, nil) + agglayerClient.EXPECT().GetNetworkState(ctx, uint32(0)).Return(agglayertypes.NetworkState{}, nil) aggchainQuerier.EXPECT().GetLastSettledL2Block().Return(uint64(0), errors.New("L2 block query failed")) }, expectedErr: "failed to get last settled L2 block", @@ -165,7 +165,7 @@ func TestGetLastSettledCertificateToBlock(t *testing.T) { NewLocalExitRoot: types.EmptyLER, }, mockFn: func(aggchainQuerier *mocks.AggchainFEPRollupQuerier, agglayerClient *agglayermocks.AgglayerClientMock, bridgeSyncer *mocks.L2BridgeSyncer) { - agglayerClient.EXPECT().GetNetworkStatus(ctx, uint32(0)).Return(agglayertypes.NetworkStatus{}, nil) + agglayerClient.EXPECT().GetNetworkState(ctx, uint32(0)).Return(agglayertypes.NetworkState{}, nil) aggchainQuerier.EXPECT().GetLastSettledL2Block().Return(uint64(0), nil) }, expectedBlock: 0, diff --git a/aggsender/types/interfaces.go b/aggsender/types/interfaces.go index 11c54278c..426bfc398 100644 --- a/aggsender/types/interfaces.go +++ b/aggsender/types/interfaces.go @@ -32,6 +32,8 @@ type AggsenderFlow interface { preParams *CertificatePreBuildParams) (*CertificateBuildParams, error) // ValidateCertificate validates the built certificate ValidateCertificate(ctx context.Context, cert *agglayertypes.Certificate) error + // UpdateAggchainData updates the aggchain data field for the given certificate + UpdateAggchainData(cert *agglayertypes.Certificate, multisig *agglayertypes.Multisig) error } type AggsenderFlowBaser interface { @@ -50,7 +52,6 @@ type AggsenderFlowBaser interface { newFromBlock, newToBlock uint64) error ConvertClaimToImportedBridgeExit(claim bridgesync.Claim) (*agglayertypes.ImportedBridgeExit, error) StartL2Block() uint64 - GeneratePreBuildParams(ctx context.Context, certType CertificateType) (*CertificatePreBuildParams, error) GenerateBuildParams(ctx context.Context, @@ -232,6 +233,7 @@ type CertificateValidateAndSigner interface { URL() string String() string Address() common.Address + Index() uint32 } // ValidatorClient is an interface defining functions that a ValidatorClient should implement diff --git a/aggsender/types/multisig_committee.go b/aggsender/types/multisig_committee.go index 31c5b3dbe..d6be4500c 100644 --- a/aggsender/types/multisig_committee.go +++ b/aggsender/types/multisig_committee.go @@ -23,7 +23,10 @@ type SignerInfo struct { // NewSignerInfo creates a new instance of a signer func NewSignerInfo(url string, address common.Address) *SignerInfo { - return &SignerInfo{URL: url, Address: address} + return &SignerInfo{ + URL: url, + Address: address, + } } // MultisigCommittee represents a set of authorized signers with a signing threshold. diff --git a/aggsender/validator/cert_hash_test.go b/aggsender/validator/cert_hash_test.go index d1504046b..3bcd8d0cb 100644 --- a/aggsender/validator/cert_hash_test.go +++ b/aggsender/validator/cert_hash_test.go @@ -1,13 +1,12 @@ package validator import ( - "encoding/json" + "math/big" "testing" agglayertypes "github.com/agglayer/aggkit/agglayer/types" - "github.com/agglayer/aggkit/aggsender/db" - "github.com/agglayer/aggkit/aggsender/types" - "github.com/agglayer/aggkit/log" + aggkitcommon "github.com/agglayer/aggkit/common" + "github.com/agglayer/aggkit/tree" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" ) @@ -43,50 +42,138 @@ func TestHashCertificateToSign(t *testing.T) { }) t.Run("check imported fields on hash", func(t *testing.T) { - _, cert := getCertFromAggsenderDBForTest(t) + cert := getTestCert(t) hash, err := HashCertificateToSign(cert) require.NoError(t, err) - require.Equal(t, "0x4e43189545291d5d69db51da28e7534b4fc1d501602c454111778f987a012977", hash.String()) + require.Equal(t, "0xfeed673df1cdbe38aef628f0417c22ce4439c64f452090b5b4845ba799a9b10e", hash.String()) cert.NetworkID += 1 hash, err = HashCertificateToSign(cert) require.NoError(t, err) - require.Equal(t, "0xd19f41bf5692eefcbf8efbfeed974ac9adac836abd418504c48b1f25f9480bf5", hash.String()) + require.Equal(t, "0xbc2d8f9ea5e2b024d5007139c2b5d282d4e9a2e745340960dc5a1bc0b5703524", hash.String()) cert.Height += 1 hash, err = HashCertificateToSign(cert) require.NoError(t, err) - require.Equal(t, "0x53054a4b3a9b64077e38e29726d51307303f040d9624f8399492c714ad74f268", hash.String()) + require.Equal(t, "0xa504f0f8deceb412de5902c2acec8565eab9f2ae3d70e6262615fab88c317a14", hash.String()) cert.Metadata = [32]byte{6, 7, 8, 9, 10} hash, err = HashCertificateToSign(cert) require.NoError(t, err) - require.Equal(t, "0xbb5243f1087a7e1fdb23954f20e03ac8b9b8aca0e01f2a7c38f16d1c23fbf4f1", hash.String()) + require.Equal(t, "0x87b7ebf8ed82ad9cb49e0fd11ef79ebb7890afb19d08262e74d1690fbaa651b8", hash.String()) }) } func TestCertificateIdHash(t *testing.T) { - cert, unmarshalCert := getCertFromAggsenderDBForTest(t) - id := unmarshalCert.CertificateID() - require.Equal(t, cert.Header.CertificateID, id) - hash, err := HashCertificateToSign(unmarshalCert) + cert := getTestCert(t) + hash, err := HashCertificateToSign(cert) require.NoError(t, err) - require.Equal(t, "0x4e43189545291d5d69db51da28e7534b4fc1d501602c454111778f987a012977", hash.String()) + require.Equal(t, "0xfeed673df1cdbe38aef628f0417c22ce4439c64f452090b5b4845ba799a9b10e", hash.String()) } // Returns aggsender and agglayer cert -func getCertFromAggsenderDBForTest(t *testing.T) (*types.Certificate, *agglayertypes.Certificate) { +func getTestCert(t *testing.T) *agglayertypes.Certificate { t.Helper() - dbPath := "testData/aggsender.sqlite" - cfg := db.AggSenderSQLStorageConfig{ - DBPath: dbPath, - KeepCertificatesHistory: true, + return &agglayertypes.Certificate{ + AggchainData: &agglayertypes.AggchainDataProof{ + Proof: []byte{0x01}, + AggchainParams: common.HexToHash("0x010203"), + }, + NetworkID: 1, + Height: 100, + PrevLocalExitRoot: common.HexToHash("0x010201"), + NewLocalExitRoot: common.HexToHash("0x010202"), + Metadata: aggkitcommon.ZeroHash, + CustomChainData: []byte{0x1, 0x2, 0x3}, + L1InfoTreeLeafCount: 11, + BridgeExits: []*agglayertypes.BridgeExit{ + { + LeafType: agglayertypes.LeafTypeAsset, + TokenInfo: &agglayertypes.TokenInfo{ + OriginNetwork: 2, + OriginTokenAddress: common.HexToAddress("0x010203"), + }, + DestinationNetwork: 1, + DestinationAddress: common.HexToAddress("0x010204"), + Amount: big.NewInt(100), + }, + }, + ImportedBridgeExits: []*agglayertypes.ImportedBridgeExit{ + { + BridgeExit: &agglayertypes.BridgeExit{ + LeafType: agglayertypes.LeafTypeAsset, + TokenInfo: &agglayertypes.TokenInfo{ + OriginNetwork: 1, + OriginTokenAddress: common.HexToAddress("0x01111"), + }, + DestinationNetwork: 2, + DestinationAddress: common.HexToAddress("0x011112"), + Amount: big.NewInt(101), + }, + GlobalIndex: &agglayertypes.GlobalIndex{ + MainnetFlag: true, + RollupIndex: 0, + LeafIndex: 1, + }, + ClaimData: &agglayertypes.ClaimFromMainnet{ + ProofLeafMER: &agglayertypes.MerkleProof{ + Root: common.HexToHash("0x010203"), + Proof: tree.EmptyProof, + }, + ProofGERToL1Root: &agglayertypes.MerkleProof{ + Root: common.HexToHash("0x0102011"), + Proof: tree.EmptyProof, + }, + L1Leaf: &agglayertypes.L1InfoTreeLeaf{ + L1InfoTreeIndex: 1, + RollupExitRoot: common.HexToHash("0x0102012"), + MainnetExitRoot: common.HexToHash("0x0102013"), + Inner: &agglayertypes.L1InfoTreeLeafInner{ + GlobalExitRoot: common.HexToHash("0x0102014"), + BlockHash: common.HexToHash("0x0102015"), + Timestamp: 1234567890, + }, + }, + }, + }, + { + BridgeExit: &agglayertypes.BridgeExit{ + LeafType: agglayertypes.LeafTypeMessage, + TokenInfo: &agglayertypes.TokenInfo{ + OriginNetwork: 11, + OriginTokenAddress: common.HexToAddress("0x011"), + }, + DestinationNetwork: 22, + DestinationAddress: common.HexToAddress("0x012"), + }, + GlobalIndex: &agglayertypes.GlobalIndex{ + MainnetFlag: false, + RollupIndex: 11, + LeafIndex: 2, + }, + ClaimData: &agglayertypes.ClaimFromRollup{ + ProofLeafLER: &agglayertypes.MerkleProof{ + Root: common.HexToHash("0x0112"), + Proof: tree.EmptyProof, + }, + ProofGERToL1Root: &agglayertypes.MerkleProof{ + Root: common.HexToHash("0x0122"), + Proof: tree.EmptyProof, + }, + ProofLERToRER: &agglayertypes.MerkleProof{ + Root: common.HexToHash("0x0123"), + Proof: tree.EmptyProof, + }, + L1Leaf: &agglayertypes.L1InfoTreeLeaf{ + L1InfoTreeIndex: 2, + RollupExitRoot: common.HexToHash("0x11"), + MainnetExitRoot: common.HexToHash("0x12"), + Inner: &agglayertypes.L1InfoTreeLeafInner{ + GlobalExitRoot: common.HexToHash("0x13"), + BlockHash: common.HexToHash("0x14"), + Timestamp: 122222, + }, + }, + }, + }, + }, } - logger := log.WithFields("test", "TestCertificateHash") - database, err := db.NewAggSenderSQLStorage(logger, cfg) - require.NoError(t, err) - cert, err := database.GetLastSentCertificate() - require.NoError(t, err) - var unmarshalCert *agglayertypes.Certificate - err = json.Unmarshal([]byte(*cert.SignedCertificate), &unmarshalCert) - require.NoError(t, err) - return cert, unmarshalCert } diff --git a/aggsender/validator/compare_certs.go b/aggsender/validator/compare_certs.go index 60d82fc66..8792ffa8f 100644 --- a/aggsender/validator/compare_certs.go +++ b/aggsender/validator/compare_certs.go @@ -57,6 +57,10 @@ func DiffsCertificate( // BridgeExits diffs = append(diffs, DiffsBridgeExits(expectedCertificate.BridgeExits, validatingCertificate.BridgeExits)...) + // ImportedBridge exits + diffs = append(diffs, DiffsImportedBridgeExits(expectedCertificate.ImportedBridgeExits, + validatingCertificate.ImportedBridgeExits)...) + return diffs } @@ -80,3 +84,28 @@ func DiffsBridgeExits( } return diffs } + +// DiffsImportedBridgeExits compares two slices of ImportedBridgeExit and returns a slice of strings +// containing the differences between them. +func DiffsImportedBridgeExits( + expected []*agglayertypes.ImportedBridgeExit, + validating []*agglayertypes.ImportedBridgeExit) []string { + diffs := make([]string, 0) + if len(expected) != len(validating) { + diffs = append(diffs, fmt.Sprintf("ImportedBridgeExits length mismatch. Expected: %d, Certificate: %d", + len(expected), len(validating))) + return diffs + } + + for i, expectedImportedBridge := range expected { + importedBridgeValidating := validating[i] + + // check if global index matches + if importedBridgeValidating.GlobalIndex.Hash() != expectedImportedBridge.GlobalIndex.Hash() { + diffs = append(diffs, fmt.Sprintf("ImportedBridgeExit %d GlobalIndex mismatch. Expected: %s, Certificate: %s", + i, expectedImportedBridge.GlobalIndex.String(), importedBridgeValidating.GlobalIndex.String())) + } + } + + return diffs +} diff --git a/aggsender/validator/compare_certs_test.go b/aggsender/validator/compare_certs_test.go index 0016eb6b0..b3bc1905e 100644 --- a/aggsender/validator/compare_certs_test.go +++ b/aggsender/validator/compare_certs_test.go @@ -52,6 +52,20 @@ func TestDiffsCertificates(t *testing.T) { }, DiffsCertificate( &agglayertypes.Certificate{L1InfoTreeLeafCount: 123}, &agglayertypes.Certificate{})) + + require.Equal(t, []string{ + "BridgeExits length mismatch. Expected: 1, Certificate: 0", + }, DiffsCertificate( + &agglayertypes.Certificate{BridgeExits: []*agglayertypes.BridgeExit{ + {DestinationNetwork: 1}, + }}, + &agglayertypes.Certificate{})) + + require.Equal(t, []string{ + "ImportedBridgeExits length mismatch. Expected: 1, Certificate: 0", + }, DiffsCertificate( + &agglayertypes.Certificate{ImportedBridgeExits: []*agglayertypes.ImportedBridgeExit{{}}}, + &agglayertypes.Certificate{})) } func TestDiffsBridgeExits(t *testing.T) { @@ -82,3 +96,62 @@ func TestDiffsBridgeExits(t *testing.T) { }, })) } + +func TestDiffsImportedBridgeExits(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + expected []*agglayertypes.ImportedBridgeExit + validating []*agglayertypes.ImportedBridgeExit + want []string + }{ + { + name: "both nil -> no diffs", + expected: nil, + validating: nil, + want: []string{}, + }, + { + name: "expected longer -> length mismatch", + expected: []*agglayertypes.ImportedBridgeExit{{}}, + validating: nil, + want: []string{"ImportedBridgeExits length mismatch. Expected: 1, Certificate: 0"}, + }, + { + name: "validating longer -> length mismatch", + expected: nil, + validating: []*agglayertypes.ImportedBridgeExit{{}}, + want: []string{"ImportedBridgeExits length mismatch. Expected: 0, Certificate: 1"}, + }, + { + name: "same length, different content -> no diffs (no content comparison implemented)", + expected: []*agglayertypes.ImportedBridgeExit{ + {GlobalIndex: &agglayertypes.GlobalIndex{MainnetFlag: true, RollupIndex: 0, LeafIndex: 1}}, + }, + validating: []*agglayertypes.ImportedBridgeExit{ + {GlobalIndex: &agglayertypes.GlobalIndex{MainnetFlag: true, RollupIndex: 0, LeafIndex: 1}}, + }, + want: []string{}, + }, + { + name: "global index mismatch", + expected: []*agglayertypes.ImportedBridgeExit{ + {GlobalIndex: &agglayertypes.GlobalIndex{MainnetFlag: true, RollupIndex: 0, LeafIndex: 1}}, + }, + validating: []*agglayertypes.ImportedBridgeExit{ + {GlobalIndex: &agglayertypes.GlobalIndex{MainnetFlag: true, RollupIndex: 0, LeafIndex: 2}}, + }, + want: []string{"ImportedBridgeExit 0 GlobalIndex mismatch. Expected: MainnetFlag: true, RollupIndex: 0, LeafIndex: 1, Certificate: MainnetFlag: true, RollupIndex: 0, LeafIndex: 2"}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + got := DiffsImportedBridgeExits(tc.expected, tc.validating) + require.Equal(t, tc.want, got) + }) + } +} diff --git a/aggsender/validator/local_validator.go b/aggsender/validator/local_validator.go index 379b38045..d73de4310 100644 --- a/aggsender/validator/local_validator.go +++ b/aggsender/validator/local_validator.go @@ -54,6 +54,12 @@ func (a *LocalValidator) Address() common.Address { return common.Address{} } +// Index returns the index of the validator in the signers list +// For local validator it is always 0 +func (a *LocalValidator) Index() uint32 { + return 0 +} + func (a *LocalValidator) HealthCheck(ctx context.Context) (*types.HealthCheckResponse, error) { return &types.HealthCheckResponse{ Status: "OK", diff --git a/aggsender/validator/remote_validator.go b/aggsender/validator/remote_validator.go index 3b272b136..606b76091 100644 --- a/aggsender/validator/remote_validator.go +++ b/aggsender/validator/remote_validator.go @@ -20,12 +20,17 @@ type RemoteValidator struct { address common.Address client types.ValidatorClient storage db.AggSenderStorage + index uint32 } // NewRemoteValidator initializes a new RemoteValidator with the provided gRPC client configuration. // It returns an error if the gRPC client cannot be created. -func NewRemoteValidator(cfg *grpc.ClientConfig, - storage db.AggSenderStorage, address common.Address) (*RemoteValidator, error) { +func NewRemoteValidator( + cfg *grpc.ClientConfig, + storage db.AggSenderStorage, + address common.Address, + index uint32, +) (*RemoteValidator, error) { client, err := NewValidatorClient(cfg) if err != nil { return nil, err @@ -36,6 +41,7 @@ func NewRemoteValidator(cfg *grpc.ClientConfig, client: client, storage: storage, address: address, + index: index, }, nil } @@ -54,6 +60,11 @@ func (v *RemoteValidator) Address() common.Address { return v.address } +// Index is the index of the signer in the signers list on the Multisig contract +func (v *RemoteValidator) Index() uint32 { + return v.index +} + // HealthCheck performs a health check on the AggsenderValidator service. func (v *RemoteValidator) HealthCheck(ctx context.Context) (*types.HealthCheckResponse, error) { return v.client.HealthCheck(ctx) diff --git a/aggsender/validator/validate_certificate.go b/aggsender/validator/validate_certificate.go index 2e5df4ea6..8f6b1b81e 100644 --- a/aggsender/validator/validate_certificate.go +++ b/aggsender/validator/validate_certificate.go @@ -117,6 +117,12 @@ func (a *CertificateValidator) ValidateCertificate(ctx context.Context, params t return fmt.Errorf("failed flow.ValidateCertificate: %w", err) } + // Compare the incoming certificate with the one generated + err = a.compareCertificates(params.Certificate, certificate) + if err != nil { + return fmt.Errorf("certificate not equal to expected: %w", err) + } + // Verify claim proofs if err := a.verifyClaimProofs( params.Certificate.ImportedBridgeExits, @@ -124,12 +130,6 @@ func (a *CertificateValidator) ValidateCertificate(ctx context.Context, params t return fmt.Errorf("failed to verify claim proofs: %w", err) } - // Compare the incoming certificate with the one generated - err = a.compareCertificates(params.Certificate, certificate) - if err != nil { - return fmt.Errorf("certificate not equal to expected: %w", err) - } - return nil } diff --git a/go.mod b/go.mod index 9efbfffb9..a6d927351 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,9 @@ module github.com/agglayer/aggkit go 1.24.6 require ( - buf.build/gen/go/agglayer/agglayer/grpc/go v1.5.1-00000000000000-f90c5d0c4997.2 - buf.build/gen/go/agglayer/agglayer/protocolbuffers/go v1.36.8-00000000000000-f90c5d0c4997.1 - buf.build/gen/go/agglayer/interop/protocolbuffers/go v1.36.8-20250519093743-85e8a3d9f59c.1 + buf.build/gen/go/agglayer/agglayer/grpc/go v1.5.1-00000000000000-c02e21a8ce3e.2 + buf.build/gen/go/agglayer/agglayer/protocolbuffers/go v1.36.8-00000000000000-c02e21a8ce3e.1 + buf.build/gen/go/agglayer/interop/protocolbuffers/go v1.36.8-00000000000000-29048f73ad53.1 buf.build/gen/go/agglayer/provers/grpc/go v1.5.1-20250520163122-7efa0a2f81a8.2 buf.build/gen/go/agglayer/provers/protocolbuffers/go v1.36.7-20250520163122-7efa0a2f81a8.1 github.com/0xPolygon/cdk-contracts-tooling v0.0.8 diff --git a/go.sum b/go.sum index f5c8e834f..c5ca36e68 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,9 @@ -buf.build/gen/go/agglayer/agglayer/grpc/go v1.5.1-00000000000000-f90c5d0c4997.2 h1:QJxR0y+PDtJDdS/wLDI9zPLbqYGw/hpBO8gL9IvEd2Q= -buf.build/gen/go/agglayer/agglayer/grpc/go v1.5.1-00000000000000-f90c5d0c4997.2/go.mod h1:xAHXtiEcd245Zgu5vmdR3parOZbpsStrQZgGSKVKW7M= -buf.build/gen/go/agglayer/agglayer/protocolbuffers/go v1.36.8-00000000000000-f90c5d0c4997.1 h1:iksFpz9bMgOCZtPV5Jw72K9W++zdiRkgO2E/u+Esj6E= -buf.build/gen/go/agglayer/agglayer/protocolbuffers/go v1.36.8-00000000000000-f90c5d0c4997.1/go.mod h1:OT5cDxRQ5ixa5nUeaOCY5ApXI52SXPts12ZLqkUmMHU= -buf.build/gen/go/agglayer/interop/protocolbuffers/go v1.36.8-20250519093743-85e8a3d9f59c.1 h1:9/nz+/m8tkTsgjUtIIpFKO9lWbl/iz1wqfOqNNxRnxo= -buf.build/gen/go/agglayer/interop/protocolbuffers/go v1.36.8-20250519093743-85e8a3d9f59c.1/go.mod h1:xwTWh+PhAFSJfEUfDHHMjwoBXlmT1OAtiJzKfZ0pWDY= +buf.build/gen/go/agglayer/agglayer/grpc/go v1.5.1-00000000000000-c02e21a8ce3e.2 h1:YQJQIq+YXvKuWqBQnrvwFmLCP3v5sbtubIMh0JZZR8M= +buf.build/gen/go/agglayer/agglayer/grpc/go v1.5.1-00000000000000-c02e21a8ce3e.2/go.mod h1:BgHTUzH+OaypPErP+AexT1K+seZ4OKQ3CNOivBnAq2k= +buf.build/gen/go/agglayer/agglayer/protocolbuffers/go v1.36.8-00000000000000-c02e21a8ce3e.1 h1:YNB4jwuDEaumll6C8iy+75EyYJBl7zAlUDmyD27b1pg= +buf.build/gen/go/agglayer/agglayer/protocolbuffers/go v1.36.8-00000000000000-c02e21a8ce3e.1/go.mod h1:NIF+RZUPROC4aUuPjGAA3p1so3i1Bb1k5Ivhj9yKKAY= +buf.build/gen/go/agglayer/interop/protocolbuffers/go v1.36.8-00000000000000-29048f73ad53.1 h1:q0qzn8nxZ2e/ZRZL4+FwH/A60rB5nnTXigNGXSSEqmY= +buf.build/gen/go/agglayer/interop/protocolbuffers/go v1.36.8-00000000000000-29048f73ad53.1/go.mod h1:xwTWh+PhAFSJfEUfDHHMjwoBXlmT1OAtiJzKfZ0pWDY= buf.build/gen/go/agglayer/provers/grpc/go v1.5.1-20250520163122-7efa0a2f81a8.2 h1:gV+6xXfMHDXA4XI5jCpJmMITl6A51SUaoECeRO4/GAY= buf.build/gen/go/agglayer/provers/grpc/go v1.5.1-20250520163122-7efa0a2f81a8.2/go.mod h1:NJnqmcfr760oCyvQ8MC6H8HsXRTHtzqAkzhEj+ms0uU= buf.build/gen/go/agglayer/provers/protocolbuffers/go v1.36.7-20250520163122-7efa0a2f81a8.1 h1:5tWNGmDGpAEMrQ0yS+C9fAH48rAmXi3lEW2tRXRK9qQ=