From 0e4addf6001ed260547479a0884546df592806ba Mon Sep 17 00:00:00 2001 From: Arnau Date: Tue, 29 Oct 2024 14:49:11 -0600 Subject: [PATCH 1/4] use l1info root insetead of ger to generate merkleproof --- aggsender/aggsender.go | 57 +++++++++++++++++++++----------- aggsender/types/types.go | 6 ++-- l1infotreesync/processor_test.go | 11 +++--- 3 files changed, 48 insertions(+), 26 deletions(-) diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index 817a7c89..5076a400 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -304,17 +304,31 @@ func (a *AggSender) getBridgeExits(bridges []bridgesync.Bridge) []*agglayer.Brid } // getImportedBridgeExits converts claims to agglayer.ImportedBridgeExit objects and calculates necessary proofs -func (a *AggSender) getImportedBridgeExits(ctx context.Context, - claims []bridgesync.Claim) ([]*agglayer.ImportedBridgeExit, error) { +func (a *AggSender) getImportedBridgeExits( + ctx context.Context, claims []bridgesync.Claim, +) ([]*agglayer.ImportedBridgeExit, error) { importedBridgeExits := make([]*agglayer.ImportedBridgeExit, 0, len(claims)) - - for i, claim := range claims { - a.log.Debugf("claim[%d]: destAddr: %s GER:%s", i, claim.DestinationAddress.String(), claim.GlobalExitRoot.String()) - l1Info, err := a.l1infoTreeSyncer.GetInfoByGlobalExitRoot(claim.GlobalExitRoot) + claimL1Info := make([]*l1infotreesync.L1InfoTreeLeaf, 0, len(claims)) + var ( + greatestL1InfoTreeIndexUsed uint32 + ) + for _, claim := range claims { + info, err := a.l1infoTreeSyncer.GetInfoByGlobalExitRoot(claim.GlobalExitRoot) if err != nil { return nil, fmt.Errorf("error getting info by global exit root: %w", err) } + claimL1Info = append(claimL1Info, info) + if info.L1InfoTreeIndex > greatestL1InfoTreeIndexUsed { + greatestL1InfoTreeIndexUsed = info.L1InfoTreeIndex + } + } + rootToProve, err := a.l1infoTreeSyncer.GetL1InfoTreeRootByIndex(ctx, greatestL1InfoTreeIndexUsed) + if err != nil { + return nil, err + } + for i, claim := range claims { + a.log.Debugf("claim[%d]: destAddr: %s GER:%s", i, claim.DestinationAddress.String(), claim.GlobalExitRoot.String()) ibe, err := a.convertClaimToImportedBridgeExit(claim) if err != nil { return nil, fmt.Errorf("error converting claim to imported bridge exit: %w", err) @@ -322,24 +336,27 @@ func (a *AggSender) getImportedBridgeExits(ctx context.Context, importedBridgeExits = append(importedBridgeExits, ibe) - gerToL1Proof, err := a.l1infoTreeSyncer.GetL1InfoTreeMerkleProofFromIndexToRoot(ctx, - l1Info.L1InfoTreeIndex, l1Info.GlobalExitRoot) + gerToL1Proof, err := a.l1infoTreeSyncer.GetL1InfoTreeMerkleProofFromIndexToRoot( + ctx, claimL1Info[i].L1InfoTreeIndex, rootToProve.Hash, + ) if err != nil { - return nil, fmt.Errorf("error getting L1 Info tree merkle proof for leaf index: %d. GER: %s. Error: %w", - l1Info.L1InfoTreeIndex, l1Info.GlobalExitRoot, err) + return nil, fmt.Errorf( + "error getting L1 Info tree merkle proof for leaf index: %d and root: %s. Error: %w", + claimL1Info[i].L1InfoTreeIndex, rootToProve.Hash, err, + ) } claim := claims[i] if ibe.GlobalIndex.MainnetFlag { ibe.ClaimData = &agglayer.ClaimFromMainnnet{ L1Leaf: &agglayer.L1InfoTreeLeaf{ - L1InfoTreeIndex: l1Info.L1InfoTreeIndex, + L1InfoTreeIndex: claimL1Info[i].L1InfoTreeIndex, RollupExitRoot: claim.RollupExitRoot, MainnetExitRoot: claim.MainnetExitRoot, Inner: &agglayer.L1InfoTreeLeafInner{ - GlobalExitRoot: l1Info.GlobalExitRoot, - Timestamp: l1Info.Timestamp, - BlockHash: l1Info.PreviousBlockHash, + GlobalExitRoot: claimL1Info[i].GlobalExitRoot, + Timestamp: claimL1Info[i].Timestamp, + BlockHash: claimL1Info[i].PreviousBlockHash, }, }, ProofLeafMER: &agglayer.MerkleProof{ @@ -347,20 +364,20 @@ func (a *AggSender) getImportedBridgeExits(ctx context.Context, Proof: claim.ProofLocalExitRoot, }, ProofGERToL1Root: &agglayer.MerkleProof{ - Root: l1Info.GlobalExitRoot, + Root: claimL1Info[i].GlobalExitRoot, Proof: gerToL1Proof, }, } } else { ibe.ClaimData = &agglayer.ClaimFromRollup{ L1Leaf: &agglayer.L1InfoTreeLeaf{ - L1InfoTreeIndex: l1Info.L1InfoTreeIndex, + L1InfoTreeIndex: claimL1Info[i].L1InfoTreeIndex, RollupExitRoot: claim.RollupExitRoot, MainnetExitRoot: claim.MainnetExitRoot, Inner: &agglayer.L1InfoTreeLeafInner{ - GlobalExitRoot: l1Info.GlobalExitRoot, - Timestamp: l1Info.Timestamp, - BlockHash: l1Info.PreviousBlockHash, + GlobalExitRoot: claimL1Info[i].GlobalExitRoot, + Timestamp: claimL1Info[i].Timestamp, + BlockHash: claimL1Info[i].PreviousBlockHash, }, }, ProofLeafLER: &agglayer.MerkleProof{ @@ -372,7 +389,7 @@ func (a *AggSender) getImportedBridgeExits(ctx context.Context, Proof: claim.ProofRollupExitRoot, }, ProofGERToL1Root: &agglayer.MerkleProof{ - Root: l1Info.GlobalExitRoot, + Root: claimL1Info[i].GlobalExitRoot, Proof: gerToL1Proof, }, } diff --git a/aggsender/types/types.go b/aggsender/types/types.go index 18d3011b..d6421132 100644 --- a/aggsender/types/types.go +++ b/aggsender/types/types.go @@ -17,8 +17,10 @@ import ( // L1InfoTreeSyncer is an interface defining functions that an L1InfoTreeSyncer should implement type L1InfoTreeSyncer interface { GetInfoByGlobalExitRoot(globalExitRoot common.Hash) (*l1infotreesync.L1InfoTreeLeaf, error) - GetL1InfoTreeMerkleProofFromIndexToRoot(ctx context.Context, - index uint32, root common.Hash) (treeTypes.Proof, error) + GetL1InfoTreeMerkleProofFromIndexToRoot( + ctx context.Context, index uint32, root common.Hash, + ) (treeTypes.Proof, error) + GetL1InfoTreeRootByIndex(ctx context.Context, index uint32) (treeTypes.Root, error) } // L2BridgeSyncer is an interface defining functions that an L2BridgeSyncer should implement diff --git a/l1infotreesync/processor_test.go b/l1infotreesync/processor_test.go index 13595263..f8918cf2 100644 --- a/l1infotreesync/processor_test.go +++ b/l1infotreesync/processor_test.go @@ -270,12 +270,10 @@ func Test_processor_Reorg(t *testing.T) { } func TestProofsFromDifferentTrees(t *testing.T) { - t.Skip("This is an experiment") - l1Tree, err := l1infotree.NewL1InfoTree(log.WithFields("test"), types.DefaultHeight, [][32]byte{}) require.NoError(t, err) - leaves := createTestLeaves(1) + leaves := createTestLeaves(2) aLeaves := make([][32]byte, len(leaves)) for i, leaf := range leaves { @@ -319,8 +317,13 @@ func TestProofsFromDifferentTrees(t *testing.T) { require.NoError(t, tx.Commit()) - pro, err := l1InfoTree.GetProof(context.Background(), leaves[0].L1InfoTreeIndex, leaves[0].GlobalExitRoot) + rootToProof, err := l1InfoTree.GetRootByIndex(context.Background(), leaves[1].L1InfoTreeIndex) + require.NoError(t, err) + pro, err := l1InfoTree.GetProof(context.Background(), leaves[0].L1InfoTreeIndex, rootToProof.Hash) require.NoError(t, err) + for i, l := range proof { + require.Equal(t, common.Hash(l), pro[i]) + } fmt.Println(leaves[0].GlobalExitRoot) fmt.Println(pro) From bc1767c2147b1e4580cfb6c031a48920b1e66b21 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Tue, 29 Oct 2024 22:20:40 +0100 Subject: [PATCH 2/4] fix: uts --- aggsender/aggsender.go | 41 +- aggsender/aggsender_test.go | 538 +++++++++++++--------- aggsender/mocks/mock_l1infotree_syncer.go | 57 +++ 3 files changed, 404 insertions(+), 232 deletions(-) diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index 5076a400..a228e1a9 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -307,27 +307,38 @@ func (a *AggSender) getBridgeExits(bridges []bridgesync.Bridge) []*agglayer.Brid func (a *AggSender) getImportedBridgeExits( ctx context.Context, claims []bridgesync.Claim, ) ([]*agglayer.ImportedBridgeExit, error) { - importedBridgeExits := make([]*agglayer.ImportedBridgeExit, 0, len(claims)) - claimL1Info := make([]*l1infotreesync.L1InfoTreeLeaf, 0, len(claims)) + if len(claims) == 0 { + // no claims to convert + return nil, nil + } + var ( greatestL1InfoTreeIndexUsed uint32 + importedBridgeExits = make([]*agglayer.ImportedBridgeExit, 0, len(claims)) + claimL1Info = make([]*l1infotreesync.L1InfoTreeLeaf, 0, len(claims)) ) + for _, claim := range claims { info, err := a.l1infoTreeSyncer.GetInfoByGlobalExitRoot(claim.GlobalExitRoot) if err != nil { return nil, fmt.Errorf("error getting info by global exit root: %w", err) } + claimL1Info = append(claimL1Info, info) + if info.L1InfoTreeIndex > greatestL1InfoTreeIndexUsed { greatestL1InfoTreeIndexUsed = info.L1InfoTreeIndex } } + rootToProve, err := a.l1infoTreeSyncer.GetL1InfoTreeRootByIndex(ctx, greatestL1InfoTreeIndexUsed) if err != nil { - return nil, err + return nil, fmt.Errorf("error getting L1 Info tree root by index: %d. Error: %w", greatestL1InfoTreeIndexUsed, err) } for i, claim := range claims { + l1Info := claimL1Info[i] + a.log.Debugf("claim[%d]: destAddr: %s GER:%s", i, claim.DestinationAddress.String(), claim.GlobalExitRoot.String()) ibe, err := a.convertClaimToImportedBridgeExit(claim) if err != nil { @@ -337,12 +348,12 @@ func (a *AggSender) getImportedBridgeExits( importedBridgeExits = append(importedBridgeExits, ibe) gerToL1Proof, err := a.l1infoTreeSyncer.GetL1InfoTreeMerkleProofFromIndexToRoot( - ctx, claimL1Info[i].L1InfoTreeIndex, rootToProve.Hash, + ctx, l1Info.L1InfoTreeIndex, rootToProve.Hash, ) if err != nil { return nil, fmt.Errorf( "error getting L1 Info tree merkle proof for leaf index: %d and root: %s. Error: %w", - claimL1Info[i].L1InfoTreeIndex, rootToProve.Hash, err, + l1Info.L1InfoTreeIndex, rootToProve.Hash, err, ) } @@ -350,13 +361,13 @@ func (a *AggSender) getImportedBridgeExits( if ibe.GlobalIndex.MainnetFlag { ibe.ClaimData = &agglayer.ClaimFromMainnnet{ L1Leaf: &agglayer.L1InfoTreeLeaf{ - L1InfoTreeIndex: claimL1Info[i].L1InfoTreeIndex, + L1InfoTreeIndex: l1Info.L1InfoTreeIndex, RollupExitRoot: claim.RollupExitRoot, MainnetExitRoot: claim.MainnetExitRoot, Inner: &agglayer.L1InfoTreeLeafInner{ - GlobalExitRoot: claimL1Info[i].GlobalExitRoot, - Timestamp: claimL1Info[i].Timestamp, - BlockHash: claimL1Info[i].PreviousBlockHash, + GlobalExitRoot: l1Info.GlobalExitRoot, + Timestamp: l1Info.Timestamp, + BlockHash: l1Info.PreviousBlockHash, }, }, ProofLeafMER: &agglayer.MerkleProof{ @@ -364,20 +375,20 @@ func (a *AggSender) getImportedBridgeExits( Proof: claim.ProofLocalExitRoot, }, ProofGERToL1Root: &agglayer.MerkleProof{ - Root: claimL1Info[i].GlobalExitRoot, + Root: rootToProve.Hash, Proof: gerToL1Proof, }, } } else { ibe.ClaimData = &agglayer.ClaimFromRollup{ L1Leaf: &agglayer.L1InfoTreeLeaf{ - L1InfoTreeIndex: claimL1Info[i].L1InfoTreeIndex, + L1InfoTreeIndex: l1Info.L1InfoTreeIndex, RollupExitRoot: claim.RollupExitRoot, MainnetExitRoot: claim.MainnetExitRoot, Inner: &agglayer.L1InfoTreeLeafInner{ - GlobalExitRoot: claimL1Info[i].GlobalExitRoot, - Timestamp: claimL1Info[i].Timestamp, - BlockHash: claimL1Info[i].PreviousBlockHash, + GlobalExitRoot: l1Info.GlobalExitRoot, + Timestamp: l1Info.Timestamp, + BlockHash: l1Info.PreviousBlockHash, }, }, ProofLeafLER: &agglayer.MerkleProof{ @@ -389,7 +400,7 @@ func (a *AggSender) getImportedBridgeExits( Proof: claim.ProofRollupExitRoot, }, ProofGERToL1Root: &agglayer.MerkleProof{ - Root: claimL1Info[i].GlobalExitRoot, + Root: rootToProve.Hash, Proof: gerToL1Proof, }, } diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go index dd4e3901..c2d83c35 100644 --- a/aggsender/aggsender_test.go +++ b/aggsender/aggsender_test.go @@ -258,6 +258,8 @@ func TestGetImportedBridgeExits(t *testing.T) { PreviousBlockHash: common.HexToHash("0xabc"), GlobalExitRoot: common.HexToHash("0x7891"), }, nil) + mockL1InfoTreeSyncer.On("GetL1InfoTreeRootByIndex", mock.Anything, mock.Anything).Return( + treeTypes.Root{Hash: common.HexToHash("0x7891")}, nil) mockL1InfoTreeSyncer.On("GetL1InfoTreeMerkleProofFromIndexToRoot", mock.Anything, mock.Anything, mock.Anything).Return(mockProof, nil) @@ -481,207 +483,221 @@ func TestGetImportedBridgeExits(t *testing.T) { } } -// func TestBuildCertificate(t *testing.T) { -// mockL2BridgeSyncer := mocks.NewL2BridgeSyncerMock(t) -// mockL1InfoTreeSyncer := mocks.NewL1InfoTreeSyncerMock(t) -// mockProof := generateTestProof(t) - -// tests := []struct { -// name string -// bridges []bridgesync.Bridge -// claims []bridgesync.Claim -// previousExit common.Hash -// lastHeight uint64 -// mockFn func() -// expectedCert *agglayer.Certificate -// expectedError bool -// }{ -// { -// name: "Valid certificate with bridges and claims", -// bridges: []bridgesync.Bridge{ -// { -// LeafType: agglayer.LeafTypeAsset.Uint8(), -// OriginNetwork: 1, -// OriginAddress: common.HexToAddress("0x123"), -// DestinationNetwork: 2, -// DestinationAddress: common.HexToAddress("0x456"), -// Amount: big.NewInt(100), -// Metadata: []byte("metadata"), -// DepositCount: 1, -// }, -// }, -// claims: []bridgesync.Claim{ -// { -// IsMessage: false, -// OriginNetwork: 1, -// OriginAddress: common.HexToAddress("0x1234"), -// DestinationNetwork: 2, -// DestinationAddress: common.HexToAddress("0x4567"), -// Amount: big.NewInt(111), -// Metadata: []byte("metadata1"), -// GlobalIndex: big.NewInt(1), -// GlobalExitRoot: common.HexToHash("0x7891"), -// RollupExitRoot: common.HexToHash("0xaaab"), -// MainnetExitRoot: common.HexToHash("0xbbba"), -// ProofLocalExitRoot: mockProof, -// }, -// }, -// previousExit: common.HexToHash("0x123"), -// lastHeight: 1, -// expectedCert: &agglayer.Certificate{ -// NetworkID: 1, -// PrevLocalExitRoot: common.HexToHash("0x123"), -// NewLocalExitRoot: common.HexToHash("0x789"), -// BridgeExits: []*agglayer.BridgeExit{ -// { -// LeafType: agglayer.LeafTypeAsset, -// TokenInfo: &agglayer.TokenInfo{ -// OriginNetwork: 1, -// OriginTokenAddress: common.HexToAddress("0x123"), -// }, -// DestinationNetwork: 2, -// DestinationAddress: common.HexToAddress("0x456"), -// Amount: big.NewInt(100), -// Metadata: []byte("metadata"), -// }, -// }, -// ImportedBridgeExits: []*agglayer.ImportedBridgeExit{ -// { -// BridgeExit: &agglayer.BridgeExit{ -// LeafType: agglayer.LeafTypeAsset, -// TokenInfo: &agglayer.TokenInfo{ -// OriginNetwork: 1, -// OriginTokenAddress: common.HexToAddress("0x1234"), -// }, -// DestinationNetwork: 2, -// DestinationAddress: common.HexToAddress("0x4567"), -// Amount: big.NewInt(111), -// Metadata: []byte("metadata1"), -// }, -// GlobalIndex: &agglayer.GlobalIndex{ -// MainnetFlag: false, -// RollupIndex: 0, -// LeafIndex: 1, -// }, -// ClaimData: &agglayer.ClaimFromRollup{ -// L1Leaf: &agglayer.L1InfoTreeLeaf{ -// L1InfoTreeIndex: 1, -// RollupExitRoot: common.HexToHash("0xaaab"), -// MainnetExitRoot: common.HexToHash("0xbbba"), -// Inner: &agglayer.L1InfoTreeLeafInner{ -// GlobalExitRoot: common.HexToHash("0x7891"), -// Timestamp: 123456789, -// BlockHash: common.HexToHash("0xabc"), -// }, -// }, -// ProofLeafLER: &agglayer.MerkleProof{ -// Root: common.HexToHash("0xbbba"), -// Proof: mockProof, -// }, -// ProofLERToRER: &agglayer.MerkleProof{}, -// ProofGERToL1Root: &agglayer.MerkleProof{ -// Root: common.HexToHash("0x7891"), -// Proof: mockProof, -// }, -// }, -// }, -// }, -// Height: 2, -// }, -// mockFn: func() { -// mockL2BridgeSyncer.On("OriginNetwork").Return(uint32(1)) -// mockL2BridgeSyncer.On("GetExitRootByIndex", mock.Anything, mock.Anything).Return(treeTypes.Root{Hash: common.HexToHash("0x789")}, nil) - -// mockL1InfoTreeSyncer.On("GetInfoByGlobalExitRoot", mock.Anything).Return(&l1infotreesync.L1InfoTreeLeaf{ -// L1InfoTreeIndex: 1, -// Timestamp: 123456789, -// PreviousBlockHash: common.HexToHash("0xabc"), -// }, nil) -// mockL1InfoTreeSyncer.On("GetL1InfoTreeMerkleProofFromIndexToRoot", mock.Anything, mock.Anything, mock.Anything).Return(mockProof, nil) -// }, -// expectedError: false, -// }, -// { -// name: "No bridges or claims", -// bridges: []bridgesync.Bridge{}, -// claims: []bridgesync.Claim{}, -// previousExit: common.HexToHash("0x123"), -// lastHeight: 1, -// expectedCert: nil, -// expectedError: true, -// }, -// { -// name: "Error getting imported bridge exits", -// bridges: []bridgesync.Bridge{ -// { -// LeafType: agglayer.LeafTypeAsset.Uint8(), -// OriginNetwork: 1, -// OriginAddress: common.HexToAddress("0x123"), -// DestinationNetwork: 2, -// DestinationAddress: common.HexToAddress("0x456"), -// Amount: big.NewInt(100), -// Metadata: []byte("metadata"), -// DepositCount: 1, -// }, -// }, -// claims: []bridgesync.Claim{ -// { -// IsMessage: false, -// OriginNetwork: 1, -// OriginAddress: common.HexToAddress("0x1234"), -// DestinationNetwork: 2, -// DestinationAddress: common.HexToAddress("0x4567"), -// Amount: big.NewInt(111), -// Metadata: []byte("metadata1"), -// GlobalIndex: new(big.Int).SetBytes([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}), -// GlobalExitRoot: common.HexToHash("0x7891"), -// RollupExitRoot: common.HexToHash("0xaaab"), -// MainnetExitRoot: common.HexToHash("0xbbba"), -// ProofLocalExitRoot: mockProof, -// }, -// }, -// previousExit: common.HexToHash("0x123"), -// lastHeight: 1, -// mockFn: func() { -// mockL1InfoTreeSyncer.On("GetInfoByGlobalExitRoot", mock.Anything).Return(&l1infotreesync.L1InfoTreeLeaf{ -// L1InfoTreeIndex: 1, -// Timestamp: 123456789, -// PreviousBlockHash: common.HexToHash("0xabc"), -// }, nil) -// }, -// expectedCert: nil, -// expectedError: true, -// }, -// } - -// for _, tt := range tests { -// tt := tt - -// t.Run(tt.name, func(t *testing.T) { -// mockL1InfoTreeSyncer.ExpectedCalls = nil -// mockL2BridgeSyncer.ExpectedCalls = nil - -// if tt.mockFn != nil { -// tt.mockFn() -// } - -// aggSender := &AggSender{ -// l2Syncer: mockL2BridgeSyncer, -// l1infoTreeSyncer: mockL1InfoTreeSyncer, -// log: log.WithFields("test", "unittest"), -// } -// cert, err := aggSender.buildCertificate(context.Background(), tt.bridges, tt.claims, tt.previousExit, tt.lastHeight) - -// if tt.expectedError { -// require.Error(t, err) -// require.Nil(t, cert) -// } else { -// require.NoError(t, err) -// require.Equal(t, tt.expectedCert, cert) -// } -// }) -// } -// } +func TestBuildCertificate(t *testing.T) { + mockL2BridgeSyncer := mocks.NewL2BridgeSyncerMock(t) + mockL1InfoTreeSyncer := mocks.NewL1InfoTreeSyncerMock(t) + mockProof := generateTestProof(t) + + tests := []struct { + name string + bridges []bridgesync.Bridge + claims []bridgesync.Claim + lastSentCertificateInfo aggsendertypes.CertificateInfo + mockFn func() + expectedCert *agglayer.Certificate + expectedError bool + }{ + { + name: "Valid certificate with bridges and claims", + bridges: []bridgesync.Bridge{ + { + LeafType: agglayer.LeafTypeAsset.Uint8(), + OriginNetwork: 1, + OriginAddress: common.HexToAddress("0x123"), + DestinationNetwork: 2, + DestinationAddress: common.HexToAddress("0x456"), + Amount: big.NewInt(100), + Metadata: []byte("metadata"), + DepositCount: 1, + }, + }, + claims: []bridgesync.Claim{ + { + IsMessage: false, + OriginNetwork: 1, + OriginAddress: common.HexToAddress("0x1234"), + DestinationNetwork: 2, + DestinationAddress: common.HexToAddress("0x4567"), + Amount: big.NewInt(111), + Metadata: []byte("metadata1"), + GlobalIndex: big.NewInt(1), + GlobalExitRoot: common.HexToHash("0x7891"), + RollupExitRoot: common.HexToHash("0xaaab"), + MainnetExitRoot: common.HexToHash("0xbbba"), + ProofLocalExitRoot: mockProof, + ProofRollupExitRoot: mockProof, + }, + }, + lastSentCertificateInfo: aggsendertypes.CertificateInfo{ + NewLocalExitRoot: common.HexToHash("0x123"), + Height: 1, + }, + expectedCert: &agglayer.Certificate{ + NetworkID: 1, + PrevLocalExitRoot: common.HexToHash("0x123"), + NewLocalExitRoot: common.HexToHash("0x789"), + BridgeExits: []*agglayer.BridgeExit{ + { + LeafType: agglayer.LeafTypeAsset, + TokenInfo: &agglayer.TokenInfo{ + OriginNetwork: 1, + OriginTokenAddress: common.HexToAddress("0x123"), + }, + DestinationNetwork: 2, + DestinationAddress: common.HexToAddress("0x456"), + Amount: big.NewInt(100), + Metadata: []byte("metadata"), + }, + }, + ImportedBridgeExits: []*agglayer.ImportedBridgeExit{ + { + BridgeExit: &agglayer.BridgeExit{ + LeafType: agglayer.LeafTypeAsset, + TokenInfo: &agglayer.TokenInfo{ + OriginNetwork: 1, + OriginTokenAddress: common.HexToAddress("0x1234"), + }, + DestinationNetwork: 2, + DestinationAddress: common.HexToAddress("0x4567"), + Amount: big.NewInt(111), + Metadata: []byte("metadata1"), + }, + GlobalIndex: &agglayer.GlobalIndex{ + MainnetFlag: false, + RollupIndex: 0, + LeafIndex: 1, + }, + ClaimData: &agglayer.ClaimFromRollup{ + L1Leaf: &agglayer.L1InfoTreeLeaf{ + L1InfoTreeIndex: 1, + RollupExitRoot: common.HexToHash("0xaaab"), + MainnetExitRoot: common.HexToHash("0xbbba"), + Inner: &agglayer.L1InfoTreeLeafInner{ + GlobalExitRoot: common.HexToHash("0x7891"), + Timestamp: 123456789, + BlockHash: common.HexToHash("0xabc"), + }, + }, + ProofLeafLER: &agglayer.MerkleProof{ + Root: common.HexToHash("0xbbba"), + Proof: mockProof, + }, + ProofLERToRER: &agglayer.MerkleProof{ + Root: common.HexToHash("0xaaab"), + Proof: mockProof, + }, + ProofGERToL1Root: &agglayer.MerkleProof{ + Root: common.HexToHash("0x7891"), + Proof: mockProof, + }, + }, + }, + }, + Height: 2, + }, + mockFn: func() { + mockL2BridgeSyncer.On("OriginNetwork").Return(uint32(1)) + mockL2BridgeSyncer.On("GetExitRootByIndex", mock.Anything, mock.Anything).Return(treeTypes.Root{Hash: common.HexToHash("0x789")}, nil) + + mockL1InfoTreeSyncer.On("GetInfoByGlobalExitRoot", mock.Anything).Return(&l1infotreesync.L1InfoTreeLeaf{ + L1InfoTreeIndex: 1, + Timestamp: 123456789, + PreviousBlockHash: common.HexToHash("0xabc"), + GlobalExitRoot: common.HexToHash("0x7891"), + }, nil) + mockL1InfoTreeSyncer.On("GetL1InfoTreeRootByIndex", mock.Anything, mock.Anything).Return(treeTypes.Root{Hash: common.HexToHash("0x7891")}, nil) + mockL1InfoTreeSyncer.On("GetL1InfoTreeMerkleProofFromIndexToRoot", mock.Anything, mock.Anything, mock.Anything).Return(mockProof, nil) + }, + expectedError: false, + }, + { + name: "No bridges or claims", + bridges: []bridgesync.Bridge{}, + claims: []bridgesync.Claim{}, + lastSentCertificateInfo: aggsendertypes.CertificateInfo{ + NewLocalExitRoot: common.HexToHash("0x123"), + Height: 1, + }, + expectedCert: nil, + expectedError: true, + }, + { + name: "Error getting imported bridge exits", + bridges: []bridgesync.Bridge{ + { + LeafType: agglayer.LeafTypeAsset.Uint8(), + OriginNetwork: 1, + OriginAddress: common.HexToAddress("0x123"), + DestinationNetwork: 2, + DestinationAddress: common.HexToAddress("0x456"), + Amount: big.NewInt(100), + Metadata: []byte("metadata"), + DepositCount: 1, + }, + }, + claims: []bridgesync.Claim{ + { + IsMessage: false, + OriginNetwork: 1, + OriginAddress: common.HexToAddress("0x1234"), + DestinationNetwork: 2, + DestinationAddress: common.HexToAddress("0x4567"), + Amount: big.NewInt(111), + Metadata: []byte("metadata1"), + GlobalIndex: new(big.Int).SetBytes([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}), + GlobalExitRoot: common.HexToHash("0x7891"), + RollupExitRoot: common.HexToHash("0xaaab"), + MainnetExitRoot: common.HexToHash("0xbbba"), + ProofLocalExitRoot: mockProof, + }, + }, + lastSentCertificateInfo: aggsendertypes.CertificateInfo{ + NewLocalExitRoot: common.HexToHash("0x123"), + Height: 1, + }, + mockFn: func() { + mockL1InfoTreeSyncer.On("GetInfoByGlobalExitRoot", mock.Anything).Return(&l1infotreesync.L1InfoTreeLeaf{ + L1InfoTreeIndex: 1, + Timestamp: 123456789, + PreviousBlockHash: common.HexToHash("0xabc"), + GlobalExitRoot: common.HexToHash("0x7891"), + }, nil) + mockL1InfoTreeSyncer.On("GetL1InfoTreeRootByIndex", mock.Anything, mock.Anything).Return( + treeTypes.Root{Hash: common.HexToHash("0x7891")}, nil) + }, + expectedCert: nil, + expectedError: true, + }, + } + + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + mockL1InfoTreeSyncer.ExpectedCalls = nil + mockL2BridgeSyncer.ExpectedCalls = nil + + if tt.mockFn != nil { + tt.mockFn() + } + + aggSender := &AggSender{ + l2Syncer: mockL2BridgeSyncer, + l1infoTreeSyncer: mockL1InfoTreeSyncer, + log: log.WithFields("test", "unittest"), + } + cert, err := aggSender.buildCertificate(context.Background(), tt.bridges, tt.claims, tt.lastSentCertificateInfo) + + if tt.expectedError { + require.Error(t, err) + require.Nil(t, cert) + } else { + require.NoError(t, err) + require.Equal(t, tt.expectedCert, cert) + } + }) + } +} func generateTestProof(t *testing.T) treeTypes.Proof { t.Helper() @@ -840,19 +856,21 @@ func TestSendCertificate(t *testing.T) { require.NoError(t, err) type testCfg struct { - name string - sequencerKey *ecdsa.PrivateKey - shouldSendCertificate []interface{} - getLastSentCertificate []interface{} - lastL2BlockProcessed []interface{} - getBridges []interface{} - getClaims []interface{} - getInfoByGlobalExitRoot []interface{} - getExitRootByIndex []interface{} - originNetwork []interface{} - sendCertificate []interface{} - saveLastSentCertificate []interface{} - expectedError string + name string + sequencerKey *ecdsa.PrivateKey + shouldSendCertificate []interface{} + getLastSentCertificate []interface{} + lastL2BlockProcessed []interface{} + getBridges []interface{} + getClaims []interface{} + getInfoByGlobalExitRoot []interface{} + getL1InfoTreeRootByIndex []interface{} + getL1InfoTreeMerkleProofFromIndexToRoot []interface{} + getExitRootByIndex []interface{} + originNetwork []interface{} + sendCertificate []interface{} + saveLastSentCertificate []interface{} + expectedError string } setupTest := func(cfg testCfg) (*AggSender, *mocks.AggSenderStorageMock, *mocks.L2BridgeSyncerMock, @@ -918,10 +936,20 @@ func TestSendCertificate(t *testing.T) { aggsender.aggLayerClient = mockAggLayerClient } - if cfg.getInfoByGlobalExitRoot != nil { + if cfg.getInfoByGlobalExitRoot != nil || + cfg.getL1InfoTreeRootByIndex != nil || cfg.getL1InfoTreeMerkleProofFromIndexToRoot != nil { mockL1InfoTreeSyncer = mocks.NewL1InfoTreeSyncerMock(t) mockL1InfoTreeSyncer.On("GetInfoByGlobalExitRoot", mock.Anything).Return(cfg.getInfoByGlobalExitRoot...).Once() + if cfg.getL1InfoTreeRootByIndex != nil { + mockL1InfoTreeSyncer.On("GetL1InfoTreeRootByIndex", mock.Anything, mock.Anything).Return(cfg.getL1InfoTreeRootByIndex...).Once() + } + + if cfg.getL1InfoTreeMerkleProofFromIndexToRoot != nil { + mockL1InfoTreeSyncer.On("GetL1InfoTreeMerkleProofFromIndexToRoot", mock.Anything, mock.Anything, mock.Anything). + Return(cfg.getL1InfoTreeMerkleProofFromIndexToRoot...).Once() + } + aggsender.l1infoTreeSyncer = mockL1InfoTreeSyncer } @@ -1009,7 +1037,7 @@ func TestSendCertificate(t *testing.T) { expectedError: "error getting claims", }, { - name: "error building certificate", + name: "error getting info by global exit root", shouldSendCertificate: []interface{}{[]*aggsendertypes.CertificateInfo{}, nil}, lastL2BlockProcessed: []interface{}{uint64(89), nil}, getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ @@ -1033,7 +1061,83 @@ func TestSendCertificate(t *testing.T) { }, }, nil}, getInfoByGlobalExitRoot: []interface{}{nil, errors.New("error getting info by global exit root")}, - expectedError: "error building certificate", + expectedError: "error getting info by global exit root", + }, + { + name: "error getting L1 Info tree root by index", + shouldSendCertificate: []interface{}{[]*aggsendertypes.CertificateInfo{}, nil}, + lastL2BlockProcessed: []interface{}{uint64(89), nil}, + getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ + Height: 80, + CertificateID: common.HexToHash("0x1321111"), + NewLocalExitRoot: common.HexToHash("0x131122233"), + FromBlock: 70, + ToBlock: 71, + }, nil}, + getBridges: []interface{}{[]bridgesync.Bridge{ + { + BlockNum: 71, + BlockPos: 0, + LeafType: agglayer.LeafTypeAsset.Uint8(), + OriginNetwork: 1, + }, + }, nil}, + getClaims: []interface{}{[]bridgesync.Claim{ + { + IsMessage: false, + }, + }, nil}, + getInfoByGlobalExitRoot: []interface{}{&l1infotreesync.L1InfoTreeLeaf{ + L1InfoTreeIndex: 1, + BlockNumber: 1, + BlockPosition: 0, + PreviousBlockHash: common.HexToHash("0x123"), + Timestamp: 123456789, + MainnetExitRoot: common.HexToHash("0xccc"), + RollupExitRoot: common.HexToHash("0xddd"), + GlobalExitRoot: common.HexToHash("0xeee"), + }, nil}, + getL1InfoTreeRootByIndex: []interface{}{treeTypes.Root{}, errors.New("error getting L1 Info tree root by index")}, + expectedError: "error getting L1 Info tree root by index", + }, + { + name: "error getting L1 Info tree merkle proof from index to root", + shouldSendCertificate: []interface{}{[]*aggsendertypes.CertificateInfo{}, nil}, + lastL2BlockProcessed: []interface{}{uint64(89), nil}, + getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ + Height: 80, + CertificateID: common.HexToHash("0x1321111"), + NewLocalExitRoot: common.HexToHash("0x131122233"), + FromBlock: 70, + ToBlock: 71, + }, nil}, + getBridges: []interface{}{[]bridgesync.Bridge{ + { + BlockNum: 71, + BlockPos: 0, + LeafType: agglayer.LeafTypeAsset.Uint8(), + OriginNetwork: 1, + }, + }, nil}, + getClaims: []interface{}{[]bridgesync.Claim{ + { + IsMessage: false, + GlobalIndex: big.NewInt(1), + }, + }, nil}, + getInfoByGlobalExitRoot: []interface{}{&l1infotreesync.L1InfoTreeLeaf{ + L1InfoTreeIndex: 1, + BlockNumber: 1, + BlockPosition: 0, + PreviousBlockHash: common.HexToHash("0x123"), + Timestamp: 123456789, + MainnetExitRoot: common.HexToHash("0xccc"), + RollupExitRoot: common.HexToHash("0xddd"), + GlobalExitRoot: common.HexToHash("0xeee"), + }, nil}, + getL1InfoTreeRootByIndex: []interface{}{treeTypes.Root{Hash: common.HexToHash("0xeee")}, nil}, + getL1InfoTreeMerkleProofFromIndexToRoot: []interface{}{treeTypes.Proof{}, errors.New("error getting L1 Info tree merkle proof")}, + expectedError: "error getting L1 Info tree merkle proof for leaf index", }, { name: "send certificate error", diff --git a/aggsender/mocks/mock_l1infotree_syncer.go b/aggsender/mocks/mock_l1infotree_syncer.go index 3fe26bd2..e113d4ed 100644 --- a/aggsender/mocks/mock_l1infotree_syncer.go +++ b/aggsender/mocks/mock_l1infotree_syncer.go @@ -145,6 +145,63 @@ func (_c *L1InfoTreeSyncerMock_GetL1InfoTreeMerkleProofFromIndexToRoot_Call) Run return _c } +// GetL1InfoTreeRootByIndex provides a mock function with given fields: ctx, index +func (_m *L1InfoTreeSyncerMock) GetL1InfoTreeRootByIndex(ctx context.Context, index uint32) (treetypes.Root, error) { + ret := _m.Called(ctx, index) + + if len(ret) == 0 { + panic("no return value specified for GetL1InfoTreeRootByIndex") + } + + var r0 treetypes.Root + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint32) (treetypes.Root, error)); ok { + return rf(ctx, index) + } + if rf, ok := ret.Get(0).(func(context.Context, uint32) treetypes.Root); ok { + r0 = rf(ctx, index) + } else { + r0 = ret.Get(0).(treetypes.Root) + } + + if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { + r1 = rf(ctx, index) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// L1InfoTreeSyncerMock_GetL1InfoTreeRootByIndex_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetL1InfoTreeRootByIndex' +type L1InfoTreeSyncerMock_GetL1InfoTreeRootByIndex_Call struct { + *mock.Call +} + +// GetL1InfoTreeRootByIndex is a helper method to define mock.On call +// - ctx context.Context +// - index uint32 +func (_e *L1InfoTreeSyncerMock_Expecter) GetL1InfoTreeRootByIndex(ctx interface{}, index interface{}) *L1InfoTreeSyncerMock_GetL1InfoTreeRootByIndex_Call { + return &L1InfoTreeSyncerMock_GetL1InfoTreeRootByIndex_Call{Call: _e.mock.On("GetL1InfoTreeRootByIndex", ctx, index)} +} + +func (_c *L1InfoTreeSyncerMock_GetL1InfoTreeRootByIndex_Call) Run(run func(ctx context.Context, index uint32)) *L1InfoTreeSyncerMock_GetL1InfoTreeRootByIndex_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint32)) + }) + return _c +} + +func (_c *L1InfoTreeSyncerMock_GetL1InfoTreeRootByIndex_Call) Return(_a0 treetypes.Root, _a1 error) *L1InfoTreeSyncerMock_GetL1InfoTreeRootByIndex_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *L1InfoTreeSyncerMock_GetL1InfoTreeRootByIndex_Call) RunAndReturn(run func(context.Context, uint32) (treetypes.Root, error)) *L1InfoTreeSyncerMock_GetL1InfoTreeRootByIndex_Call { + _c.Call.Return(run) + return _c +} + // NewL1InfoTreeSyncerMock creates a new instance of L1InfoTreeSyncerMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewL1InfoTreeSyncerMock(t interface { From a814eef1c4bc21bc429f3bf1b0d77dba37677b56 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Tue, 29 Oct 2024 22:38:13 +0100 Subject: [PATCH 3/4] fix: different l1 info trees test --- l1infotreesync/processor_test.go | 35 +++++++++++++++++++------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/l1infotreesync/processor_test.go b/l1infotreesync/processor_test.go index f8918cf2..34c5daef 100644 --- a/l1infotreesync/processor_test.go +++ b/l1infotreesync/processor_test.go @@ -270,10 +270,12 @@ func Test_processor_Reorg(t *testing.T) { } func TestProofsFromDifferentTrees(t *testing.T) { + fmt.Println("aggregator L1InfoTree ===============================================") + l1Tree, err := l1infotree.NewL1InfoTree(log.WithFields("test"), types.DefaultHeight, [][32]byte{}) require.NoError(t, err) - leaves := createTestLeaves(2) + leaves := createTestLeaves(t, 2) aLeaves := make([][32]byte, len(leaves)) for i, leaf := range leaves { @@ -283,17 +285,17 @@ func TestProofsFromDifferentTrees(t *testing.T) { leaf.Timestamp) } - proof, root, err := l1Tree.ComputeMerkleProof(leaves[0].L1InfoTreeIndex, aLeaves) + aggregatorL1InfoTree, aggregatorRoot, err := l1Tree.ComputeMerkleProof(leaves[0].L1InfoTreeIndex, aLeaves) require.NoError(t, err) - hashProof := make([]common.Hash, len(proof)) - for i, p := range proof { - hashProof[i] = common.BytesToHash(p[:]) + aggregatorProof := types.Proof{} + for i, p := range aggregatorL1InfoTree { + aggregatorProof[i] = common.BytesToHash(p[:]) } - fmt.Println(root) - fmt.Println(hashProof) - fmt.Println("===========================================================================================================") + fmt.Println(aggregatorRoot) + fmt.Println(aggregatorProof) + fmt.Println("l1 info tree syncer L1InfoTree ===============================================") dbPath := "file:l1InfoTreeTest?mode=memory&cache=shared" require.NoError(t, migrations.RunMigrations(dbPath)) @@ -317,19 +319,24 @@ func TestProofsFromDifferentTrees(t *testing.T) { require.NoError(t, tx.Commit()) - rootToProof, err := l1InfoTree.GetRootByIndex(context.Background(), leaves[1].L1InfoTreeIndex) + l1InfoTreeSyncerRoot, err := l1InfoTree.GetRootByIndex(context.Background(), leaves[1].L1InfoTreeIndex) require.NoError(t, err) - pro, err := l1InfoTree.GetProof(context.Background(), leaves[0].L1InfoTreeIndex, rootToProof.Hash) + l1InfoTreeSyncerProof, err := l1InfoTree.GetProof(context.Background(), leaves[0].L1InfoTreeIndex, l1InfoTreeSyncerRoot.Hash) require.NoError(t, err) - for i, l := range proof { - require.Equal(t, common.Hash(l), pro[i]) + for i, l := range aggregatorL1InfoTree { + require.Equal(t, common.Hash(l), l1InfoTreeSyncerProof[i]) } fmt.Println(leaves[0].GlobalExitRoot) - fmt.Println(pro) + fmt.Println(l1InfoTreeSyncerProof) + + require.Equal(t, aggregatorRoot, l1InfoTreeSyncerRoot.Hash) + require.Equal(t, aggregatorProof, l1InfoTreeSyncerProof) } -func createTestLeaves(numOfLeaves int) []*L1InfoTreeLeaf { +func createTestLeaves(t *testing.T, numOfLeaves int) []*L1InfoTreeLeaf { + t.Helper() + leaves := make([]*L1InfoTreeLeaf, 0, numOfLeaves) for i := 0; i < numOfLeaves; i++ { From a58a4c83f418ceaa0cf69414cc18a8f7ea1a5e4b Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Tue, 29 Oct 2024 22:46:00 +0100 Subject: [PATCH 4/4] fix: ut --- aggsender/aggsender_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go index c2d83c35..69dc6ed1 100644 --- a/aggsender/aggsender_test.go +++ b/aggsender/aggsender_test.go @@ -456,7 +456,7 @@ func TestGetImportedBridgeExits(t *testing.T) { name: "No claims", claims: []bridgesync.Claim{}, expectedError: false, - expectedExits: []*agglayer.ImportedBridgeExit{}, + expectedExits: nil, }, }