From bc38b5ef8e124df69fcdec5f165cedd1b4330240 Mon Sep 17 00:00:00 2001 From: Victor Castell <0x@vcastellm.xyz> Date: Mon, 14 Oct 2024 16:04:04 +0000 Subject: [PATCH 01/84] test: L2 to L1 withdrawal --- test/bridge-e2e.bats | 28 ++++++++++++++++++-- test/helpers/common.bash | 3 ++- test/helpers/lxly-bridge-test.bash | 42 ++++++++++++++++++++++++------ 3 files changed, 62 insertions(+), 11 deletions(-) diff --git a/test/bridge-e2e.bats b/test/bridge-e2e.bats index fcea86d9..6fe1ebf7 100644 --- a/test/bridge-e2e.bats +++ b/test/bridge-e2e.bats @@ -24,6 +24,7 @@ setup() { token_addr=${TOKEN_ADDRESS:-"0x0000000000000000000000000000000000000000"} readonly is_forced=${IS_FORCED:-"true"} readonly bridge_addr=$BRIDGE_ADDRESS + readonly bridge_sig='bridgeAsset(uint32,address,uint256,address,bool,bytes)' readonly meta_bytes=${META_BYTES:-"0x"} readonly l1_rpc_url=${L1_ETH_RPC_URL:-"$(kurtosis port print $enclave el-1-geth-lighthouse rpc)"} @@ -47,7 +48,7 @@ setup() { timeout="120" claim_frequency="10" - run wait_for_claim "$timeout" "$claim_frequency" + run wait_for_claim "$timeout" "$claim_frequency" "$l2_rpc_url" assert_success } @@ -115,10 +116,33 @@ setup() { # Claim deposits (settle them on the L2) timeout="120" claim_frequency="10" - run wait_for_claim "$timeout" "$claim_frequency" + run wait_for_claim "$timeout" "$claim_frequency" "$l2_rpc_url" assert_success # Validate that the native token of receiver on L2 has increased by the bridge tokens amount run verify_native_token_balance "$l2_rpc_url" "$receiver" "$initial_receiver_balance" "$tokens_amount" assert_success } + +@test "Run withdrawal" { + echo "Running LxLy withdrawal" >&3 + + local initial_receiver_balance=$(cast balance -e "$destination_addr" --rpc-url "$l1_rpc_url") + + run withdrawal + assert_success + + # Claim withdrawals (settle them on the L1) + timeout="120" + claim_frequency="10" + destination_net=$l1_rpc_network_id + run wait_for_claim "$timeout" "$claim_frequency" "$l1_rpc_url" + assert_success + + # Validate that the native token of receiver on L1 has increased by the bridge tokens amount + run verify_native_token_balance "$l1_rpc_url" "$destination_addr" "$initial_receiver_balance" "$ether_value" + if [ $status -eq 0 ]; then + break + fi + assert_success +} diff --git a/test/helpers/common.bash b/test/helpers/common.bash index 821a1f59..c0dcc88e 100644 --- a/test/helpers/common.bash +++ b/test/helpers/common.bash @@ -289,6 +289,7 @@ function verify_native_token_balance() { # Get final balance in wei (after the operation) local final_balance_wei=$(cast balance "$account" --rpc-url "$rpc_url" | awk '{print $1}') + echo "Final balance of $account in $rpc_url: $final_balance_wei wei" >&3 # Calculate expected final balance (initial_balance + amount) local expected_final_balance_wei=$(echo "$initial_balance_wei + $amount_wei" | bc) @@ -297,7 +298,7 @@ function verify_native_token_balance() { if [ "$(echo "$final_balance_wei == $expected_final_balance_wei" | bc)" -eq 1 ]; then echo "✅ Balance verification successful: final balance is correct." else - echo "❌ Balance verification failed: expected $expected_final_balance_wei but got $final_balance_wei." + echo "❌ Balance verification failed: expected $expected_final_balance_wei but got $final_balance_wei." >&3 exit 1 fi } diff --git a/test/helpers/lxly-bridge-test.bash b/test/helpers/lxly-bridge-test.bash index c1b43533..a34f1159 100644 --- a/test/helpers/lxly-bridge-test.bash +++ b/test/helpers/lxly-bridge-test.bash @@ -1,8 +1,6 @@ #!/usr/bin/env bash # Error code reference https://hackmd.io/WwahVBZERJKdfK3BbKxzQQ function deposit() { - readonly deposit_sig='bridgeAsset(uint32,address,uint256,address,bool,bytes)' - if [[ $token_addr == "0x0000000000000000000000000000000000000000" ]]; then echo "The ETH balance for sender "$sender_addr":" >&3 cast balance -e --rpc-url $l1_rpc_url $sender_addr >&3 @@ -15,17 +13,18 @@ function deposit() { echo "Attempting to deposit $amount [wei] to $destination_addr, token $token_addr (sender=$sender_addr, network id=$destination_net, rpc url=$l1_rpc_url)" >&3 if [[ $dry_run == "true" ]]; then - cast calldata $deposit_sig $destination_net $destination_addr $amount $token_addr $is_forced $meta_bytes + cast calldata $bridge_sig $destination_net $destination_addr $amount $token_addr $is_forced $meta_bytes else if [[ $token_addr == "0x0000000000000000000000000000000000000000" ]]; then - cast send --legacy --private-key $sender_private_key --value $amount --rpc-url $l1_rpc_url $bridge_addr $deposit_sig $destination_net $destination_addr $amount $token_addr $is_forced $meta_bytes + cast send --legacy --private-key $sender_private_key --value $amount --rpc-url $l1_rpc_url $bridge_addr $bridge_sig $destination_net $destination_addr $amount $token_addr $is_forced $meta_bytes else - cast send --legacy --private-key $sender_private_key --rpc-url $l1_rpc_url $bridge_addr $deposit_sig $destination_net $destination_addr $amount $token_addr $is_forced $meta_bytes + cast send --legacy --private-key $sender_private_key --rpc-url $l1_rpc_url $bridge_addr $bridge_sig $destination_net $destination_addr $amount $token_addr $is_forced $meta_bytes fi fi } function claim() { + local destination_rpc_url="$1" readonly claim_sig="claimAsset(bytes32[32],bytes32[32],uint256,bytes32,bytes32,uint32,address,uint32,address,uint256,bytes)" readonly bridge_deposit_file=$(mktemp) readonly claimable_deposit_file=$(mktemp) @@ -68,9 +67,9 @@ function claim() { if [[ $dry_run == "true" ]]; then cast calldata $claim_sig "$in_merkle_proof" "$in_rollup_merkle_proof" $in_global_index $in_main_exit_root $in_rollup_exit_root $in_orig_net $in_orig_addr $in_dest_net $in_dest_addr $in_amount $in_metadata - cast call --rpc-url $l2_rpc_url $bridge_addr $claim_sig "$in_merkle_proof" "$in_rollup_merkle_proof" $in_global_index $in_main_exit_root $in_rollup_exit_root $in_orig_net $in_orig_addr $in_dest_net $in_dest_addr $in_amount $in_metadata + cast call --rpc-url $destination_rpc_url $bridge_addr $claim_sig "$in_merkle_proof" "$in_rollup_merkle_proof" $in_global_index $in_main_exit_root $in_rollup_exit_root $in_orig_net $in_orig_addr $in_dest_net $in_dest_addr $in_amount $in_metadata else - cast send --legacy --rpc-url $l2_rpc_url --private-key $sender_private_key $bridge_addr $claim_sig "$in_merkle_proof" "$in_rollup_merkle_proof" $in_global_index $in_main_exit_root $in_rollup_exit_root $in_orig_net $in_orig_addr $in_dest_net $in_dest_addr $in_amount $in_metadata + cast send --legacy --rpc-url $destination_rpc_url --private-key $sender_private_key $bridge_addr $claim_sig "$in_merkle_proof" "$in_rollup_merkle_proof" $in_global_index $in_main_exit_root $in_rollup_exit_root $in_orig_net $in_orig_addr $in_dest_net $in_dest_addr $in_amount $in_metadata fi done < <(seq 0 $((claimable_count - 1))) @@ -79,6 +78,7 @@ function claim() { function wait_for_claim() { local timeout="$1" # timeout (in seconds) local claim_frequency="$2" # claim frequency (in seconds) + local destination_rpc_url="$3" # destination rpc url local start_time=$(date +%s) local end_time=$((start_time + timeout)) @@ -89,7 +89,7 @@ function wait_for_claim() { exit 1 fi - run claim + run claim $destination_rpc_url if [ $status -eq 0 ]; then break fi @@ -97,3 +97,29 @@ function wait_for_claim() { sleep "$claim_frequency" done } + +function withdrawal() { + if [[ $token_addr == "0x0000000000000000000000000000000000000000" ]]; then + echo "The ETH balance for sender "$sender_addr":" >&3 + cast balance -e --rpc-url $l2_rpc_url $sender_addr >&3 + + echo "The ETH balance for receiver "$destination_addr ":" >&3 + cast balance -e --rpc-url $l1_rpc_url $destination_addr >&3 + else + echo "The "$token_addr" token balance for sender "$sender_addr":" >&3 + balance_wei=$(cast call --rpc-url "$l2_rpc_url" "$token_addr" "$balance_of_fn_sig" "$sender_addr") + echo "$(cast --from-wei "$balance_wei")" >&3 + fi + + echo "Attempting to withdraw $amount [wei] to $destination_addr, token $token_addr (sender=$sender_addr, network id=$l1_rpc_network_id, rpc url=$l2_rpc_url)" >&3 + + if [[ $dry_run == "true" ]]; then + cast calldata $bridge_sig $l1_rpc_network_id $destination_addr $amount $token_addr $is_forced $meta_bytes + else + if [[ $token_addr == "0x0000000000000000000000000000000000000000" ]]; then + cast send --legacy --private-key $sender_private_key --value $amount --rpc-url $l2_rpc_url $bridge_addr $bridge_sig $l1_rpc_network_id $destination_addr $amount $token_addr $is_forced $meta_bytes + else + cast send --legacy --private-key $sender_private_key --rpc-url $l2_rpc_url $bridge_addr $bridge_sig $destination_net $l1_rpc_network_id $amount $token_addr $is_forced $meta_bytes + fi + fi +} From a5791ed9f504dd138713ee47335d96ad16d510ec Mon Sep 17 00:00:00 2001 From: Victor Castell <0x@vcastellm.xyz> Date: Tue, 15 Oct 2024 08:05:28 +0000 Subject: [PATCH 02/84] test: increase timeout --- test/bridge-e2e.bats | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/bridge-e2e.bats b/test/bridge-e2e.bats index 6fe1ebf7..7854218a 100644 --- a/test/bridge-e2e.bats +++ b/test/bridge-e2e.bats @@ -133,7 +133,7 @@ setup() { assert_success # Claim withdrawals (settle them on the L1) - timeout="120" + timeout="240" claim_frequency="10" destination_net=$l1_rpc_network_id run wait_for_claim "$timeout" "$claim_frequency" "$l1_rpc_url" From 710b200444396db880b8a2d4a3e8c39266446b40 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Wed, 9 Oct 2024 12:10:36 +0200 Subject: [PATCH 03/84] faet: aggsender feat: expand config fix: nothing to send feat: use finality as the syncer fix: rebase feat: add sequencer private key to config fix: add new fields fix: config fix: merge fix fix: remove ImportedExitRoots field feat: build certificates for L1 as well feat: set previous and new exit root in certificate fix: reorg detector for both l1 and l2 fix: rebase feat: add aggsender to run cmd feat: sign certificate POC feat: latest spec types fix: update comments fix: resolve TODOs on claim fix: height fix: rebase feat: new changes fix: lint --- .../agglayer_client.go => agglayer/client.go | 15 + .../agglayer/agglayer_tx.go => agglayer/tx.go | 0 agglayer/types.go | 104 ++++++ aggregator/aggregator.go | 5 +- aggregator/aggregator_test.go | 5 +- aggregator/config.go | 21 -- aggregator/mocks/mock_agglayer_client.go | 79 ----- aggsender/aggsender.go | 314 ++++++++++++++++++ aggsender/config.go | 13 + bridgesync/bridgesync.go | 29 +- bridgesync/config.go | 2 + bridgesync/e2e_test.go | 2 +- bridgesync/processor.go | 56 +++- bridgesync/processor_test.go | 89 +++++ claimsponsor/e2e_test.go | 2 +- cmd/main.go | 3 +- cmd/run.go | 31 +- common/common.go | 21 ++ common/components.go | 2 + config/config.go | 5 +- config/default.go | 6 + tree/tree.go | 18 +- 22 files changed, 704 insertions(+), 118 deletions(-) rename aggregator/agglayer/agglayer_client.go => agglayer/client.go (83%) rename aggregator/agglayer/agglayer_tx.go => agglayer/tx.go (100%) create mode 100644 agglayer/types.go delete mode 100644 aggregator/mocks/mock_agglayer_client.go create mode 100644 aggsender/aggsender.go create mode 100644 aggsender/config.go diff --git a/aggregator/agglayer/agglayer_client.go b/agglayer/client.go similarity index 83% rename from aggregator/agglayer/agglayer_client.go rename to agglayer/client.go index a5222571..1cfd0de6 100644 --- a/aggregator/agglayer/agglayer_client.go +++ b/agglayer/client.go @@ -21,6 +21,7 @@ var ErrAgglayerRateLimitExceeded = fmt.Errorf("agglayer rate limit exceeded") type AgglayerClientInterface interface { SendTx(signedTx SignedTx) (common.Hash, error) WaitTxToBeMined(hash common.Hash, ctx context.Context) error + SendCertificate(certificate *SignedCertificate) error } // AggLayerClient is the client that will be used to interact with the AggLayer @@ -86,3 +87,17 @@ func (c *AggLayerClient) WaitTxToBeMined(hash common.Hash, ctx context.Context) } } } + +// SendCertificate sends a certificate to the AggLayer +func (c *AggLayerClient) SendCertificate(certificate *SignedCertificate) error { + response, err := rpc.JSONRPCCall(c.url, "interop_sendCertificate", certificate) + if err != nil { + return err + } + + if response.Error != nil { + return fmt.Errorf("%v %v", response.Error.Code, response.Error.Message) + } + + return nil +} diff --git a/aggregator/agglayer/agglayer_tx.go b/agglayer/tx.go similarity index 100% rename from aggregator/agglayer/agglayer_tx.go rename to agglayer/tx.go diff --git a/agglayer/types.go b/agglayer/types.go new file mode 100644 index 00000000..8b915068 --- /dev/null +++ b/agglayer/types.go @@ -0,0 +1,104 @@ +package agglayer + +import ( + "math/big" + + cdkcommon "github.com/0xPolygon/cdk/common" + "github.com/0xPolygon/cdk/tree/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +type LeafType uint8 + +func (l LeafType) Uint8() uint8 { + return uint8(l) +} + +const ( + LeafTypeAsset LeafType = 0 + LeafTypeMessage LeafType = 1 +) + +// Certificate is the data structure that will be sent to the aggLayer +type Certificate struct { + NetworkID uint32 `json:"network_id"` + Height uint64 `json:"height"` + PrevLocalExitRoot common.Hash `json:"prev_local_exit_root"` + NewLocalExitRoot common.Hash `json:"new_local_exit_root"` + BridgeExits []*BridgeExit `json:"bridge_exits"` + ImportedBridgeExits []*ImportedBridgeExit `json:"imported_bridge_exits"` +} + +// Hash returns a hash that uniquely identifies the certificate +func (c *Certificate) Hash() common.Hash { + importedBridgeHashes := make([][]byte, 0, len(c.ImportedBridgeExits)) + for _, claim := range c.ImportedBridgeExits { + importedBridgeHashes = append(importedBridgeHashes, claim.Hash().Bytes()) + } + + incomeCommitment := crypto.Keccak256Hash(importedBridgeHashes...) + outcomeCommitment := c.NewLocalExitRoot + + return crypto.Keccak256Hash(outcomeCommitment.Bytes(), incomeCommitment.Bytes()) +} + +// SignedCertificate is the struct that contains the certificate and the signature of the signer +type SignedCertificate struct { + *Certificate + Signature []byte `json:"signature"` +} + +// TokenInfo encapsulates the information to uniquely identify a token on the origin network. +type TokenInfo struct { + OriginNetwork uint32 `json:"origin_network"` + OriginTokenAddress common.Address `json:"origin_token_address"` +} + +// GlobalIndex represents the global index of an imported bridge exit +type GlobalIndex struct { + MainnetFlag bool `json:"mainnet_flag"` + RollupIndex uint32 `json:"rollup_index"` + LeafIndex uint32 `json:"leaf_index"` +} + +// BridgeExit represents a token bridge exit +type BridgeExit struct { + LeafType LeafType `json:"leaf_type"` + TokenInfo *TokenInfo `json:"token_info"` + DestinationNetwork uint32 `json:"destination_network"` + DestinationAddress common.Address `json:"destination_address"` + Amount *big.Int `json:"amount"` + Metadata []byte `json:"metadata"` +} + +// Hash returns a hash that uniquely identifies the bridge exit +func (c *BridgeExit) Hash() common.Hash { + if c.Amount == nil { + c.Amount = big.NewInt(0) + } + + return crypto.Keccak256Hash( + []byte{c.LeafType.Uint8()}, + cdkcommon.Uint32ToBytes(c.TokenInfo.OriginNetwork), + c.TokenInfo.OriginTokenAddress.Bytes(), + cdkcommon.Uint32ToBytes(c.DestinationNetwork), + c.DestinationAddress.Bytes(), + c.Amount.Bytes(), + crypto.Keccak256(c.Metadata), + ) +} + +// ImportedBridgeExit represents a token bridge exit originating on another network but claimed on the current network. +type ImportedBridgeExit struct { + BridgeExit *BridgeExit `json:"bridge_exit"` + ImportedLocalExitRoot common.Hash `json:"imported_local_exit_root"` + InclusionProof [types.DefaultHeight]common.Hash `json:"inclusion_proof"` + InclusionProofRER [types.DefaultHeight]common.Hash `json:"inclusion_proof_rer"` + GlobalIndex *GlobalIndex `json:"global_index"` +} + +// Hash returns a hash that uniquely identifies the imported bridge exit +func (c *ImportedBridgeExit) Hash() common.Hash { + return c.BridgeExit.Hash() +} diff --git a/aggregator/aggregator.go b/aggregator/aggregator.go index 7106b615..15c6ba9a 100644 --- a/aggregator/aggregator.go +++ b/aggregator/aggregator.go @@ -16,7 +16,7 @@ import ( "github.com/0xPolygon/cdk-rpc/rpc" cdkTypes "github.com/0xPolygon/cdk-rpc/types" - "github.com/0xPolygon/cdk/aggregator/agglayer" + "github.com/0xPolygon/cdk/agglayer" ethmanTypes "github.com/0xPolygon/cdk/aggregator/ethmantypes" "github.com/0xPolygon/cdk/aggregator/prover" cdkcommon "github.com/0xPolygon/cdk/common" @@ -175,7 +175,7 @@ func New( if !cfg.SyncModeOnlyEnabled && cfg.SettlementBackend == AggLayer { aggLayerClient = agglayer.NewAggLayerClient(cfg.AggLayerURL) - sequencerPrivateKey, err = newKeyFromKeystore(cfg.SequencerPrivateKey) + sequencerPrivateKey, err = cdkcommon.NewKeyFromKeystore(cfg.SequencerPrivateKey) if err != nil { return nil, err } @@ -928,7 +928,6 @@ func (a *Aggregator) settleWithAggLayer( inputs ethmanTypes.FinalProofInputs) bool { proofStrNo0x := strings.TrimPrefix(inputs.FinalProof.Proof, "0x") proofBytes := common.Hex2Bytes(proofStrNo0x) - tx := agglayer.Tx{ LastVerifiedBatch: cdkTypes.ArgUint64(proof.BatchNumber - 1), NewVerifiedBatch: cdkTypes.ArgUint64(proof.BatchNumberFinal), diff --git a/aggregator/aggregator_test.go b/aggregator/aggregator_test.go index 657a34cf..461ed8df 100644 --- a/aggregator/aggregator_test.go +++ b/aggregator/aggregator_test.go @@ -16,6 +16,7 @@ import ( "testing" "time" + "github.com/0xPolygon/cdk/agglayer" mocks "github.com/0xPolygon/cdk/aggregator/mocks" "github.com/0xPolygon/cdk/aggregator/prover" "github.com/0xPolygon/cdk/config/types" @@ -457,7 +458,7 @@ func Test_sendFinalProofSuccess(t *testing.T) { stateMock := mocks.NewStateInterfaceMock(t) ethTxManager := mocks.NewEthTxManagerClientMock(t) etherman := mocks.NewEthermanMock(t) - aggLayerClient := mocks.NewAgglayerClientInterfaceMock(t) + aggLayerClient := agglayer.NewAggLayerClientMock(t) curve := elliptic.P256() privateKey, err := ecdsa.GenerateKey(curve, rand.Reader) @@ -663,7 +664,7 @@ func Test_sendFinalProofError(t *testing.T) { stateMock := mocks.NewStateInterfaceMock(t) ethTxManager := mocks.NewEthTxManagerClientMock(t) etherman := mocks.NewEthermanMock(t) - aggLayerClient := mocks.NewAgglayerClientInterfaceMock(t) + aggLayerClient := agglayer.NewAggLayerClientMock(t) curve := elliptic.P256() privateKey, err := ecdsa.GenerateKey(curve, rand.Reader) diff --git a/aggregator/config.go b/aggregator/config.go index fbbc9c9b..30de6b7c 100644 --- a/aggregator/config.go +++ b/aggregator/config.go @@ -1,18 +1,14 @@ package aggregator import ( - "crypto/ecdsa" "fmt" "math/big" - "os" - "path/filepath" "github.com/0xPolygon/cdk/aggregator/db" "github.com/0xPolygon/cdk/config/types" "github.com/0xPolygon/cdk/log" "github.com/0xPolygon/zkevm-ethtx-manager/ethtxmanager" syncronizerConfig "github.com/0xPolygonHermez/zkevm-synchronizer-l1/config" - "github.com/ethereum/go-ethereum/accounts/keystore" ) // SettlementBackend is the type of the settlement backend @@ -164,20 +160,3 @@ type StreamClientCfg struct { // Log is the log configuration Log log.Config `mapstructure:"Log"` } - -// newKeyFromKeystore creates a private key from a keystore file -func newKeyFromKeystore(cfg types.KeystoreFileConfig) (*ecdsa.PrivateKey, error) { - if cfg.Path == "" && cfg.Password == "" { - return nil, nil - } - keystoreEncrypted, err := os.ReadFile(filepath.Clean(cfg.Path)) - if err != nil { - return nil, err - } - key, err := keystore.DecryptKey(keystoreEncrypted, cfg.Password) - if err != nil { - return nil, err - } - - return key.PrivateKey, nil -} diff --git a/aggregator/mocks/mock_agglayer_client.go b/aggregator/mocks/mock_agglayer_client.go deleted file mode 100644 index 2923ebe0..00000000 --- a/aggregator/mocks/mock_agglayer_client.go +++ /dev/null @@ -1,79 +0,0 @@ -// Code generated by mockery v2.39.0. DO NOT EDIT. - -package mocks - -import ( - agglayer "github.com/0xPolygon/cdk/aggregator/agglayer" - common "github.com/ethereum/go-ethereum/common" - - context "context" - - mock "github.com/stretchr/testify/mock" -) - -// AgglayerClientInterfaceMock is an autogenerated mock type for the AgglayerClientInterface type -type AgglayerClientInterfaceMock struct { - mock.Mock -} - -// SendTx provides a mock function with given fields: signedTx -func (_m *AgglayerClientInterfaceMock) SendTx(signedTx agglayer.SignedTx) (common.Hash, error) { - ret := _m.Called(signedTx) - - if len(ret) == 0 { - panic("no return value specified for SendTx") - } - - var r0 common.Hash - var r1 error - if rf, ok := ret.Get(0).(func(agglayer.SignedTx) (common.Hash, error)); ok { - return rf(signedTx) - } - if rf, ok := ret.Get(0).(func(agglayer.SignedTx) common.Hash); ok { - r0 = rf(signedTx) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(common.Hash) - } - } - - if rf, ok := ret.Get(1).(func(agglayer.SignedTx) error); ok { - r1 = rf(signedTx) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// WaitTxToBeMined provides a mock function with given fields: hash, ctx -func (_m *AgglayerClientInterfaceMock) WaitTxToBeMined(hash common.Hash, ctx context.Context) error { - ret := _m.Called(hash, ctx) - - if len(ret) == 0 { - panic("no return value specified for WaitTxToBeMined") - } - - var r0 error - if rf, ok := ret.Get(0).(func(common.Hash, context.Context) error); ok { - r0 = rf(hash, ctx) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// NewAgglayerClientInterfaceMock creates a new instance of AgglayerClientInterfaceMock. 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 NewAgglayerClientInterfaceMock(t interface { - mock.TestingT - Cleanup(func()) -}) *AgglayerClientInterfaceMock { - mock := &AgglayerClientInterfaceMock{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go new file mode 100644 index 00000000..3238fd94 --- /dev/null +++ b/aggsender/aggsender.go @@ -0,0 +1,314 @@ +package aggsender + +import ( + "context" + "crypto/ecdsa" + "encoding/json" + "fmt" + "path/filepath" + "time" + + "github.com/0xPolygon/cdk/agglayer" + "github.com/0xPolygon/cdk/bridgesync" + cdkcommon "github.com/0xPolygon/cdk/common" + "github.com/0xPolygon/cdk/config/types" + "github.com/0xPolygon/cdk/l1infotreesync" + "github.com/0xPolygon/cdk/log" + "github.com/0xPolygon/cdk/tree" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ledgerwatch/erigon-lib/kv" + "github.com/ledgerwatch/erigon-lib/kv/mdbx" +) + +const ( + aggSenderDBFolder = "aggsender" + sentCertificatesL2Table = "sent_certificates_l2" +) + +func tableCfgFunc(defaultBuckets kv.TableCfg) kv.TableCfg { + return kv.TableCfg{ + sentCertificatesL2Table: {}, + } +} + +// AggSender is a component that will send certificates to the aggLayer +type AggSender struct { + l2Syncer *bridgesync.BridgeSync + l2Client bridgesync.EthClienter + l1infoTreeSyncer *l1infotreesync.L1InfoTreeSync + + db kv.RwDB + aggLayerClient agglayer.AgglayerClientInterface + + sendInterval types.Duration + + sequencerKey *ecdsa.PrivateKey +} + +// New returns a new AggSender +func New( + ctx context.Context, + cfg Config, + aggLayerClient agglayer.AgglayerClientInterface, + l1InfoTreeSyncer *l1infotreesync.L1InfoTreeSync, + l2Syncer *bridgesync.BridgeSync, + l2Client bridgesync.EthClienter) (*AggSender, error) { + db, err := mdbx.NewMDBX(nil). + Path(filepath.Join(cfg.DBPath, aggSenderDBFolder)). + WithTableCfg(tableCfgFunc). + Open() + if err != nil { + return nil, err + } + + sequencerPrivateKey, err := cdkcommon.NewKeyFromKeystore(cfg.SequencerPrivateKey) + if err != nil { + return nil, err + } + + return &AggSender{ + db: db, + l2Syncer: l2Syncer, + l2Client: l2Client, + aggLayerClient: aggLayerClient, + l1infoTreeSyncer: l1InfoTreeSyncer, + sequencerKey: sequencerPrivateKey, + sendInterval: cfg.CertificateSendInterval, + }, nil +} + +// Start starts the AggSender +func (a *AggSender) Start(ctx context.Context) { + go a.sendCertificates(ctx) +} + +// sendCertificates sends certificates to the aggLayer +func (a *AggSender) sendCertificates(ctx context.Context) { + ticker := time.NewTicker(a.sendInterval.Duration) + + for { + select { + case <-ticker.C: + if err := a.sendCertificatesForNetwork(ctx); err != nil { + log.Error(err) + } + case <-ctx.Done(): + log.Info("AggSender stopped") + return + } + } +} + +// buildCertificate builds a certificate from the bridge events +func (a *AggSender) buildCertificate(ctx context.Context, + bridges []bridgesync.Bridge, + claims []bridgesync.Claim, + previousExitRoot common.Hash, lastHeight uint64) (*agglayer.Certificate, error) { + bridgeExits := make([]*agglayer.BridgeExit, 0, len(bridges)) + importedBridgeExits := make([]*agglayer.ImportedBridgeExit, 0, len(claims)) + + for _, bridge := range bridges { + bridgeExit := convertBridgeToBridgeExit(bridge) + bridgeExits = append(bridgeExits, bridgeExit) + } + + for _, claim := range claims { + importedBridgeExit, err := convertClaimToImportedBridgeExit(claim) + if err != nil { + return nil, fmt.Errorf("error converting claim to imported bridge exit: %w", err) + } + + importedBridgeExits = append(importedBridgeExits, importedBridgeExit) + } + + var depositCount uint32 + if len(bridges) > 0 { + depositCount = bridges[len(bridges)-1].DepositCount + } + + exitRoot, err := a.l2Syncer.GetExitRootByIndex(ctx, depositCount) + if err != nil { + return nil, fmt.Errorf("error getting exit root by index: %d. Error: %w", depositCount, err) + } + + return &agglayer.Certificate{ + NetworkID: a.l2Syncer.OriginNetwork(), + PrevLocalExitRoot: previousExitRoot, + NewLocalExitRoot: exitRoot.Hash, + BridgeExits: bridgeExits, + ImportedBridgeExits: importedBridgeExits, + Height: lastHeight + 1, + }, nil +} + +func convertBridgeToBridgeExit(bridge bridgesync.Bridge) *agglayer.BridgeExit { + return &agglayer.BridgeExit{ + LeafType: agglayer.LeafType(bridge.LeafType), + TokenInfo: &agglayer.TokenInfo{ + OriginNetwork: bridge.OriginNetwork, + OriginTokenAddress: bridge.OriginAddress, + }, + DestinationNetwork: bridge.DestinationNetwork, + DestinationAddress: bridge.DestinationAddress, + Amount: bridge.Amount, + Metadata: bridge.Metadata, + } +} + +func convertClaimToImportedBridgeExit(claim bridgesync.Claim) (*agglayer.ImportedBridgeExit, error) { + leafType := agglayer.LeafTypeAsset + if claim.IsMessage { + leafType = agglayer.LeafTypeMessage + } + + bridgeExit := &agglayer.BridgeExit{ + LeafType: leafType, + TokenInfo: &agglayer.TokenInfo{ + OriginNetwork: claim.OriginNetwork, + OriginTokenAddress: claim.OriginAddress, + }, + DestinationNetwork: claim.DestinationNetwork, + DestinationAddress: claim.DestinationAddress, + Amount: claim.Amount, + Metadata: claim.Metadata, + } + + mainnetFlag, rollupIndex, leafIndex, err := bridgesync.DecodeGlobalIndex(claim.GlobalIndex) + if err != nil { + return nil, fmt.Errorf("error decoding global index: %w", err) + } + + return &agglayer.ImportedBridgeExit{ + BridgeExit: bridgeExit, + ImportedLocalExitRoot: tree.CalculateRoot(bridgeExit.Hash(), + claim.ProofLocalExitRoot, uint32(claim.GlobalIndex.Uint64())), + InclusionProof: claim.ProofLocalExitRoot, + InclusionProofRER: claim.ProofRollupExitRoot, + GlobalIndex: &agglayer.GlobalIndex{ + MainnetFlag: mainnetFlag, + RollupIndex: rollupIndex, + LeafIndex: leafIndex, + }, + }, nil +} + +// sendCertificatesForNetwork sends certificates for a network +func (a *AggSender) sendCertificatesForNetwork(ctx context.Context) error { + lastSentCertificateBlock, lastSentCertificate, err := a.getLastSentCertificate(ctx) + if err != nil { + return fmt.Errorf("error getting last sent certificate: %w", err) + } + + finality := a.l2Syncer.BlockFinality() + blockFinality, err := finality.ToBlockNum() + if err != nil { + return fmt.Errorf("error getting block finality: %w", err) + } + + lastFinalizedBlock, err := a.l2Client.HeaderByNumber(ctx, blockFinality) + if err != nil { + return fmt.Errorf("error getting block number: %w", err) + } + + bridges, err := a.l2Syncer.GetBridges(ctx, lastSentCertificateBlock+1, lastFinalizedBlock.Nonce.Uint64()) + if err != nil { + return fmt.Errorf("error getting bridges: %w", err) + } + + claims, err := a.l2Syncer.GetClaims(ctx, lastSentCertificateBlock+1, lastFinalizedBlock.Nonce.Uint64()) + if err != nil { + return fmt.Errorf("error getting claims: %w", err) + } + + if len(bridges) == 0 && len(claims) == 0 { + // nothing to send + return nil + } + + previousExitRoot := common.Hash{} + lastHeight := uint64(0) + if lastSentCertificate != nil { + previousExitRoot = lastSentCertificate.NewLocalExitRoot + lastHeight = lastSentCertificate.Height + } + + certificate, err := a.buildCertificate(ctx, bridges, claims, previousExitRoot, lastHeight) + if err != nil { + return fmt.Errorf("error building certificate: %w", err) + } + + signedCertificate, err := a.signCertificate(certificate) + if err != nil { + return fmt.Errorf("error signing certificate: %w", err) + } + + if err := a.aggLayerClient.SendCertificate(signedCertificate); err != nil { + return fmt.Errorf("error sending certificate: %w", err) + } + + if err := a.saveLastSentCertificate(ctx, lastFinalizedBlock.Nonce.Uint64(), certificate); err != nil { + return fmt.Errorf("error saving last sent certificate in db: %w", err) + } + + return nil +} + +// saveLastSentCertificate saves the last sent certificate +func (a *AggSender) saveLastSentCertificate(ctx context.Context, blockNum uint64, + certificate *agglayer.Certificate) error { + return a.db.Update(ctx, func(tx kv.RwTx) error { + raw, err := json.Marshal(certificate) + if err != nil { + return err + } + + return tx.Put(sentCertificatesL2Table, cdkcommon.Uint64ToBytes(blockNum), raw) + }) +} + +// getLastSentCertificate returns the last sent certificate +func (a *AggSender) getLastSentCertificate(ctx context.Context) (uint64, *agglayer.Certificate, error) { + var ( + lastSentCertificateBlock uint64 + lastCertificate *agglayer.Certificate + ) + + err := a.db.View(ctx, func(tx kv.Tx) error { + cursor, err := tx.Cursor(sentCertificatesL2Table) + if err != nil { + return err + } + + k, v, err := cursor.Last() + if err != nil { + return err + } + + if k != nil { + lastSentCertificateBlock = cdkcommon.BytesToUint64(k) + if err := json.Unmarshal(v, &lastCertificate); err != nil { + return err + } + } + + return nil + }) + + return lastSentCertificateBlock, lastCertificate, err +} + +// signCertificate signs a certificate with the sequencer key +func (a *AggSender) signCertificate(certificate *agglayer.Certificate) (*agglayer.SignedCertificate, error) { + hashToSign := certificate.Hash() + + sig, err := crypto.Sign(hashToSign.Bytes(), a.sequencerKey) + if err != nil { + return nil, err + } + + return &agglayer.SignedCertificate{ + Certificate: certificate, + Signature: sig, + }, nil +} diff --git a/aggsender/config.go b/aggsender/config.go new file mode 100644 index 00000000..f640496e --- /dev/null +++ b/aggsender/config.go @@ -0,0 +1,13 @@ +package aggsender + +import ( + "github.com/0xPolygon/cdk/config/types" +) + +// Config is the configuration for the AggSender +type Config struct { + DBPath string `mapstructure:"DBPath"` + AggLayerURL string `mapstructure:"AggLayerURL"` + CertificateSendInterval types.Duration `mapstructure:"CertificateSendInterval"` + SequencerPrivateKey types.KeystoreFileConfig `mapstructure:"SequencerPrivateKey"` +} diff --git a/bridgesync/bridgesync.go b/bridgesync/bridgesync.go index e6a61c5e..7e89f0ff 100644 --- a/bridgesync/bridgesync.go +++ b/bridgesync/bridgesync.go @@ -20,6 +20,9 @@ const ( type BridgeSync struct { processor *processor driver *sync.EVMDriver + + originNetwork uint32 + blockFinality etherman.BlockNumberFinality } // NewL1 creates a bridge syncer that synchronizes the mainnet exit tree @@ -35,6 +38,7 @@ func NewL1( waitForNewBlocksPeriod time.Duration, retryAfterErrorPeriod time.Duration, maxRetryAttemptsAfterError int, + originNetwork uint32, ) (*BridgeSync, error) { return newBridgeSync( ctx, @@ -49,6 +53,7 @@ func NewL1( waitForNewBlocksPeriod, retryAfterErrorPeriod, maxRetryAttemptsAfterError, + originNetwork, false, ) } @@ -66,6 +71,7 @@ func NewL2( waitForNewBlocksPeriod time.Duration, retryAfterErrorPeriod time.Duration, maxRetryAttemptsAfterError int, + originNetwork uint32, ) (*BridgeSync, error) { return newBridgeSync( ctx, @@ -80,6 +86,7 @@ func NewL2( waitForNewBlocksPeriod, retryAfterErrorPeriod, maxRetryAttemptsAfterError, + originNetwork, true, ) } @@ -97,6 +104,7 @@ func newBridgeSync( waitForNewBlocksPeriod time.Duration, retryAfterErrorPeriod time.Duration, maxRetryAttemptsAfterError int, + originNetwork uint32, syncFullClaims bool, ) (*BridgeSync, error) { processor, err := newProcessor(dbPath, l1OrL2ID) @@ -146,8 +154,10 @@ func newBridgeSync( } return &BridgeSync{ - processor: processor, - driver: driver, + processor: processor, + driver: driver, + originNetwork: originNetwork, + blockFinality: blockFinalityType, }, nil } @@ -191,3 +201,18 @@ func (s *BridgeSync) GetRootByLER(ctx context.Context, ler common.Hash) (*tree.R } return root, nil } + +// GetExitRootByIndex returns the root of the exit tree at the moment the leaf with the given index was added +func (s *BridgeSync) GetExitRootByIndex(ctx context.Context, index uint32) (tree.Root, error) { + return s.processor.exitTree.GetRootByIndex(ctx, index) +} + +// OriginNetwork returns the network ID of the origin chain +func (s *BridgeSync) OriginNetwork() uint32 { + return s.originNetwork +} + +// BlockFinality returns the block finality type +func (s *BridgeSync) BlockFinality() etherman.BlockNumberFinality { + return s.blockFinality +} diff --git a/bridgesync/config.go b/bridgesync/config.go index 66eb00ed..d2373b53 100644 --- a/bridgesync/config.go +++ b/bridgesync/config.go @@ -24,4 +24,6 @@ type Config struct { MaxRetryAttemptsAfterError int `mapstructure:"MaxRetryAttemptsAfterError"` // WaitForNewBlocksPeriod time that will be waited when the synchronizer has reached the latest block WaitForNewBlocksPeriod types.Duration `mapstructure:"WaitForNewBlocksPeriod"` + // OriginNetwork is the id of the network where the bridge is deployed + OriginNetwork uint32 `mapstructure:"OriginNetwork"` } diff --git a/bridgesync/e2e_test.go b/bridgesync/e2e_test.go index c0a22484..a8868ce1 100644 --- a/bridgesync/e2e_test.go +++ b/bridgesync/e2e_test.go @@ -29,7 +29,7 @@ func TestBridgeEventE2E(t *testing.T) { go rd.Start(ctx) //nolint:errcheck testClient := helpers.TestClient{ClientRenamed: client.Client()} - syncer, err := bridgesync.NewL1(ctx, dbPathSyncer, setup.EBZkevmBridgeAddr, 10, etherman.LatestBlock, rd, testClient, 0, time.Millisecond*10, 0, 0) + syncer, err := bridgesync.NewL1(ctx, dbPathSyncer, setup.EBZkevmBridgeAddr, 10, etherman.LatestBlock, rd, testClient, 0, time.Millisecond*10, 0, 0, 1) require.NoError(t, err) go syncer.Start(ctx) diff --git a/bridgesync/processor.go b/bridgesync/processor.go index e4ba5423..cf279c41 100644 --- a/bridgesync/processor.go +++ b/bridgesync/processor.go @@ -20,6 +20,11 @@ import ( _ "modernc.org/sqlite" ) +const ( + globalIndexPartSize = 4 + globalIndexMaxSize = 9 +) + var ( // ErrBlockNotProcessed indicates that the given block(s) have not been processed yet. ErrBlockNotProcessed = errors.New("given block(s) have not been processed yet") @@ -300,7 +305,7 @@ func (p *processor) ProcessBlock(ctx context.Context, block sync.Block) error { func GenerateGlobalIndex(mainnetFlag bool, rollupIndex uint32, localExitRootIndex uint32) *big.Int { var ( globalIndexBytes []byte - buf [4]byte + buf [globalIndexPartSize]byte ) if mainnetFlag { globalIndexBytes = append(globalIndexBytes, big.NewInt(1).Bytes()...) @@ -313,5 +318,52 @@ func GenerateGlobalIndex(mainnetFlag bool, rollupIndex uint32, localExitRootInde leri := big.NewInt(0).SetUint64(uint64(localExitRootIndex)).FillBytes(buf[:]) globalIndexBytes = append(globalIndexBytes, leri...) - return big.NewInt(0).SetBytes(globalIndexBytes) + result := big.NewInt(0).SetBytes(globalIndexBytes) + + return result +} + +// Decodes global index to its three parts: +// 1. mainnetFlag - first byte +// 2. rollupIndex - next 4 bytes +// 3. localExitRootIndex - last 4 bytes +// NOTE - mainnet flag is not in the global index bytes if it is false +// NOTE - rollup index is 0 if mainnet flag is true +// NOTE - rollup index is not in the global index bytes if mainnet flag is false and rollup index is 0 +func DecodeGlobalIndex(globalIndex *big.Int) (mainnetFlag bool, + rollupIndex uint32, localExitRootIndex uint32, err error) { + globalIndexBytes := globalIndex.Bytes() + l := len(globalIndexBytes) + if l > globalIndexMaxSize { + return false, 0, 0, errors.New("invalid global index length") + } + + if l == 0 { + // false, 0, 0 + return + } + + if l == globalIndexMaxSize { + // true, rollupIndex, localExitRootIndex + mainnetFlag = true + } + + localExitRootFromIdx := l - globalIndexPartSize + if localExitRootFromIdx < 0 { + localExitRootFromIdx = 0 + } + + rollupIndexFromIdx := localExitRootFromIdx - globalIndexPartSize + if rollupIndexFromIdx < 0 { + rollupIndexFromIdx = 0 + } + + rollupIndex = convertBytesToUint32(globalIndexBytes[rollupIndexFromIdx:localExitRootFromIdx]) + localExitRootIndex = convertBytesToUint32(globalIndexBytes[localExitRootFromIdx:]) + + return +} + +func convertBytesToUint32(bytes []byte) uint32 { + return uint32(big.NewInt(0).SetBytes(bytes).Uint64()) } diff --git a/bridgesync/processor_test.go b/bridgesync/processor_test.go index 2ff03c76..86dd33c5 100644 --- a/bridgesync/processor_test.go +++ b/bridgesync/processor_test.go @@ -3,6 +3,7 @@ package bridgesync import ( "context" "encoding/json" + "errors" "fmt" "math/big" "os" @@ -582,3 +583,91 @@ func TestHashBridge(t *testing.T) { }) } } + +func TestDecodeGlobalIndex(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + globalIndex *big.Int + expectedMainnetFlag bool + expectedRollupIndex uint32 + expectedLocalIndex uint32 + expectedErr error + }{ + { + name: "Mainnet flag true, rollup index 0", + globalIndex: GenerateGlobalIndex(true, 0, 2), + expectedMainnetFlag: true, + expectedRollupIndex: 0, + expectedLocalIndex: 2, + expectedErr: nil, + }, + { + name: "Mainnet flag true, indexes 0", + globalIndex: GenerateGlobalIndex(true, 0, 0), + expectedMainnetFlag: true, + expectedRollupIndex: 0, + expectedLocalIndex: 0, + expectedErr: nil, + }, + { + name: "Mainnet flag false, rollup index 0", + globalIndex: GenerateGlobalIndex(false, 0, 2), + expectedMainnetFlag: false, + expectedRollupIndex: 0, + expectedLocalIndex: 2, + expectedErr: nil, + }, + { + name: "Mainnet flag false, rollup index non-zero", + globalIndex: GenerateGlobalIndex(false, 11, 0), + expectedMainnetFlag: false, + expectedRollupIndex: 11, + expectedLocalIndex: 0, + expectedErr: nil, + }, + { + name: "Mainnet flag false, indexes 0", + globalIndex: GenerateGlobalIndex(false, 0, 0), + expectedMainnetFlag: false, + expectedRollupIndex: 0, + expectedLocalIndex: 0, + expectedErr: nil, + }, + { + name: "Mainnet flag false, indexes non zero", + globalIndex: GenerateGlobalIndex(false, 1231, 111234), + expectedMainnetFlag: false, + expectedRollupIndex: 1231, + expectedLocalIndex: 111234, + expectedErr: nil, + }, + { + name: "Invalid global index length", + globalIndex: big.NewInt(0).SetBytes([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}), + expectedMainnetFlag: false, + expectedRollupIndex: 0, + expectedLocalIndex: 0, + expectedErr: errors.New("invalid global index length"), + }, + } + + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + mainnetFlag, rollupIndex, localExitRootIndex, err := DecodeGlobalIndex(tt.globalIndex) + if tt.expectedErr != nil { + require.EqualError(t, err, tt.expectedErr.Error()) + } else { + require.NoError(t, err) + } + require.Equal(t, tt.expectedMainnetFlag, mainnetFlag) + require.Equal(t, tt.expectedRollupIndex, rollupIndex) + require.Equal(t, tt.expectedLocalIndex, localExitRootIndex) + }) + } +} diff --git a/claimsponsor/e2e_test.go b/claimsponsor/e2e_test.go index b4fce499..426d7b3e 100644 --- a/claimsponsor/e2e_test.go +++ b/claimsponsor/e2e_test.go @@ -26,7 +26,7 @@ func TestE2EL1toEVML2(t *testing.T) { env := aggoraclehelpers.SetupAggoracleWithEVMChain(t) dbPathBridgeSyncL1 := path.Join(t.TempDir(), "file::memory:?cache=shared") testClient := helpers.TestClient{ClientRenamed: env.L1Client.Client()} - bridgeSyncL1, err := bridgesync.NewL1(ctx, dbPathBridgeSyncL1, env.BridgeL1Addr, 10, etherman.LatestBlock, env.ReorgDetector, testClient, 0, time.Millisecond*10, 0, 0) + bridgeSyncL1, err := bridgesync.NewL1(ctx, dbPathBridgeSyncL1, env.BridgeL1Addr, 10, etherman.LatestBlock, env.ReorgDetector, testClient, 0, time.Millisecond*10, 0, 0, 1) require.NoError(t, err) go bridgeSyncL1.Start(ctx) diff --git a/cmd/main.go b/cmd/main.go index 23c01783..15b0fdc6 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -41,7 +41,8 @@ var ( Aliases: []string{"co"}, Usage: "List of components to run", Required: false, - Value: cli.NewStringSlice(common.SEQUENCE_SENDER, common.AGGREGATOR, common.AGGORACLE, common.RPC), + Value: cli.NewStringSlice(common.SEQUENCE_SENDER, common.AGGREGATOR, + common.AGGORACLE, common.RPC, common.AGGSENDER), } saveConfigFlag = cli.StringFlag{ Name: config.FlagSaveConfigPath, diff --git a/cmd/run.go b/cmd/run.go index 4bd4dd0d..3ef8ba5f 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -12,10 +12,12 @@ import ( zkevm "github.com/0xPolygon/cdk" dataCommitteeClient "github.com/0xPolygon/cdk-data-availability/client" jRPC "github.com/0xPolygon/cdk-rpc/rpc" + "github.com/0xPolygon/cdk/agglayer" "github.com/0xPolygon/cdk/aggoracle" "github.com/0xPolygon/cdk/aggoracle/chaingersender" "github.com/0xPolygon/cdk/aggregator" "github.com/0xPolygon/cdk/aggregator/db" + "github.com/0xPolygon/cdk/aggsender" "github.com/0xPolygon/cdk/bridgesync" "github.com/0xPolygon/cdk/claimsponsor" cdkcommon "github.com/0xPolygon/cdk/common" @@ -119,6 +121,13 @@ func start(cliCtx *cli.Context) error { log.Fatal(err) } }() + case cdkcommon.AGGSENDER: + aggsender, err := createAggSender(cliCtx.Context, c.AggSender, l1InfoTreeSync, l2BridgeSync, l2Client) + if err != nil { + log.Fatal(err) + } + + go aggsender.Start(cliCtx.Context) } } @@ -127,6 +136,18 @@ func start(cliCtx *cli.Context) error { return nil } +func createAggSender( + ctx context.Context, + cfg aggsender.Config, + l1InfoTreeSync *l1infotreesync.L1InfoTreeSync, + l2Syncer *bridgesync.BridgeSync, + l2Client bridgesync.EthClienter, +) (*aggsender.AggSender, error) { + agglayerClient := agglayer.NewAggLayerClient(cfg.AggLayerURL) + + return aggsender.New(ctx, cfg, agglayerClient, l1InfoTreeSync, l2Syncer, l2Client) +} + func createAggregator(ctx context.Context, c config.Config, runMigrations bool) *aggregator.Aggregator { logger := log.WithFields("module", cdkcommon.AGGREGATOR) // Migrations @@ -479,7 +500,8 @@ func runL1InfoTreeSyncerIfNeeded( l1Client *ethclient.Client, reorgDetector *reorgdetector.ReorgDetector, ) *l1infotreesync.L1InfoTreeSync { - if !isNeeded([]string{cdkcommon.AGGORACLE, cdkcommon.RPC, cdkcommon.SEQUENCE_SENDER}, components) { + if !isNeeded([]string{cdkcommon.AGGORACLE, cdkcommon.RPC, + cdkcommon.SEQUENCE_SENDER, cdkcommon.AGGSENDER}, components) { return nil } l1InfoTreeSync, err := l1infotreesync.New( @@ -522,7 +544,7 @@ func runL1ClientIfNeeded(components []string, urlRPCL1 string) *ethclient.Client } func runL2ClientIfNeeded(components []string, urlRPCL2 string) *ethclient.Client { - if !isNeeded([]string{cdkcommon.AGGORACLE, cdkcommon.RPC}, components) { + if !isNeeded([]string{cdkcommon.AGGORACLE, cdkcommon.RPC, cdkcommon.AGGSENDER}, components) { return nil } log.Debugf("dialing L2 client at: %s", urlRPCL2) @@ -675,6 +697,7 @@ func runBridgeSyncL1IfNeeded( cfg.WaitForNewBlocksPeriod.Duration, cfg.RetryAfterErrorPeriod.Duration, cfg.MaxRetryAttemptsAfterError, + cfg.OriginNetwork, ) if err != nil { log.Fatalf("error creating bridgeSyncL1: %s", err) @@ -691,8 +714,7 @@ func runBridgeSyncL2IfNeeded( reorgDetectorL2 *reorgdetector.ReorgDetector, l2Client *ethclient.Client, ) *bridgesync.BridgeSync { - // TODO: will be needed by AGGSENDER - if !isNeeded([]string{cdkcommon.RPC}, components) { + if !isNeeded([]string{cdkcommon.RPC, cdkcommon.AGGSENDER}, components) { return nil } bridgeSyncL2, err := bridgesync.NewL2( @@ -707,6 +729,7 @@ func runBridgeSyncL2IfNeeded( cfg.WaitForNewBlocksPeriod.Duration, cfg.RetryAfterErrorPeriod.Duration, cfg.MaxRetryAttemptsAfterError, + cfg.OriginNetwork, ) if err != nil { log.Fatalf("error creating bridgeSyncL2: %s", err) diff --git a/common/common.go b/common/common.go index cd5b5d70..c74f56e4 100644 --- a/common/common.go +++ b/common/common.go @@ -1,10 +1,15 @@ package common import ( + "crypto/ecdsa" "encoding/binary" "math/big" + "os" + "path/filepath" + "github.com/0xPolygon/cdk/config/types" "github.com/0xPolygon/cdk/log" + "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/common" "github.com/iden3/go-iden3-crypto/keccak256" ) @@ -88,3 +93,19 @@ func CalculateAccInputHash( return common.BytesToHash(keccak256.Hash(v1, v2, v3, v4, v5, v6)) } + +// NewKeyFromKeystore creates a private key from a keystore file +func NewKeyFromKeystore(cfg types.KeystoreFileConfig) (*ecdsa.PrivateKey, error) { + if cfg.Path == "" && cfg.Password == "" { + return nil, nil + } + keystoreEncrypted, err := os.ReadFile(filepath.Clean(cfg.Path)) + if err != nil { + return nil, err + } + key, err := keystore.DecryptKey(keystoreEncrypted, cfg.Password) + if err != nil { + return nil, err + } + return key.PrivateKey, nil +} diff --git a/common/components.go b/common/components.go index 0c2df8d7..7ef9d285 100644 --- a/common/components.go +++ b/common/components.go @@ -13,4 +13,6 @@ const ( CLAIM_SPONSOR = "claim-sponsor" //nolint:stylecheck // PROVER name to identify the prover component PROVER = "prover" + // AGGSENDER name to identify the aggsender component + AGGSENDER = "aggsender" ) diff --git a/config/config.go b/config/config.go index b21ba971..9363b93b 100644 --- a/config/config.go +++ b/config/config.go @@ -10,6 +10,7 @@ import ( jRPC "github.com/0xPolygon/cdk-rpc/rpc" "github.com/0xPolygon/cdk/aggoracle" "github.com/0xPolygon/cdk/aggregator" + "github.com/0xPolygon/cdk/aggsender" "github.com/0xPolygon/cdk/bridgesync" "github.com/0xPolygon/cdk/claimsponsor" "github.com/0xPolygon/cdk/common" @@ -135,7 +136,6 @@ type Config struct { NetworkConfig NetworkConfig // Configuration of the sequence sender service SequenceSender sequencesender.Config - // Common Config that affects all the services Common common.Config // Configuration of the reorg detector service to be used for the L1 @@ -162,6 +162,9 @@ type Config struct { // LastGERSync is the config for the synchronizer in charge of syncing the last GER injected on L2. // Needed for the bridge service (RPC) LastGERSync lastgersync.Config + + // AggSender is the configuration of the agg sender service + AggSender aggsender.Config } // Load loads the configuration diff --git a/config/default.go b/config/default.go index 442a44e0..2940a663 100644 --- a/config/default.go +++ b/config/default.go @@ -339,4 +339,10 @@ RollupManagerAddr = "{{L1Config.polygonRollupManagerAddress}}" GlobalExitRootManagerAddr = "{{L1Config.polygonZkEVMGlobalExitRootAddress}}" +[AggSender] +DBPath = "/tmp/aggsender" +AggLayerURL = "http://zkevm-agglayer" +SequencerPrivateKey = {Path = "/pk/sequencer.keystore", Password = "testonly"} +CertificateSendInterval = "1m" + ` diff --git a/tree/tree.go b/tree/tree.go index 0e3a0c69..ffcc2c54 100644 --- a/tree/tree.go +++ b/tree/tree.go @@ -9,6 +9,7 @@ import ( "github.com/0xPolygon/cdk/db" "github.com/0xPolygon/cdk/tree/types" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/russross/meddler" "golang.org/x/crypto/sha3" ) @@ -250,5 +251,20 @@ func (t *Tree) Reorg(tx db.Txer, firstReorgedBlock uint64) error { firstReorgedBlock, ) return err - // NOTE: rht is not cleaned, this could be done in the future as optimization +} + +// CalculateRoot calculates the Merkle Root based on the leaf and proof of inclusion +func CalculateRoot(leafHash common.Hash, proof [types.DefaultHeight]common.Hash, index uint32) common.Hash { + node := leafHash + + // Compute the Merkle root + for height := uint8(0); height < types.DefaultHeight; height++ { + if (index>>height)&1 == 1 { + node = crypto.Keccak256Hash(proof[height].Bytes(), node.Bytes()) + } else { + node = crypto.Keccak256Hash(node.Bytes(), proof[height].Bytes()) + } + } + + return node } From 5ad8a9a1004b1884e81791c754425e9885d8440b Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Wed, 9 Oct 2024 12:10:37 +0200 Subject: [PATCH 04/84] fix: update the api types --- agglayer/types.go | 78 ++++++++++--- aggsender/aggsender.go | 251 +++++++++++++++++++++++++++-------------- cmd/run.go | 3 +- 3 files changed, 232 insertions(+), 100 deletions(-) diff --git a/agglayer/types.go b/agglayer/types.go index 8b915068..26af7749 100644 --- a/agglayer/types.go +++ b/agglayer/types.go @@ -3,6 +3,7 @@ package agglayer import ( "math/big" + "github.com/0xPolygon/cdk/bridgesync" cdkcommon "github.com/0xPolygon/cdk/common" "github.com/0xPolygon/cdk/tree/types" "github.com/ethereum/go-ethereum/common" @@ -32,15 +33,12 @@ type Certificate struct { // Hash returns a hash that uniquely identifies the certificate func (c *Certificate) Hash() common.Hash { - importedBridgeHashes := make([][]byte, 0, len(c.ImportedBridgeExits)) - for _, claim := range c.ImportedBridgeExits { - importedBridgeHashes = append(importedBridgeHashes, claim.Hash().Bytes()) - } - - incomeCommitment := crypto.Keccak256Hash(importedBridgeHashes...) - outcomeCommitment := c.NewLocalExitRoot - - return crypto.Keccak256Hash(outcomeCommitment.Bytes(), incomeCommitment.Bytes()) + return crypto.Keccak256Hash( + cdkcommon.Uint32ToBytes(c.NetworkID), + cdkcommon.Uint64ToBytes(c.Height), + c.PrevLocalExitRoot.Bytes(), + c.NewLocalExitRoot.Bytes(), + ) } // SignedCertificate is the struct that contains the certificate and the signature of the signer @@ -89,16 +87,66 @@ func (c *BridgeExit) Hash() common.Hash { ) } +type MerkleProof struct { + Root common.Hash `json:"root"` + Proof [types.DefaultHeight]common.Hash `json:"proof"` +} + +type L1InfoTreeLeafInner struct { + GlobalExitRoot common.Hash `json:"global_exit_root"` + BlockHash common.Hash `json:"block_hash"` + Timestamp uint64 `json:"timestamp"` +} + +func (l L1InfoTreeLeafInner) Hash() common.Hash { + return crypto.Keccak256Hash(l.GlobalExitRoot.Bytes(), l.BlockHash.Bytes(), cdkcommon.Uint64ToBytes(l.Timestamp)) +} + +type L1InfoTreeLeaf struct { + L1InfoTreeIndex uint32 `json:"l1_info_tree_index"` + RollupExitRoot common.Hash `json:"rer"` + MainnetExitRoot common.Hash `json:"mer"` + Inner L1InfoTreeLeafInner `json:"inner"` +} + +func (l L1InfoTreeLeaf) Hash() common.Hash { + return l.Inner.Hash() +} + +type Claim interface { + Type() string +} + +type ClaimFromMainnnet struct { + ProofLeafMER MerkleProof `json:"proof_leaf_mer"` + ProofGERToL1Root MerkleProof `json:"proof_ger_l1root"` + L1Leaf L1InfoTreeLeaf `json:"l1_leaf"` +} + +func (c ClaimFromMainnnet) Type() string { + return "Mainnet" +} + +type ClaimFromRollup struct { + ProofLeafLER MerkleProof `json:"proof_leaf_ler"` + ProofLERToRER MerkleProof `json:"proof_ler_rer"` + ProofGERToL1Root MerkleProof `json:"proof_ger_l1root"` + L1Leaf L1InfoTreeLeaf `json:"l1_leaf"` +} + +func (c ClaimFromRollup) Type() string { + return "Rollup" +} + // ImportedBridgeExit represents a token bridge exit originating on another network but claimed on the current network. type ImportedBridgeExit struct { - BridgeExit *BridgeExit `json:"bridge_exit"` - ImportedLocalExitRoot common.Hash `json:"imported_local_exit_root"` - InclusionProof [types.DefaultHeight]common.Hash `json:"inclusion_proof"` - InclusionProofRER [types.DefaultHeight]common.Hash `json:"inclusion_proof_rer"` - GlobalIndex *GlobalIndex `json:"global_index"` + BridgeExit *BridgeExit `json:"bridge_exit"` + ClaimData Claim `json:"claim"` + GlobalIndex *GlobalIndex `json:"global_index"` } // Hash returns a hash that uniquely identifies the imported bridge exit func (c *ImportedBridgeExit) Hash() common.Hash { - return c.BridgeExit.Hash() + globalIndexBig := bridgesync.GenerateGlobalIndex(c.GlobalIndex.MainnetFlag, c.GlobalIndex.RollupIndex, c.GlobalIndex.LeafIndex) + return crypto.Keccak256Hash(globalIndexBig.Bytes()) } diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index 3238fd94..8029dfad 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -14,7 +14,6 @@ import ( "github.com/0xPolygon/cdk/config/types" "github.com/0xPolygon/cdk/l1infotreesync" "github.com/0xPolygon/cdk/log" - "github.com/0xPolygon/cdk/tree" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ledgerwatch/erigon-lib/kv" @@ -34,6 +33,8 @@ func tableCfgFunc(defaultBuckets kv.TableCfg) kv.TableCfg { // AggSender is a component that will send certificates to the aggLayer type AggSender struct { + log *log.Logger + l2Syncer *bridgesync.BridgeSync l2Client bridgesync.EthClienter l1infoTreeSyncer *l1infotreesync.L1InfoTreeSync @@ -49,6 +50,7 @@ type AggSender struct { // New returns a new AggSender func New( ctx context.Context, + logger *log.Logger, cfg Config, aggLayerClient agglayer.AgglayerClientInterface, l1InfoTreeSyncer *l1infotreesync.L1InfoTreeSync, @@ -69,6 +71,7 @@ func New( return &AggSender{ db: db, + log: logger, l2Syncer: l2Syncer, l2Client: l2Client, aggLayerClient: aggLayerClient, @@ -80,7 +83,7 @@ func New( // Start starts the AggSender func (a *AggSender) Start(ctx context.Context) { - go a.sendCertificates(ctx) + a.sendCertificates(ctx) } // sendCertificates sends certificates to the aggLayer @@ -90,36 +93,89 @@ func (a *AggSender) sendCertificates(ctx context.Context) { for { select { case <-ticker.C: - if err := a.sendCertificatesForNetwork(ctx); err != nil { + if err := a.sendCertificate(ctx); err != nil { log.Error(err) } case <-ctx.Done(): - log.Info("AggSender stopped") + a.log.Info("AggSender stopped") return } } } +// sendCertificate sends certificate for a network +func (a *AggSender) sendCertificate(ctx context.Context) error { + lastSentCertificateBlock, lastSentCertificate, err := a.getLastSentCertificate(ctx) + if err != nil { + return fmt.Errorf("error getting last sent certificate: %w", err) + } + + finality := a.l2Syncer.BlockFinality() + blockFinality, err := finality.ToBlockNum() + if err != nil { + return fmt.Errorf("error getting block finality: %w", err) + } + + lastFinalizedBlock, err := a.l2Client.HeaderByNumber(ctx, blockFinality) + if err != nil { + return fmt.Errorf("error getting block number: %w", err) + } + + fromBlock := lastSentCertificateBlock + 1 + toBlock := lastFinalizedBlock.Nonce.Uint64() + + bridges, err := a.l2Syncer.GetBridges(ctx, fromBlock, toBlock) + if err != nil { + return fmt.Errorf("error getting bridges: %w", err) + } + + if len(bridges) == 0 { + a.log.Info("no bridges consumed, no need to send a certificate from block: %d to block: %d", fromBlock, toBlock) + return nil + } + + claims, err := a.l2Syncer.GetClaims(ctx, fromBlock, toBlock) + if err != nil { + return fmt.Errorf("error getting claims: %w", err) + } + + previousExitRoot := common.Hash{} + lastHeight := uint64(0) + if lastSentCertificate != nil { + previousExitRoot = lastSentCertificate.NewLocalExitRoot + lastHeight = lastSentCertificate.Height + } + + certificate, err := a.buildCertificate(ctx, bridges, claims, previousExitRoot, lastHeight) + if err != nil { + return fmt.Errorf("error building certificate: %w", err) + } + + signedCertificate, err := a.signCertificate(certificate) + if err != nil { + return fmt.Errorf("error signing certificate: %w", err) + } + + if err := a.aggLayerClient.SendCertificate(signedCertificate); err != nil { + return fmt.Errorf("error sending certificate: %w", err) + } + + if err := a.saveLastSentCertificate(ctx, lastFinalizedBlock.Nonce.Uint64(), certificate); err != nil { + return fmt.Errorf("error saving last sent certificate in db: %w", err) + } + + return nil +} + // buildCertificate builds a certificate from the bridge events func (a *AggSender) buildCertificate(ctx context.Context, bridges []bridgesync.Bridge, claims []bridgesync.Claim, previousExitRoot common.Hash, lastHeight uint64) (*agglayer.Certificate, error) { - bridgeExits := make([]*agglayer.BridgeExit, 0, len(bridges)) - importedBridgeExits := make([]*agglayer.ImportedBridgeExit, 0, len(claims)) - - for _, bridge := range bridges { - bridgeExit := convertBridgeToBridgeExit(bridge) - bridgeExits = append(bridgeExits, bridgeExit) - } - - for _, claim := range claims { - importedBridgeExit, err := convertClaimToImportedBridgeExit(claim) - if err != nil { - return nil, fmt.Errorf("error converting claim to imported bridge exit: %w", err) - } - - importedBridgeExits = append(importedBridgeExits, importedBridgeExit) + bridgeExits := a.getBridgeExits(bridges) + importedBridgeExits, err := a.getImportedBridgeExits(ctx, claims) + if err != nil { + return nil, fmt.Errorf("error getting imported bridge exits: %w", err) } var depositCount uint32 @@ -142,21 +198,7 @@ func (a *AggSender) buildCertificate(ctx context.Context, }, nil } -func convertBridgeToBridgeExit(bridge bridgesync.Bridge) *agglayer.BridgeExit { - return &agglayer.BridgeExit{ - LeafType: agglayer.LeafType(bridge.LeafType), - TokenInfo: &agglayer.TokenInfo{ - OriginNetwork: bridge.OriginNetwork, - OriginTokenAddress: bridge.OriginAddress, - }, - DestinationNetwork: bridge.DestinationNetwork, - DestinationAddress: bridge.DestinationAddress, - Amount: bridge.Amount, - Metadata: bridge.Metadata, - } -} - -func convertClaimToImportedBridgeExit(claim bridgesync.Claim) (*agglayer.ImportedBridgeExit, error) { +func (a *AggSender) convertClaimToImportedBridgeExit(claim bridgesync.Claim) (*agglayer.ImportedBridgeExit, error) { leafType := agglayer.LeafTypeAsset if claim.IsMessage { leafType = agglayer.LeafTypeMessage @@ -181,10 +223,6 @@ func convertClaimToImportedBridgeExit(claim bridgesync.Claim) (*agglayer.Importe return &agglayer.ImportedBridgeExit{ BridgeExit: bridgeExit, - ImportedLocalExitRoot: tree.CalculateRoot(bridgeExit.Hash(), - claim.ProofLocalExitRoot, uint32(claim.GlobalIndex.Uint64())), - InclusionProof: claim.ProofLocalExitRoot, - InclusionProofRER: claim.ProofRollupExitRoot, GlobalIndex: &agglayer.GlobalIndex{ MainnetFlag: mainnetFlag, RollupIndex: rollupIndex, @@ -193,65 +231,110 @@ func convertClaimToImportedBridgeExit(claim bridgesync.Claim) (*agglayer.Importe }, nil } -// sendCertificatesForNetwork sends certificates for a network -func (a *AggSender) sendCertificatesForNetwork(ctx context.Context) error { - lastSentCertificateBlock, lastSentCertificate, err := a.getLastSentCertificate(ctx) - if err != nil { - return fmt.Errorf("error getting last sent certificate: %w", err) - } - - finality := a.l2Syncer.BlockFinality() - blockFinality, err := finality.ToBlockNum() - if err != nil { - return fmt.Errorf("error getting block finality: %w", err) - } +// getBridgeExits converts bridges to agglayer.BridgeExit objects +func (a *AggSender) getBridgeExits(bridges []bridgesync.Bridge) []*agglayer.BridgeExit { + bridgeExits := make([]*agglayer.BridgeExit, 0, len(bridges)) - lastFinalizedBlock, err := a.l2Client.HeaderByNumber(ctx, blockFinality) - if err != nil { - return fmt.Errorf("error getting block number: %w", err) + for _, bridge := range bridges { + bridgeExits = append(bridgeExits, &agglayer.BridgeExit{ + LeafType: agglayer.LeafType(bridge.LeafType), + TokenInfo: &agglayer.TokenInfo{ + OriginNetwork: bridge.OriginNetwork, + OriginTokenAddress: bridge.OriginAddress, + }, + DestinationNetwork: bridge.DestinationNetwork, + DestinationAddress: bridge.DestinationAddress, + Amount: bridge.Amount, + Metadata: bridge.Metadata, + }) } - bridges, err := a.l2Syncer.GetBridges(ctx, lastSentCertificateBlock+1, lastFinalizedBlock.Nonce.Uint64()) - if err != nil { - return fmt.Errorf("error getting bridges: %w", err) - } + return bridgeExits +} - claims, err := a.l2Syncer.GetClaims(ctx, lastSentCertificateBlock+1, lastFinalizedBlock.Nonce.Uint64()) - if err != nil { - return fmt.Errorf("error getting claims: %w", err) - } +// getImportedBridgeExits converts claims to agglayer.ImportedBridgeExit objects and calculates necessary proofs +func (a *AggSender) getImportedBridgeExits(ctx context.Context, claims []bridgesync.Claim) ([]*agglayer.ImportedBridgeExit, error) { + var ( + importedBridgeExits = make([]*agglayer.ImportedBridgeExit, 0, len(claims)) + greatestL1InfoTreeIndex = uint32(0) + ger = common.Hash{} + timestamp uint64 + ) - if len(bridges) == 0 && len(claims) == 0 { - // nothing to send - return nil - } + 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) + } - previousExitRoot := common.Hash{} - lastHeight := uint64(0) - if lastSentCertificate != nil { - previousExitRoot = lastSentCertificate.NewLocalExitRoot - lastHeight = lastSentCertificate.Height - } + if greatestL1InfoTreeIndex < info.L1InfoTreeIndex { + greatestL1InfoTreeIndex = info.L1InfoTreeIndex + ger = claim.GlobalExitRoot + timestamp = info.Timestamp + } - certificate, err := a.buildCertificate(ctx, bridges, claims, previousExitRoot, lastHeight) - if err != nil { - return fmt.Errorf("error building certificate: %w", err) - } + importedBridgeExit, err := a.convertClaimToImportedBridgeExit(claim) + if err != nil { + return nil, fmt.Errorf("error converting claim to imported bridge exit: %w", err) + } - signedCertificate, err := a.signCertificate(certificate) - if err != nil { - return fmt.Errorf("error signing certificate: %w", err) + importedBridgeExits = append(importedBridgeExits, importedBridgeExit) } - if err := a.aggLayerClient.SendCertificate(signedCertificate); err != nil { - return fmt.Errorf("error sending certificate: %w", err) - } + for i, ibe := range importedBridgeExits { + gerToL1Proof, err := a.l1infoTreeSyncer.GetL1InfoTreeMerkleProofFromIndexToRoot(ctx, ibe.GlobalIndex.LeafIndex, ger) + if err != nil { + return nil, fmt.Errorf("error getting L1 Info tree merkle proof: %w", err) + } - if err := a.saveLastSentCertificate(ctx, lastFinalizedBlock.Nonce.Uint64(), certificate); err != nil { - return fmt.Errorf("error saving last sent certificate in db: %w", err) + claim := claims[i] + if ibe.GlobalIndex.MainnetFlag { + ibe.ClaimData = &agglayer.ClaimFromMainnnet{ + L1Leaf: agglayer.L1InfoTreeLeaf{ + L1InfoTreeIndex: ibe.GlobalIndex.LeafIndex, + RollupExitRoot: claims[i].RollupExitRoot, + MainnetExitRoot: claims[i].MainnetExitRoot, + Inner: agglayer.L1InfoTreeLeafInner{ + GlobalExitRoot: ger, + Timestamp: timestamp, + // BlockHash: TODO, + }, + }, + ProofLeafMER: agglayer.MerkleProof{ + Root: claim.MainnetExitRoot, + Proof: claim.ProofLocalExitRoot, + }, + ProofGERToL1Root: agglayer.MerkleProof{ + Root: ger, + Proof: gerToL1Proof, + }, + } + } else { + ibe.ClaimData = &agglayer.ClaimFromRollup{ + L1Leaf: agglayer.L1InfoTreeLeaf{ + L1InfoTreeIndex: ibe.GlobalIndex.LeafIndex, + RollupExitRoot: claim.RollupExitRoot, + MainnetExitRoot: claim.MainnetExitRoot, + Inner: agglayer.L1InfoTreeLeafInner{ + GlobalExitRoot: ger, + Timestamp: timestamp, + // BlockHash: TODO, + }, + }, + ProofLeafLER: agglayer.MerkleProof{ + Root: claim.MainnetExitRoot, + Proof: claim.ProofLocalExitRoot, + }, + ProofLERToRER: agglayer.MerkleProof{}, + ProofGERToL1Root: agglayer.MerkleProof{ + Root: ger, + Proof: gerToL1Proof, + }, + } + } } - return nil + return importedBridgeExits, nil } // saveLastSentCertificate saves the last sent certificate diff --git a/cmd/run.go b/cmd/run.go index 3ef8ba5f..649cf83b 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -143,9 +143,10 @@ func createAggSender( l2Syncer *bridgesync.BridgeSync, l2Client bridgesync.EthClienter, ) (*aggsender.AggSender, error) { + logger := log.WithFields("module", cdkcommon.AGGSENDER) agglayerClient := agglayer.NewAggLayerClient(cfg.AggLayerURL) - return aggsender.New(ctx, cfg, agglayerClient, l1InfoTreeSync, l2Syncer, l2Client) + return aggsender.New(ctx, logger, cfg, agglayerClient, l1InfoTreeSync, l2Syncer, l2Client) } func createAggregator(ctx context.Context, c config.Config, runMigrations bool) *aggregator.Aggregator { From bc6d69e9ccc255ea5cb1943b0da0fe17d8231e25 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Wed, 9 Oct 2024 12:10:37 +0200 Subject: [PATCH 05/84] fix: ling --- agglayer/types.go | 3 ++- aggsender/aggsender.go | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/agglayer/types.go b/agglayer/types.go index 26af7749..caf4ae8f 100644 --- a/agglayer/types.go +++ b/agglayer/types.go @@ -147,6 +147,7 @@ type ImportedBridgeExit struct { // Hash returns a hash that uniquely identifies the imported bridge exit func (c *ImportedBridgeExit) Hash() common.Hash { - globalIndexBig := bridgesync.GenerateGlobalIndex(c.GlobalIndex.MainnetFlag, c.GlobalIndex.RollupIndex, c.GlobalIndex.LeafIndex) + globalIndexBig := bridgesync.GenerateGlobalIndex(c.GlobalIndex.MainnetFlag, + c.GlobalIndex.RollupIndex, c.GlobalIndex.LeafIndex) return crypto.Keccak256Hash(globalIndexBig.Bytes()) } diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index 8029dfad..c1e9848e 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -253,7 +253,8 @@ 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) { var ( importedBridgeExits = make([]*agglayer.ImportedBridgeExit, 0, len(claims)) greatestL1InfoTreeIndex = uint32(0) From 323c9705bba32e5a0525185b76547532970567fa Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Wed, 9 Oct 2024 12:10:37 +0200 Subject: [PATCH 06/84] fix: todos --- aggsender/aggsender.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index c1e9848e..bc4ccd30 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -258,8 +258,9 @@ func (a *AggSender) getImportedBridgeExits(ctx context.Context, var ( importedBridgeExits = make([]*agglayer.ImportedBridgeExit, 0, len(claims)) greatestL1InfoTreeIndex = uint32(0) - ger = common.Hash{} + ger common.Hash timestamp uint64 + blockHash common.Hash ) for _, claim := range claims { @@ -272,6 +273,7 @@ func (a *AggSender) getImportedBridgeExits(ctx context.Context, greatestL1InfoTreeIndex = info.L1InfoTreeIndex ger = claim.GlobalExitRoot timestamp = info.Timestamp + blockHash = info.PreviousBlockHash } importedBridgeExit, err := a.convertClaimToImportedBridgeExit(claim) @@ -298,7 +300,7 @@ func (a *AggSender) getImportedBridgeExits(ctx context.Context, Inner: agglayer.L1InfoTreeLeafInner{ GlobalExitRoot: ger, Timestamp: timestamp, - // BlockHash: TODO, + BlockHash: blockHash, }, }, ProofLeafMER: agglayer.MerkleProof{ @@ -319,7 +321,7 @@ func (a *AggSender) getImportedBridgeExits(ctx context.Context, Inner: agglayer.L1InfoTreeLeafInner{ GlobalExitRoot: ger, Timestamp: timestamp, - // BlockHash: TODO, + BlockHash: blockHash, }, }, ProofLeafLER: agglayer.MerkleProof{ From 5df5b653dc5f319f5afc6e71d2ba9bf2659731f1 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Wed, 9 Oct 2024 12:10:38 +0200 Subject: [PATCH 07/84] fix: running l2 detector with aggsender --- cmd/run.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/run.go b/cmd/run.go index 649cf83b..86cdc295 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -588,7 +588,7 @@ func runReorgDetectorL2IfNeeded( l2Client *ethclient.Client, cfg *reorgdetector.Config, ) (*reorgdetector.ReorgDetector, chan error) { - if !isNeeded([]string{cdkcommon.AGGORACLE, cdkcommon.RPC}, components) { + if !isNeeded([]string{cdkcommon.AGGORACLE, cdkcommon.RPC, cdkcommon.AGGSENDER}, components) { return nil, nil } rd := newReorgDetector(cfg, l2Client) From 1f10deac412aeb556674db41eb2d832ef3bc2f79 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Wed, 9 Oct 2024 12:10:38 +0200 Subject: [PATCH 08/84] fix: l2client rpc url --- aggsender/aggsender.go | 8 ++------ aggsender/config.go | 1 + cmd/run.go | 13 +++++++++++-- config/default.go | 1 + 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index bc4ccd30..8deebf3c 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -5,7 +5,6 @@ import ( "crypto/ecdsa" "encoding/json" "fmt" - "path/filepath" "time" "github.com/0xPolygon/cdk/agglayer" @@ -20,10 +19,7 @@ import ( "github.com/ledgerwatch/erigon-lib/kv/mdbx" ) -const ( - aggSenderDBFolder = "aggsender" - sentCertificatesL2Table = "sent_certificates_l2" -) +const sentCertificatesL2Table = "sent_certificates_l2" func tableCfgFunc(defaultBuckets kv.TableCfg) kv.TableCfg { return kv.TableCfg{ @@ -57,7 +53,7 @@ func New( l2Syncer *bridgesync.BridgeSync, l2Client bridgesync.EthClienter) (*AggSender, error) { db, err := mdbx.NewMDBX(nil). - Path(filepath.Join(cfg.DBPath, aggSenderDBFolder)). + Path(cfg.DBPath). WithTableCfg(tableCfgFunc). Open() if err != nil { diff --git a/aggsender/config.go b/aggsender/config.go index f640496e..3f335975 100644 --- a/aggsender/config.go +++ b/aggsender/config.go @@ -10,4 +10,5 @@ type Config struct { AggLayerURL string `mapstructure:"AggLayerURL"` CertificateSendInterval types.Duration `mapstructure:"CertificateSendInterval"` SequencerPrivateKey types.KeystoreFileConfig `mapstructure:"SequencerPrivateKey"` + URLRPCL2 string `mapstructure:"URLRPCL2"` } diff --git a/cmd/run.go b/cmd/run.go index 86cdc295..5ea89a8b 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -63,7 +63,7 @@ func start(cliCtx *cli.Context) error { components := cliCtx.StringSlice(config.FlagComponents) l1Client := runL1ClientIfNeeded(components, c.Etherman.URL) - l2Client := runL2ClientIfNeeded(components, c.AggOracle.EVMSender.URLRPCL2) + l2Client := runL2ClientIfNeeded(components, getL2RPCUrl(c)) reorgDetectorL1, errChanL1 := runReorgDetectorL1IfNeeded(cliCtx.Context, components, l1Client, &c.ReorgDetectorL1) go func() { if err := <-errChanL1; err != nil { @@ -548,7 +548,8 @@ func runL2ClientIfNeeded(components []string, urlRPCL2 string) *ethclient.Client if !isNeeded([]string{cdkcommon.AGGORACLE, cdkcommon.RPC, cdkcommon.AGGSENDER}, components) { return nil } - log.Debugf("dialing L2 client at: %s", urlRPCL2) + + log.Infof("dialing L2 client at: %s", urlRPCL2) l2CLient, err := ethclient.Dial(urlRPCL2) if err != nil { log.Fatal(err) @@ -769,3 +770,11 @@ func createRPC( return jRPC.NewServer(cfg, services, jRPC.WithLogger(logger.GetSugaredLogger())) } + +func getL2RPCUrl(c *config.Config) string { + if c.AggSender.URLRPCL2 != "" { + return c.AggSender.URLRPCL2 + } + + return c.AggOracle.EVMSender.URLRPCL2 +} diff --git a/config/default.go b/config/default.go index 2940a663..62a833cd 100644 --- a/config/default.go +++ b/config/default.go @@ -344,5 +344,6 @@ DBPath = "/tmp/aggsender" AggLayerURL = "http://zkevm-agglayer" SequencerPrivateKey = {Path = "/pk/sequencer.keystore", Password = "testonly"} CertificateSendInterval = "1m" +URLRPCL2="http://test-aggoracle-l2:8545" ` From fcfa9b4f1cc9d5bc24ee1e5113387fd61ebeed8c Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Wed, 9 Oct 2024 12:10:38 +0200 Subject: [PATCH 09/84] fix: add logs --- aggsender/aggsender.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index 8deebf3c..4f44d215 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -101,11 +101,15 @@ func (a *AggSender) sendCertificates(ctx context.Context) { // sendCertificate sends certificate for a network func (a *AggSender) sendCertificate(ctx context.Context) error { + a.log.Info("trying to send a new certificate...") + lastSentCertificateBlock, lastSentCertificate, err := a.getLastSentCertificate(ctx) if err != nil { return fmt.Errorf("error getting last sent certificate: %w", err) } + a.log.Info("last sent certificate block: %d", lastSentCertificateBlock) + finality := a.l2Syncer.BlockFinality() blockFinality, err := finality.ToBlockNum() if err != nil { @@ -160,6 +164,8 @@ func (a *AggSender) sendCertificate(ctx context.Context) error { return fmt.Errorf("error saving last sent certificate in db: %w", err) } + a.log.Info("certificate sent successfully for block: %d", lastFinalizedBlock.Nonce.Uint64()) + return nil } From 589dab50251304c7b686a6355bca0f5625e945b2 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Wed, 9 Oct 2024 12:10:39 +0200 Subject: [PATCH 10/84] feat: incorporate getCertificateHeader call --- agglayer/client.go | 37 ++++++++++++++++++--- agglayer/types.go | 24 ++++++++++++++ aggsender/aggsender.go | 69 ++++++++++++++++++++++++++-------------- bridgesync/bridgesync.go | 4 +-- 4 files changed, 104 insertions(+), 30 deletions(-) diff --git a/agglayer/client.go b/agglayer/client.go index 1cfd0de6..b1ac680f 100644 --- a/agglayer/client.go +++ b/agglayer/client.go @@ -21,7 +21,8 @@ var ErrAgglayerRateLimitExceeded = fmt.Errorf("agglayer rate limit exceeded") type AgglayerClientInterface interface { SendTx(signedTx SignedTx) (common.Hash, error) WaitTxToBeMined(hash common.Hash, ctx context.Context) error - SendCertificate(certificate *SignedCertificate) error + SendCertificate(certificate *SignedCertificate) (common.Hash, error) + GetCertificateHeader(certificateHash common.Hash) (*CertificateHeader, error) } // AggLayerClient is the client that will be used to interact with the AggLayer @@ -89,15 +90,41 @@ func (c *AggLayerClient) WaitTxToBeMined(hash common.Hash, ctx context.Context) } // SendCertificate sends a certificate to the AggLayer -func (c *AggLayerClient) SendCertificate(certificate *SignedCertificate) error { +func (c *AggLayerClient) SendCertificate(certificate *SignedCertificate) (common.Hash, error) { response, err := rpc.JSONRPCCall(c.url, "interop_sendCertificate", certificate) if err != nil { - return err + return common.Hash{}, err } if response.Error != nil { - return fmt.Errorf("%v %v", response.Error.Code, response.Error.Message) + return common.Hash{}, fmt.Errorf("%v %v", response.Error.Code, response.Error.Message) + } + + var result types.ArgHash + err = json.Unmarshal(response.Result, &result) + if err != nil { + return common.Hash{}, err + } + + return result.Hash(), nil +} + +// GetCertificateHeader returns the certificate header associated to the hash +func (c *AggLayerClient) GetCertificateHeader(certificateHash common.Hash) (*CertificateHeader, error) { + response, err := rpc.JSONRPCCall(c.url, "interop_getCertificateHeader", certificateHash) + if err != nil { + return nil, err + } + + if response.Error != nil { + return nil, fmt.Errorf("%v %v", response.Error.Code, response.Error.Message) + } + + var result *CertificateHeader + err = json.Unmarshal(response.Result, &result) + if err != nil { + return nil, err } - return nil + return result, nil } diff --git a/agglayer/types.go b/agglayer/types.go index caf4ae8f..d7ec932a 100644 --- a/agglayer/types.go +++ b/agglayer/types.go @@ -10,6 +10,19 @@ import ( "github.com/ethereum/go-ethereum/crypto" ) +type CertificateStatus int + +const ( + Pending CertificateStatus = iota + InError + Settled +) + +// String representation of the enum +func (c CertificateStatus) String() string { + return [...]string{"Pending", "InError", "Settled"}[c] +} + type LeafType uint8 func (l LeafType) Uint8() uint8 { @@ -151,3 +164,14 @@ func (c *ImportedBridgeExit) Hash() common.Hash { c.GlobalIndex.RollupIndex, c.GlobalIndex.LeafIndex) return crypto.Keccak256Hash(globalIndexBig.Bytes()) } + +// CertificateHeader is the structure returned by the interop_getCertificateHeader RPC call +type CertificateHeader struct { + NetworkID uint32 `json:"network_id"` + Height uint64 `json:"height"` + EpochNumber *uint64 `json:"epoch_number"` + CertificateIndex *uint64 `json:"certificate_index"` + CertificateID common.Hash `json:"certificate_id"` + NewLocalExitRoot common.Hash `json:"new_local_exit_root"` + Status CertificateStatus `json:"status"` +} diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index 4f44d215..917683cd 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -103,12 +103,12 @@ func (a *AggSender) sendCertificates(ctx context.Context) { func (a *AggSender) sendCertificate(ctx context.Context) error { a.log.Info("trying to send a new certificate...") - lastSentCertificateBlock, lastSentCertificate, err := a.getLastSentCertificate(ctx) + lastSentCertificateHash, _, err := a.getLastSentCertificate(ctx) if err != nil { return fmt.Errorf("error getting last sent certificate: %w", err) } - a.log.Info("last sent certificate block: %d", lastSentCertificateBlock) + a.log.Info("last sent certificate: %s", lastSentCertificateHash) finality := a.l2Syncer.BlockFinality() blockFinality, err := finality.ToBlockNum() @@ -116,13 +116,40 @@ func (a *AggSender) sendCertificate(ctx context.Context) error { return fmt.Errorf("error getting block finality: %w", err) } - lastFinalizedBlock, err := a.l2Client.HeaderByNumber(ctx, blockFinality) + lastL2Block, err := a.l2Client.HeaderByNumber(ctx, blockFinality) if err != nil { - return fmt.Errorf("error getting block number: %w", err) + return fmt.Errorf("error getting block from l2: %w", err) } - fromBlock := lastSentCertificateBlock + 1 - toBlock := lastFinalizedBlock.Nonce.Uint64() + var ( + previousLocalExitRoot common.Hash + previousHeight uint64 + lastCertificateBlock uint64 + ) + + if lastSentCertificateHash != (common.Hash{}) { + // we have sent a certificate before, get the last certificate header + lastSentCertificateHeader, err := a.aggLayerClient.GetCertificateHeader(lastSentCertificateHash) + if err != nil { + return fmt.Errorf("error getting certificate %s header: %w", lastSentCertificateHash, err) + } + + previousLocalExitRoot = lastSentCertificateHeader.NewLocalExitRoot + previousHeight = lastSentCertificateHeader.Height + + lastCertificateBlock, err = a.l2Syncer.GetBlockByLER(ctx, lastSentCertificateHeader.NewLocalExitRoot) + if err != nil { + return fmt.Errorf("error getting block by LER %s: %w", lastSentCertificateHash, err) + } + } + + if lastL2Block.Number.Uint64() <= lastCertificateBlock { + a.log.Info("no new blocks to send a certificate") + return nil + } + + fromBlock := lastCertificateBlock + 1 + toBlock := lastL2Block.Number.Uint64() bridges, err := a.l2Syncer.GetBridges(ctx, fromBlock, toBlock) if err != nil { @@ -139,14 +166,9 @@ func (a *AggSender) sendCertificate(ctx context.Context) error { return fmt.Errorf("error getting claims: %w", err) } - previousExitRoot := common.Hash{} - lastHeight := uint64(0) - if lastSentCertificate != nil { - previousExitRoot = lastSentCertificate.NewLocalExitRoot - lastHeight = lastSentCertificate.Height - } + a.log.Info("building certificate for block: %d to block: %d", fromBlock, toBlock) - certificate, err := a.buildCertificate(ctx, bridges, claims, previousExitRoot, lastHeight) + certificate, err := a.buildCertificate(ctx, bridges, claims, previousLocalExitRoot, previousHeight) if err != nil { return fmt.Errorf("error building certificate: %w", err) } @@ -156,15 +178,16 @@ func (a *AggSender) sendCertificate(ctx context.Context) error { return fmt.Errorf("error signing certificate: %w", err) } - if err := a.aggLayerClient.SendCertificate(signedCertificate); err != nil { + certificateHash, err := a.aggLayerClient.SendCertificate(signedCertificate) + if err != nil { return fmt.Errorf("error sending certificate: %w", err) } - if err := a.saveLastSentCertificate(ctx, lastFinalizedBlock.Nonce.Uint64(), certificate); err != nil { + if err := a.saveLastSentCertificate(ctx, certificateHash, certificate); err != nil { return fmt.Errorf("error saving last sent certificate in db: %w", err) } - a.log.Info("certificate sent successfully for block: %d", lastFinalizedBlock.Nonce.Uint64()) + a.log.Info("certificate: %s sent successfully for block: %d to block: %d", certificateHash, fromBlock, toBlock) return nil } @@ -343,7 +366,7 @@ func (a *AggSender) getImportedBridgeExits(ctx context.Context, } // saveLastSentCertificate saves the last sent certificate -func (a *AggSender) saveLastSentCertificate(ctx context.Context, blockNum uint64, +func (a *AggSender) saveLastSentCertificate(ctx context.Context, certificateHash common.Hash, certificate *agglayer.Certificate) error { return a.db.Update(ctx, func(tx kv.RwTx) error { raw, err := json.Marshal(certificate) @@ -351,15 +374,15 @@ func (a *AggSender) saveLastSentCertificate(ctx context.Context, blockNum uint64 return err } - return tx.Put(sentCertificatesL2Table, cdkcommon.Uint64ToBytes(blockNum), raw) + return tx.Put(sentCertificatesL2Table, certificateHash.Bytes(), raw) }) } // getLastSentCertificate returns the last sent certificate -func (a *AggSender) getLastSentCertificate(ctx context.Context) (uint64, *agglayer.Certificate, error) { +func (a *AggSender) getLastSentCertificate(ctx context.Context) (common.Hash, *agglayer.Certificate, error) { var ( - lastSentCertificateBlock uint64 - lastCertificate *agglayer.Certificate + lastSentCertificateHash common.Hash + lastCertificate *agglayer.Certificate ) err := a.db.View(ctx, func(tx kv.Tx) error { @@ -374,7 +397,7 @@ func (a *AggSender) getLastSentCertificate(ctx context.Context) (uint64, *agglay } if k != nil { - lastSentCertificateBlock = cdkcommon.BytesToUint64(k) + lastSentCertificateHash = common.BytesToHash(k) if err := json.Unmarshal(v, &lastCertificate); err != nil { return err } @@ -383,7 +406,7 @@ func (a *AggSender) getLastSentCertificate(ctx context.Context) (uint64, *agglay return nil }) - return lastSentCertificateBlock, lastCertificate, err + return lastSentCertificateHash, lastCertificate, err } // signCertificate signs a certificate with the sequencer key diff --git a/bridgesync/bridgesync.go b/bridgesync/bridgesync.go index 7e89f0ff..95c2f371 100644 --- a/bridgesync/bridgesync.go +++ b/bridgesync/bridgesync.go @@ -186,8 +186,8 @@ func (s *BridgeSync) GetProof(ctx context.Context, depositCount uint32, localExi return s.processor.exitTree.GetProof(ctx, depositCount, localExitRoot) } -func (p *processor) GetBlockByLER(ctx context.Context, ler common.Hash) (uint64, error) { - root, err := p.exitTree.GetRootByHash(ctx, ler) +func (s *BridgeSync) GetBlockByLER(ctx context.Context, ler common.Hash) (uint64, error) { + root, err := s.processor.exitTree.GetRootByHash(ctx, ler) if err != nil { return 0, err } From d272d2e7a83ae96e384a1ca734de571ef1b84d25 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Wed, 9 Oct 2024 12:10:39 +0200 Subject: [PATCH 11/84] feat: sql storage --- aggsender/aggsender.go | 106 ++++++---------- aggsender/db/aggsender_db_storage.go | 173 ++++++++++++++++++++++++++ aggsender/db/migrations/0001.sql | 13 ++ aggsender/db/migrations/migrations.go | 23 ++++ aggsender/types/types.go | 15 +++ 5 files changed, 260 insertions(+), 70 deletions(-) create mode 100644 aggsender/db/aggsender_db_storage.go create mode 100644 aggsender/db/migrations/0001.sql create mode 100644 aggsender/db/migrations/migrations.go create mode 100644 aggsender/types/types.go diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index 917683cd..06e206ca 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -3,11 +3,12 @@ package aggsender import ( "context" "crypto/ecdsa" - "encoding/json" "fmt" "time" "github.com/0xPolygon/cdk/agglayer" + "github.com/0xPolygon/cdk/aggsender/db" + aggsendertypes "github.com/0xPolygon/cdk/aggsender/types" "github.com/0xPolygon/cdk/bridgesync" cdkcommon "github.com/0xPolygon/cdk/common" "github.com/0xPolygon/cdk/config/types" @@ -15,18 +16,8 @@ import ( "github.com/0xPolygon/cdk/log" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - "github.com/ledgerwatch/erigon-lib/kv" - "github.com/ledgerwatch/erigon-lib/kv/mdbx" ) -const sentCertificatesL2Table = "sent_certificates_l2" - -func tableCfgFunc(defaultBuckets kv.TableCfg) kv.TableCfg { - return kv.TableCfg{ - sentCertificatesL2Table: {}, - } -} - // AggSender is a component that will send certificates to the aggLayer type AggSender struct { log *log.Logger @@ -35,7 +26,7 @@ type AggSender struct { l2Client bridgesync.EthClienter l1infoTreeSyncer *l1infotreesync.L1InfoTreeSync - db kv.RwDB + storage db.AggSenderStorage aggLayerClient agglayer.AgglayerClientInterface sendInterval types.Duration @@ -52,10 +43,7 @@ func New( l1InfoTreeSyncer *l1infotreesync.L1InfoTreeSync, l2Syncer *bridgesync.BridgeSync, l2Client bridgesync.EthClienter) (*AggSender, error) { - db, err := mdbx.NewMDBX(nil). - Path(cfg.DBPath). - WithTableCfg(tableCfgFunc). - Open() + storage, err := db.NewAggSenderSQLStorage(logger, cfg.DBPath) if err != nil { return nil, err } @@ -66,8 +54,8 @@ func New( } return &AggSender{ - db: db, log: logger, + storage: storage, l2Syncer: l2Syncer, l2Client: l2Client, aggLayerClient: aggLayerClient, @@ -103,12 +91,12 @@ func (a *AggSender) sendCertificates(ctx context.Context) { func (a *AggSender) sendCertificate(ctx context.Context) error { a.log.Info("trying to send a new certificate...") - lastSentCertificateHash, _, err := a.getLastSentCertificate(ctx) + lastSentCertificate, err := a.storage.GetLastSentCertificate(ctx) if err != nil { return fmt.Errorf("error getting last sent certificate: %w", err) } - a.log.Info("last sent certificate: %s", lastSentCertificateHash) + a.log.Info("last sent certificate: %s", lastSentCertificate.CertificateID) finality := a.l2Syncer.BlockFinality() blockFinality, err := finality.ToBlockNum() @@ -127,19 +115,35 @@ func (a *AggSender) sendCertificate(ctx context.Context) error { lastCertificateBlock uint64 ) - if lastSentCertificateHash != (common.Hash{}) { + if lastSentCertificate.CertificateID != (common.Hash{}) { // we have sent a certificate before, get the last certificate header - lastSentCertificateHeader, err := a.aggLayerClient.GetCertificateHeader(lastSentCertificateHash) + lastSentCertificateHeader, err := a.aggLayerClient.GetCertificateHeader(lastSentCertificate.CertificateID) if err != nil { - return fmt.Errorf("error getting certificate %s header: %w", lastSentCertificateHash, err) + return fmt.Errorf("error getting certificate %s header: %w", lastSentCertificate.CertificateID, err) } - previousLocalExitRoot = lastSentCertificateHeader.NewLocalExitRoot - previousHeight = lastSentCertificateHeader.Height + if lastSentCertificateHeader.Status == agglayer.InError { + // last sent certificate had errors, we need to remove it from the db + // and build a new certificate from that block + if err := a.storage.DeleteCertificate(ctx, lastSentCertificateHeader.CertificateID); err != nil { + return fmt.Errorf("error deleting certificate %s: %w", lastSentCertificate.CertificateID, err) + } + + lastValidCertificate, err := a.storage.GetCertificateByHeight(ctx, lastSentCertificateHeader.Height) + if err != nil { + return fmt.Errorf("error getting certificate by height %d: %w", lastSentCertificateHeader.Height, err) + } + + previousLocalExitRoot = lastValidCertificate.NewLocalExitRoot + previousHeight = lastValidCertificate.Height + } else { + previousLocalExitRoot = lastSentCertificateHeader.NewLocalExitRoot + previousHeight = lastSentCertificateHeader.Height + } lastCertificateBlock, err = a.l2Syncer.GetBlockByLER(ctx, lastSentCertificateHeader.NewLocalExitRoot) if err != nil { - return fmt.Errorf("error getting block by LER %s: %w", lastSentCertificateHash, err) + return fmt.Errorf("error getting block by LER %s: %w", lastSentCertificate.CertificateID, err) } } @@ -183,7 +187,13 @@ func (a *AggSender) sendCertificate(ctx context.Context) error { return fmt.Errorf("error sending certificate: %w", err) } - if err := a.saveLastSentCertificate(ctx, certificateHash, certificate); err != nil { + if err := a.storage.SaveLastSentCertificate(ctx, aggsendertypes.CertificateInfo{ + Height: certificate.Height, + CertificateID: certificateHash, + NewLocalExitRoot: certificate.NewLocalExitRoot, + FromBlock: fromBlock, + ToBlock: toBlock, + }); err != nil { return fmt.Errorf("error saving last sent certificate in db: %w", err) } @@ -365,50 +375,6 @@ func (a *AggSender) getImportedBridgeExits(ctx context.Context, return importedBridgeExits, nil } -// saveLastSentCertificate saves the last sent certificate -func (a *AggSender) saveLastSentCertificate(ctx context.Context, certificateHash common.Hash, - certificate *agglayer.Certificate) error { - return a.db.Update(ctx, func(tx kv.RwTx) error { - raw, err := json.Marshal(certificate) - if err != nil { - return err - } - - return tx.Put(sentCertificatesL2Table, certificateHash.Bytes(), raw) - }) -} - -// getLastSentCertificate returns the last sent certificate -func (a *AggSender) getLastSentCertificate(ctx context.Context) (common.Hash, *agglayer.Certificate, error) { - var ( - lastSentCertificateHash common.Hash - lastCertificate *agglayer.Certificate - ) - - err := a.db.View(ctx, func(tx kv.Tx) error { - cursor, err := tx.Cursor(sentCertificatesL2Table) - if err != nil { - return err - } - - k, v, err := cursor.Last() - if err != nil { - return err - } - - if k != nil { - lastSentCertificateHash = common.BytesToHash(k) - if err := json.Unmarshal(v, &lastCertificate); err != nil { - return err - } - } - - return nil - }) - - return lastSentCertificateHash, lastCertificate, err -} - // signCertificate signs a certificate with the sequencer key func (a *AggSender) signCertificate(certificate *agglayer.Certificate) (*agglayer.SignedCertificate, error) { hashToSign := certificate.Hash() diff --git a/aggsender/db/aggsender_db_storage.go b/aggsender/db/aggsender_db_storage.go new file mode 100644 index 00000000..4e491403 --- /dev/null +++ b/aggsender/db/aggsender_db_storage.go @@ -0,0 +1,173 @@ +package db + +import ( + "context" + "database/sql" + "errors" + "fmt" + + "github.com/0xPolygon/cdk/aggsender/db/migrations" + "github.com/0xPolygon/cdk/aggsender/types" + "github.com/0xPolygon/cdk/db" + "github.com/0xPolygon/cdk/log" + "github.com/ethereum/go-ethereum/common" + "github.com/russross/meddler" +) + +// AggSenderStorage is the interface that defines the methods to interact with the storage +type AggSenderStorage interface { + // GetCertificateByHeight returns a certificate by its height + GetCertificateByHeight(ctx context.Context, height uint64) (types.CertificateInfo, error) + // GetLastSentCertificate returns the last certificate sent to the aggLayer + GetLastSentCertificate(ctx context.Context) (types.CertificateInfo, error) + // SaveLastSentCertificate saves the last certificate sent to the aggLayer + SaveLastSentCertificate(ctx context.Context, certificate types.CertificateInfo) error + // DeleteCertificate deletes a certificate from the storage + DeleteCertificate(ctx context.Context, certificateID common.Hash) error +} + +var _ AggSenderStorage = (*AggSenderSQLStorage)(nil) + +// AggSenderSQLStorage is the struct that implements the AggSenderStorage interface +type AggSenderSQLStorage struct { + logger *log.Logger + db *sql.DB +} + +// NewAggSenderSQLStorage creates a new AggSenderSQLStorage +func NewAggSenderSQLStorage(logger *log.Logger, dbPath string) (*AggSenderSQLStorage, error) { + if err := migrations.RunMigrations(dbPath); err != nil { + return nil, err + } + + db, err := db.NewSQLiteDB(dbPath) + if err != nil { + return nil, err + } + + return &AggSenderSQLStorage{ + db: db, + logger: logger, + }, nil +} + +func (a *AggSenderSQLStorage) GetCertificateByHeight(ctx context.Context, height uint64) (types.CertificateInfo, error) { + tx, err := a.db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) + if err != nil { + return types.CertificateInfo{}, err + } + + defer func() { + if err := tx.Rollback(); err != nil { + a.logger.Warnf("error rolling back tx: %w", err) + } + }() + + rows, err := tx.Query(`SELECT * FROM certificate_info WHERE height = $1;`, height) + if err != nil { + return types.CertificateInfo{}, getSelectQueryError(height, err) + } + + var certificateInfo *types.CertificateInfo + if err = meddler.ScanAll(rows, &certificateInfo); err != nil { + return types.CertificateInfo{}, err + } + + return *certificateInfo, nil +} + +// GetLastSentCertificate returns the last certificate sent to the aggLayer +func (a *AggSenderSQLStorage) GetLastSentCertificate(ctx context.Context) (types.CertificateInfo, error) { + tx, err := a.db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) + if err != nil { + return types.CertificateInfo{}, err + } + + defer func() { + if err := tx.Rollback(); err != nil { + a.logger.Warnf("error rolling back tx: %w", err) + } + }() + + rows, err := tx.Query(`SELECT * FROM certificate_info ORDER BY height DESC LIMIT 1;`) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return types.CertificateInfo{}, db.ErrNotFound + } + return types.CertificateInfo{}, err + } + + var certificateInfo *types.CertificateInfo + if err = meddler.ScanAll(rows, &certificateInfo); err != nil { + return types.CertificateInfo{}, err + } + + return *certificateInfo, nil +} + +// SaveLastSentCertificate saves the last certificate sent to the aggLayer +func (a *AggSenderSQLStorage) SaveLastSentCertificate(ctx context.Context, certificate types.CertificateInfo) error { + tx, err := db.NewTx(ctx, a.db) + if err != nil { + return err + } + defer func() { + if err != nil { + if errRllbck := tx.Rollback(); errRllbck != nil { + a.logger.Errorf("error while rolling back tx %w", errRllbck) + } + } + }() + + if err := meddler.Insert(tx, "certificate_info", &certificate); err != nil { + return fmt.Errorf("error inserting certificate info: %w", err) + } + if err := tx.Commit(); err != nil { + return err + } + + a.logger.Debugf("inserted certificate - Height: %d. Hash: %s", certificate.Height, certificate.CertificateID) + + return nil +} + +// DeleteCertificate deletes a certificate from the storage +func (a *AggSenderSQLStorage) DeleteCertificate(ctx context.Context, certificateID common.Hash) error { + tx, err := db.NewTx(ctx, a.db) + if err != nil { + return err + } + defer func() { + if err != nil { + if errRllbck := tx.Rollback(); errRllbck != nil { + a.logger.Errorf("error while rolling back tx %w", errRllbck) + } + } + }() + + if _, err := tx.Exec(`DELETE FROM certificate_info WHERE certificate_id = $1;`, certificateID); err != nil { + return fmt.Errorf("error deleting certificate info: %w", err) + } + if err := tx.Commit(); err != nil { + return err + } + + a.logger.Debugf("deleted certificate - CertificateID: %s", certificateID) + + return nil +} + +func getSelectQueryError(height uint64, err error) error { + errToReturn := err + if errors.Is(err, sql.ErrNoRows) { + if height == 0 { + // height 0 is never sent to the aggLayer + // so we don't return an error in this case + errToReturn = nil + } else { + errToReturn = db.ErrNotFound + } + } + + return errToReturn +} diff --git a/aggsender/db/migrations/0001.sql b/aggsender/db/migrations/0001.sql new file mode 100644 index 00000000..e97c84f2 --- /dev/null +++ b/aggsender/db/migrations/0001.sql @@ -0,0 +1,13 @@ +-- +migrate Down +DROP TABLE IF EXISTS certificate_info; + +CREATE TABLE certificate_info ( + height INTEGER NOT NULL, + certificate_id VARCHAR NOT NULL, + status INTEGER NOT NULL, + new_local_exit_root VARCHAR NOT NULL, + from_block INTEGER NOT NULL, + to_block INTEGER NOT NULL, + + PRIMARY KEY (height, certificate_id) +); \ No newline at end of file diff --git a/aggsender/db/migrations/migrations.go b/aggsender/db/migrations/migrations.go new file mode 100644 index 00000000..9b6c8379 --- /dev/null +++ b/aggsender/db/migrations/migrations.go @@ -0,0 +1,23 @@ +package migrations + +import ( + _ "embed" + + "github.com/0xPolygon/cdk/db" + "github.com/0xPolygon/cdk/db/types" + treeMigrations "github.com/0xPolygon/cdk/tree/migrations" +) + +//go:embed 0001.sql +var mig001 string + +func RunMigrations(dbPath string) error { + migrations := []types.Migration{ + { + ID: "0001", + SQL: mig001, + }, + } + migrations = append(migrations, treeMigrations.Migrations...) + return db.RunMigrations(dbPath, migrations) +} diff --git a/aggsender/types/types.go b/aggsender/types/types.go new file mode 100644 index 00000000..852dc229 --- /dev/null +++ b/aggsender/types/types.go @@ -0,0 +1,15 @@ +package types + +import ( + "github.com/0xPolygon/cdk/agglayer" + "github.com/ethereum/go-ethereum/common" +) + +type CertificateInfo struct { + Height uint64 + CertificateID common.Hash + NewLocalExitRoot common.Hash + FromBlock uint64 + ToBlock uint64 + Status agglayer.CertificateStatus +} From 40fc5998da8d747dcd86cf807713c8f49d7e7add Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Wed, 9 Oct 2024 12:10:40 +0200 Subject: [PATCH 12/84] feat: sql lite --- aggsender/db/aggsender_db_storage.go | 16 +++-- aggsender/db/aggsender_db_storage_test.go | 80 +++++++++++++++++++++++ aggsender/db/migrations/0001.sql | 1 + aggsender/db/migrations/migrations.go | 3 +- aggsender/types/types.go | 12 ++-- 5 files changed, 97 insertions(+), 15 deletions(-) create mode 100644 aggsender/db/aggsender_db_storage_test.go diff --git a/aggsender/db/aggsender_db_storage.go b/aggsender/db/aggsender_db_storage.go index 4e491403..b73619e2 100644 --- a/aggsender/db/aggsender_db_storage.go +++ b/aggsender/db/aggsender_db_storage.go @@ -51,7 +51,9 @@ func NewAggSenderSQLStorage(logger *log.Logger, dbPath string) (*AggSenderSQLSto }, nil } -func (a *AggSenderSQLStorage) GetCertificateByHeight(ctx context.Context, height uint64) (types.CertificateInfo, error) { +// GetCertificateByHeight returns a certificate by its height +func (a *AggSenderSQLStorage) GetCertificateByHeight(ctx context.Context, + height uint64) (types.CertificateInfo, error) { tx, err := a.db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) if err != nil { return types.CertificateInfo{}, err @@ -68,12 +70,12 @@ func (a *AggSenderSQLStorage) GetCertificateByHeight(ctx context.Context, height return types.CertificateInfo{}, getSelectQueryError(height, err) } - var certificateInfo *types.CertificateInfo - if err = meddler.ScanAll(rows, &certificateInfo); err != nil { + var certificateInfo types.CertificateInfo + if err = meddler.ScanRow(rows, &certificateInfo); err != nil { return types.CertificateInfo{}, err } - return *certificateInfo, nil + return certificateInfo, nil } // GetLastSentCertificate returns the last certificate sent to the aggLayer @@ -97,12 +99,12 @@ func (a *AggSenderSQLStorage) GetLastSentCertificate(ctx context.Context) (types return types.CertificateInfo{}, err } - var certificateInfo *types.CertificateInfo - if err = meddler.ScanAll(rows, &certificateInfo); err != nil { + var certificateInfo types.CertificateInfo + if err = meddler.ScanRow(rows, &certificateInfo); err != nil { return types.CertificateInfo{}, err } - return *certificateInfo, nil + return certificateInfo, nil } // SaveLastSentCertificate saves the last certificate sent to the aggLayer diff --git a/aggsender/db/aggsender_db_storage_test.go b/aggsender/db/aggsender_db_storage_test.go new file mode 100644 index 00000000..b0be8ed2 --- /dev/null +++ b/aggsender/db/aggsender_db_storage_test.go @@ -0,0 +1,80 @@ +package db + +import ( + "context" + "database/sql" + "errors" + "path" + "testing" + + "github.com/0xPolygon/cdk/agglayer" + "github.com/0xPolygon/cdk/aggsender/db/migrations" + "github.com/0xPolygon/cdk/aggsender/types" + "github.com/0xPolygon/cdk/log" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func Test_SaveLastSentCertificate(t *testing.T) { + ctx := context.Background() + + path := path.Join(t.TempDir(), "file::memory:?cache=shared") + log.Debugf("sqlite path: %s", path) + require.NoError(t, migrations.RunMigrations(path)) + + storage, err := NewAggSenderSQLStorage(log.WithFields("aggsender-db"), path) + require.NoError(t, err) + + t.Run("SaveLastSentCertificate", func(t *testing.T) { + certificate := types.CertificateInfo{ + Height: 1, + CertificateID: common.HexToHash("0x1"), + NewLocalExitRoot: common.HexToHash("0x2"), + FromBlock: 1, + ToBlock: 2, + Status: agglayer.Settled, + } + require.NoError(t, storage.SaveLastSentCertificate(ctx, certificate)) + + certificateFromDB, err := storage.GetCertificateByHeight(ctx, certificate.Height) + require.NoError(t, err) + + require.Equal(t, certificate, certificateFromDB) + }) + + t.Run("DeleteCertificate", func(t *testing.T) { + certificate := types.CertificateInfo{ + Height: 2, + CertificateID: common.HexToHash("0x3"), + NewLocalExitRoot: common.HexToHash("0x4"), + FromBlock: 3, + ToBlock: 4, + Status: agglayer.Settled, + } + require.NoError(t, storage.SaveLastSentCertificate(ctx, certificate)) + + require.NoError(t, storage.DeleteCertificate(ctx, certificate.CertificateID)) + + certificateFromDB, err := storage.GetCertificateByHeight(ctx, certificate.Height) + require.Error(t, err) + require.True(t, errors.Is(err, sql.ErrNoRows)) + require.Equal(t, types.CertificateInfo{}, certificateFromDB) + }) + + t.Run("GetLastSentCertificate", func(t *testing.T) { + certificate := types.CertificateInfo{ + Height: 3, + CertificateID: common.HexToHash("0x5"), + NewLocalExitRoot: common.HexToHash("0x6"), + FromBlock: 5, + ToBlock: 6, + Status: agglayer.Settled, + } + require.NoError(t, storage.SaveLastSentCertificate(ctx, certificate)) + + certificateFromDB, err := storage.GetLastSentCertificate(ctx) + require.NoError(t, err) + + require.Equal(t, certificate, certificateFromDB) + }) +} diff --git a/aggsender/db/migrations/0001.sql b/aggsender/db/migrations/0001.sql index e97c84f2..28b17098 100644 --- a/aggsender/db/migrations/0001.sql +++ b/aggsender/db/migrations/0001.sql @@ -1,6 +1,7 @@ -- +migrate Down DROP TABLE IF EXISTS certificate_info; +-- +migrate Up CREATE TABLE certificate_info ( height INTEGER NOT NULL, certificate_id VARCHAR NOT NULL, diff --git a/aggsender/db/migrations/migrations.go b/aggsender/db/migrations/migrations.go index 9b6c8379..31f16fd2 100644 --- a/aggsender/db/migrations/migrations.go +++ b/aggsender/db/migrations/migrations.go @@ -5,7 +5,6 @@ import ( "github.com/0xPolygon/cdk/db" "github.com/0xPolygon/cdk/db/types" - treeMigrations "github.com/0xPolygon/cdk/tree/migrations" ) //go:embed 0001.sql @@ -18,6 +17,6 @@ func RunMigrations(dbPath string) error { SQL: mig001, }, } - migrations = append(migrations, treeMigrations.Migrations...) + return db.RunMigrations(dbPath, migrations) } diff --git a/aggsender/types/types.go b/aggsender/types/types.go index 852dc229..edb542f9 100644 --- a/aggsender/types/types.go +++ b/aggsender/types/types.go @@ -6,10 +6,10 @@ import ( ) type CertificateInfo struct { - Height uint64 - CertificateID common.Hash - NewLocalExitRoot common.Hash - FromBlock uint64 - ToBlock uint64 - Status agglayer.CertificateStatus + Height uint64 `meddler:"height"` + CertificateID common.Hash `meddler:"certificate_id"` + NewLocalExitRoot common.Hash `meddler:"new_local_exit_root"` + FromBlock uint64 `meddler:"from_block"` + ToBlock uint64 `meddler:"to_block"` + Status agglayer.CertificateStatus `meddler:"status"` } From 88df544be93b9cc03eef95565f77b0cb98df6b71 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Wed, 9 Oct 2024 12:10:40 +0200 Subject: [PATCH 13/84] feat: unit tests --- aggsender/aggsender.go | 10 +- aggsender/aggsender_test.go | 565 ++++++++++++++++++++++++++++ aggsender/mock_l1infotree_syncer.go | 94 +++++ test/Makefile | 9 +- 4 files changed, 674 insertions(+), 4 deletions(-) create mode 100644 aggsender/aggsender_test.go create mode 100644 aggsender/mock_l1infotree_syncer.go diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index 06e206ca..37a83535 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -14,17 +14,25 @@ import ( "github.com/0xPolygon/cdk/config/types" "github.com/0xPolygon/cdk/l1infotreesync" "github.com/0xPolygon/cdk/log" + treeTypes "github.com/0xPolygon/cdk/tree/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" ) +// 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) +} + // AggSender is a component that will send certificates to the aggLayer type AggSender struct { log *log.Logger l2Syncer *bridgesync.BridgeSync l2Client bridgesync.EthClienter - l1infoTreeSyncer *l1infotreesync.L1InfoTreeSync + l1infoTreeSyncer L1InfoTreeSyncer storage db.AggSenderStorage aggLayerClient agglayer.AgglayerClientInterface diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go new file mode 100644 index 00000000..0572b590 --- /dev/null +++ b/aggsender/aggsender_test.go @@ -0,0 +1,565 @@ +package aggsender + +import ( + "context" + "crypto/ecdsa" + "fmt" + "math/big" + "testing" + + "github.com/0xPolygon/cdk/agglayer" + "github.com/0xPolygon/cdk/bridgesync" + "github.com/0xPolygon/cdk/l1infotreesync" + treeTypes "github.com/0xPolygon/cdk/tree/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +func TestConvertClaimToImportedBridgeExit(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + claim bridgesync.Claim + expectedError bool + expectedExit *agglayer.ImportedBridgeExit + }{ + { + name: "Asset claim", + claim: bridgesync.Claim{ + IsMessage: false, + OriginNetwork: 1, + OriginAddress: common.HexToAddress("0x123"), + DestinationNetwork: 2, + DestinationAddress: common.HexToAddress("0x456"), + Amount: big.NewInt(100), + Metadata: []byte("metadata"), + GlobalIndex: big.NewInt(1), + }, + expectedError: false, + expectedExit: &agglayer.ImportedBridgeExit{ + BridgeExit: &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"), + }, + GlobalIndex: &agglayer.GlobalIndex{ + MainnetFlag: false, + RollupIndex: 0, + LeafIndex: 1, + }, + }, + }, + { + name: "Message claim", + claim: bridgesync.Claim{ + IsMessage: true, + OriginNetwork: 1, + OriginAddress: common.HexToAddress("0x123"), + DestinationNetwork: 2, + DestinationAddress: common.HexToAddress("0x456"), + Amount: big.NewInt(100), + Metadata: []byte("metadata"), + GlobalIndex: big.NewInt(2), + }, + expectedError: false, + expectedExit: &agglayer.ImportedBridgeExit{ + BridgeExit: &agglayer.BridgeExit{ + LeafType: agglayer.LeafTypeMessage, + TokenInfo: &agglayer.TokenInfo{ + OriginNetwork: 1, + OriginTokenAddress: common.HexToAddress("0x123"), + }, + DestinationNetwork: 2, + DestinationAddress: common.HexToAddress("0x456"), + Amount: big.NewInt(100), + Metadata: []byte("metadata"), + }, + GlobalIndex: &agglayer.GlobalIndex{ + MainnetFlag: false, + RollupIndex: 0, + LeafIndex: 2, + }, + }, + }, + { + name: "Invalid global index", + claim: bridgesync.Claim{ + IsMessage: false, + OriginNetwork: 1, + OriginAddress: common.HexToAddress("0x123"), + DestinationNetwork: 2, + DestinationAddress: common.HexToAddress("0x456"), + Amount: big.NewInt(100), + Metadata: []byte("metadata"), + GlobalIndex: new(big.Int).SetBytes([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}), + }, + expectedError: true, + expectedExit: nil, + }, + } + + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + aggSender := &AggSender{} + exit, err := aggSender.convertClaimToImportedBridgeExit(tt.claim) + + if tt.expectedError { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, tt.expectedExit, exit) + } + }) + } +} +func TestGetBridgeExits(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + bridges []bridgesync.Bridge + expectedExits []*agglayer.BridgeExit + }{ + { + name: "Single bridge", + 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"), + }, + }, + expectedExits: []*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"), + }, + }, + }, + { + name: "Multiple bridges", + 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"), + }, + { + LeafType: agglayer.LeafTypeMessage.Uint8(), + OriginNetwork: 3, + OriginAddress: common.HexToAddress("0x789"), + DestinationNetwork: 4, + DestinationAddress: common.HexToAddress("0xabc"), + Amount: big.NewInt(200), + Metadata: []byte("data"), + }, + }, + expectedExits: []*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"), + }, + { + LeafType: agglayer.LeafTypeMessage, + TokenInfo: &agglayer.TokenInfo{ + OriginNetwork: 3, + OriginTokenAddress: common.HexToAddress("0x789"), + }, + DestinationNetwork: 4, + DestinationAddress: common.HexToAddress("0xabc"), + Amount: big.NewInt(200), + Metadata: []byte("data"), + }, + }, + }, + { + name: "No bridges", + bridges: []bridgesync.Bridge{}, + expectedExits: []*agglayer.BridgeExit{}, + }, + } + + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + aggSender := &AggSender{} + exits := aggSender.getBridgeExits(tt.bridges) + + require.Equal(t, tt.expectedExits, exits) + }) + } +} + +func TestSignCertificate(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + certificate *agglayer.Certificate + sequencerKey *ecdsa.PrivateKey + expectedError bool + }{ + { + name: "Valid certificate", + certificate: &agglayer.Certificate{ + NetworkID: 1, + PrevLocalExitRoot: common.HexToHash("0x123"), + NewLocalExitRoot: common.HexToHash("0x456"), + BridgeExits: []*agglayer.BridgeExit{ + { + LeafType: agglayer.LeafTypeAsset, + TokenInfo: &agglayer.TokenInfo{ + OriginNetwork: 1, + OriginTokenAddress: common.HexToAddress("0x789"), + }, + DestinationNetwork: 2, + DestinationAddress: common.HexToAddress("0xabc"), + Amount: big.NewInt(100), + Metadata: []byte("metadata"), + }, + }, + ImportedBridgeExits: []*agglayer.ImportedBridgeExit{}, + Height: 1, + }, + sequencerKey: func() *ecdsa.PrivateKey { + key, _ := crypto.GenerateKey() + return key + }(), + expectedError: false, + }, + { + name: "Invalid certificate", + certificate: &agglayer.Certificate{ + NetworkID: 1, + PrevLocalExitRoot: common.HexToHash("0x123"), + NewLocalExitRoot: common.HexToHash("0x456"), + BridgeExits: []*agglayer.BridgeExit{}, + ImportedBridgeExits: []*agglayer.ImportedBridgeExit{ + { + BridgeExit: &agglayer.BridgeExit{ + LeafType: agglayer.LeafTypeAsset, + TokenInfo: &agglayer.TokenInfo{ + OriginNetwork: 1, + OriginTokenAddress: common.HexToAddress("0x789"), + }, + DestinationNetwork: 2, + DestinationAddress: common.HexToAddress("0xabc"), + Amount: big.NewInt(100), + Metadata: []byte("metadata"), + }, + GlobalIndex: &agglayer.GlobalIndex{ + MainnetFlag: false, + RollupIndex: 0, + LeafIndex: 1, + }, + }, + }, + Height: 1, + }, + sequencerKey: func() *ecdsa.PrivateKey { + key, _ := crypto.GenerateKey() + return key + }(), + expectedError: false, + }, + } + + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + aggSender := &AggSender{ + sequencerKey: tt.sequencerKey, + } + signedCert, err := aggSender.signCertificate(tt.certificate) + + if tt.expectedError { + require.Error(t, err) + require.Nil(t, signedCert) + } else { + require.NoError(t, err) + require.NotNil(t, signedCert) + require.Equal(t, tt.certificate, signedCert.Certificate) + require.NotEmpty(t, signedCert.Signature) + } + }) + } +} + +//nolint:dupl +func TestGetImportedBridgeExits(t *testing.T) { + t.Parallel() + + mockProof := generateTestProof(t) + + mockL1InfoTreeSyncer := NewL1InfoTreeSyncerMock(t) + 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) + + tests := []struct { + name string + claims []bridgesync.Claim + expectedError bool + expectedExits []*agglayer.ImportedBridgeExit + }{ + { + name: "Single claim", + 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, + }, + }, + expectedError: false, + expectedExits: []*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, + }, + }, + }, + }, + }, + { + name: "Multiple claims", + claims: []bridgesync.Claim{ + { + IsMessage: false, + OriginNetwork: 1, + OriginAddress: common.HexToAddress("0x123"), + DestinationNetwork: 2, + DestinationAddress: common.HexToAddress("0x456"), + Amount: big.NewInt(100), + Metadata: []byte("metadata"), + GlobalIndex: big.NewInt(1), + GlobalExitRoot: common.HexToHash("0x789"), + RollupExitRoot: common.HexToHash("0xaaa"), + MainnetExitRoot: common.HexToHash("0xbbb"), + ProofLocalExitRoot: mockProof, + }, + { + IsMessage: true, + OriginNetwork: 3, + OriginAddress: common.HexToAddress("0x789"), + DestinationNetwork: 4, + DestinationAddress: common.HexToAddress("0xabc"), + Amount: big.NewInt(200), + Metadata: []byte("data"), + GlobalIndex: big.NewInt(2), + GlobalExitRoot: common.HexToHash("0xdef"), + RollupExitRoot: common.HexToHash("0xbbb"), + MainnetExitRoot: common.HexToHash("0xccc"), + ProofLocalExitRoot: mockProof, + }, + }, + expectedError: false, + expectedExits: []*agglayer.ImportedBridgeExit{ + { + BridgeExit: &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"), + }, + GlobalIndex: &agglayer.GlobalIndex{ + MainnetFlag: false, + RollupIndex: 0, + LeafIndex: 1, + }, + ClaimData: &agglayer.ClaimFromRollup{ + L1Leaf: agglayer.L1InfoTreeLeaf{ + L1InfoTreeIndex: 1, + RollupExitRoot: common.HexToHash("0xaaa"), + MainnetExitRoot: common.HexToHash("0xbbb"), + Inner: agglayer.L1InfoTreeLeafInner{ + GlobalExitRoot: common.HexToHash("0x789"), + Timestamp: 123456789, + BlockHash: common.HexToHash("0xabc"), + }, + }, + ProofLeafLER: agglayer.MerkleProof{ + Root: common.HexToHash("0xbbb"), + Proof: mockProof, + }, + ProofLERToRER: agglayer.MerkleProof{}, + ProofGERToL1Root: agglayer.MerkleProof{ + Root: common.HexToHash("0x789"), + Proof: mockProof, + }, + }, + }, + { + BridgeExit: &agglayer.BridgeExit{ + LeafType: agglayer.LeafTypeMessage, + TokenInfo: &agglayer.TokenInfo{ + OriginNetwork: 3, + OriginTokenAddress: common.HexToAddress("0x789"), + }, + DestinationNetwork: 4, + DestinationAddress: common.HexToAddress("0xabc"), + Amount: big.NewInt(200), + Metadata: []byte("data"), + }, + GlobalIndex: &agglayer.GlobalIndex{ + MainnetFlag: false, + RollupIndex: 0, + LeafIndex: 2, + }, + ClaimData: &agglayer.ClaimFromRollup{ + L1Leaf: agglayer.L1InfoTreeLeaf{ + L1InfoTreeIndex: 2, + RollupExitRoot: common.HexToHash("0xbbb"), + MainnetExitRoot: common.HexToHash("0xccc"), + Inner: agglayer.L1InfoTreeLeafInner{ + GlobalExitRoot: common.HexToHash("0x789"), + Timestamp: 123456789, + BlockHash: common.HexToHash("0xabc"), + }, + }, + ProofLeafLER: agglayer.MerkleProof{ + Root: common.HexToHash("0xccc"), + Proof: mockProof, + }, + ProofLERToRER: agglayer.MerkleProof{}, + ProofGERToL1Root: agglayer.MerkleProof{ + Root: common.HexToHash("0x789"), + Proof: mockProof, + }, + }, + }, + }, + }, + { + name: "No claims", + claims: []bridgesync.Claim{}, + expectedError: false, + expectedExits: []*agglayer.ImportedBridgeExit{}, + }, + } + + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + aggSender := &AggSender{ + l1infoTreeSyncer: mockL1InfoTreeSyncer, + } + exits, err := aggSender.getImportedBridgeExits(context.Background(), tt.claims) + + if tt.expectedError { + require.Error(t, err) + require.Nil(t, exits) + } else { + require.NoError(t, err) + require.Equal(t, tt.expectedExits, exits) + } + }) + } +} + +func generateTestProof(t *testing.T) treeTypes.Proof { + t.Helper() + + proof := treeTypes.Proof{} + + for i := 0; i < int(treeTypes.DefaultHeight) && i < 10; i++ { + proof[i] = common.HexToHash(fmt.Sprintf("0x%d", i)) + } + + return proof +} diff --git a/aggsender/mock_l1infotree_syncer.go b/aggsender/mock_l1infotree_syncer.go new file mode 100644 index 00000000..9e036c54 --- /dev/null +++ b/aggsender/mock_l1infotree_syncer.go @@ -0,0 +1,94 @@ +// Code generated by mockery v2.45.0. DO NOT EDIT. + +package aggsender + +import ( + context "context" + + common "github.com/ethereum/go-ethereum/common" + + l1infotreesync "github.com/0xPolygon/cdk/l1infotreesync" + + mock "github.com/stretchr/testify/mock" + + types "github.com/0xPolygon/cdk/tree/types" +) + +// L1InfoTreeSyncerMock is an autogenerated mock type for the L1InfoTreeSyncer type +type L1InfoTreeSyncerMock struct { + mock.Mock +} + +// GetInfoByGlobalExitRoot provides a mock function with given fields: globalExitRoot +func (_m *L1InfoTreeSyncerMock) GetInfoByGlobalExitRoot(globalExitRoot common.Hash) (*l1infotreesync.L1InfoTreeLeaf, error) { + ret := _m.Called(globalExitRoot) + + if len(ret) == 0 { + panic("no return value specified for GetInfoByGlobalExitRoot") + } + + var r0 *l1infotreesync.L1InfoTreeLeaf + var r1 error + if rf, ok := ret.Get(0).(func(common.Hash) (*l1infotreesync.L1InfoTreeLeaf, error)); ok { + return rf(globalExitRoot) + } + if rf, ok := ret.Get(0).(func(common.Hash) *l1infotreesync.L1InfoTreeLeaf); ok { + r0 = rf(globalExitRoot) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*l1infotreesync.L1InfoTreeLeaf) + } + } + + if rf, ok := ret.Get(1).(func(common.Hash) error); ok { + r1 = rf(globalExitRoot) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetL1InfoTreeMerkleProofFromIndexToRoot provides a mock function with given fields: ctx, index, root +func (_m *L1InfoTreeSyncerMock) GetL1InfoTreeMerkleProofFromIndexToRoot(ctx context.Context, index uint32, root common.Hash) (types.Proof, error) { + ret := _m.Called(ctx, index, root) + + if len(ret) == 0 { + panic("no return value specified for GetL1InfoTreeMerkleProofFromIndexToRoot") + } + + var r0 types.Proof + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint32, common.Hash) (types.Proof, error)); ok { + return rf(ctx, index, root) + } + if rf, ok := ret.Get(0).(func(context.Context, uint32, common.Hash) types.Proof); ok { + r0 = rf(ctx, index, root) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(types.Proof) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint32, common.Hash) error); ok { + r1 = rf(ctx, index, root) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// 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 { + mock.TestingT + Cleanup(func()) +}) *L1InfoTreeSyncerMock { + mock := &L1InfoTreeSyncerMock{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/test/Makefile b/test/Makefile index a1b51bb1..54393882 100644 --- a/test/Makefile +++ b/test/Makefile @@ -1,8 +1,8 @@ .PHONY: generate-mocks generate-mocks: generate-mocks-bridgesync generate-mocks-reorgdetector generate-mocks-sequencesender \ generate-mocks-da generate-mocks-l1infotreesync generate-mocks-helpers \ - generate-mocks-sync generate-mocks-l1infotreesync generate-mocks-aggregator - + generate-mocks-sync generate-mocks-l1infotreesync generate-mocks-aggregator \ + generate-mocks-aggsender .PHONY: generate-mocks-bridgesync generate-mocks-bridgesync: ## Generates mocks for bridgesync, using mockery tool @@ -53,12 +53,15 @@ generate-mocks-aggregator: ## Generates mocks for aggregator, using mockery tool export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=ProverInterface --dir=../aggregator --output=../aggregator/mocks --outpkg=mocks --structname=ProverInterfaceMock --filename=mock_prover.go export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=Etherman --dir=../aggregator --output=../aggregator/mocks --outpkg=mocks --structname=EthermanMock --filename=mock_etherman.go export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=StateInterface --dir=../aggregator --output=../aggregator/mocks --outpkg=mocks --structname=StateInterfaceMock --filename=mock_state.go - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=AgglayerClientInterface --dir=../aggregator/agglayer --output=../aggregator/mocks --outpkg=mocks --structname=AgglayerClientInterfaceMock --filename=mock_agglayer_client.go export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=Synchronizer --srcpkg=github.com/0xPolygonHermez/zkevm-synchronizer-l1/synchronizer --output=../aggregator/mocks --outpkg=mocks --structname=SynchronizerInterfaceMock --filename=mock_synchronizer.go export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=StreamClient --dir=../aggregator --output=../aggregator/mocks --outpkg=mocks --structname=StreamClientMock --filename=mock_streamclient.go export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=EthTxManagerClient --dir=../aggregator --output=../aggregator/mocks --outpkg=mocks --structname=EthTxManagerClientMock --filename=mock_eth_tx_manager.go export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=Tx --srcpkg=github.com/jackc/pgx/v4 --output=../aggregator/mocks --outpkg=mocks --structname=DbTxMock --filename=mock_dbtx.go +.PHONY: generate-mocks-aggsender +generate-mocks-aggsender: ## Generates mocks for aggsender, using mockery tool + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=L1InfoTreeSyncer --dir=../aggsender --output=../aggsender --outpkg=aggsender --inpackage --structname=L1InfoTreeSyncerMock --filename=mock_l1infotree_syncer.go + .PHONY: test-e2e-fork9-validium test-e2e-fork9-validium: stop ./run-e2e.sh fork9 cdk-validium From 14364b94d987ba05e942d4a1827e905f4d6bf339 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Wed, 9 Oct 2024 12:10:40 +0200 Subject: [PATCH 14/84] feat: more UTs --- aggsender/aggsender.go | 13 +++++- aggsender/aggsender_test.go | 9 ++--- aggsender/db/aggsender_db_storage.go | 20 +++++++--- aggsender/db/aggsender_db_storage_test.go | 48 ++++++++++++++++++++--- 4 files changed, 72 insertions(+), 18 deletions(-) diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index 37a83535..e7a25c8b 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -12,6 +12,7 @@ import ( "github.com/0xPolygon/cdk/bridgesync" cdkcommon "github.com/0xPolygon/cdk/common" "github.com/0xPolygon/cdk/config/types" + "github.com/0xPolygon/cdk/etherman" "github.com/0xPolygon/cdk/l1infotreesync" "github.com/0xPolygon/cdk/log" treeTypes "github.com/0xPolygon/cdk/tree/types" @@ -26,11 +27,21 @@ type L1InfoTreeSyncer interface { index uint32, root common.Hash) (treeTypes.Proof, error) } +// L2BridgeSyncer is an interface defining functions that an L2BridgeSyncer should implement +type L2BridgeSyncer interface { + GetBlockByLER(ctx context.Context, ler common.Hash) (uint64, error) + GetExitRootByIndex(ctx context.Context, index uint32) (treeTypes.Root, error) + GetBridges(ctx context.Context, fromBlock, toBlock uint64) ([]bridgesync.Bridge, error) + GetClaims(ctx context.Context, fromBlock, toBlock uint64) ([]bridgesync.Claim, error) + OriginNetwork() uint32 + BlockFinality() etherman.BlockNumberFinality +} + // AggSender is a component that will send certificates to the aggLayer type AggSender struct { log *log.Logger - l2Syncer *bridgesync.BridgeSync + l2Syncer L2BridgeSyncer l2Client bridgesync.EthClienter l1infoTreeSyncer L1InfoTreeSyncer diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go index 0572b590..cc031d57 100644 --- a/aggsender/aggsender_test.go +++ b/aggsender/aggsender_test.go @@ -433,7 +433,7 @@ func TestGetImportedBridgeExits(t *testing.T) { DestinationAddress: common.HexToAddress("0xabc"), Amount: big.NewInt(200), Metadata: []byte("data"), - GlobalIndex: big.NewInt(2), + GlobalIndex: bridgesync.GenerateGlobalIndex(true, 0, 2), GlobalExitRoot: common.HexToHash("0xdef"), RollupExitRoot: common.HexToHash("0xbbb"), MainnetExitRoot: common.HexToHash("0xccc"), @@ -494,11 +494,11 @@ func TestGetImportedBridgeExits(t *testing.T) { Metadata: []byte("data"), }, GlobalIndex: &agglayer.GlobalIndex{ - MainnetFlag: false, + MainnetFlag: true, RollupIndex: 0, LeafIndex: 2, }, - ClaimData: &agglayer.ClaimFromRollup{ + ClaimData: &agglayer.ClaimFromMainnnet{ L1Leaf: agglayer.L1InfoTreeLeaf{ L1InfoTreeIndex: 2, RollupExitRoot: common.HexToHash("0xbbb"), @@ -509,11 +509,10 @@ func TestGetImportedBridgeExits(t *testing.T) { BlockHash: common.HexToHash("0xabc"), }, }, - ProofLeafLER: agglayer.MerkleProof{ + ProofLeafMER: agglayer.MerkleProof{ Root: common.HexToHash("0xccc"), Proof: mockProof, }, - ProofLERToRER: agglayer.MerkleProof{}, ProofGERToL1Root: agglayer.MerkleProof{ Root: common.HexToHash("0x789"), Proof: mockProof, diff --git a/aggsender/db/aggsender_db_storage.go b/aggsender/db/aggsender_db_storage.go index b73619e2..c19d6429 100644 --- a/aggsender/db/aggsender_db_storage.go +++ b/aggsender/db/aggsender_db_storage.go @@ -5,6 +5,7 @@ import ( "database/sql" "errors" "fmt" + "math" "github.com/0xPolygon/cdk/aggsender/db/migrations" "github.com/0xPolygon/cdk/aggsender/types" @@ -72,7 +73,7 @@ func (a *AggSenderSQLStorage) GetCertificateByHeight(ctx context.Context, var certificateInfo types.CertificateInfo if err = meddler.ScanRow(rows, &certificateInfo); err != nil { - return types.CertificateInfo{}, err + return types.CertificateInfo{}, getSelectQueryError(height, err) } return certificateInfo, nil @@ -93,15 +94,12 @@ func (a *AggSenderSQLStorage) GetLastSentCertificate(ctx context.Context) (types rows, err := tx.Query(`SELECT * FROM certificate_info ORDER BY height DESC LIMIT 1;`) if err != nil { - if errors.Is(err, sql.ErrNoRows) { - return types.CertificateInfo{}, db.ErrNotFound - } - return types.CertificateInfo{}, err + return types.CertificateInfo{}, getSelectQueryError(math.MaxUint64, err) // force checking err not found } var certificateInfo types.CertificateInfo if err = meddler.ScanRow(rows, &certificateInfo); err != nil { - return types.CertificateInfo{}, err + return types.CertificateInfo{}, getSelectQueryError(math.MaxUint64, err) // force checking err not found } return certificateInfo, nil @@ -159,6 +157,16 @@ func (a *AggSenderSQLStorage) DeleteCertificate(ctx context.Context, certificate return nil } +// clean deletes all the data from the storage +// NOTE: Used only in tests +func (a *AggSenderSQLStorage) clean() error { + if _, err := a.db.Exec(`DELETE FROM certificate_info;`); err != nil { + return err + } + + return nil +} + func getSelectQueryError(height uint64, err error) error { errToReturn := err if errors.Is(err, sql.ErrNoRows) { diff --git a/aggsender/db/aggsender_db_storage_test.go b/aggsender/db/aggsender_db_storage_test.go index b0be8ed2..7b646cb7 100644 --- a/aggsender/db/aggsender_db_storage_test.go +++ b/aggsender/db/aggsender_db_storage_test.go @@ -2,20 +2,19 @@ package db import ( "context" - "database/sql" - "errors" "path" "testing" "github.com/0xPolygon/cdk/agglayer" "github.com/0xPolygon/cdk/aggsender/db/migrations" "github.com/0xPolygon/cdk/aggsender/types" + "github.com/0xPolygon/cdk/db" "github.com/0xPolygon/cdk/log" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" ) -func Test_SaveLastSentCertificate(t *testing.T) { +func Test_Storage(t *testing.T) { ctx := context.Background() path := path.Join(t.TempDir(), "file::memory:?cache=shared") @@ -40,6 +39,7 @@ func Test_SaveLastSentCertificate(t *testing.T) { require.NoError(t, err) require.Equal(t, certificate, certificateFromDB) + require.NoError(t, storage.clean()) }) t.Run("DeleteCertificate", func(t *testing.T) { @@ -56,12 +56,18 @@ func Test_SaveLastSentCertificate(t *testing.T) { require.NoError(t, storage.DeleteCertificate(ctx, certificate.CertificateID)) certificateFromDB, err := storage.GetCertificateByHeight(ctx, certificate.Height) - require.Error(t, err) - require.True(t, errors.Is(err, sql.ErrNoRows)) + require.ErrorIs(t, err, db.ErrNotFound) require.Equal(t, types.CertificateInfo{}, certificateFromDB) + require.NoError(t, storage.clean()) }) t.Run("GetLastSentCertificate", func(t *testing.T) { + // try getting a certificate that doesn't exist + certificateFromDB, err := storage.GetLastSentCertificate(ctx) + require.ErrorIs(t, err, db.ErrNotFound) + require.Equal(t, types.CertificateInfo{}, certificateFromDB) + + // try getting a certificate that exists certificate := types.CertificateInfo{ Height: 3, CertificateID: common.HexToHash("0x5"), @@ -72,9 +78,39 @@ func Test_SaveLastSentCertificate(t *testing.T) { } require.NoError(t, storage.SaveLastSentCertificate(ctx, certificate)) - certificateFromDB, err := storage.GetLastSentCertificate(ctx) + certificateFromDB, err = storage.GetLastSentCertificate(ctx) + require.NoError(t, err) + + require.Equal(t, certificate, certificateFromDB) + require.NoError(t, storage.clean()) + }) + + t.Run("GetCertificateByHeight", func(t *testing.T) { + // try getting height 0 + certificateFromDB, err := storage.GetCertificateByHeight(ctx, 0) + require.NoError(t, err) + require.Equal(t, types.CertificateInfo{}, certificateFromDB) + + // try getting a certificate that doesn't exist + certificateFromDB, err = storage.GetCertificateByHeight(ctx, 4) + require.ErrorIs(t, err, db.ErrNotFound) + require.Equal(t, types.CertificateInfo{}, certificateFromDB) + + // try getting a certificate that exists + certificate := types.CertificateInfo{ + Height: 11, + CertificateID: common.HexToHash("0x17"), + NewLocalExitRoot: common.HexToHash("0x18"), + FromBlock: 17, + ToBlock: 18, + Status: agglayer.Pending, + } + require.NoError(t, storage.SaveLastSentCertificate(ctx, certificate)) + + certificateFromDB, err = storage.GetCertificateByHeight(ctx, certificate.Height) require.NoError(t, err) require.Equal(t, certificate, certificateFromDB) + require.NoError(t, storage.clean()) }) } From 1cb7f9e387e70707808ac4b382c9576ab522ef28 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Wed, 9 Oct 2024 12:10:41 +0200 Subject: [PATCH 15/84] feat: track certificates settlement --- aggsender/aggsender.go | 45 +++++++++++-- aggsender/config.go | 1 + aggsender/db/aggsender_db_storage.go | 77 ++++++++++++++++++++++- aggsender/db/aggsender_db_storage_test.go | 66 ++++++++++++++++++- config/default.go | 2 +- 5 files changed, 182 insertions(+), 9 deletions(-) diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index e7a25c8b..240ff56f 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -11,7 +11,6 @@ import ( aggsendertypes "github.com/0xPolygon/cdk/aggsender/types" "github.com/0xPolygon/cdk/bridgesync" cdkcommon "github.com/0xPolygon/cdk/common" - "github.com/0xPolygon/cdk/config/types" "github.com/0xPolygon/cdk/etherman" "github.com/0xPolygon/cdk/l1infotreesync" "github.com/0xPolygon/cdk/log" @@ -48,7 +47,7 @@ type AggSender struct { storage db.AggSenderStorage aggLayerClient agglayer.AgglayerClientInterface - sendInterval types.Duration + cfg Config sequencerKey *ecdsa.PrivateKey } @@ -73,6 +72,7 @@ func New( } return &AggSender{ + cfg: cfg, log: logger, storage: storage, l2Syncer: l2Syncer, @@ -80,18 +80,18 @@ func New( aggLayerClient: aggLayerClient, l1infoTreeSyncer: l1InfoTreeSyncer, sequencerKey: sequencerPrivateKey, - sendInterval: cfg.CertificateSendInterval, }, nil } // Start starts the AggSender func (a *AggSender) Start(ctx context.Context) { - a.sendCertificates(ctx) + go a.sendCertificates(ctx) + go a.checkIfCertificatesAreSettled(ctx) } // sendCertificates sends certificates to the aggLayer func (a *AggSender) sendCertificates(ctx context.Context) { - ticker := time.NewTicker(a.sendInterval.Duration) + ticker := time.NewTicker(a.cfg.CertificateSendInterval.Duration) for { select { @@ -408,3 +408,38 @@ func (a *AggSender) signCertificate(certificate *agglayer.Certificate) (*agglaye Signature: sig, }, nil } + +// checkIfCertificatesAreSettled checks if certificates are settled +func (a *AggSender) checkIfCertificatesAreSettled(ctx context.Context) { + ticker := time.NewTicker(a.cfg.CertificateSendInterval.Duration) + for { + select { + case <-ticker.C: + pendingCertificates, err := a.storage.GetCertificatesByStatus(ctx, []agglayer.CertificateStatus{agglayer.Pending}) + if err != nil { + a.log.Error("error getting pending certificates: %w", err) + continue + } + + for _, certificate := range pendingCertificates { + certificateHeader, err := a.aggLayerClient.GetCertificateHeader(certificate.CertificateID) + if err != nil { + a.log.Error("error getting header of certificate %s with height: %d from agglayer: %w", + certificate.CertificateID, certificate.Height, err) + continue + } + + if certificateHeader.Status == agglayer.Settled || certificateHeader.Status == agglayer.InError { + certificate.Status = certificateHeader.Status + + if err := a.storage.UpdateCertificateStatus(ctx, *certificate); err != nil { + a.log.Error("error updating certificate status in storage: %w", err) + continue + } + } + } + case <-ctx.Done(): + return + } + } +} diff --git a/aggsender/config.go b/aggsender/config.go index 3f335975..ebdfec1e 100644 --- a/aggsender/config.go +++ b/aggsender/config.go @@ -9,6 +9,7 @@ type Config struct { DBPath string `mapstructure:"DBPath"` AggLayerURL string `mapstructure:"AggLayerURL"` CertificateSendInterval types.Duration `mapstructure:"CertificateSendInterval"` + CheckSettledInterval types.Duration `mapstructure:"CheckSettledInterval"` SequencerPrivateKey types.KeystoreFileConfig `mapstructure:"SequencerPrivateKey"` URLRPCL2 string `mapstructure:"URLRPCL2"` } diff --git a/aggsender/db/aggsender_db_storage.go b/aggsender/db/aggsender_db_storage.go index c19d6429..6d6dec25 100644 --- a/aggsender/db/aggsender_db_storage.go +++ b/aggsender/db/aggsender_db_storage.go @@ -6,7 +6,9 @@ import ( "errors" "fmt" "math" + "strings" + "github.com/0xPolygon/cdk/agglayer" "github.com/0xPolygon/cdk/aggsender/db/migrations" "github.com/0xPolygon/cdk/aggsender/types" "github.com/0xPolygon/cdk/db" @@ -25,6 +27,10 @@ type AggSenderStorage interface { SaveLastSentCertificate(ctx context.Context, certificate types.CertificateInfo) error // DeleteCertificate deletes a certificate from the storage DeleteCertificate(ctx context.Context, certificateID common.Hash) error + // GetCertificatesByStatus returns a list of certificates by their status + GetCertificatesByStatus(ctx context.Context, status []agglayer.CertificateStatus) ([]*types.CertificateInfo, error) + // UpdateCertificateStatus updates the status of a certificate + UpdateCertificateStatus(ctx context.Context, certificate types.CertificateInfo) error } var _ AggSenderStorage = (*AggSenderSQLStorage)(nil) @@ -52,6 +58,45 @@ func NewAggSenderSQLStorage(logger *log.Logger, dbPath string) (*AggSenderSQLSto }, nil } +func (a *AggSenderSQLStorage) GetCertificatesByStatus(ctx context.Context, + statuses []agglayer.CertificateStatus) ([]*types.CertificateInfo, error) { + tx, err := a.db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) + if err != nil { + return nil, err + } + + defer func() { + if err := tx.Rollback(); err != nil { + a.logger.Warnf("error rolling back tx: %w", err) + } + }() + + query := "SELECT * FROM certificate_info" + args := make([]interface{}, len(statuses)) + + if len(statuses) > 0 { + placeholders := make([]string, len(statuses)) + // Build the WHERE clause for status filtering + for i := range statuses { + placeholders[i] = fmt.Sprintf("$%d", i+1) + args[i] = statuses[i] + } + + // Build the WHERE clause with the joined placeholders + query += " WHERE status IN (" + strings.Join(placeholders, ", ") + ")" + } + + // Add ordering by creation date (oldest first) + query += " ORDER BY height ASC" + + var certificates []*types.CertificateInfo + if err = meddler.QueryAll(a.db, &certificates, query, args...); err != nil { + return nil, err + } + + return certificates, nil +} + // GetCertificateByHeight returns a certificate by its height func (a *AggSenderSQLStorage) GetCertificateByHeight(ctx context.Context, height uint64) (types.CertificateInfo, error) { @@ -79,7 +124,7 @@ func (a *AggSenderSQLStorage) GetCertificateByHeight(ctx context.Context, return certificateInfo, nil } -// GetLastSentCertificate returns the last certificate sent to the aggLayer +// GetLastSentCertificate returns the last certificate sent to the aggLayer that is still Pending func (a *AggSenderSQLStorage) GetLastSentCertificate(ctx context.Context) (types.CertificateInfo, error) { tx, err := a.db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) if err != nil { @@ -92,7 +137,8 @@ func (a *AggSenderSQLStorage) GetLastSentCertificate(ctx context.Context) (types } }() - rows, err := tx.Query(`SELECT * FROM certificate_info ORDER BY height DESC LIMIT 1;`) + rows, err := tx.Query(`SELECT * FROM certificate_info WHERE status = $1 ORDER BY height DESC LIMIT 1;`, + agglayer.Pending) if err != nil { return types.CertificateInfo{}, getSelectQueryError(math.MaxUint64, err) // force checking err not found } @@ -157,6 +203,33 @@ func (a *AggSenderSQLStorage) DeleteCertificate(ctx context.Context, certificate return nil } +// UpdateCertificateStatus updates the status of a certificate +func (a *AggSenderSQLStorage) UpdateCertificateStatus(ctx context.Context, certificate types.CertificateInfo) error { + tx, err := db.NewTx(ctx, a.db) + if err != nil { + return err + } + defer func() { + if err != nil { + if errRllbck := tx.Rollback(); errRllbck != nil { + a.logger.Errorf("error while rolling back tx %w", errRllbck) + } + } + }() + + if _, err := tx.Exec(`UPDATE certificate_info SET status = $1 WHERE certificate_id = $2;`, + certificate.Status, certificate.CertificateID); err != nil { + return fmt.Errorf("error updating certificate info: %w", err) + } + if err := tx.Commit(); err != nil { + return err + } + + a.logger.Debugf("updated certificate status - CertificateID: %s", certificate.CertificateID) + + return nil +} + // clean deletes all the data from the storage // NOTE: Used only in tests func (a *AggSenderSQLStorage) clean() error { diff --git a/aggsender/db/aggsender_db_storage_test.go b/aggsender/db/aggsender_db_storage_test.go index 7b646cb7..ee7ca1e2 100644 --- a/aggsender/db/aggsender_db_storage_test.go +++ b/aggsender/db/aggsender_db_storage_test.go @@ -74,7 +74,7 @@ func Test_Storage(t *testing.T) { NewLocalExitRoot: common.HexToHash("0x6"), FromBlock: 5, ToBlock: 6, - Status: agglayer.Settled, + Status: agglayer.Pending, } require.NoError(t, storage.SaveLastSentCertificate(ctx, certificate)) @@ -113,4 +113,68 @@ func Test_Storage(t *testing.T) { require.Equal(t, certificate, certificateFromDB) require.NoError(t, storage.clean()) }) + + t.Run("GetCertificatesByStatus", func(t *testing.T) { + // Insert some certificates with different statuses + certificates := []*types.CertificateInfo{ + { + Height: 7, + CertificateID: common.HexToHash("0x7"), + NewLocalExitRoot: common.HexToHash("0x8"), + FromBlock: 7, + ToBlock: 8, + Status: agglayer.Settled, + }, + { + Height: 9, + CertificateID: common.HexToHash("0x9"), + NewLocalExitRoot: common.HexToHash("0xA"), + FromBlock: 9, + ToBlock: 10, + Status: agglayer.Pending, + }, + { + Height: 11, + CertificateID: common.HexToHash("0xB"), + NewLocalExitRoot: common.HexToHash("0xC"), + FromBlock: 11, + ToBlock: 12, + Status: agglayer.InError, + }, + } + + for _, cert := range certificates { + require.NoError(t, storage.SaveLastSentCertificate(ctx, *cert)) + } + + // Test fetching certificates with status Settled + statuses := []agglayer.CertificateStatus{agglayer.Settled} + certificatesFromDB, err := storage.GetCertificatesByStatus(ctx, statuses) + require.NoError(t, err) + require.Len(t, certificatesFromDB, 1) + require.ElementsMatch(t, []*types.CertificateInfo{certificates[0]}, certificatesFromDB) + + // Test fetching certificates with status Pending + statuses = []agglayer.CertificateStatus{agglayer.Pending} + certificatesFromDB, err = storage.GetCertificatesByStatus(ctx, statuses) + require.NoError(t, err) + require.Len(t, certificatesFromDB, 1) + require.ElementsMatch(t, []*types.CertificateInfo{certificates[1]}, certificatesFromDB) + + // Test fetching certificates with status InError + statuses = []agglayer.CertificateStatus{agglayer.InError} + certificatesFromDB, err = storage.GetCertificatesByStatus(ctx, statuses) + require.NoError(t, err) + require.Len(t, certificatesFromDB, 1) + require.ElementsMatch(t, []*types.CertificateInfo{certificates[2]}, certificatesFromDB) + + // Test fetching certificates with status InError and Pending + statuses = []agglayer.CertificateStatus{agglayer.InError, agglayer.Pending} + certificatesFromDB, err = storage.GetCertificatesByStatus(ctx, statuses) + require.NoError(t, err) + require.Len(t, certificatesFromDB, 2) + require.ElementsMatch(t, []*types.CertificateInfo{certificates[1], certificates[2]}, certificatesFromDB) + + require.NoError(t, storage.clean()) + }) } diff --git a/config/default.go b/config/default.go index 62a833cd..4871e1c1 100644 --- a/config/default.go +++ b/config/default.go @@ -345,5 +345,5 @@ AggLayerURL = "http://zkevm-agglayer" SequencerPrivateKey = {Path = "/pk/sequencer.keystore", Password = "testonly"} CertificateSendInterval = "1m" URLRPCL2="http://test-aggoracle-l2:8545" - +CheckSettledInterval = "5s" ` From 6b82e64daf6ad409b4b9df36c1b1f719c72b7e26 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Wed, 9 Oct 2024 12:10:41 +0200 Subject: [PATCH 16/84] fix: get last sent certificate --- aggsender/db/aggsender_db_storage.go | 5 ++--- aggsender/db/aggsender_db_storage_test.go | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/aggsender/db/aggsender_db_storage.go b/aggsender/db/aggsender_db_storage.go index 6d6dec25..fbfdddb3 100644 --- a/aggsender/db/aggsender_db_storage.go +++ b/aggsender/db/aggsender_db_storage.go @@ -5,7 +5,6 @@ import ( "database/sql" "errors" "fmt" - "math" "strings" "github.com/0xPolygon/cdk/agglayer" @@ -140,12 +139,12 @@ func (a *AggSenderSQLStorage) GetLastSentCertificate(ctx context.Context) (types rows, err := tx.Query(`SELECT * FROM certificate_info WHERE status = $1 ORDER BY height DESC LIMIT 1;`, agglayer.Pending) if err != nil { - return types.CertificateInfo{}, getSelectQueryError(math.MaxUint64, err) // force checking err not found + return types.CertificateInfo{}, getSelectQueryError(0, err) } var certificateInfo types.CertificateInfo if err = meddler.ScanRow(rows, &certificateInfo); err != nil { - return types.CertificateInfo{}, getSelectQueryError(math.MaxUint64, err) // force checking err not found + return types.CertificateInfo{}, getSelectQueryError(0, err) } return certificateInfo, nil diff --git a/aggsender/db/aggsender_db_storage_test.go b/aggsender/db/aggsender_db_storage_test.go index ee7ca1e2..168f079e 100644 --- a/aggsender/db/aggsender_db_storage_test.go +++ b/aggsender/db/aggsender_db_storage_test.go @@ -64,7 +64,7 @@ func Test_Storage(t *testing.T) { t.Run("GetLastSentCertificate", func(t *testing.T) { // try getting a certificate that doesn't exist certificateFromDB, err := storage.GetLastSentCertificate(ctx) - require.ErrorIs(t, err, db.ErrNotFound) + require.NoError(t, err) require.Equal(t, types.CertificateInfo{}, certificateFromDB) // try getting a certificate that exists From 5bce16a1f99c490b00ebadf6c2a1a83881327e3c Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Wed, 9 Oct 2024 12:36:47 +0200 Subject: [PATCH 17/84] fix: run l1client for aggsender --- cmd/run.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/run.go b/cmd/run.go index 5ea89a8b..644c5ae6 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -532,6 +532,7 @@ func runL1ClientIfNeeded(components []string, urlRPCL1 string) *ethclient.Client if !isNeeded([]string{ cdkcommon.SEQUENCE_SENDER, cdkcommon.AGGREGATOR, cdkcommon.AGGORACLE, cdkcommon.RPC, + cdkcommon.AGGSENDER, }, components) { return nil } From 01f2f87bccf501d5d6a04b60b512779d7f5ba8b9 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Wed, 9 Oct 2024 13:39:26 +0200 Subject: [PATCH 18/84] feat: more unit tests --- aggsender/aggsender.go | 31 ++++ aggsender/aggsender_test.go | 253 ++++++++++++++++++++++++++++++ aggsender/config.go | 1 + aggsender/mock_l2bridge_syncer.go | 187 ++++++++++++++++++++++ cmd/run.go | 12 +- config/default.go | 1 + test/Makefile | 1 + 7 files changed, 484 insertions(+), 2 deletions(-) create mode 100644 aggsender/mock_l2bridge_syncer.go diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index 240ff56f..ee03b6ea 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -3,6 +3,7 @@ package aggsender import ( "context" "crypto/ecdsa" + "errors" "fmt" "time" @@ -19,6 +20,8 @@ import ( "github.com/ethereum/go-ethereum/crypto" ) +var errNoBridgesAndClaims = errors.New("no bridges and claims to build certificate") + // L1InfoTreeSyncer is an interface defining functions that an L1InfoTreeSyncer should implement type L1InfoTreeSyncer interface { GetInfoByGlobalExitRoot(globalExitRoot common.Hash) (*l1infotreesync.L1InfoTreeLeaf, error) @@ -43,6 +46,7 @@ type AggSender struct { l2Syncer L2BridgeSyncer l2Client bridgesync.EthClienter l1infoTreeSyncer L1InfoTreeSyncer + l1Client bridgesync.EthClienter storage db.AggSenderStorage aggLayerClient agglayer.AgglayerClientInterface @@ -58,6 +62,7 @@ func New( logger *log.Logger, cfg Config, aggLayerClient agglayer.AgglayerClientInterface, + l1Client bridgesync.EthClienter, l1InfoTreeSyncer *l1infotreesync.L1InfoTreeSync, l2Syncer *bridgesync.BridgeSync, l2Client bridgesync.EthClienter) (*AggSender, error) { @@ -77,6 +82,7 @@ func New( storage: storage, l2Syncer: l2Syncer, l2Client: l2Client, + l1Client: l1Client, aggLayerClient: aggLayerClient, l1infoTreeSyncer: l1InfoTreeSyncer, sequencerKey: sequencerPrivateKey, @@ -96,6 +102,16 @@ func (a *AggSender) sendCertificates(ctx context.Context) { for { select { case <-ticker.C: + block, err := a.l1Client.BlockNumber(ctx) + if err != nil { + a.log.Error("error getting l1 block number: %w", err) + continue + } + + if !a.shouldSendCertificate(block) { + continue + } + if err := a.sendCertificate(ctx); err != nil { log.Error(err) } @@ -226,6 +242,10 @@ func (a *AggSender) buildCertificate(ctx context.Context, bridges []bridgesync.Bridge, claims []bridgesync.Claim, previousExitRoot common.Hash, lastHeight uint64) (*agglayer.Certificate, error) { + if len(bridges) == 0 && len(claims) == 0 { + return nil, errNoBridgesAndClaims + } + bridgeExits := a.getBridgeExits(bridges) importedBridgeExits, err := a.getImportedBridgeExits(ctx, claims) if err != nil { @@ -443,3 +463,14 @@ func (a *AggSender) checkIfCertificatesAreSettled(ctx context.Context) { } } } + +// shouldSendCertificate checks if a certificate should be sent at given L1 block +// we send certificates at one block before the epoch ending so we get most of the +// bridges and claims in that epoch +func (a *AggSender) shouldSendCertificate(block uint64) bool { + if block == 0 { + return false + } + + return (block+1)%a.cfg.EpochSize == 0 +} diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go index cc031d57..34b93c09 100644 --- a/aggsender/aggsender_test.go +++ b/aggsender/aggsender_test.go @@ -551,6 +551,207 @@ func TestGetImportedBridgeExits(t *testing.T) { } } +func TestBuildCertificate(t *testing.T) { + mockL2BridgeSyncer := NewL2BridgeSyncerMock(t) + mockL1InfoTreeSyncer := 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, + } + 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 generateTestProof(t *testing.T) treeTypes.Proof { t.Helper() @@ -562,3 +763,55 @@ func generateTestProof(t *testing.T) treeTypes.Proof { return proof } + +func TestShouldSendCertificate(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + block uint64 + epochSize uint64 + expectedResult bool + }{ + { + name: "Should send certificate", + block: 9, + epochSize: 10, + expectedResult: true, + }, + { + name: "Should not send certificate", + block: 8, + epochSize: 10, + expectedResult: false, + }, + { + name: "Should not send certificate at zero block", + block: 0, + epochSize: 1, + expectedResult: false, + }, + { + name: "Should send certificate with large epoch size", + block: 999, + epochSize: 1000, + expectedResult: true, + }, + } + + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + aggSender := &AggSender{ + cfg: Config{ + EpochSize: tt.epochSize, + }, + } + result := aggSender.shouldSendCertificate(tt.block) + require.Equal(t, tt.expectedResult, result) + }) + } +} diff --git a/aggsender/config.go b/aggsender/config.go index ebdfec1e..59e8553b 100644 --- a/aggsender/config.go +++ b/aggsender/config.go @@ -12,4 +12,5 @@ type Config struct { CheckSettledInterval types.Duration `mapstructure:"CheckSettledInterval"` SequencerPrivateKey types.KeystoreFileConfig `mapstructure:"SequencerPrivateKey"` URLRPCL2 string `mapstructure:"URLRPCL2"` + EpochSize uint64 `mapstructure:"EpochSize"` } diff --git a/aggsender/mock_l2bridge_syncer.go b/aggsender/mock_l2bridge_syncer.go new file mode 100644 index 00000000..397b1dc8 --- /dev/null +++ b/aggsender/mock_l2bridge_syncer.go @@ -0,0 +1,187 @@ +// Code generated by mockery v2.45.0. DO NOT EDIT. + +package aggsender + +import ( + bridgesync "github.com/0xPolygon/cdk/bridgesync" + common "github.com/ethereum/go-ethereum/common" + + context "context" + + etherman "github.com/0xPolygon/cdk/etherman" + + mock "github.com/stretchr/testify/mock" + + types "github.com/0xPolygon/cdk/tree/types" +) + +// L2BridgeSyncerMock is an autogenerated mock type for the L2BridgeSyncer type +type L2BridgeSyncerMock struct { + mock.Mock +} + +// BlockFinality provides a mock function with given fields: +func (_m *L2BridgeSyncerMock) BlockFinality() etherman.BlockNumberFinality { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for BlockFinality") + } + + var r0 etherman.BlockNumberFinality + if rf, ok := ret.Get(0).(func() etherman.BlockNumberFinality); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(etherman.BlockNumberFinality) + } + + return r0 +} + +// GetBlockByLER provides a mock function with given fields: ctx, ler +func (_m *L2BridgeSyncerMock) GetBlockByLER(ctx context.Context, ler common.Hash) (uint64, error) { + ret := _m.Called(ctx, ler) + + if len(ret) == 0 { + panic("no return value specified for GetBlockByLER") + } + + var r0 uint64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Hash) (uint64, error)); ok { + return rf(ctx, ler) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Hash) uint64); ok { + r0 = rf(ctx, ler) + } else { + r0 = ret.Get(0).(uint64) + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Hash) error); ok { + r1 = rf(ctx, ler) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetBridges provides a mock function with given fields: ctx, fromBlock, toBlock +func (_m *L2BridgeSyncerMock) GetBridges(ctx context.Context, fromBlock uint64, toBlock uint64) ([]bridgesync.Bridge, error) { + ret := _m.Called(ctx, fromBlock, toBlock) + + if len(ret) == 0 { + panic("no return value specified for GetBridges") + } + + var r0 []bridgesync.Bridge + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64) ([]bridgesync.Bridge, error)); ok { + return rf(ctx, fromBlock, toBlock) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64) []bridgesync.Bridge); ok { + r0 = rf(ctx, fromBlock, toBlock) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]bridgesync.Bridge) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, uint64) error); ok { + r1 = rf(ctx, fromBlock, toBlock) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetClaims provides a mock function with given fields: ctx, fromBlock, toBlock +func (_m *L2BridgeSyncerMock) GetClaims(ctx context.Context, fromBlock uint64, toBlock uint64) ([]bridgesync.Claim, error) { + ret := _m.Called(ctx, fromBlock, toBlock) + + if len(ret) == 0 { + panic("no return value specified for GetClaims") + } + + var r0 []bridgesync.Claim + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64) ([]bridgesync.Claim, error)); ok { + return rf(ctx, fromBlock, toBlock) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64) []bridgesync.Claim); ok { + r0 = rf(ctx, fromBlock, toBlock) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]bridgesync.Claim) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, uint64) error); ok { + r1 = rf(ctx, fromBlock, toBlock) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetExitRootByIndex provides a mock function with given fields: ctx, index +func (_m *L2BridgeSyncerMock) GetExitRootByIndex(ctx context.Context, index uint32) (types.Root, error) { + ret := _m.Called(ctx, index) + + if len(ret) == 0 { + panic("no return value specified for GetExitRootByIndex") + } + + var r0 types.Root + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint32) (types.Root, error)); ok { + return rf(ctx, index) + } + if rf, ok := ret.Get(0).(func(context.Context, uint32) types.Root); ok { + r0 = rf(ctx, index) + } else { + r0 = ret.Get(0).(types.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 +} + +// OriginNetwork provides a mock function with given fields: +func (_m *L2BridgeSyncerMock) OriginNetwork() uint32 { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for OriginNetwork") + } + + var r0 uint32 + if rf, ok := ret.Get(0).(func() uint32); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(uint32) + } + + return r0 +} + +// NewL2BridgeSyncerMock creates a new instance of L2BridgeSyncerMock. 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 NewL2BridgeSyncerMock(t interface { + mock.TestingT + Cleanup(func()) +}) *L2BridgeSyncerMock { + mock := &L2BridgeSyncerMock{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/cmd/run.go b/cmd/run.go index 644c5ae6..7b4f196f 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -122,7 +122,14 @@ func start(cliCtx *cli.Context) error { } }() case cdkcommon.AGGSENDER: - aggsender, err := createAggSender(cliCtx.Context, c.AggSender, l1InfoTreeSync, l2BridgeSync, l2Client) + aggsender, err := createAggSender( + cliCtx.Context, + c.AggSender, + l1Client, + l1InfoTreeSync, + l2BridgeSync, + l2Client, + ) if err != nil { log.Fatal(err) } @@ -139,6 +146,7 @@ func start(cliCtx *cli.Context) error { func createAggSender( ctx context.Context, cfg aggsender.Config, + l1Client bridgesync.EthClienter, l1InfoTreeSync *l1infotreesync.L1InfoTreeSync, l2Syncer *bridgesync.BridgeSync, l2Client bridgesync.EthClienter, @@ -146,7 +154,7 @@ func createAggSender( logger := log.WithFields("module", cdkcommon.AGGSENDER) agglayerClient := agglayer.NewAggLayerClient(cfg.AggLayerURL) - return aggsender.New(ctx, logger, cfg, agglayerClient, l1InfoTreeSync, l2Syncer, l2Client) + return aggsender.New(ctx, logger, cfg, agglayerClient, l1Client, l1InfoTreeSync, l2Syncer, l2Client) } func createAggregator(ctx context.Context, c config.Config, runMigrations bool) *aggregator.Aggregator { diff --git a/config/default.go b/config/default.go index 4871e1c1..ad404180 100644 --- a/config/default.go +++ b/config/default.go @@ -346,4 +346,5 @@ SequencerPrivateKey = {Path = "/pk/sequencer.keystore", Password = "testonly"} CertificateSendInterval = "1m" URLRPCL2="http://test-aggoracle-l2:8545" CheckSettledInterval = "5s" +EpochSize = 10 ` diff --git a/test/Makefile b/test/Makefile index 54393882..fe37a79a 100644 --- a/test/Makefile +++ b/test/Makefile @@ -61,6 +61,7 @@ generate-mocks-aggregator: ## Generates mocks for aggregator, using mockery tool .PHONY: generate-mocks-aggsender generate-mocks-aggsender: ## Generates mocks for aggsender, using mockery tool export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=L1InfoTreeSyncer --dir=../aggsender --output=../aggsender --outpkg=aggsender --inpackage --structname=L1InfoTreeSyncerMock --filename=mock_l1infotree_syncer.go + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=L2BridgeSyncer --dir=../aggsender --output=../aggsender --outpkg=aggsender --inpackage --structname=L2BridgeSyncerMock --filename=mock_l2bridge_syncer.go .PHONY: test-e2e-fork9-validium test-e2e-fork9-validium: stop From ca7363e58d12e5a4a8d2b3ac763541b13ec587d1 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Wed, 9 Oct 2024 15:06:37 +0200 Subject: [PATCH 19/84] feat: more unit tests --- agglayer/mock_agglayer_client.go | 138 +++++++++++++++++ aggsender/aggsender.go | 28 ++-- aggsender/aggsender_test.go | 125 ++++++++++++++++ aggsender/db/aggsender_db_storage_test.go | 24 +++ aggsender/db/mock_aggsender_storage.go | 173 ++++++++++++++++++++++ aggsender/mock_logger.go | 54 +++++++ test/Makefile | 8 +- 7 files changed, 539 insertions(+), 11 deletions(-) create mode 100644 agglayer/mock_agglayer_client.go create mode 100644 aggsender/db/mock_aggsender_storage.go create mode 100644 aggsender/mock_logger.go diff --git a/agglayer/mock_agglayer_client.go b/agglayer/mock_agglayer_client.go new file mode 100644 index 00000000..43100a2e --- /dev/null +++ b/agglayer/mock_agglayer_client.go @@ -0,0 +1,138 @@ +// Code generated by mockery v2.45.0. DO NOT EDIT. + +package agglayer + +import ( + context "context" + + common "github.com/ethereum/go-ethereum/common" + + mock "github.com/stretchr/testify/mock" +) + +// AgglayerClientMock is an autogenerated mock type for the AgglayerClientInterface type +type AgglayerClientMock struct { + mock.Mock +} + +// GetCertificateHeader provides a mock function with given fields: certificateHash +func (_m *AgglayerClientMock) GetCertificateHeader(certificateHash common.Hash) (*CertificateHeader, error) { + ret := _m.Called(certificateHash) + + if len(ret) == 0 { + panic("no return value specified for GetCertificateHeader") + } + + var r0 *CertificateHeader + var r1 error + if rf, ok := ret.Get(0).(func(common.Hash) (*CertificateHeader, error)); ok { + return rf(certificateHash) + } + if rf, ok := ret.Get(0).(func(common.Hash) *CertificateHeader); ok { + r0 = rf(certificateHash) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*CertificateHeader) + } + } + + if rf, ok := ret.Get(1).(func(common.Hash) error); ok { + r1 = rf(certificateHash) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SendCertificate provides a mock function with given fields: certificate +func (_m *AgglayerClientMock) SendCertificate(certificate *SignedCertificate) (common.Hash, error) { + ret := _m.Called(certificate) + + if len(ret) == 0 { + panic("no return value specified for SendCertificate") + } + + var r0 common.Hash + var r1 error + if rf, ok := ret.Get(0).(func(*SignedCertificate) (common.Hash, error)); ok { + return rf(certificate) + } + if rf, ok := ret.Get(0).(func(*SignedCertificate) common.Hash); ok { + r0 = rf(certificate) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(common.Hash) + } + } + + if rf, ok := ret.Get(1).(func(*SignedCertificate) error); ok { + r1 = rf(certificate) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SendTx provides a mock function with given fields: signedTx +func (_m *AgglayerClientMock) SendTx(signedTx SignedTx) (common.Hash, error) { + ret := _m.Called(signedTx) + + if len(ret) == 0 { + panic("no return value specified for SendTx") + } + + var r0 common.Hash + var r1 error + if rf, ok := ret.Get(0).(func(SignedTx) (common.Hash, error)); ok { + return rf(signedTx) + } + if rf, ok := ret.Get(0).(func(SignedTx) common.Hash); ok { + r0 = rf(signedTx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(common.Hash) + } + } + + if rf, ok := ret.Get(1).(func(SignedTx) error); ok { + r1 = rf(signedTx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// WaitTxToBeMined provides a mock function with given fields: hash, ctx +func (_m *AgglayerClientMock) WaitTxToBeMined(hash common.Hash, ctx context.Context) error { + ret := _m.Called(hash, ctx) + + if len(ret) == 0 { + panic("no return value specified for WaitTxToBeMined") + } + + var r0 error + if rf, ok := ret.Get(0).(func(common.Hash, context.Context) error); ok { + r0 = rf(hash, ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NewAgglayerClientMock creates a new instance of AgglayerClientMock. 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 NewAgglayerClientMock(t interface { + mock.TestingT + Cleanup(func()) +}) *AgglayerClientMock { + mock := &AgglayerClientMock{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index ee03b6ea..26345baa 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -39,9 +39,17 @@ type L2BridgeSyncer interface { BlockFinality() etherman.BlockNumberFinality } +// Logger is an interface that defines the methods to log messages +type Logger interface { + Info(args ...interface{}) + Infof(format string, args ...interface{}) + Error(args ...interface{}) + Errorf(format string, args ...interface{}) +} + // AggSender is a component that will send certificates to the aggLayer type AggSender struct { - log *log.Logger + log Logger l2Syncer L2BridgeSyncer l2Client bridgesync.EthClienter @@ -104,7 +112,7 @@ func (a *AggSender) sendCertificates(ctx context.Context) { case <-ticker.C: block, err := a.l1Client.BlockNumber(ctx) if err != nil { - a.log.Error("error getting l1 block number: %w", err) + a.log.Errorf("error getting l1 block number: %w", err) continue } @@ -124,14 +132,14 @@ func (a *AggSender) sendCertificates(ctx context.Context) { // sendCertificate sends certificate for a network func (a *AggSender) sendCertificate(ctx context.Context) error { - a.log.Info("trying to send a new certificate...") + a.log.Infof("trying to send a new certificate...") lastSentCertificate, err := a.storage.GetLastSentCertificate(ctx) if err != nil { return fmt.Errorf("error getting last sent certificate: %w", err) } - a.log.Info("last sent certificate: %s", lastSentCertificate.CertificateID) + a.log.Infof("last sent certificate: %s", lastSentCertificate.CertificateID) finality := a.l2Syncer.BlockFinality() blockFinality, err := finality.ToBlockNum() @@ -196,7 +204,7 @@ func (a *AggSender) sendCertificate(ctx context.Context) error { } if len(bridges) == 0 { - a.log.Info("no bridges consumed, no need to send a certificate from block: %d to block: %d", fromBlock, toBlock) + a.log.Infof("no bridges consumed, no need to send a certificate from block: %d to block: %d", fromBlock, toBlock) return nil } @@ -205,7 +213,7 @@ func (a *AggSender) sendCertificate(ctx context.Context) error { return fmt.Errorf("error getting claims: %w", err) } - a.log.Info("building certificate for block: %d to block: %d", fromBlock, toBlock) + a.log.Infof("building certificate for block: %d to block: %d", fromBlock, toBlock) certificate, err := a.buildCertificate(ctx, bridges, claims, previousLocalExitRoot, previousHeight) if err != nil { @@ -232,7 +240,7 @@ func (a *AggSender) sendCertificate(ctx context.Context) error { return fmt.Errorf("error saving last sent certificate in db: %w", err) } - a.log.Info("certificate: %s sent successfully for block: %d to block: %d", certificateHash, fromBlock, toBlock) + a.log.Infof("certificate: %s sent successfully for block: %d to block: %d", certificateHash, fromBlock, toBlock) return nil } @@ -437,14 +445,14 @@ func (a *AggSender) checkIfCertificatesAreSettled(ctx context.Context) { case <-ticker.C: pendingCertificates, err := a.storage.GetCertificatesByStatus(ctx, []agglayer.CertificateStatus{agglayer.Pending}) if err != nil { - a.log.Error("error getting pending certificates: %w", err) + a.log.Errorf("error getting pending certificates: %w", err) continue } for _, certificate := range pendingCertificates { certificateHeader, err := a.aggLayerClient.GetCertificateHeader(certificate.CertificateID) if err != nil { - a.log.Error("error getting header of certificate %s with height: %d from agglayer: %w", + a.log.Errorf("error getting header of certificate %s with height: %d from agglayer: %w", certificate.CertificateID, certificate.Height, err) continue } @@ -453,7 +461,7 @@ func (a *AggSender) checkIfCertificatesAreSettled(ctx context.Context) { certificate.Status = certificateHeader.Status if err := a.storage.UpdateCertificateStatus(ctx, *certificate); err != nil { - a.log.Error("error updating certificate status in storage: %w", err) + a.log.Errorf("error updating certificate status in storage: %w", err) continue } } diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go index 34b93c09..c9db9d3f 100644 --- a/aggsender/aggsender_test.go +++ b/aggsender/aggsender_test.go @@ -6,9 +6,13 @@ import ( "fmt" "math/big" "testing" + "time" "github.com/0xPolygon/cdk/agglayer" + "github.com/0xPolygon/cdk/aggsender/db" + aggsendertypes "github.com/0xPolygon/cdk/aggsender/types" "github.com/0xPolygon/cdk/bridgesync" + "github.com/0xPolygon/cdk/config/types" "github.com/0xPolygon/cdk/l1infotreesync" treeTypes "github.com/0xPolygon/cdk/tree/types" "github.com/ethereum/go-ethereum/common" @@ -815,3 +819,124 @@ func TestShouldSendCertificate(t *testing.T) { }) } } + +func TestCheckIfCertificatesAreSettled(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + pendingCertificates []*aggsendertypes.CertificateInfo + certificateHeaders map[common.Hash]*agglayer.CertificateHeader + getFromDBError error + clientError error + updateDBError error + expectedLogMessages []string + }{ + { + name: "All certificates settled - update successful", + pendingCertificates: []*aggsendertypes.CertificateInfo{ + {CertificateID: common.HexToHash("0x1"), Height: 1}, + {CertificateID: common.HexToHash("0x2"), Height: 2}, + }, + certificateHeaders: map[common.Hash]*agglayer.CertificateHeader{ + common.HexToHash("0x1"): {Status: agglayer.Settled}, + common.HexToHash("0x2"): {Status: agglayer.Settled}, + }, + }, + { + name: "Some certificates in error - update successful", + pendingCertificates: []*aggsendertypes.CertificateInfo{ + {CertificateID: common.HexToHash("0x1"), Height: 1}, + {CertificateID: common.HexToHash("0x2"), Height: 2}, + }, + certificateHeaders: map[common.Hash]*agglayer.CertificateHeader{ + common.HexToHash("0x1"): {Status: agglayer.InError}, + common.HexToHash("0x2"): {Status: agglayer.Settled}, + }, + }, + { + name: "Error getting pending certificates", + getFromDBError: fmt.Errorf("storage error"), + expectedLogMessages: []string{ + "error getting pending certificates: %w", + }, + }, + { + name: "Error getting certificate header", + pendingCertificates: []*aggsendertypes.CertificateInfo{ + {CertificateID: common.HexToHash("0x1"), Height: 1}, + }, + certificateHeaders: map[common.Hash]*agglayer.CertificateHeader{ + common.HexToHash("0x1"): {Status: agglayer.InError}, + }, + clientError: fmt.Errorf("client error"), + expectedLogMessages: []string{ + "error getting header of certificate %s with height: %d from agglayer: %w", + }, + }, + { + name: "Error updating certificate status", + pendingCertificates: []*aggsendertypes.CertificateInfo{ + {CertificateID: common.HexToHash("0x1"), Height: 1}, + }, + certificateHeaders: map[common.Hash]*agglayer.CertificateHeader{ + common.HexToHash("0x1"): {Status: agglayer.Settled}, + }, + updateDBError: fmt.Errorf("update error"), + expectedLogMessages: []string{ + "error updating certificate status in storage: %w", + }, + }, + } + + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + mockStorage := db.NewAggSenderStorageMock(t) + mockAggLayerClient := agglayer.NewAgglayerClientMock(t) + mockLogger := NewLoggerMock(t) + + mockStorage.On("GetCertificatesByStatus", mock.Anything, []agglayer.CertificateStatus{agglayer.Pending}).Return(tt.pendingCertificates, tt.getFromDBError) + for certID, header := range tt.certificateHeaders { + mockAggLayerClient.On("GetCertificateHeader", certID).Return(header, tt.clientError) + } + if tt.updateDBError != nil { + mockStorage.On("UpdateCertificateStatus", mock.Anything, mock.Anything).Return(tt.updateDBError) + } else if tt.clientError == nil && tt.getFromDBError == nil { + mockStorage.On("UpdateCertificateStatus", mock.Anything, mock.Anything).Return(nil) + } + + if tt.clientError != nil { + for _, msg := range tt.expectedLogMessages { + mockLogger.On("Errorf", msg, mock.Anything, mock.Anything, mock.Anything).Return() + } + } else { + for _, msg := range tt.expectedLogMessages { + mockLogger.On("Errorf", msg, mock.Anything).Return() + } + } + + aggSender := &AggSender{ + log: mockLogger, + storage: mockStorage, + aggLayerClient: mockAggLayerClient, + cfg: Config{CertificateSendInterval: types.Duration{Duration: time.Second}}, + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + go aggSender.checkIfCertificatesAreSettled(ctx) + + time.Sleep(2 * time.Second) + cancel() + + mockLogger.AssertExpectations(t) + mockAggLayerClient.AssertExpectations(t) + mockStorage.AssertExpectations(t) + }) + } +} diff --git a/aggsender/db/aggsender_db_storage_test.go b/aggsender/db/aggsender_db_storage_test.go index 168f079e..cfb7af7c 100644 --- a/aggsender/db/aggsender_db_storage_test.go +++ b/aggsender/db/aggsender_db_storage_test.go @@ -177,4 +177,28 @@ func Test_Storage(t *testing.T) { require.NoError(t, storage.clean()) }) + + t.Run("UpdateCertificateStatus", func(t *testing.T) { + // Insert a certificate + certificate := types.CertificateInfo{ + Height: 13, + CertificateID: common.HexToHash("0xD"), + NewLocalExitRoot: common.HexToHash("0xE"), + FromBlock: 13, + ToBlock: 14, + Status: agglayer.Pending, + } + require.NoError(t, storage.SaveLastSentCertificate(ctx, certificate)) + + // Update the status of the certificate + certificate.Status = agglayer.Settled + require.NoError(t, storage.UpdateCertificateStatus(ctx, certificate)) + + // Fetch the certificate and verify the status has been updated + certificateFromDB, err := storage.GetCertificateByHeight(ctx, certificate.Height) + require.NoError(t, err) + require.Equal(t, certificate.Status, certificateFromDB.Status) + + require.NoError(t, storage.clean()) + }) } diff --git a/aggsender/db/mock_aggsender_storage.go b/aggsender/db/mock_aggsender_storage.go new file mode 100644 index 00000000..02342a80 --- /dev/null +++ b/aggsender/db/mock_aggsender_storage.go @@ -0,0 +1,173 @@ +// Code generated by mockery v2.45.0. DO NOT EDIT. + +package db + +import ( + agglayer "github.com/0xPolygon/cdk/agglayer" + common "github.com/ethereum/go-ethereum/common" + + context "context" + + mock "github.com/stretchr/testify/mock" + + types "github.com/0xPolygon/cdk/aggsender/types" +) + +// AggSenderStorageMock is an autogenerated mock type for the AggSenderStorage type +type AggSenderStorageMock struct { + mock.Mock +} + +// DeleteCertificate provides a mock function with given fields: ctx, certificateID +func (_m *AggSenderStorageMock) DeleteCertificate(ctx context.Context, certificateID common.Hash) error { + ret := _m.Called(ctx, certificateID) + + if len(ret) == 0 { + panic("no return value specified for DeleteCertificate") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, common.Hash) error); ok { + r0 = rf(ctx, certificateID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// GetCertificateByHeight provides a mock function with given fields: ctx, height +func (_m *AggSenderStorageMock) GetCertificateByHeight(ctx context.Context, height uint64) (types.CertificateInfo, error) { + ret := _m.Called(ctx, height) + + if len(ret) == 0 { + panic("no return value specified for GetCertificateByHeight") + } + + var r0 types.CertificateInfo + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64) (types.CertificateInfo, error)); ok { + return rf(ctx, height) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64) types.CertificateInfo); ok { + r0 = rf(ctx, height) + } else { + r0 = ret.Get(0).(types.CertificateInfo) + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64) error); ok { + r1 = rf(ctx, height) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetCertificatesByStatus provides a mock function with given fields: ctx, status +func (_m *AggSenderStorageMock) GetCertificatesByStatus(ctx context.Context, status []agglayer.CertificateStatus) ([]*types.CertificateInfo, error) { + ret := _m.Called(ctx, status) + + if len(ret) == 0 { + panic("no return value specified for GetCertificatesByStatus") + } + + var r0 []*types.CertificateInfo + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []agglayer.CertificateStatus) ([]*types.CertificateInfo, error)); ok { + return rf(ctx, status) + } + if rf, ok := ret.Get(0).(func(context.Context, []agglayer.CertificateStatus) []*types.CertificateInfo); ok { + r0 = rf(ctx, status) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*types.CertificateInfo) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []agglayer.CertificateStatus) error); ok { + r1 = rf(ctx, status) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetLastSentCertificate provides a mock function with given fields: ctx +func (_m *AggSenderStorageMock) GetLastSentCertificate(ctx context.Context) (types.CertificateInfo, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetLastSentCertificate") + } + + var r0 types.CertificateInfo + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (types.CertificateInfo, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) types.CertificateInfo); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(types.CertificateInfo) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SaveLastSentCertificate provides a mock function with given fields: ctx, certificate +func (_m *AggSenderStorageMock) SaveLastSentCertificate(ctx context.Context, certificate types.CertificateInfo) error { + ret := _m.Called(ctx, certificate) + + if len(ret) == 0 { + panic("no return value specified for SaveLastSentCertificate") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, types.CertificateInfo) error); ok { + r0 = rf(ctx, certificate) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// UpdateCertificateStatus provides a mock function with given fields: ctx, certificate +func (_m *AggSenderStorageMock) UpdateCertificateStatus(ctx context.Context, certificate types.CertificateInfo) error { + ret := _m.Called(ctx, certificate) + + if len(ret) == 0 { + panic("no return value specified for UpdateCertificateStatus") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, types.CertificateInfo) error); ok { + r0 = rf(ctx, certificate) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NewAggSenderStorageMock creates a new instance of AggSenderStorageMock. 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 NewAggSenderStorageMock(t interface { + mock.TestingT + Cleanup(func()) +}) *AggSenderStorageMock { + mock := &AggSenderStorageMock{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/aggsender/mock_logger.go b/aggsender/mock_logger.go new file mode 100644 index 00000000..6f00145d --- /dev/null +++ b/aggsender/mock_logger.go @@ -0,0 +1,54 @@ +// Code generated by mockery v2.45.0. DO NOT EDIT. + +package aggsender + +import mock "github.com/stretchr/testify/mock" + +// LoggerMock is an autogenerated mock type for the Logger type +type LoggerMock struct { + mock.Mock +} + +// Error provides a mock function with given fields: args +func (_m *LoggerMock) Error(args ...interface{}) { + var _ca []interface{} + _ca = append(_ca, args...) + _m.Called(_ca...) +} + +// Errorf provides a mock function with given fields: format, args +func (_m *LoggerMock) Errorf(format string, args ...interface{}) { + var _ca []interface{} + _ca = append(_ca, format) + _ca = append(_ca, args...) + _m.Called(_ca...) +} + +// Info provides a mock function with given fields: args +func (_m *LoggerMock) Info(args ...interface{}) { + var _ca []interface{} + _ca = append(_ca, args...) + _m.Called(_ca...) +} + +// Infof provides a mock function with given fields: format, args +func (_m *LoggerMock) Infof(format string, args ...interface{}) { + var _ca []interface{} + _ca = append(_ca, format) + _ca = append(_ca, args...) + _m.Called(_ca...) +} + +// NewLoggerMock creates a new instance of LoggerMock. 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 NewLoggerMock(t interface { + mock.TestingT + Cleanup(func()) +}) *LoggerMock { + mock := &LoggerMock{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/test/Makefile b/test/Makefile index fe37a79a..fde9b5b4 100644 --- a/test/Makefile +++ b/test/Makefile @@ -2,7 +2,7 @@ generate-mocks: generate-mocks-bridgesync generate-mocks-reorgdetector generate-mocks-sequencesender \ generate-mocks-da generate-mocks-l1infotreesync generate-mocks-helpers \ generate-mocks-sync generate-mocks-l1infotreesync generate-mocks-aggregator \ - generate-mocks-aggsender + generate-mocks-aggsender generate-mocks-agglayer .PHONY: generate-mocks-bridgesync generate-mocks-bridgesync: ## Generates mocks for bridgesync, using mockery tool @@ -62,6 +62,12 @@ generate-mocks-aggregator: ## Generates mocks for aggregator, using mockery tool generate-mocks-aggsender: ## Generates mocks for aggsender, using mockery tool export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=L1InfoTreeSyncer --dir=../aggsender --output=../aggsender --outpkg=aggsender --inpackage --structname=L1InfoTreeSyncerMock --filename=mock_l1infotree_syncer.go export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=L2BridgeSyncer --dir=../aggsender --output=../aggsender --outpkg=aggsender --inpackage --structname=L2BridgeSyncerMock --filename=mock_l2bridge_syncer.go + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=Logger --dir=../aggsender --output=../aggsender --outpkg=aggsender --inpackage --structname=LoggerMock --filename=mock_logger.go + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=AggSenderStorage --dir=../aggsender/db --output=../aggsender/db --outpkg=db --inpackage --structname=AggSenderStorageMock --filename=mock_aggsender_storage.go + +.PHONY: generate-mocks-agglayer +generate-mocks-agglayer: ## Generates mocks for agglayer, using mockery tool + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=AgglayerClientInterface --dir=../agglayer --output=../agglayer --outpkg=agglayer --inpackage --structname=AgglayerClientMock --filename=mock_agglayer_client.go .PHONY: test-e2e-fork9-validium test-e2e-fork9-validium: stop From 6a7fcdc3481c5de59d6d637344054a2edeede1d0 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Wed, 9 Oct 2024 15:13:03 +0200 Subject: [PATCH 20/84] fix: update sonar properties to exclude mocks --- sonar-project.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sonar-project.properties b/sonar-project.properties index 815d53a8..1ffe75f5 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -7,11 +7,11 @@ sonar.projectName=cdk sonar.organization=0xpolygon sonar.sources=. -sonar.exclusions=**/test/**,**/vendor/**,**/mocks/**,**/build/**,**/target/**,**/proto/include/**,**/*.pb.go,**/docs/**,**/*.sql,**/mocks_*/*, scripts/** +sonar.exclusions=**/test/**,**/vendor/**,**/mocks/**,**/build/**,**/target/**,**/proto/include/**,**/*.pb.go,**/docs/**,**/*.sql,**/mocks_*/*,scripts/**,**/mock_*.go sonar.tests=. sonar.test.inclusions=**/*_test.go -sonar.test.exclusions=**/vendor/**,**/docs/**,**/mocks/**,**/*.pb.go,**/*.yml,**/*.yaml,**/*.json,**/*.xml,**/*.toml,**/mocks_*/* +sonar.test.exclusions=**/vendor/**,**/docs/**,**/mocks/**,**/*.pb.go,**/*.yml,**/*.yaml,**/*.json,**/*.xml,**/*.toml,**/mocks_*/*,**/mock_*.go sonar.issue.enforceSemantic=true # ===================================================== From 0b331be5571fd6f0afca5096d2d7147dd57e4b78 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Thu, 10 Oct 2024 10:53:49 +0200 Subject: [PATCH 21/84] feat: even more unit tests --- aggsender/aggsender.go | 36 ++- aggsender/aggsender_test.go | 518 +++++++++++++++++++++++++++++++++++ aggsender/mock_eth_client.go | 89 ++++++ test/Makefile | 3 +- 4 files changed, 631 insertions(+), 15 deletions(-) create mode 100644 aggsender/mock_eth_client.go diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index 26345baa..5453cefc 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -5,6 +5,7 @@ import ( "crypto/ecdsa" "errors" "fmt" + "math/big" "time" "github.com/0xPolygon/cdk/agglayer" @@ -17,6 +18,7 @@ import ( "github.com/0xPolygon/cdk/log" treeTypes "github.com/0xPolygon/cdk/tree/types" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" ) @@ -39,6 +41,12 @@ type L2BridgeSyncer interface { BlockFinality() etherman.BlockNumberFinality } +// EthClient is an interface defining functions that an EthClient should implement +type EthClient interface { + BlockNumber(ctx context.Context) (uint64, error) + HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) +} + // Logger is an interface that defines the methods to log messages type Logger interface { Info(args ...interface{}) @@ -52,9 +60,9 @@ type AggSender struct { log Logger l2Syncer L2BridgeSyncer - l2Client bridgesync.EthClienter + l2Client EthClient l1infoTreeSyncer L1InfoTreeSyncer - l1Client bridgesync.EthClienter + l1Client EthClient storage db.AggSenderStorage aggLayerClient agglayer.AgglayerClientInterface @@ -70,10 +78,10 @@ func New( logger *log.Logger, cfg Config, aggLayerClient agglayer.AgglayerClientInterface, - l1Client bridgesync.EthClienter, + l1Client EthClient, l1InfoTreeSyncer *l1infotreesync.L1InfoTreeSync, l2Syncer *bridgesync.BridgeSync, - l2Client bridgesync.EthClienter) (*AggSender, error) { + l2Client EthClient) (*AggSender, error) { storage, err := db.NewAggSenderSQLStorage(logger, cfg.DBPath) if err != nil { return nil, err @@ -110,16 +118,6 @@ func (a *AggSender) sendCertificates(ctx context.Context) { for { select { case <-ticker.C: - block, err := a.l1Client.BlockNumber(ctx) - if err != nil { - a.log.Errorf("error getting l1 block number: %w", err) - continue - } - - if !a.shouldSendCertificate(block) { - continue - } - if err := a.sendCertificate(ctx); err != nil { log.Error(err) } @@ -134,6 +132,16 @@ func (a *AggSender) sendCertificates(ctx context.Context) { func (a *AggSender) sendCertificate(ctx context.Context) error { a.log.Infof("trying to send a new certificate...") + block, err := a.l1Client.BlockNumber(ctx) + if err != nil { + return fmt.Errorf("error getting l1 block number: %w", err) + } + + if !a.shouldSendCertificate(block) { + a.log.Infof("block %d on L1 not near epoch ending, so we don't send a certificate", block) + return nil + } + lastSentCertificate, err := a.storage.GetLastSentCertificate(ctx) if err != nil { return fmt.Errorf("error getting last sent certificate: %w", err) diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go index c9db9d3f..31f1b531 100644 --- a/aggsender/aggsender_test.go +++ b/aggsender/aggsender_test.go @@ -3,6 +3,7 @@ package aggsender import ( "context" "crypto/ecdsa" + "errors" "fmt" "math/big" "testing" @@ -13,9 +14,12 @@ import ( aggsendertypes "github.com/0xPolygon/cdk/aggsender/types" "github.com/0xPolygon/cdk/bridgesync" "github.com/0xPolygon/cdk/config/types" + "github.com/0xPolygon/cdk/etherman" "github.com/0xPolygon/cdk/l1infotreesync" + "github.com/0xPolygon/cdk/log" treeTypes "github.com/0xPolygon/cdk/tree/types" "github.com/ethereum/go-ethereum/common" + gethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -940,3 +944,517 @@ func TestCheckIfCertificatesAreSettled(t *testing.T) { }) } } + +func TestSendCertificate(t *testing.T) { + t.Parallel() + + privateKey, err := crypto.GenerateKey() + require.NoError(t, err) + + type testCfg struct { + name string + sequencerKey *ecdsa.PrivateKey + l1BlockNumber []interface{} + getLastSentCertificate []interface{} + l2BlockFinality []interface{} + l2HeaderByNumber []interface{} + getCertificateHeader []interface{} + deleteCertificate []interface{} + getCertificateByHeight []interface{} + getBlockByLER []interface{} + getBridges []interface{} + getClaims []interface{} + getInfoByGlobalExitRoot []interface{} + getExitRootByIndex []interface{} + originNetwork []interface{} + sendCertificate []interface{} + saveLastSentCertificate []interface{} + expectedError string + } + + setupTest := func(cfg testCfg) (*AggSender, *db.AggSenderStorageMock, *EthClientMock, *L2BridgeSyncerMock, + *EthClientMock, *agglayer.AgglayerClientMock, *L1InfoTreeSyncerMock) { + var ( + aggsender = &AggSender{ + log: log.WithFields("aggsender", 1), + cfg: Config{EpochSize: 10}, + sequencerKey: cfg.sequencerKey, + } + mockL1Client *EthClientMock + mockStorage *db.AggSenderStorageMock + mockL2Syncer *L2BridgeSyncerMock + mockL2Client *EthClientMock + mockAggLayerClient *agglayer.AgglayerClientMock + mockL1InfoTreeSyncer *L1InfoTreeSyncerMock + ) + + if cfg.l1BlockNumber != nil { + mockL1Client = NewEthClientMock(t) + mockL1Client.On("BlockNumber", mock.Anything).Return(cfg.l1BlockNumber...).Once() + + aggsender.l1Client = mockL1Client + } + + if cfg.getLastSentCertificate != nil || cfg.deleteCertificate != nil || + cfg.getCertificateByHeight != nil || cfg.saveLastSentCertificate != nil { + mockStorage = db.NewAggSenderStorageMock(t) + mockStorage.On("GetLastSentCertificate", mock.Anything).Return(cfg.getLastSentCertificate...).Once() + + if cfg.deleteCertificate != nil { + mockStorage.On("DeleteCertificate", mock.Anything, mock.Anything).Return(cfg.deleteCertificate...).Once() + } + + if cfg.getCertificateByHeight != nil { + mockStorage.On("GetCertificateByHeight", mock.Anything, mock.Anything).Return(cfg.getCertificateByHeight...).Once() + } + + if cfg.saveLastSentCertificate != nil { + mockStorage.On("SaveLastSentCertificate", mock.Anything, mock.Anything).Return(cfg.saveLastSentCertificate...).Once() + } + + aggsender.storage = mockStorage + } + + if cfg.l2BlockFinality != nil || cfg.getBlockByLER != nil || cfg.originNetwork != nil || + cfg.getBridges != nil || cfg.getClaims != nil || cfg.getInfoByGlobalExitRoot != nil { + mockL2Syncer = NewL2BridgeSyncerMock(t) + mockL2Syncer.On("BlockFinality").Return(cfg.l2BlockFinality...).Once() + + if cfg.getBlockByLER != nil { + mockL2Syncer.On("GetBlockByLER", mock.Anything, mock.Anything).Return(cfg.getBlockByLER...).Once() + } + + if cfg.getBridges != nil { + mockL2Syncer.On("GetBridges", mock.Anything, mock.Anything, mock.Anything).Return(cfg.getBridges...).Once() + } + + if cfg.getClaims != nil { + mockL2Syncer.On("GetClaims", mock.Anything, mock.Anything, mock.Anything).Return(cfg.getClaims...).Once() + } + + if cfg.getExitRootByIndex != nil { + mockL2Syncer.On("GetExitRootByIndex", mock.Anything, mock.Anything).Return(cfg.getExitRootByIndex...).Once() + } + + if cfg.originNetwork != nil { + mockL2Syncer.On("OriginNetwork").Return(cfg.originNetwork...).Once() + } + + aggsender.l2Syncer = mockL2Syncer + } + + if cfg.l2HeaderByNumber != nil { + mockL2Client = NewEthClientMock(t) + mockL2Client.On("HeaderByNumber", mock.Anything, mock.Anything).Return(cfg.l2HeaderByNumber...).Once() + + aggsender.l2Client = mockL2Client + } + + if cfg.getCertificateHeader != nil || cfg.sendCertificate != nil { + mockAggLayerClient = agglayer.NewAgglayerClientMock(t) + mockAggLayerClient.On("GetCertificateHeader", mock.Anything).Return(cfg.getCertificateHeader...).Once() + + if cfg.sendCertificate != nil { + mockAggLayerClient.On("SendCertificate", mock.Anything).Return(cfg.sendCertificate...).Once() + } + + aggsender.aggLayerClient = mockAggLayerClient + } + + if cfg.getInfoByGlobalExitRoot != nil { + mockL1InfoTreeSyncer = NewL1InfoTreeSyncerMock(t) + mockL1InfoTreeSyncer.On("GetInfoByGlobalExitRoot", mock.Anything).Return(cfg.getInfoByGlobalExitRoot...).Once() + + aggsender.l1infoTreeSyncer = mockL1InfoTreeSyncer + } + + return aggsender, mockStorage, mockL1Client, mockL2Syncer, mockL2Client, mockAggLayerClient, mockL1InfoTreeSyncer + } + + tests := []testCfg{ + { + name: "error getting L1 block", + l1BlockNumber: []interface{}{uint64(0), errors.New("error getting block")}, + expectedError: "error getting block", + }, + { + name: "should not send certificate", + l1BlockNumber: []interface{}{uint64(1), nil}, + }, + { + name: "error getting last sent certificate", + l1BlockNumber: []interface{}{uint64(9), nil}, + getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{}, errors.New("error getting last sent certificate")}, + expectedError: "error getting last sent certificate", + }, + { + name: "error getting block finality", + l1BlockNumber: []interface{}{uint64(9), nil}, + getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ + Height: 1, + CertificateID: common.HexToHash("0x1"), + NewLocalExitRoot: common.HexToHash("0x123"), + FromBlock: 1, + ToBlock: 10, + }, nil}, + l2BlockFinality: []interface{}{etherman.BlockNumberFinality("invalid")}, + expectedError: "error getting block finality", + }, + { + name: "error getting last l2 block", + l1BlockNumber: []interface{}{uint64(9), nil}, + getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ + Height: 1, + CertificateID: common.HexToHash("0x1"), + NewLocalExitRoot: common.HexToHash("0x123"), + FromBlock: 1, + ToBlock: 10, + }, nil}, + l2BlockFinality: []interface{}{etherman.FinalizedBlock}, + l2HeaderByNumber: []interface{}{nil, errors.New("error getting block")}, + expectedError: "error getting block from l2", + }, + { + name: "error getting last certificate header", + l1BlockNumber: []interface{}{uint64(19), nil}, + getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ + Height: 1, + CertificateID: common.HexToHash("0x1"), + NewLocalExitRoot: common.HexToHash("0x123"), + FromBlock: 1, + ToBlock: 10, + }, nil}, + l2BlockFinality: []interface{}{etherman.FinalizedBlock}, + l2HeaderByNumber: []interface{}{&gethTypes.Header{Number: big.NewInt(20)}, nil}, + getCertificateHeader: []interface{}{nil, errors.New("error getting certificate header")}, + expectedError: "error getting certificate", + }, + { + name: "error deleting in error certificate", + l1BlockNumber: []interface{}{uint64(19), nil}, + getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ + Height: 1, + CertificateID: common.HexToHash("0x1"), + NewLocalExitRoot: common.HexToHash("0x123"), + FromBlock: 1, + ToBlock: 10, + }, nil}, + l2BlockFinality: []interface{}{etherman.FinalizedBlock}, + l2HeaderByNumber: []interface{}{&gethTypes.Header{Number: big.NewInt(20)}, nil}, + getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ + Status: agglayer.InError, + }, nil}, + deleteCertificate: []interface{}{errors.New("error deleting certificate")}, + expectedError: "error deleting certificate", + }, + { + name: "error getting certificate by height", + l1BlockNumber: []interface{}{uint64(29), nil}, + getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ + Height: 11, + CertificateID: common.HexToHash("0x1"), + NewLocalExitRoot: common.HexToHash("0x123"), + FromBlock: 19, + ToBlock: 28, + }, nil}, + l2BlockFinality: []interface{}{etherman.FinalizedBlock}, + l2HeaderByNumber: []interface{}{&gethTypes.Header{Number: big.NewInt(29)}, nil}, + getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ + Status: agglayer.InError, + }, nil}, + deleteCertificate: []interface{}{nil}, + getCertificateByHeight: []interface{}{aggsendertypes.CertificateInfo{}, errors.New("error getting certificate by height")}, + expectedError: "error getting certificate by height", + }, + { + name: "error getting block by LER", + l1BlockNumber: []interface{}{uint64(39), nil}, + getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ + Height: 21, + CertificateID: common.HexToHash("0x11"), + NewLocalExitRoot: common.HexToHash("0x1223"), + FromBlock: 29, + ToBlock: 38, + }, nil}, + l2BlockFinality: []interface{}{etherman.PendingBlock}, + l2HeaderByNumber: []interface{}{&gethTypes.Header{Number: big.NewInt(39)}, nil}, + getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ + Status: agglayer.InError, + }, nil}, + deleteCertificate: []interface{}{nil}, + getCertificateByHeight: []interface{}{aggsendertypes.CertificateInfo{ + NewLocalExitRoot: common.HexToHash("0x1222"), + Height: 20, + }, nil}, + getBlockByLER: []interface{}{uint64(0), errors.New("error getting block by LER")}, + expectedError: "error getting block by LER", + }, + { + name: "no new blocks to send certificate", + l1BlockNumber: []interface{}{uint64(39), nil}, + getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ + Height: 41, + CertificateID: common.HexToHash("0x111"), + NewLocalExitRoot: common.HexToHash("0x13223"), + FromBlock: 31, + ToBlock: 40, + }, nil}, + l2BlockFinality: []interface{}{etherman.FinalizedBlock}, + l2HeaderByNumber: []interface{}{&gethTypes.Header{Number: big.NewInt(41)}, nil}, + getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ + Status: agglayer.Settled, + }, nil}, + getBlockByLER: []interface{}{uint64(41), nil}, + }, + { + name: "get bridges error", + l1BlockNumber: []interface{}{uint64(99), nil}, + getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ + Height: 50, + CertificateID: common.HexToHash("0x1111"), + NewLocalExitRoot: common.HexToHash("0x132233"), + FromBlock: 40, + ToBlock: 49, + }, nil}, + l2BlockFinality: []interface{}{etherman.FinalizedBlock}, + l2HeaderByNumber: []interface{}{&gethTypes.Header{Number: big.NewInt(59)}, nil}, + getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ + Status: agglayer.Settled, + CertificateID: common.HexToHash("0x1110"), + Height: 49, + }, nil}, + getBlockByLER: []interface{}{uint64(41), nil}, + getBridges: []interface{}{nil, errors.New("error getting bridges")}, + expectedError: "error getting bridges", + }, + { + name: "no bridges", + l1BlockNumber: []interface{}{uint64(199), nil}, + getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ + Height: 60, + CertificateID: common.HexToHash("0x11111"), + NewLocalExitRoot: common.HexToHash("0x1322233"), + FromBlock: 50, + ToBlock: 59, + }, nil}, + l2BlockFinality: []interface{}{etherman.PendingBlock}, + l2HeaderByNumber: []interface{}{&gethTypes.Header{Number: big.NewInt(69)}, nil}, + getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ + Status: agglayer.Settled, + CertificateID: common.HexToHash("0x1110"), + Height: 59, + }, nil}, + getBlockByLER: []interface{}{uint64(51), nil}, + getBridges: []interface{}{[]bridgesync.Bridge{}, nil}, + }, + { + name: "get claims error", + l1BlockNumber: []interface{}{uint64(159), nil}, + getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ + Height: 70, + CertificateID: common.HexToHash("0x121111"), + NewLocalExitRoot: common.HexToHash("0x13122233"), + FromBlock: 60, + ToBlock: 69, + }, nil}, + l2BlockFinality: []interface{}{etherman.FinalizedBlock}, + l2HeaderByNumber: []interface{}{&gethTypes.Header{Number: big.NewInt(79)}, nil}, + getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ + Status: agglayer.Settled, + CertificateID: common.HexToHash("0x1110"), + Height: 69, + }, nil}, + getBlockByLER: []interface{}{uint64(61), nil}, + getBridges: []interface{}{[]bridgesync.Bridge{ + { + BlockNum: 61, + BlockPos: 0, + LeafType: agglayer.LeafTypeAsset.Uint8(), + OriginNetwork: 1, + }, + }, nil}, + getClaims: []interface{}{nil, errors.New("error getting claims")}, + expectedError: "error getting claims", + }, + { + name: "error building certificate", + l1BlockNumber: []interface{}{uint64(149), nil}, + getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ + Height: 80, + CertificateID: common.HexToHash("0x1321111"), + NewLocalExitRoot: common.HexToHash("0x131122233"), + FromBlock: 70, + ToBlock: 79, + }, nil}, + l2BlockFinality: []interface{}{etherman.PendingBlock}, + l2HeaderByNumber: []interface{}{&gethTypes.Header{Number: big.NewInt(89)}, nil}, + getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ + Status: agglayer.Settled, + CertificateID: common.HexToHash("0x1110"), + Height: 79, + }, nil}, + getBlockByLER: []interface{}{uint64(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{}{nil, errors.New("error getting info by global exit root")}, + expectedError: "error building certificate", + }, + { + name: "send certificate error", + l1BlockNumber: []interface{}{uint64(139), nil}, + getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ + Height: 90, + CertificateID: common.HexToHash("0x1121111"), + NewLocalExitRoot: common.HexToHash("0x111122211"), + FromBlock: 80, + ToBlock: 89, + }, nil}, + l2BlockFinality: []interface{}{etherman.FinalizedBlock}, + l2HeaderByNumber: []interface{}{&gethTypes.Header{Number: big.NewInt(99)}, nil}, + getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ + Status: agglayer.Settled, + CertificateID: common.HexToHash("0x1110"), + Height: 89, + }, nil}, + getBlockByLER: []interface{}{uint64(81), nil}, + getBridges: []interface{}{[]bridgesync.Bridge{ + { + BlockNum: 81, + BlockPos: 0, + LeafType: agglayer.LeafTypeAsset.Uint8(), + OriginNetwork: 1, + DepositCount: 1, + }, + }, nil}, + getClaims: []interface{}{[]bridgesync.Claim{}, nil}, + getExitRootByIndex: []interface{}{treeTypes.Root{}, nil}, + originNetwork: []interface{}{uint32(1), nil}, + sendCertificate: []interface{}{common.Hash{}, errors.New("error sending certificate")}, + sequencerKey: privateKey, + expectedError: "error sending certificate", + }, + { + name: "store last sent certificate error", + l1BlockNumber: []interface{}{uint64(129), nil}, + getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ + Height: 100, + CertificateID: common.HexToHash("0x11121111"), + NewLocalExitRoot: common.HexToHash("0x1211122211"), + FromBlock: 90, + ToBlock: 99, + }, nil}, + l2BlockFinality: []interface{}{etherman.FinalizedBlock}, + l2HeaderByNumber: []interface{}{&gethTypes.Header{Number: big.NewInt(109)}, nil}, + getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ + Status: agglayer.Settled, + CertificateID: common.HexToHash("0x11110"), + Height: 99, + }, nil}, + getBlockByLER: []interface{}{uint64(91), nil}, + getBridges: []interface{}{[]bridgesync.Bridge{ + { + BlockNum: 91, + BlockPos: 0, + LeafType: agglayer.LeafTypeAsset.Uint8(), + OriginNetwork: 1, + DepositCount: 1, + }, + }, nil}, + getClaims: []interface{}{[]bridgesync.Claim{}, nil}, + getExitRootByIndex: []interface{}{treeTypes.Root{}, nil}, + originNetwork: []interface{}{uint32(1), nil}, + sendCertificate: []interface{}{common.Hash{}, nil}, + saveLastSentCertificate: []interface{}{errors.New("error saving last sent certificate in db")}, + sequencerKey: privateKey, + expectedError: "error saving last sent certificate in db", + }, + { + name: "successful sending of certificate", + l1BlockNumber: []interface{}{uint64(179), nil}, + getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ + Height: 110, + CertificateID: common.HexToHash("0x12121111"), + NewLocalExitRoot: common.HexToHash("0x1221122211"), + FromBlock: 100, + ToBlock: 109, + }, nil}, + l2BlockFinality: []interface{}{etherman.FinalizedBlock}, + l2HeaderByNumber: []interface{}{&gethTypes.Header{Number: big.NewInt(119)}, nil}, + getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ + Status: agglayer.Settled, + CertificateID: common.HexToHash("0x11110"), + Height: 109, + }, nil}, + getBlockByLER: []interface{}{uint64(91), nil}, + getBridges: []interface{}{[]bridgesync.Bridge{ + { + BlockNum: 101, + BlockPos: 0, + LeafType: agglayer.LeafTypeAsset.Uint8(), + OriginNetwork: 1, + DepositCount: 1, + }, + }, nil}, + getClaims: []interface{}{[]bridgesync.Claim{}, nil}, + getExitRootByIndex: []interface{}{treeTypes.Root{}, nil}, + originNetwork: []interface{}{uint32(1), nil}, + sendCertificate: []interface{}{common.Hash{}, nil}, + saveLastSentCertificate: []interface{}{nil}, + sequencerKey: privateKey, + }, + } + + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + aggsender, mockStorage, mockL1Client, mockL2Syncer, mockL2Client, + mockAggLayerClient, mockL1InfoTreeSyncer := setupTest(tt) + + err := aggsender.sendCertificate(context.Background()) + + if tt.expectedError != "" { + require.ErrorContains(t, err, tt.expectedError) + } else { + require.NoError(t, err) + } + + if mockStorage != nil { + mockStorage.AssertExpectations(t) + } + + if mockL1Client != nil { + mockL1Client.AssertExpectations(t) + } + + if mockL2Syncer != nil { + mockL2Syncer.AssertExpectations(t) + } + + if mockL2Client != nil { + mockL2Client.AssertExpectations(t) + } + + if mockAggLayerClient != nil { + mockAggLayerClient.AssertExpectations(t) + } + + if mockL1InfoTreeSyncer != nil { + mockL1InfoTreeSyncer.AssertExpectations(t) + } + }) + } +} diff --git a/aggsender/mock_eth_client.go b/aggsender/mock_eth_client.go new file mode 100644 index 00000000..6742c054 --- /dev/null +++ b/aggsender/mock_eth_client.go @@ -0,0 +1,89 @@ +// Code generated by mockery v2.45.0. DO NOT EDIT. + +package aggsender + +import ( + context "context" + big "math/big" + + mock "github.com/stretchr/testify/mock" + + types "github.com/ethereum/go-ethereum/core/types" +) + +// EthClientMock is an autogenerated mock type for the EthClient type +type EthClientMock struct { + mock.Mock +} + +// BlockNumber provides a mock function with given fields: ctx +func (_m *EthClientMock) BlockNumber(ctx context.Context) (uint64, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for BlockNumber") + } + + var r0 uint64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (uint64, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) uint64); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(uint64) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// HeaderByNumber provides a mock function with given fields: ctx, number +func (_m *EthClientMock) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { + ret := _m.Called(ctx, number) + + if len(ret) == 0 { + panic("no return value specified for HeaderByNumber") + } + + var r0 *types.Header + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *big.Int) (*types.Header, error)); ok { + return rf(ctx, number) + } + if rf, ok := ret.Get(0).(func(context.Context, *big.Int) *types.Header); ok { + r0 = rf(ctx, number) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Header) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *big.Int) error); ok { + r1 = rf(ctx, number) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewEthClientMock creates a new instance of EthClientMock. 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 NewEthClientMock(t interface { + mock.TestingT + Cleanup(func()) +}) *EthClientMock { + mock := &EthClientMock{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/test/Makefile b/test/Makefile index fde9b5b4..f25f4f6e 100644 --- a/test/Makefile +++ b/test/Makefile @@ -62,8 +62,9 @@ generate-mocks-aggregator: ## Generates mocks for aggregator, using mockery tool generate-mocks-aggsender: ## Generates mocks for aggsender, using mockery tool export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=L1InfoTreeSyncer --dir=../aggsender --output=../aggsender --outpkg=aggsender --inpackage --structname=L1InfoTreeSyncerMock --filename=mock_l1infotree_syncer.go export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=L2BridgeSyncer --dir=../aggsender --output=../aggsender --outpkg=aggsender --inpackage --structname=L2BridgeSyncerMock --filename=mock_l2bridge_syncer.go - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=Logger --dir=../aggsender --output=../aggsender --outpkg=aggsender --inpackage --structname=LoggerMock --filename=mock_logger.go + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=Logger --dir=../aggsender --output=../aggsender --outpkg=aggsender --inpackage --structname=LoggerMock --filename=mock_logger.go export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=AggSenderStorage --dir=../aggsender/db --output=../aggsender/db --outpkg=db --inpackage --structname=AggSenderStorageMock --filename=mock_aggsender_storage.go + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=EthClient --dir=../aggsender --output=../aggsender --outpkg=aggsender --inpackage --structname=EthClientMock --filename=mock_eth_client.go .PHONY: generate-mocks-agglayer generate-mocks-agglayer: ## Generates mocks for agglayer, using mockery tool From 55072973e7b220045215fa2cf24e443a7b787d74 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Thu, 10 Oct 2024 14:59:42 +0200 Subject: [PATCH 22/84] fix: rebase --- aggregator/aggregator_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/aggregator/aggregator_test.go b/aggregator/aggregator_test.go index 461ed8df..25b66d34 100644 --- a/aggregator/aggregator_test.go +++ b/aggregator/aggregator_test.go @@ -56,7 +56,7 @@ type mox struct { ethTxManager *mocks.EthTxManagerClientMock etherman *mocks.EthermanMock proverMock *mocks.ProverInterfaceMock - aggLayerClientMock *mocks.AgglayerClientInterfaceMock + aggLayerClientMock *agglayer.AgglayerClientMock synchronizerMock *mocks.SynchronizerInterfaceMock } @@ -458,7 +458,7 @@ func Test_sendFinalProofSuccess(t *testing.T) { stateMock := mocks.NewStateInterfaceMock(t) ethTxManager := mocks.NewEthTxManagerClientMock(t) etherman := mocks.NewEthermanMock(t) - aggLayerClient := agglayer.NewAggLayerClientMock(t) + aggLayerClient := agglayer.NewAgglayerClientMock(t) curve := elliptic.P256() privateKey, err := ecdsa.GenerateKey(curve, rand.Reader) @@ -664,7 +664,7 @@ func Test_sendFinalProofError(t *testing.T) { stateMock := mocks.NewStateInterfaceMock(t) ethTxManager := mocks.NewEthTxManagerClientMock(t) etherman := mocks.NewEthermanMock(t) - aggLayerClient := agglayer.NewAggLayerClientMock(t) + aggLayerClient := agglayer.NewAgglayerClientMock(t) curve := elliptic.P256() privateKey, err := ecdsa.GenerateKey(curve, rand.Reader) From 9c9ba418da10a4d673b43bd0886cdfe34bc446bf Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Wed, 16 Oct 2024 13:17:19 +0200 Subject: [PATCH 23/84] fix: small fixes --- aggsender/aggsender.go | 11 +++++---- aggsender/aggsender_test.go | 38 ++++++++++++++++---------------- l1infotreesync/processor_test.go | 4 ---- 3 files changed, 26 insertions(+), 27 deletions(-) diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index 5453cefc..6e12b163 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -22,6 +22,9 @@ import ( "github.com/ethereum/go-ethereum/crypto" ) +// defines number of blocks before epoch ending to send a certificate +const numOfBlocksBeforeEpochEnding = 2 + var errNoBridgesAndClaims = errors.New("no bridges and claims to build certificate") // L1InfoTreeSyncer is an interface defining functions that an L1InfoTreeSyncer should implement @@ -180,7 +183,7 @@ func (a *AggSender) sendCertificate(ctx context.Context) error { return fmt.Errorf("error deleting certificate %s: %w", lastSentCertificate.CertificateID, err) } - lastValidCertificate, err := a.storage.GetCertificateByHeight(ctx, lastSentCertificateHeader.Height) + lastValidCertificate, err := a.storage.GetCertificateByHeight(ctx, lastSentCertificateHeader.Height-1) if err != nil { return fmt.Errorf("error getting certificate by height %d: %w", lastSentCertificateHeader.Height, err) } @@ -192,7 +195,7 @@ func (a *AggSender) sendCertificate(ctx context.Context) error { previousHeight = lastSentCertificateHeader.Height } - lastCertificateBlock, err = a.l2Syncer.GetBlockByLER(ctx, lastSentCertificateHeader.NewLocalExitRoot) + lastCertificateBlock, err = a.l2Syncer.GetBlockByLER(ctx, previousLocalExitRoot) if err != nil { return fmt.Errorf("error getting block by LER %s: %w", lastSentCertificate.CertificateID, err) } @@ -481,12 +484,12 @@ func (a *AggSender) checkIfCertificatesAreSettled(ctx context.Context) { } // shouldSendCertificate checks if a certificate should be sent at given L1 block -// we send certificates at one block before the epoch ending so we get most of the +// we send certificates at two blocks before the epoch ending so we get most of the // bridges and claims in that epoch func (a *AggSender) shouldSendCertificate(block uint64) bool { if block == 0 { return false } - return (block+1)%a.cfg.EpochSize == 0 + return (block+numOfBlocksBeforeEpochEnding)%a.cfg.EpochSize == 0 } diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go index 31f1b531..38fdf1b3 100644 --- a/aggsender/aggsender_test.go +++ b/aggsender/aggsender_test.go @@ -783,13 +783,13 @@ func TestShouldSendCertificate(t *testing.T) { }{ { name: "Should send certificate", - block: 9, + block: 8, epochSize: 10, expectedResult: true, }, { name: "Should not send certificate", - block: 8, + block: 9, epochSize: 10, expectedResult: false, }, @@ -801,7 +801,7 @@ func TestShouldSendCertificate(t *testing.T) { }, { name: "Should send certificate with large epoch size", - block: 999, + block: 998, epochSize: 1000, expectedResult: true, }, @@ -1083,13 +1083,13 @@ func TestSendCertificate(t *testing.T) { }, { name: "error getting last sent certificate", - l1BlockNumber: []interface{}{uint64(9), nil}, + l1BlockNumber: []interface{}{uint64(8), nil}, getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{}, errors.New("error getting last sent certificate")}, expectedError: "error getting last sent certificate", }, { name: "error getting block finality", - l1BlockNumber: []interface{}{uint64(9), nil}, + l1BlockNumber: []interface{}{uint64(8), nil}, getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ Height: 1, CertificateID: common.HexToHash("0x1"), @@ -1102,7 +1102,7 @@ func TestSendCertificate(t *testing.T) { }, { name: "error getting last l2 block", - l1BlockNumber: []interface{}{uint64(9), nil}, + l1BlockNumber: []interface{}{uint64(8), nil}, getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ Height: 1, CertificateID: common.HexToHash("0x1"), @@ -1116,7 +1116,7 @@ func TestSendCertificate(t *testing.T) { }, { name: "error getting last certificate header", - l1BlockNumber: []interface{}{uint64(19), nil}, + l1BlockNumber: []interface{}{uint64(18), nil}, getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ Height: 1, CertificateID: common.HexToHash("0x1"), @@ -1131,7 +1131,7 @@ func TestSendCertificate(t *testing.T) { }, { name: "error deleting in error certificate", - l1BlockNumber: []interface{}{uint64(19), nil}, + l1BlockNumber: []interface{}{uint64(18), nil}, getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ Height: 1, CertificateID: common.HexToHash("0x1"), @@ -1149,7 +1149,7 @@ func TestSendCertificate(t *testing.T) { }, { name: "error getting certificate by height", - l1BlockNumber: []interface{}{uint64(29), nil}, + l1BlockNumber: []interface{}{uint64(28), nil}, getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ Height: 11, CertificateID: common.HexToHash("0x1"), @@ -1168,7 +1168,7 @@ func TestSendCertificate(t *testing.T) { }, { name: "error getting block by LER", - l1BlockNumber: []interface{}{uint64(39), nil}, + l1BlockNumber: []interface{}{uint64(38), nil}, getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ Height: 21, CertificateID: common.HexToHash("0x11"), @@ -1177,7 +1177,7 @@ func TestSendCertificate(t *testing.T) { ToBlock: 38, }, nil}, l2BlockFinality: []interface{}{etherman.PendingBlock}, - l2HeaderByNumber: []interface{}{&gethTypes.Header{Number: big.NewInt(39)}, nil}, + l2HeaderByNumber: []interface{}{&gethTypes.Header{Number: big.NewInt(38)}, nil}, getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ Status: agglayer.InError, }, nil}, @@ -1191,7 +1191,7 @@ func TestSendCertificate(t *testing.T) { }, { name: "no new blocks to send certificate", - l1BlockNumber: []interface{}{uint64(39), nil}, + l1BlockNumber: []interface{}{uint64(38), nil}, getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ Height: 41, CertificateID: common.HexToHash("0x111"), @@ -1208,7 +1208,7 @@ func TestSendCertificate(t *testing.T) { }, { name: "get bridges error", - l1BlockNumber: []interface{}{uint64(99), nil}, + l1BlockNumber: []interface{}{uint64(98), nil}, getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ Height: 50, CertificateID: common.HexToHash("0x1111"), @@ -1229,7 +1229,7 @@ func TestSendCertificate(t *testing.T) { }, { name: "no bridges", - l1BlockNumber: []interface{}{uint64(199), nil}, + l1BlockNumber: []interface{}{uint64(198), nil}, getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ Height: 60, CertificateID: common.HexToHash("0x11111"), @@ -1249,7 +1249,7 @@ func TestSendCertificate(t *testing.T) { }, { name: "get claims error", - l1BlockNumber: []interface{}{uint64(159), nil}, + l1BlockNumber: []interface{}{uint64(158), nil}, getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ Height: 70, CertificateID: common.HexToHash("0x121111"), @@ -1278,7 +1278,7 @@ func TestSendCertificate(t *testing.T) { }, { name: "error building certificate", - l1BlockNumber: []interface{}{uint64(149), nil}, + l1BlockNumber: []interface{}{uint64(148), nil}, getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ Height: 80, CertificateID: common.HexToHash("0x1321111"), @@ -1312,7 +1312,7 @@ func TestSendCertificate(t *testing.T) { }, { name: "send certificate error", - l1BlockNumber: []interface{}{uint64(139), nil}, + l1BlockNumber: []interface{}{uint64(138), nil}, getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ Height: 90, CertificateID: common.HexToHash("0x1121111"), @@ -1346,7 +1346,7 @@ func TestSendCertificate(t *testing.T) { }, { name: "store last sent certificate error", - l1BlockNumber: []interface{}{uint64(129), nil}, + l1BlockNumber: []interface{}{uint64(128), nil}, getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ Height: 100, CertificateID: common.HexToHash("0x11121111"), @@ -1381,7 +1381,7 @@ func TestSendCertificate(t *testing.T) { }, { name: "successful sending of certificate", - l1BlockNumber: []interface{}{uint64(179), nil}, + l1BlockNumber: []interface{}{uint64(178), nil}, getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ Height: 110, CertificateID: common.HexToHash("0x12121111"), diff --git a/l1infotreesync/processor_test.go b/l1infotreesync/processor_test.go index 52a81ce8..1c64d875 100644 --- a/l1infotreesync/processor_test.go +++ b/l1infotreesync/processor_test.go @@ -124,8 +124,6 @@ func TestGetLatestInfoUntilBlockIfNotFoundReturnsErrNotFound(t *testing.T) { } func Test_processor_GetL1InfoTreeMerkleProof(t *testing.T) { - t.Parallel() - testTable := []struct { name string getProcessor func(t *testing.T) *processor @@ -184,8 +182,6 @@ func Test_processor_GetL1InfoTreeMerkleProof(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { - t.Parallel() - p := tt.getProcessor(t) proof, root, err := p.GetL1InfoTreeMerkleProof(context.Background(), tt.idx) if tt.expectedErr != nil { From 7cce81fe272ea7126b10c157225f60eb067e8768 Mon Sep 17 00:00:00 2001 From: joanestebanr <129153821+joanestebanr@users.noreply.github.com> Date: Wed, 16 Oct 2024 15:16:48 +0200 Subject: [PATCH 24/84] fix: aggSender needs ReorgDetectorL1 --- cmd/run.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/run.go b/cmd/run.go index 7b4f196f..cf2453d9 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -575,7 +575,7 @@ func runReorgDetectorL1IfNeeded( ) (*reorgdetector.ReorgDetector, chan error) { if !isNeeded([]string{ cdkcommon.SEQUENCE_SENDER, cdkcommon.AGGREGATOR, - cdkcommon.AGGORACLE, cdkcommon.RPC}, + cdkcommon.AGGORACLE, cdkcommon.RPC, cdkcommon.AGGSENDER}, components) { return nil, nil } From f2a9ef7da7b6b6cf56e744f984ad8293b45563bb Mon Sep 17 00:00:00 2001 From: joanestebanr <129153821+joanestebanr@users.noreply.github.com> Date: Wed, 16 Oct 2024 17:16:17 +0200 Subject: [PATCH 25/84] fix: local/script support to AggSender --- scripts/local_config | 23 ++++++++++++++++--- .../kurtosis-cdk-node-config.toml.template | 12 +++++++++- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/scripts/local_config b/scripts/local_config index 9a1f55cf..13f8b784 100755 --- a/scripts/local_config +++ b/scripts/local_config @@ -30,10 +30,13 @@ function get_value_from_toml_file(){ local _LINE local _inside_section=0 local _return_next_line=0 + local _TMP_FILE=$(mktemp) + cat $_FILE > $_TMP_FILE + # Maybe the file doesnt end with a new line so we added just in case + echo "\n" >> $_TMP_FILE while read -r _LINE; do # Clean up line from spaces and tabs _LINE=$(echo $_LINE | tr -d '[:space:]') - #echo $_LINE if [ $_inside_section -eq 1 ]; then if [[ "$_LINE" == [* ]]; then return 1 @@ -51,6 +54,7 @@ function get_value_from_toml_file(){ if [ $_key_value == "[" ]; then _return_next_line=1 else + rm $_TMP_FILE # sed sentence remove quotes echo $_key_value | sed 's/^[[:space:]]*"//;s/"$//' return 0 @@ -61,7 +65,8 @@ function get_value_from_toml_file(){ fi - done < "$_FILE" + done < "$_TMP_FILE" + rm $_TMP_FILE return 2 } @@ -142,6 +147,9 @@ function export_values_of_cdk_node_config(){ export_obj_key_from_toml_file_or_fatal zkevm_l2_aggregator_keystore_password $_CDK_CONFIG_FILE Aggregator.EthTxManager PrivateKeys Password export_key_from_toml_file_or_fatal zkevm_rollup_fork_id $_CDK_CONFIG_FILE Aggregator ForkId + export_key_from_toml_file_or_fatal zkevm_l2_agglayer_keystore_password $_CDK_CONFIG_FILE AggSender.SequencerPrivateKey Path Password + + export is_cdk_validium=$zkevm_is_validium export zkevm_rollup_chain_id=$l2_chain_id @@ -205,6 +213,7 @@ function export_ports_from_kurtosis(){ export aggregator_db_hostname="127.0.0.1" export l1_rpc_url="http://localhost:${l1_rpc_port}" export l2_rpc_url="http://localhost:${zkevm_rpc_http_port}" + export agglayer_url="http://localhost:${agglayer_port}" } ############################################################################### @@ -263,6 +272,10 @@ function download_kurtosis_artifacts(){ kurtosis files download $KURTOSIS_ENCLAVE aggregator-keystore $DEST ok_or_fatal "Error downloading kurtosis artifact cdk-node-config-artifact to $DEST" export zkevm_l2_aggregator_keystore_file=$DEST/aggregator.keystore + + kurtosis files download $KURTOSIS_ENCLAVE agglayer-keystore $DEST + ok_or_fatal "Error downloading kurtosis artifact agglayer to $DEST" + export zkevm_l2_agglayer_keystore_file=$DEST/agglayer.keystore } ############################################################################### @@ -281,6 +294,7 @@ function check_generated_config_file(){ # MAIN ############################################################################### set -o pipefail # enable strict command pipe error detection + check_requirements create_dest_folder @@ -313,7 +327,7 @@ echo " " echo "- Add next configuration to vscode launch.json" cat << EOF { - "name": "Debug cdk"", + "name": "Debug cdk", "type": "go", "request": "launch", "mode": "auto", @@ -325,5 +339,8 @@ cat << EOF "-components", "sequence-sender,aggregator", ] }, + + To run AggSender change components to: + "-components", "aggsender", EOF diff --git a/test/config/kurtosis-cdk-node-config.toml.template b/test/config/kurtosis-cdk-node-config.toml.template index 8fd9e82b..9795594d 100644 --- a/test/config/kurtosis-cdk-node-config.toml.template +++ b/test/config/kurtosis-cdk-node-config.toml.template @@ -59,4 +59,14 @@ Outputs = ["stderr"] Host = "{{.aggregator_db.hostname}}" Port = "{{.aggregator_db.port}}" EnableLog = false - MaxConns = 200 \ No newline at end of file + MaxConns = 200 +[AggSender] +DBPath = "/tmp/aggsender.sqlite" +AggLayerURL = "{{.agglayer_url}}" +#SequencerPrivateKey = {Path = "/pk/sequencer.keystore", Password = "testonly"} +SequencerPrivateKey = {Path = "{{or .zkevm_l2_agglayer_keystore_file "/pk/sequencer.keystore"}}", Password = "{{.zkevm_l2_agglayer_keystore_password}}"} + +CertificateSendInterval = "1m" +URLRPCL2="{{.l2_rpc_url}}" +CheckSettledInterval = "5s" +EpochSize = 10 \ No newline at end of file From 800ce5073fef25011dda03901a25c68a0b7c6952 Mon Sep 17 00:00:00 2001 From: joanestebanr <129153821+joanestebanr@users.noreply.github.com> Date: Wed, 16 Oct 2024 17:21:45 +0200 Subject: [PATCH 26/84] fix: local/script support to AggSender --- scripts/local_config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/local_config b/scripts/local_config index 13f8b784..b0d63617 100755 --- a/scripts/local_config +++ b/scripts/local_config @@ -147,7 +147,7 @@ function export_values_of_cdk_node_config(){ export_obj_key_from_toml_file_or_fatal zkevm_l2_aggregator_keystore_password $_CDK_CONFIG_FILE Aggregator.EthTxManager PrivateKeys Password export_key_from_toml_file_or_fatal zkevm_rollup_fork_id $_CDK_CONFIG_FILE Aggregator ForkId - export_key_from_toml_file_or_fatal zkevm_l2_agglayer_keystore_password $_CDK_CONFIG_FILE AggSender.SequencerPrivateKey Path Password + export_key_from_toml_file_or_fatal zkevm_l2_agglayer_keystore_password $_CDK_CONFIG_FILE AggSender.SequencerPrivateKey Password export is_cdk_validium=$zkevm_is_validium From dcfa76e36411823794fc0ba8810993be104a8010 Mon Sep 17 00:00:00 2001 From: joanestebanr <129153821+joanestebanr@users.noreply.github.com> Date: Wed, 16 Oct 2024 17:23:15 +0200 Subject: [PATCH 27/84] fix: local/script support to AggSender --- scripts/local_config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/local_config b/scripts/local_config index b0d63617..5be041bf 100755 --- a/scripts/local_config +++ b/scripts/local_config @@ -33,7 +33,7 @@ function get_value_from_toml_file(){ local _TMP_FILE=$(mktemp) cat $_FILE > $_TMP_FILE # Maybe the file doesnt end with a new line so we added just in case - echo "\n" >> $_TMP_FILE + echo " " >> $_TMP_FILE while read -r _LINE; do # Clean up line from spaces and tabs _LINE=$(echo $_LINE | tr -d '[:space:]') From 8d6447a5dea4c6cec99f710e7beac3b4efd2e41c Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Thu, 17 Oct 2024 10:33:48 +0200 Subject: [PATCH 28/84] fix: block get interval --- aggsender/aggsender.go | 4 ++-- aggsender/aggsender_test.go | 2 +- aggsender/config.go | 14 +++++++------- config/default.go | 4 ++-- test/config/kurtosis-cdk-node-config.toml.template | 6 +++--- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index 6e12b163..2c73e51e 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -116,7 +116,7 @@ func (a *AggSender) Start(ctx context.Context) { // sendCertificates sends certificates to the aggLayer func (a *AggSender) sendCertificates(ctx context.Context) { - ticker := time.NewTicker(a.cfg.CertificateSendInterval.Duration) + ticker := time.NewTicker(a.cfg.BlockGetInterval.Duration) for { select { @@ -450,7 +450,7 @@ func (a *AggSender) signCertificate(certificate *agglayer.Certificate) (*agglaye // checkIfCertificatesAreSettled checks if certificates are settled func (a *AggSender) checkIfCertificatesAreSettled(ctx context.Context) { - ticker := time.NewTicker(a.cfg.CertificateSendInterval.Duration) + ticker := time.NewTicker(a.cfg.CheckSettledInterval.Duration) for { select { case <-ticker.C: diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go index 38fdf1b3..375ab416 100644 --- a/aggsender/aggsender_test.go +++ b/aggsender/aggsender_test.go @@ -927,7 +927,7 @@ func TestCheckIfCertificatesAreSettled(t *testing.T) { log: mockLogger, storage: mockStorage, aggLayerClient: mockAggLayerClient, - cfg: Config{CertificateSendInterval: types.Duration{Duration: time.Second}}, + cfg: Config{BlockGetInterval: types.Duration{Duration: time.Second}}, } ctx, cancel := context.WithCancel(context.Background()) diff --git a/aggsender/config.go b/aggsender/config.go index 59e8553b..95cc859d 100644 --- a/aggsender/config.go +++ b/aggsender/config.go @@ -6,11 +6,11 @@ import ( // Config is the configuration for the AggSender type Config struct { - DBPath string `mapstructure:"DBPath"` - AggLayerURL string `mapstructure:"AggLayerURL"` - CertificateSendInterval types.Duration `mapstructure:"CertificateSendInterval"` - CheckSettledInterval types.Duration `mapstructure:"CheckSettledInterval"` - SequencerPrivateKey types.KeystoreFileConfig `mapstructure:"SequencerPrivateKey"` - URLRPCL2 string `mapstructure:"URLRPCL2"` - EpochSize uint64 `mapstructure:"EpochSize"` + DBPath string `mapstructure:"DBPath"` + AggLayerURL string `mapstructure:"AggLayerURL"` + BlockGetInterval types.Duration `mapstructure:"BlockGetInterval"` + CheckSettledInterval types.Duration `mapstructure:"CheckSettledInterval"` + SequencerPrivateKey types.KeystoreFileConfig `mapstructure:"SequencerPrivateKey"` + URLRPCL2 string `mapstructure:"URLRPCL2"` + EpochSize uint64 `mapstructure:"EpochSize"` } diff --git a/config/default.go b/config/default.go index ad404180..89495d41 100644 --- a/config/default.go +++ b/config/default.go @@ -343,8 +343,8 @@ GlobalExitRootManagerAddr = "{{L1Config.polygonZkEVMGlobalExitRootAddress}}" DBPath = "/tmp/aggsender" AggLayerURL = "http://zkevm-agglayer" SequencerPrivateKey = {Path = "/pk/sequencer.keystore", Password = "testonly"} -CertificateSendInterval = "1m" +BlockGetInterval = "2s" URLRPCL2="http://test-aggoracle-l2:8545" -CheckSettledInterval = "5s" +CheckSettledInterval = "2s" EpochSize = 10 ` diff --git a/test/config/kurtosis-cdk-node-config.toml.template b/test/config/kurtosis-cdk-node-config.toml.template index 9795594d..658981e4 100644 --- a/test/config/kurtosis-cdk-node-config.toml.template +++ b/test/config/kurtosis-cdk-node-config.toml.template @@ -66,7 +66,7 @@ AggLayerURL = "{{.agglayer_url}}" #SequencerPrivateKey = {Path = "/pk/sequencer.keystore", Password = "testonly"} SequencerPrivateKey = {Path = "{{or .zkevm_l2_agglayer_keystore_file "/pk/sequencer.keystore"}}", Password = "{{.zkevm_l2_agglayer_keystore_password}}"} -CertificateSendInterval = "1m" +BlockGetInterval = "2s" URLRPCL2="{{.l2_rpc_url}}" -CheckSettledInterval = "5s" -EpochSize = 10 \ No newline at end of file +CheckSettledInterval = "2s" +EpochSize = 10 From df8ee93a62bdd1a94c671e529ea606e1525cf74f Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Thu, 17 Oct 2024 15:12:34 +0200 Subject: [PATCH 29/84] fix: skip cmd and agglayer folders in sonar test coverage --- aggsender/aggsender_test.go | 5 ++++- sonar-project.properties | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go index 375ab416..21485c60 100644 --- a/aggsender/aggsender_test.go +++ b/aggsender/aggsender_test.go @@ -927,7 +927,10 @@ func TestCheckIfCertificatesAreSettled(t *testing.T) { log: mockLogger, storage: mockStorage, aggLayerClient: mockAggLayerClient, - cfg: Config{BlockGetInterval: types.Duration{Duration: time.Second}}, + cfg: Config{ + BlockGetInterval: types.Duration{Duration: time.Second}, + CheckSettledInterval: types.Duration{Duration: time.Second}, + }, } ctx, cancel := context.WithCancel(context.Background()) diff --git a/sonar-project.properties b/sonar-project.properties index 1ffe75f5..b2cc1a8b 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -11,7 +11,7 @@ sonar.exclusions=**/test/**,**/vendor/**,**/mocks/**,**/build/**,**/target/**,** sonar.tests=. sonar.test.inclusions=**/*_test.go -sonar.test.exclusions=**/vendor/**,**/docs/**,**/mocks/**,**/*.pb.go,**/*.yml,**/*.yaml,**/*.json,**/*.xml,**/*.toml,**/mocks_*/*,**/mock_*.go +sonar.test.exclusions=**/vendor/**,**/docs/**,**/mocks/**,**/*.pb.go,**/*.yml,**/*.yaml,**/*.json,**/*.xml,**/*.toml,**/mocks_*/*,**/mock_*.go,**/agglayer/**,**/cmd/** sonar.issue.enforceSemantic=true # ===================================================== From 8c65487449ff3b4dafeb64b6be1d4b05f6a14477 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Thu, 17 Oct 2024 16:00:58 +0200 Subject: [PATCH 30/84] fix: big ints as TEXT --- bridgesync/migrations/bridgesync0001.sql | 6 +-- bridgesync/processor_test.go | 53 ++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/bridgesync/migrations/bridgesync0001.sql b/bridgesync/migrations/bridgesync0001.sql index de90910c..74adc6d5 100644 --- a/bridgesync/migrations/bridgesync0001.sql +++ b/bridgesync/migrations/bridgesync0001.sql @@ -16,7 +16,7 @@ CREATE TABLE bridge ( origin_address VARCHAR NOT NULL, destination_network INTEGER NOT NULL, destination_address VARCHAR NOT NULL, - amount DECIMAL(78, 0) NOT NULL, + amount TEXT NOT NULL, metadata BLOB, deposit_count INTEGER NOT NULL, PRIMARY KEY (block_num, block_pos) @@ -25,11 +25,11 @@ CREATE TABLE bridge ( CREATE TABLE claim ( block_num INTEGER NOT NULL REFERENCES block(num) ON DELETE CASCADE, block_pos INTEGER NOT NULL, - global_index DECIMAL(78, 0) NOT NULL, + global_index TEXT NOT NULL, origin_network INTEGER NOT NULL, origin_address VARCHAR NOT NULL, destination_address VARCHAR NOT NULL, - amount DECIMAL(78, 0) NOT NULL, + amount TEXT NOT NULL, proof_local_exit_root VARCHAR, proof_rollup_exit_root VARCHAR, mainnet_exit_root VARCHAR, diff --git a/bridgesync/processor_test.go b/bridgesync/processor_test.go index 86dd33c5..11d1ea7b 100644 --- a/bridgesync/processor_test.go +++ b/bridgesync/processor_test.go @@ -15,10 +15,20 @@ import ( "github.com/0xPolygon/cdk/log" "github.com/0xPolygon/cdk/sync" "github.com/0xPolygon/cdk/tree/testvectors" + "github.com/0xPolygon/cdk/tree/types" "github.com/ethereum/go-ethereum/common" + "github.com/russross/meddler" "github.com/stretchr/testify/require" ) +func TestBigIntString(t *testing.T) { + globalIndex := GenerateGlobalIndex(true, 0, 1093) + fmt.Println(globalIndex.String()) + + _, ok := new(big.Int).SetString(globalIndex.String(), 10) + require.True(t, ok) +} + func TestProceessor(t *testing.T) { path := path.Join(t.TempDir(), "file::memory:?cache=shared") log.Debugf("sqlite path: %s", path) @@ -671,3 +681,46 @@ func TestDecodeGlobalIndex(t *testing.T) { }) } } + +func TestInsertAndGetClaim(t *testing.T) { + path := path.Join(t.TempDir(), "file::memory:?cache=shared") + log.Debugf("sqlite path: %s", path) + err := migrationsBridge.RunMigrations(path) + require.NoError(t, err) + p, err := newProcessor(path, "foo") + require.NoError(t, err) + + tx, err := p.db.BeginTx(context.Background(), nil) + require.NoError(t, err) + + // insert test claim + testClaim := &Claim{ + BlockNum: 1, + BlockPos: 0, + GlobalIndex: GenerateGlobalIndex(true, 0, 1093), + OriginNetwork: 11, + OriginAddress: common.HexToAddress("0x11"), + DestinationAddress: common.HexToAddress("0x11"), + Amount: big.NewInt(11), + ProofLocalExitRoot: types.Proof{}, + ProofRollupExitRoot: types.Proof{}, + MainnetExitRoot: common.Hash{}, + RollupExitRoot: common.Hash{}, + GlobalExitRoot: common.Hash{}, + DestinationNetwork: 12, + Metadata: []byte("0x11"), + IsMessage: false, + } + + _, err = tx.Exec(`INSERT INTO block (num) VALUES ($1)`, testClaim.BlockNum) + require.NoError(t, err) + require.NoError(t, meddler.Insert(tx, "claim", testClaim)) + + require.NoError(t, tx.Commit()) + + // get test claim + claims, err := p.GetClaims(context.Background(), 1, 1) + require.NoError(t, err) + require.Len(t, claims, 1) + require.Equal(t, testClaim, &claims[0]) +} From 09ae71e831e6dc4cd6529197cce69cdd579447ef Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Thu, 17 Oct 2024 16:14:11 +0200 Subject: [PATCH 31/84] fix: skip agglayer and cmd part 2 --- sonar-project.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonar-project.properties b/sonar-project.properties index b2cc1a8b..f46e9863 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -7,7 +7,7 @@ sonar.projectName=cdk sonar.organization=0xpolygon sonar.sources=. -sonar.exclusions=**/test/**,**/vendor/**,**/mocks/**,**/build/**,**/target/**,**/proto/include/**,**/*.pb.go,**/docs/**,**/*.sql,**/mocks_*/*,scripts/**,**/mock_*.go +sonar.exclusions=**/test/**,**/vendor/**,**/mocks/**,**/build/**,**/target/**,**/proto/include/**,**/*.pb.go,**/docs/**,**/*.sql,**/mocks_*/*,scripts/**,**/mock_*.go,**/agglayer/**,**/cmd/** sonar.tests=. sonar.test.inclusions=**/*_test.go From fb51b03a62fc07e8427925ae4f93e2235dc16c22 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Fri, 18 Oct 2024 11:21:59 +0200 Subject: [PATCH 32/84] fix: rebase --- test/config/kurtosis-cdk-node-config.toml.template | 1 - 1 file changed, 1 deletion(-) diff --git a/test/config/kurtosis-cdk-node-config.toml.template b/test/config/kurtosis-cdk-node-config.toml.template index 658981e4..95f766ce 100644 --- a/test/config/kurtosis-cdk-node-config.toml.template +++ b/test/config/kurtosis-cdk-node-config.toml.template @@ -65,7 +65,6 @@ DBPath = "/tmp/aggsender.sqlite" AggLayerURL = "{{.agglayer_url}}" #SequencerPrivateKey = {Path = "/pk/sequencer.keystore", Password = "testonly"} SequencerPrivateKey = {Path = "{{or .zkevm_l2_agglayer_keystore_file "/pk/sequencer.keystore"}}", Password = "{{.zkevm_l2_agglayer_keystore_password}}"} - BlockGetInterval = "2s" URLRPCL2="{{.l2_rpc_url}}" CheckSettledInterval = "2s" From 08a9b0d0e6aeadff235d5b68b023c7258b478fe0 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Fri, 18 Oct 2024 12:02:28 +0200 Subject: [PATCH 33/84] fix: sonar analysis issues --- agglayer/types.go | 5 + aggsender/aggsender.go | 174 ++++++++++++++++----------- aggsender/aggsender_test.go | 62 +++++----- aggsender/db/aggsender_db_storage.go | 14 ++- aggsender/types/types.go | 6 + 5 files changed, 152 insertions(+), 109 deletions(-) diff --git a/agglayer/types.go b/agglayer/types.go index d7ec932a..93d5ebfb 100644 --- a/agglayer/types.go +++ b/agglayer/types.go @@ -1,6 +1,7 @@ package agglayer import ( + "fmt" "math/big" "github.com/0xPolygon/cdk/bridgesync" @@ -175,3 +176,7 @@ type CertificateHeader struct { NewLocalExitRoot common.Hash `json:"new_local_exit_root"` Status CertificateStatus `json:"status"` } + +func (c CertificateHeader) String() string { + return fmt.Sprintf("Height: %d, CertificateID: %s", c.Height, c.CertificateID.String()) +} diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index 2c73e51e..7551b06c 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -145,60 +145,14 @@ func (a *AggSender) sendCertificate(ctx context.Context) error { return nil } - lastSentCertificate, err := a.storage.GetLastSentCertificate(ctx) - if err != nil { - return fmt.Errorf("error getting last sent certificate: %w", err) - } - - a.log.Infof("last sent certificate: %s", lastSentCertificate.CertificateID) - - finality := a.l2Syncer.BlockFinality() - blockFinality, err := finality.ToBlockNum() - if err != nil { - return fmt.Errorf("error getting block finality: %w", err) - } - - lastL2Block, err := a.l2Client.HeaderByNumber(ctx, blockFinality) + lastL2Block, err := a.getLastL2Block(ctx) if err != nil { return fmt.Errorf("error getting block from l2: %w", err) } - var ( - previousLocalExitRoot common.Hash - previousHeight uint64 - lastCertificateBlock uint64 - ) - - if lastSentCertificate.CertificateID != (common.Hash{}) { - // we have sent a certificate before, get the last certificate header - lastSentCertificateHeader, err := a.aggLayerClient.GetCertificateHeader(lastSentCertificate.CertificateID) - if err != nil { - return fmt.Errorf("error getting certificate %s header: %w", lastSentCertificate.CertificateID, err) - } - - if lastSentCertificateHeader.Status == agglayer.InError { - // last sent certificate had errors, we need to remove it from the db - // and build a new certificate from that block - if err := a.storage.DeleteCertificate(ctx, lastSentCertificateHeader.CertificateID); err != nil { - return fmt.Errorf("error deleting certificate %s: %w", lastSentCertificate.CertificateID, err) - } - - lastValidCertificate, err := a.storage.GetCertificateByHeight(ctx, lastSentCertificateHeader.Height-1) - if err != nil { - return fmt.Errorf("error getting certificate by height %d: %w", lastSentCertificateHeader.Height, err) - } - - previousLocalExitRoot = lastValidCertificate.NewLocalExitRoot - previousHeight = lastValidCertificate.Height - } else { - previousLocalExitRoot = lastSentCertificateHeader.NewLocalExitRoot - previousHeight = lastSentCertificateHeader.Height - } - - lastCertificateBlock, err = a.l2Syncer.GetBlockByLER(ctx, previousLocalExitRoot) - if err != nil { - return fmt.Errorf("error getting block by LER %s: %w", lastSentCertificate.CertificateID, err) - } + previousLocalExitRoot, previousHeight, lastCertificateBlock, err := a.getLastSentCertificateData(ctx) + if err != nil { + return err } if lastL2Block.Number.Uint64() <= lastCertificateBlock { @@ -291,6 +245,73 @@ func (a *AggSender) buildCertificate(ctx context.Context, }, nil } +// getLastSentCertificateData gets the previous local exit root, previous certificate height +// and last certificate block sent (gotten from the last sent certificate in db) +func (a *AggSender) getLastSentCertificateData(ctx context.Context) (common.Hash, uint64, uint64, error) { + lastSentCertificate, err := a.storage.GetLastSentCertificate(ctx) + if err != nil { + return common.Hash{}, 0, 0, fmt.Errorf("error getting last sent certificate: %w", err) + } + + a.log.Infof("last sent certificate: %s", lastSentCertificate.String()) + + var ( + previousLocalExitRoot common.Hash + previousHeight uint64 + lastCertificateBlock uint64 + ) + + if lastSentCertificate.CertificateID != (common.Hash{}) { + // we have sent a certificate before, get the last certificate header + lastSentCertificateHeader, err := a.aggLayerClient.GetCertificateHeader(lastSentCertificate.CertificateID) + if err != nil { + return common.Hash{}, 0, 0, fmt.Errorf("error getting certificate %s header: %w", + lastSentCertificate.CertificateID, err) + } + + if lastSentCertificateHeader.Status == agglayer.InError { + // last sent certificate had errors, we need to remove it from the db + // and build a new certificate from that block + if err := a.storage.DeleteCertificate(ctx, lastSentCertificateHeader.CertificateID); err != nil { + return common.Hash{}, 0, 0, fmt.Errorf("error deleting certificate %s: %w", + lastSentCertificate.CertificateID, err) + } + + lastValidCertificate, err := a.storage.GetCertificateByHeight(ctx, lastSentCertificateHeader.Height-1) + if err != nil { + return common.Hash{}, 0, 0, fmt.Errorf("error getting certificate by height %d: %w", + lastSentCertificateHeader.Height, err) + } + + previousLocalExitRoot = lastValidCertificate.NewLocalExitRoot + previousHeight = lastValidCertificate.Height + } else { + previousLocalExitRoot = lastSentCertificateHeader.NewLocalExitRoot + previousHeight = lastSentCertificateHeader.Height + } + + lastCertificateBlock, err = a.l2Syncer.GetBlockByLER(ctx, previousLocalExitRoot) + if err != nil { + return common.Hash{}, 0, 0, fmt.Errorf("error getting block by LER %s: %w", + lastSentCertificate.CertificateID, err) + } + } + + return previousLocalExitRoot, previousHeight, lastCertificateBlock, nil +} + +// getLastL2Block gets the last L2 block based on the finality configured for l2 bridge syncer +func (a *AggSender) getLastL2Block(ctx context.Context) (*types.Header, error) { + finality := a.l2Syncer.BlockFinality() + blockFinality, err := finality.ToBlockNum() + if err != nil { + return nil, fmt.Errorf("error getting block finality: %w", err) + } + + return a.l2Client.HeaderByNumber(ctx, blockFinality) +} + +// convertClaimToImportedBridgeExit converts a claim to an ImportedBridgeExit object func (a *AggSender) convertClaimToImportedBridgeExit(claim bridgesync.Claim) (*agglayer.ImportedBridgeExit, error) { leafType := agglayer.LeafTypeAsset if claim.IsMessage { @@ -454,35 +475,42 @@ func (a *AggSender) checkIfCertificatesAreSettled(ctx context.Context) { for { select { case <-ticker.C: - pendingCertificates, err := a.storage.GetCertificatesByStatus(ctx, []agglayer.CertificateStatus{agglayer.Pending}) - if err != nil { - a.log.Errorf("error getting pending certificates: %w", err) - continue - } - - for _, certificate := range pendingCertificates { - certificateHeader, err := a.aggLayerClient.GetCertificateHeader(certificate.CertificateID) - if err != nil { - a.log.Errorf("error getting header of certificate %s with height: %d from agglayer: %w", - certificate.CertificateID, certificate.Height, err) - continue - } - - if certificateHeader.Status == agglayer.Settled || certificateHeader.Status == agglayer.InError { - certificate.Status = certificateHeader.Status - - if err := a.storage.UpdateCertificateStatus(ctx, *certificate); err != nil { - a.log.Errorf("error updating certificate status in storage: %w", err) - continue - } - } - } + a.checkPendingCertificatesStatus(ctx) case <-ctx.Done(): return } } } +// checkPendingCertificatesStatus checks the status of pending certificates +// and updates in the storage if it changed on agglayer +func (a *AggSender) checkPendingCertificatesStatus(ctx context.Context) { + pendingCertificates, err := a.storage.GetCertificatesByStatus(ctx, []agglayer.CertificateStatus{agglayer.Pending}) + if err != nil { + a.log.Errorf("error getting pending certificates: %w", err) + } + + for _, certificate := range pendingCertificates { + certificateHeader, err := a.aggLayerClient.GetCertificateHeader(certificate.CertificateID) + if err != nil { + a.log.Errorf("error getting header of certificate %s with height: %d from agglayer: %w", + certificate.CertificateID, certificate.Height, err) + continue + } + + if certificateHeader.Status == agglayer.Settled || certificateHeader.Status == agglayer.InError { + certificate.Status = certificateHeader.Status + + a.log.Infof("certificate %s changed status to %s", certificateHeader.String(), certificate.Status) + + if err := a.storage.UpdateCertificateStatus(ctx, *certificate); err != nil { + a.log.Errorf("error updating certificate status in storage: %w", err) + continue + } + } + } +} + // shouldSendCertificate checks if a certificate should be sent at given L1 block // we send certificates at two blocks before the epoch ending so we get most of the // bridges and claims in that epoch diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go index 21485c60..5089ff66 100644 --- a/aggsender/aggsender_test.go +++ b/aggsender/aggsender_test.go @@ -828,13 +828,14 @@ func TestCheckIfCertificatesAreSettled(t *testing.T) { t.Parallel() tests := []struct { - name string - pendingCertificates []*aggsendertypes.CertificateInfo - certificateHeaders map[common.Hash]*agglayer.CertificateHeader - getFromDBError error - clientError error - updateDBError error - expectedLogMessages []string + name string + pendingCertificates []*aggsendertypes.CertificateInfo + certificateHeaders map[common.Hash]*agglayer.CertificateHeader + getFromDBError error + clientError error + updateDBError error + expectedErrorLogMessages []string + expectedInfoMessages []string }{ { name: "All certificates settled - update successful", @@ -846,6 +847,9 @@ func TestCheckIfCertificatesAreSettled(t *testing.T) { common.HexToHash("0x1"): {Status: agglayer.Settled}, common.HexToHash("0x2"): {Status: agglayer.Settled}, }, + expectedInfoMessages: []string{ + "certificate %s changed status to %s", + }, }, { name: "Some certificates in error - update successful", @@ -857,11 +861,14 @@ func TestCheckIfCertificatesAreSettled(t *testing.T) { common.HexToHash("0x1"): {Status: agglayer.InError}, common.HexToHash("0x2"): {Status: agglayer.Settled}, }, + expectedInfoMessages: []string{ + "certificate %s changed status to %s", + }, }, { name: "Error getting pending certificates", getFromDBError: fmt.Errorf("storage error"), - expectedLogMessages: []string{ + expectedErrorLogMessages: []string{ "error getting pending certificates: %w", }, }, @@ -874,7 +881,7 @@ func TestCheckIfCertificatesAreSettled(t *testing.T) { common.HexToHash("0x1"): {Status: agglayer.InError}, }, clientError: fmt.Errorf("client error"), - expectedLogMessages: []string{ + expectedErrorLogMessages: []string{ "error getting header of certificate %s with height: %d from agglayer: %w", }, }, @@ -887,9 +894,12 @@ func TestCheckIfCertificatesAreSettled(t *testing.T) { common.HexToHash("0x1"): {Status: agglayer.Settled}, }, updateDBError: fmt.Errorf("update error"), - expectedLogMessages: []string{ + expectedErrorLogMessages: []string{ "error updating certificate status in storage: %w", }, + expectedInfoMessages: []string{ + "certificate %s changed status to %s", + }, }, } @@ -914,13 +924,17 @@ func TestCheckIfCertificatesAreSettled(t *testing.T) { } if tt.clientError != nil { - for _, msg := range tt.expectedLogMessages { + for _, msg := range tt.expectedErrorLogMessages { mockLogger.On("Errorf", msg, mock.Anything, mock.Anything, mock.Anything).Return() } } else { - for _, msg := range tt.expectedLogMessages { + for _, msg := range tt.expectedErrorLogMessages { mockLogger.On("Errorf", msg, mock.Anything).Return() } + + for _, msg := range tt.expectedInfoMessages { + mockLogger.On("Infof", msg, mock.Anything, mock.Anything).Return() + } } aggSender := &AggSender{ @@ -1087,32 +1101,20 @@ func TestSendCertificate(t *testing.T) { { name: "error getting last sent certificate", l1BlockNumber: []interface{}{uint64(8), nil}, + l2BlockFinality: []interface{}{etherman.FinalizedBlock}, + l2HeaderByNumber: []interface{}{&gethTypes.Header{Number: big.NewInt(8)}, nil}, getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{}, errors.New("error getting last sent certificate")}, expectedError: "error getting last sent certificate", }, { - name: "error getting block finality", - l1BlockNumber: []interface{}{uint64(8), nil}, - getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ - Height: 1, - CertificateID: common.HexToHash("0x1"), - NewLocalExitRoot: common.HexToHash("0x123"), - FromBlock: 1, - ToBlock: 10, - }, nil}, + name: "error getting block finality", + l1BlockNumber: []interface{}{uint64(8), nil}, l2BlockFinality: []interface{}{etherman.BlockNumberFinality("invalid")}, expectedError: "error getting block finality", }, { - name: "error getting last l2 block", - l1BlockNumber: []interface{}{uint64(8), nil}, - getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ - Height: 1, - CertificateID: common.HexToHash("0x1"), - NewLocalExitRoot: common.HexToHash("0x123"), - FromBlock: 1, - ToBlock: 10, - }, nil}, + name: "error getting last l2 block", + l1BlockNumber: []interface{}{uint64(8), nil}, l2BlockFinality: []interface{}{etherman.FinalizedBlock}, l2HeaderByNumber: []interface{}{nil, errors.New("error getting block")}, expectedError: "error getting block from l2", diff --git a/aggsender/db/aggsender_db_storage.go b/aggsender/db/aggsender_db_storage.go index fbfdddb3..b5bdb58d 100644 --- a/aggsender/db/aggsender_db_storage.go +++ b/aggsender/db/aggsender_db_storage.go @@ -16,6 +16,8 @@ import ( "github.com/russross/meddler" ) +const errWhileRollbackFormat = "error while rolling back tx: %w" + // AggSenderStorage is the interface that defines the methods to interact with the storage type AggSenderStorage interface { // GetCertificateByHeight returns a certificate by its height @@ -66,7 +68,7 @@ func (a *AggSenderSQLStorage) GetCertificatesByStatus(ctx context.Context, defer func() { if err := tx.Rollback(); err != nil { - a.logger.Warnf("error rolling back tx: %w", err) + a.logger.Warnf(errWhileRollbackFormat, err) } }() @@ -106,7 +108,7 @@ func (a *AggSenderSQLStorage) GetCertificateByHeight(ctx context.Context, defer func() { if err := tx.Rollback(); err != nil { - a.logger.Warnf("error rolling back tx: %w", err) + a.logger.Warnf(errWhileRollbackFormat, err) } }() @@ -132,7 +134,7 @@ func (a *AggSenderSQLStorage) GetLastSentCertificate(ctx context.Context) (types defer func() { if err := tx.Rollback(); err != nil { - a.logger.Warnf("error rolling back tx: %w", err) + a.logger.Warnf(errWhileRollbackFormat, err) } }() @@ -159,7 +161,7 @@ func (a *AggSenderSQLStorage) SaveLastSentCertificate(ctx context.Context, certi defer func() { if err != nil { if errRllbck := tx.Rollback(); errRllbck != nil { - a.logger.Errorf("error while rolling back tx %w", errRllbck) + a.logger.Errorf(errWhileRollbackFormat, errRllbck) } } }() @@ -185,7 +187,7 @@ func (a *AggSenderSQLStorage) DeleteCertificate(ctx context.Context, certificate defer func() { if err != nil { if errRllbck := tx.Rollback(); errRllbck != nil { - a.logger.Errorf("error while rolling back tx %w", errRllbck) + a.logger.Errorf(errWhileRollbackFormat, errRllbck) } } }() @@ -211,7 +213,7 @@ func (a *AggSenderSQLStorage) UpdateCertificateStatus(ctx context.Context, certi defer func() { if err != nil { if errRllbck := tx.Rollback(); errRllbck != nil { - a.logger.Errorf("error while rolling back tx %w", errRllbck) + a.logger.Errorf(errWhileRollbackFormat, errRllbck) } } }() diff --git a/aggsender/types/types.go b/aggsender/types/types.go index edb542f9..8d47a5f2 100644 --- a/aggsender/types/types.go +++ b/aggsender/types/types.go @@ -1,6 +1,8 @@ package types import ( + "fmt" + "github.com/0xPolygon/cdk/agglayer" "github.com/ethereum/go-ethereum/common" ) @@ -13,3 +15,7 @@ type CertificateInfo struct { ToBlock uint64 `meddler:"to_block"` Status agglayer.CertificateStatus `meddler:"status"` } + +func (c CertificateInfo) String() string { + return fmt.Sprintf("Height: %d, CertificateID: %s", c.Height, c.CertificateID.String()) +} From f9a6cafdd8b2ce5044cd5cab9498f2a2ecd8cdb3 Mon Sep 17 00:00:00 2001 From: joanestebanr <129153821+joanestebanr@users.noreply.github.com> Date: Fri, 18 Oct 2024 14:14:41 +0200 Subject: [PATCH 34/84] feat: adapted to new config-file --- config/default.go | 19 +++++------ scripts/local_config | 33 ++++++++++++++++--- .../kurtosis-cdk-node-config.toml.template | 17 +++------- 3 files changed, 42 insertions(+), 27 deletions(-) diff --git a/config/default.go b/config/default.go index 89495d41..27d97801 100644 --- a/config/default.go +++ b/config/default.go @@ -7,7 +7,7 @@ L1URL = "http://localhost:8545" L2URL = "localhost:8123" L1AggOracleURL = "http://test-aggoracle-l1:8545" L2AggOracleURL = "http://test-aggoracle-l2:8545" - +AggLayerURL = "https://agglayer-dev.polygon.technology" ForkId = 9 ContractVersions = "elderberry" @@ -17,14 +17,13 @@ L2Coinbase = "0xfa3b44587990f97ba8b6ba7e230a5f0e95d14b3d" SequencerPrivateKeyPath = "/app/sequencer.keystore" SequencerPrivateKeyPassword = "test" WitnessURL = "localhost:8123" -AggLayerURL = "https://agglayer-dev.polygon.technology" StreamServer = "localhost:6900" AggregatorPrivateKeyPath = "/app/keystore/aggregator.keystore" AggregatorPrivateKeyPassword = "testonly" # Who send Proof to L1? AggLayer addr, or aggregator addr? SenderProofToL1Addr = "0x0000000000000000000000000000000000000000" - +polygonBridgeAddr = "0x0000000000000000000000000000000000000000" # This values can be override directly from genesis.json @@ -37,7 +36,7 @@ genesisBlockNumber = 0 polygonRollupManagerAddress = "0x0000000000000000000000000000000000000000" polTokenAddress = "0x0000000000000000000000000000000000000000" polygonZkEVMAddress = "0x0000000000000000000000000000000000000000" - polygonBridgeAddr = "0x0000000000000000000000000000000000000000" + [L2Config] GlobalExitRootAddr = "0x0000000000000000000000000000000000000000" @@ -304,7 +303,7 @@ GasOffset = 0 DBPath = "{{PathRWData}}/bridgel1sync" BlockFinality = "LatestBlock" InitialBlockNum = 0 -BridgeAddr = "{{L1Config.polygonBridgeAddr}}" +BridgeAddr = "{{polygonBridgeAddr}}" SyncBlockChunkSize = 100 RetryAfterErrorPeriod = "1s" MaxRetryAttemptsAfterError = -1 @@ -314,7 +313,7 @@ WaitForNewBlocksPeriod = "3s" DBPath = "{{PathRWData}}/bridgel2sync" BlockFinality = "LatestBlock" InitialBlockNum = 0 -BridgeAddr = "{{L1Config.polygonBridgeAddr}}" +BridgeAddr = "{{polygonBridgeAddr}}" SyncBlockChunkSize = 100 RetryAfterErrorPeriod = "1s" MaxRetryAttemptsAfterError = -1 @@ -340,11 +339,11 @@ GlobalExitRootManagerAddr = "{{L1Config.polygonZkEVMGlobalExitRootAddress}}" [AggSender] -DBPath = "/tmp/aggsender" -AggLayerURL = "http://zkevm-agglayer" -SequencerPrivateKey = {Path = "/pk/sequencer.keystore", Password = "testonly"} +DBPath = "{{PathRWData}}/aggsender.sqlite" +AggLayerURL = "{{AggLayerURL}}" +SequencerPrivateKey = {Path = "{{SequencerPrivateKeyPath}}", Password = "{{SequencerPrivateKeyPassword}}"} BlockGetInterval = "2s" -URLRPCL2="http://test-aggoracle-l2:8545" +URLRPCL2="{{L2URL}}" CheckSettledInterval = "2s" EpochSize = 10 ` diff --git a/scripts/local_config b/scripts/local_config index 5be041bf..1d5aa751 100755 --- a/scripts/local_config +++ b/scripts/local_config @@ -146,9 +146,9 @@ function export_values_of_cdk_node_config(){ export_key_from_toml_file_or_fatal aggregator_db_password $_CDK_CONFIG_FILE Aggregator.DB Password export_obj_key_from_toml_file_or_fatal zkevm_l2_aggregator_keystore_password $_CDK_CONFIG_FILE Aggregator.EthTxManager PrivateKeys Password - export_key_from_toml_file_or_fatal zkevm_rollup_fork_id $_CDK_CONFIG_FILE Aggregator ForkId - export_key_from_toml_file_or_fatal zkevm_l2_agglayer_keystore_password $_CDK_CONFIG_FILE AggSender.SequencerPrivateKey Password - + export_key_from_toml_file_or_fatal zkevm_rollup_fork_id $_CDK_CONFIG_FILE Aggregator ForkId + export_key_from_toml_file_or_fatal zkevm_l2_agglayer_keystore_password $_CDK_CONFIG_FILE AggSender.SequencerPrivateKey Password + export_key_from_toml_file_or_fatal zkevm_bridge_address $_CDK_CONFIG_FILE BridgeL1Sync BridgeAddr export is_cdk_validium=$zkevm_is_validium export zkevm_rollup_chain_id=$l2_chain_id @@ -206,7 +206,7 @@ function export_portnum_from_kurtosis_or_fail(){ ############################################################################### function export_ports_from_kurtosis(){ export_portnum_from_kurtosis_or_fail l1_rpc_port el-1-geth-lighthouse rpc - export_portnum_from_kurtosis_or_fail zkevm_rpc_http_port cdk-erigon-node-001 rpc rpc + export_portnum_from_kurtosis_or_fail zkevm_rpc_http_port cdk-erigon-node-001 http-rpc rpc export_portnum_from_kurtosis_or_fail zkevm_data_streamer_port cdk-erigon-sequencer-001 data-streamer export_portnum_from_kurtosis_or_fail aggregator_db_port postgres-001 postgres export_portnum_from_kurtosis_or_fail agglayer_port agglayer agglayer @@ -253,8 +253,10 @@ EOF ############################################################################### function create_dest_folder(){ export DEST=${TMP_CDK_FOLDER}/local_config + export path_rw_data=${TMP_CDK_FOLDER}/runtime [ ! -d ${DEST} ] && mkdir -p ${DEST} rm $DEST/* + mkdir $path_rw_data } ############################################################################### function download_kurtosis_artifacts(){ @@ -291,10 +293,31 @@ function check_generated_config_file(){ fi } ############################################################################### +function parse_command_line_args(){ + while [[ $# -gt 0 ]]; do + case $1 in + -h|--help) + echo "Usage: $0" + echo " -h: help" + exit 0 + ;; + -e|--enclave) + KURTOSIS_ENCLAVE=$2 + shift + shift + ;; + -*) + echo "Invalid Option: $1" 1>&2 + exit 1 + ;; + esac + done +} +############################################################################### # MAIN ############################################################################### set -o pipefail # enable strict command pipe error detection - +parse_command_line_args $* check_requirements create_dest_folder diff --git a/test/config/kurtosis-cdk-node-config.toml.template b/test/config/kurtosis-cdk-node-config.toml.template index 95f766ce..0b147ef8 100644 --- a/test/config/kurtosis-cdk-node-config.toml.template +++ b/test/config/kurtosis-cdk-node-config.toml.template @@ -1,8 +1,9 @@ -PathRWData = "/data/" +PathRWData = "{{.path_rw_data}}/" L1URL="{{.l1_rpc_url}}" L2URL="http://{{.l2_rpc_name}}{{.deployment_suffix}}:{{.zkevm_rpc_http_port}}" L1AggOracleURL = "http://test-aggoracle-l1:8545" L2AggOracleURL = "http://test-aggoracle-l2:8545" +AggLayerURL="{{.agglayer_url}}" ForkId = {{.zkevm_rollup_fork_id}} IsValidiumMode = {{.is_cdk_validium}} @@ -19,12 +20,11 @@ SequencerPrivateKeyPassword = "{{.zkevm_l2_keystore_password}}" AggregatorPrivateKeyPath = "{{or .zkevm_l2_aggregator_keystore_file "/etc/cdk/aggregator.keystore"}}" AggregatorPrivateKeyPassword = "{{.zkevm_l2_keystore_password}}" SenderProofToL1Addr = "{{.zkevm_l2_agglayer_address}}" - +polygonBridgeAddr = "{{.zkevm_bridge_address}}" WitnessURL = "http://{{.l2_rpc_name}}{{.deployment_suffix}}:{{.zkevm_rpc_http_port}}" -AggLayerURL = "http://agglayer:{{.agglayer_port}}" StreamServer = "{{.sequencer_name}}{{.deployment_suffix}}:{{.zkevm_data_streamer_port}}" @@ -39,8 +39,7 @@ genesisBlockNumber = "{{.zkevm_rollup_manager_block_number}}" polygonRollupManagerAddress = "{{.zkevm_rollup_manager_address}}" polTokenAddress = "{{.pol_token_address}}" polygonZkEVMAddress = "{{.zkevm_rollup_address}}" - polygonBridgeAddr = "0x0000000000000000000000000000000000000000" - + [L2Config] GlobalExitRootAddr = "{{.zkevm_global_exit_root_address}}" @@ -60,12 +59,6 @@ Outputs = ["stderr"] Port = "{{.aggregator_db.port}}" EnableLog = false MaxConns = 200 + [AggSender] -DBPath = "/tmp/aggsender.sqlite" -AggLayerURL = "{{.agglayer_url}}" -#SequencerPrivateKey = {Path = "/pk/sequencer.keystore", Password = "testonly"} SequencerPrivateKey = {Path = "{{or .zkevm_l2_agglayer_keystore_file "/pk/sequencer.keystore"}}", Password = "{{.zkevm_l2_agglayer_keystore_password}}"} -BlockGetInterval = "2s" -URLRPCL2="{{.l2_rpc_url}}" -CheckSettledInterval = "2s" -EpochSize = 10 From 4f8a3e7a451a95d4aff801b3e6cd0e5db5c858df Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Mon, 21 Oct 2024 08:41:27 +0200 Subject: [PATCH 35/84] fix: calculate GlobalExitRoot on a claim --- bridgesync/claimcalldata_test.go | 3 +++ bridgesync/downloader.go | 3 +++ 2 files changed, 6 insertions(+) diff --git a/bridgesync/claimcalldata_test.go b/bridgesync/claimcalldata_test.go index b8b432ae..a4ab49de 100644 --- a/bridgesync/claimcalldata_test.go +++ b/bridgesync/claimcalldata_test.go @@ -74,6 +74,7 @@ func TestClaimCalldata(t *testing.T) { ProofRollupExitRoot: proofRollupH, DestinationNetwork: 0, Metadata: []byte{}, + GlobalExitRoot: crypto.Keccak256Hash(common.HexToHash("5ca1e").Bytes(), common.HexToHash("dead").Bytes()), } expectedClaim2 := Claim{ OriginNetwork: 87, @@ -86,6 +87,7 @@ func TestClaimCalldata(t *testing.T) { ProofRollupExitRoot: proofRollupH, DestinationNetwork: 0, Metadata: []byte{}, + GlobalExitRoot: crypto.Keccak256Hash(common.HexToHash("5ca1e").Bytes(), common.HexToHash("dead").Bytes()), } expectedClaim3 := Claim{ OriginNetwork: 69, @@ -98,6 +100,7 @@ func TestClaimCalldata(t *testing.T) { ProofRollupExitRoot: proofRollupH, DestinationNetwork: 0, Metadata: []byte{}, + GlobalExitRoot: crypto.Keccak256Hash(common.HexToHash("5ca1e").Bytes(), common.HexToHash("dead").Bytes()), } auth.GasLimit = 999999 // for some reason gas estimation fails :( diff --git a/bridgesync/downloader.go b/bridgesync/downloader.go index dbea8c8f..782d5f1b 100644 --- a/bridgesync/downloader.go +++ b/bridgesync/downloader.go @@ -288,11 +288,14 @@ func decodeClaimCallDataAndSetIfFound(data []interface{}, claim *Claim) (bool, e if !ok { return false, fmt.Errorf("unexpected type for 'DestinationNetwork'. Expected 'uint32', got '%T'", data[7]) } + claim.Metadata, ok = data[10].([]byte) if !ok { return false, fmt.Errorf("unexpected type for 'claim Metadata'. Expected '[]byte', got '%T'", data[10]) } + claim.GlobalExitRoot = crypto.Keccak256Hash(claim.MainnetExitRoot.Bytes(), claim.RollupExitRoot.Bytes()) + return true, nil } } From ec83005ae6e297d6e8978909055f1243bf7d7785 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Mon, 21 Oct 2024 10:19:18 +0200 Subject: [PATCH 36/84] fix: remove unnecessary read only txn on db --- aggsender/db/aggsender_db_storage.go | 53 ++++------------------------ 1 file changed, 6 insertions(+), 47 deletions(-) diff --git a/aggsender/db/aggsender_db_storage.go b/aggsender/db/aggsender_db_storage.go index b5bdb58d..57a2d3a7 100644 --- a/aggsender/db/aggsender_db_storage.go +++ b/aggsender/db/aggsender_db_storage.go @@ -61,17 +61,6 @@ func NewAggSenderSQLStorage(logger *log.Logger, dbPath string) (*AggSenderSQLSto func (a *AggSenderSQLStorage) GetCertificatesByStatus(ctx context.Context, statuses []agglayer.CertificateStatus) ([]*types.CertificateInfo, error) { - tx, err := a.db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) - if err != nil { - return nil, err - } - - defer func() { - if err := tx.Rollback(); err != nil { - a.logger.Warnf(errWhileRollbackFormat, err) - } - }() - query := "SELECT * FROM certificate_info" args := make([]interface{}, len(statuses)) @@ -91,7 +80,7 @@ func (a *AggSenderSQLStorage) GetCertificatesByStatus(ctx context.Context, query += " ORDER BY height ASC" var certificates []*types.CertificateInfo - if err = meddler.QueryAll(a.db, &certificates, query, args...); err != nil { + if err := meddler.QueryAll(a.db, &certificates, query, args...); err != nil { return nil, err } @@ -101,24 +90,9 @@ func (a *AggSenderSQLStorage) GetCertificatesByStatus(ctx context.Context, // GetCertificateByHeight returns a certificate by its height func (a *AggSenderSQLStorage) GetCertificateByHeight(ctx context.Context, height uint64) (types.CertificateInfo, error) { - tx, err := a.db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) - if err != nil { - return types.CertificateInfo{}, err - } - - defer func() { - if err := tx.Rollback(); err != nil { - a.logger.Warnf(errWhileRollbackFormat, err) - } - }() - - rows, err := tx.Query(`SELECT * FROM certificate_info WHERE height = $1;`, height) - if err != nil { - return types.CertificateInfo{}, getSelectQueryError(height, err) - } - var certificateInfo types.CertificateInfo - if err = meddler.ScanRow(rows, &certificateInfo); err != nil { + if err := meddler.QueryRow(a.db, &certificateInfo, + "SELECT * FROM certificate_info WHERE height = $1;", height); err != nil { return types.CertificateInfo{}, getSelectQueryError(height, err) } @@ -127,25 +101,10 @@ func (a *AggSenderSQLStorage) GetCertificateByHeight(ctx context.Context, // GetLastSentCertificate returns the last certificate sent to the aggLayer that is still Pending func (a *AggSenderSQLStorage) GetLastSentCertificate(ctx context.Context) (types.CertificateInfo, error) { - tx, err := a.db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) - if err != nil { - return types.CertificateInfo{}, err - } - - defer func() { - if err := tx.Rollback(); err != nil { - a.logger.Warnf(errWhileRollbackFormat, err) - } - }() - - rows, err := tx.Query(`SELECT * FROM certificate_info WHERE status = $1 ORDER BY height DESC LIMIT 1;`, - agglayer.Pending) - if err != nil { - return types.CertificateInfo{}, getSelectQueryError(0, err) - } - var certificateInfo types.CertificateInfo - if err = meddler.ScanRow(rows, &certificateInfo); err != nil { + if err := meddler.QueryRow(a.db, &certificateInfo, + "SELECT * FROM certificate_info WHERE status = $1 ORDER BY height DESC LIMIT 1;", + agglayer.Pending); err != nil { return types.CertificateInfo{}, getSelectQueryError(0, err) } From 8da0c737bd9dd4054d48ef97caa09fd4f77290f0 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Mon, 21 Oct 2024 13:49:57 +0200 Subject: [PATCH 37/84] fix: should send certificate --- aggsender/aggsender.go | 22 ++++++++++++++++++---- aggsender/aggsender_test.go | 30 ++++++++++++++++++++---------- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index 7551b06c..f7d5610c 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "math/big" + "sync" "time" "github.com/0xPolygon/cdk/agglayer" @@ -73,6 +74,9 @@ type AggSender struct { cfg Config sequencerKey *ecdsa.PrivateKey + + lock sync.Mutex + lastL1CertificateBlock uint64 } // New returns a new AggSender @@ -135,13 +139,13 @@ func (a *AggSender) sendCertificates(ctx context.Context) { func (a *AggSender) sendCertificate(ctx context.Context) error { a.log.Infof("trying to send a new certificate...") - block, err := a.l1Client.BlockNumber(ctx) + l1Block, err := a.l1Client.BlockNumber(ctx) if err != nil { return fmt.Errorf("error getting l1 block number: %w", err) } - if !a.shouldSendCertificate(block) { - a.log.Infof("block %d on L1 not near epoch ending, so we don't send a certificate", block) + if !a.shouldSendCertificate(l1Block) { + a.log.Infof("block %d on L1 not near epoch ending, so we don't send a certificate", l1Block) return nil } @@ -205,6 +209,10 @@ func (a *AggSender) sendCertificate(ctx context.Context) error { return fmt.Errorf("error saving last sent certificate in db: %w", err) } + a.lock.Lock() + a.lastL1CertificateBlock = l1Block + a.lock.Unlock() + a.log.Infof("certificate: %s sent successfully for block: %d to block: %d", certificateHash, fromBlock, toBlock) return nil @@ -519,5 +527,11 @@ func (a *AggSender) shouldSendCertificate(block uint64) bool { return false } - return (block+numOfBlocksBeforeEpochEnding)%a.cfg.EpochSize == 0 + a.lock.Lock() + lastL1BlockSeen := a.lastL1CertificateBlock + a.lock.Unlock() + + shouldSend := lastL1BlockSeen+a.cfg.EpochSize-numOfBlocksBeforeEpochEnding <= block + + return shouldSend } diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go index 5089ff66..87448e16 100644 --- a/aggsender/aggsender_test.go +++ b/aggsender/aggsender_test.go @@ -776,10 +776,11 @@ func TestShouldSendCertificate(t *testing.T) { t.Parallel() tests := []struct { - name string - block uint64 - epochSize uint64 - expectedResult bool + name string + block uint64 + epochSize uint64 + lastL1CertificateBlock uint64 + expectedResult bool }{ { name: "Should send certificate", @@ -788,10 +789,17 @@ func TestShouldSendCertificate(t *testing.T) { expectedResult: true, }, { - name: "Should not send certificate", + name: "Should send certificate - another case", block: 9, epochSize: 10, - expectedResult: false, + expectedResult: true, + }, + { + name: "Should not send certificate", + block: 25, + epochSize: 10, + lastL1CertificateBlock: 18, + expectedResult: false, }, { name: "Should not send certificate at zero block", @@ -800,10 +808,11 @@ func TestShouldSendCertificate(t *testing.T) { expectedResult: false, }, { - name: "Should send certificate with large epoch size", - block: 998, - epochSize: 1000, - expectedResult: true, + name: "Should send certificate with large epoch size", + block: 1998, + epochSize: 1000, + lastL1CertificateBlock: 998, + expectedResult: true, }, } @@ -817,6 +826,7 @@ func TestShouldSendCertificate(t *testing.T) { cfg: Config{ EpochSize: tt.epochSize, }, + lastL1CertificateBlock: tt.lastL1CertificateBlock, } result := aggSender.shouldSendCertificate(tt.block) require.Equal(t, tt.expectedResult, result) From 718cd967bdefbadf6fe504489ffa4c47c5a0f69d Mon Sep 17 00:00:00 2001 From: Victor Castell <0x@vcastellm.xyz> Date: Mon, 21 Oct 2024 19:18:09 +0000 Subject: [PATCH 38/84] refactor: remove functions and test withdrawal --- test/bridge-e2e.bats | 65 +++++++++++++++++------------- test/helpers/common.bash | 21 +++++----- test/helpers/lxly-bridge-test.bash | 41 ++++--------------- 3 files changed, 56 insertions(+), 71 deletions(-) diff --git a/test/bridge-e2e.bats b/test/bridge-e2e.bats index f2f85850..b6c76b9f 100644 --- a/test/bridge-e2e.bats +++ b/test/bridge-e2e.bats @@ -21,7 +21,18 @@ setup() { destination_addr=${DESTINATION_ADDRESS:-"0x0bb7AA0b4FdC2D2862c088424260e99ed6299148"} ether_value=${ETHER_VALUE:-"0.0200000054"} amount=$(cast to-wei $ether_value ether) - token_addr=${TOKEN_ADDRESS:-"0x0000000000000000000000000000000000000000"} + readonly native_token_addr=${TOKEN_ADDRESS:-"0x0000000000000000000000000000000000000000"} + if [[ -n "$GAS_TOKEN_ADDR" ]]; then + echo "Using provided GAS_TOKEN_ADDR: $GAS_TOKEN_ADDR" >&3 + gas_token_addr="$GAS_TOKEN_ADDR" + else + echo "GAS_TOKEN_ADDR not provided, retrieving from rollup parameters file." >&3 + readonly rollup_params_file=/opt/zkevm/create_rollup_parameters.json + run bash -c "$contracts_service_wrapper 'cat $rollup_params_file' | tail -n +2 | jq -r '.gasTokenAddress'" + assert_success + assert_output --regexp "0x[a-fA-F0-9]{40}" + gas_token_addr=$output + fi readonly is_forced=${IS_FORCED:-"true"} readonly bridge_addr=$BRIDGE_ADDRESS readonly bridge_sig='bridgeAsset(uint32,address,uint256,address,bool,bytes)' @@ -35,43 +46,36 @@ setup() { readonly l1_rpc_network_id=$(cast call --rpc-url $l1_rpc_url $bridge_addr 'networkID() (uint32)') readonly l2_rpc_network_id=$(cast call --rpc-url $l2_rpc_url $bridge_addr 'networkID() (uint32)') gas_price=$(cast gas-price --rpc-url "$l2_rpc_url") + readonly weth_token_addr=$(cast call --rpc-url $l2_rpc_url $bridge_addr 'WETHToken()' | cast parse-bytes32-address) } -@test "Run deposit" { +@test "Native gas token deposit to WETH" { + local initial_receiver_balance=$(cast call --rpc-url "$l2_rpc_url" "$weth_token_addr" "$balance_of_fn_sig" "$destination_addr" | awk '{print $1}') + echo "Initial receiver balance of native token on L2 $initial_receiver_balance" >&3 + echo "Running LxLy deposit" >&3 - run deposit + run deposit "$native_token_addr" "$l1_rpc_url" assert_success - assert_output --partial 'transactionHash' -} -@test "Run claim" { echo "Running LxLy claim" >&3 - timeout="120" claim_frequency="10" run wait_for_claim "$timeout" "$claim_frequency" "$l2_rpc_url" assert_success -} -@test "Custom native token transfer" { - # Use GAS_TOKEN_ADDR if provided, otherwise retrieve from file - if [[ -n "$GAS_TOKEN_ADDR" ]]; then - echo "Using provided GAS_TOKEN_ADDR: $GAS_TOKEN_ADDR" >&3 - local gas_token_addr="$GAS_TOKEN_ADDR" - else - echo "GAS_TOKEN_ADDR not provided, retrieving from rollup parameters file." >&3 - readonly rollup_params_file=/opt/zkevm/create_rollup_parameters.json - run bash -c "$contracts_service_wrapper 'cat $rollup_params_file' | tail -n +2 | jq -r '.gasTokenAddress'" - assert_success - assert_output --regexp "0x[a-fA-F0-9]{40}" - local gas_token_addr=$output + run verify_balance "$l2_rpc_url" "$weth_token_addr" "$destination_addr" "$initial_receiver_balance" "$ether_value" + if [ $status -eq 0 ]; then + break fi + assert_success +} +@test "Custom gas token deposit" { echo "Gas token addr $gas_token_addr, L1 RPC: $l1_rpc_url" >&3 # Set receiver address and query for its initial native token balance on the L2 receiver=${RECEIVER:-"0x85dA99c8a7C2C95964c8EfD687E95E632Fc533D6"} - local initial_receiver_balance=$(cast balance --ether "$receiver" --rpc-url "$l2_rpc_url") + local initial_receiver_balance=$(cast balance "$receiver" --rpc-url "$l2_rpc_url") echo "Initial receiver balance of native token on L2 $initial_receiver_balance" >&3 # Query for initial sender balance @@ -107,11 +111,10 @@ setup() { assert_output --regexp "Transaction successful \(transaction hash: 0x[a-fA-F0-9]{64}\)" # Deposit - token_addr=$gas_token_addr destination_addr=$receiver destination_net=$l2_rpc_network_id amount=$wei_amount - run deposit + run deposit "$gas_token_addr" "$l1_rpc_url" assert_success # Claim deposits (settle them on the L2) @@ -121,16 +124,21 @@ setup() { assert_success # Validate that the native token of receiver on L2 has increased by the bridge tokens amount - run verify_native_token_balance "$l2_rpc_url" "$receiver" "$initial_receiver_balance" "$tokens_amount" + run verify_balance "$l2_rpc_url" "$native_token_addr" "$receiver" "$initial_receiver_balance" "$tokens_amount" assert_success } -@test "Run withdrawal" { +@test "Custom gas token withdrawal" { echo "Running LxLy withdrawal" >&3 + echo "Gas token addr $gas_token_addr, L1 RPC: $l1_rpc_url" >&3 - local initial_receiver_balance=$(cast balance -e "$destination_addr" --rpc-url "$l1_rpc_url") + # Validate that the native token of receiver on L1 has increased by the bridge tokens amount + local initial_receiver_balance=$(cast call --rpc-url "$l1_rpc_url" "$gas_token_addr" "$balance_of_fn_sig" "$destination_addr" 2>&1) + echo "Receiver balance of gas token on L1 $initial_receiver_balance" >&3 + assert_success - run withdrawal + destination_net=$l1_rpc_network_id + run deposit "$gas_token_addr" "$l2_rpc_url" assert_success # Claim withdrawals (settle them on the L1) @@ -140,8 +148,7 @@ setup() { run wait_for_claim "$timeout" "$claim_frequency" "$l1_rpc_url" assert_success - # Validate that the native token of receiver on L1 has increased by the bridge tokens amount - run verify_native_token_balance "$l1_rpc_url" "$destination_addr" "$initial_receiver_balance" "$ether_value" + run verify_gas_token_balance "$l1_rpc_url" "$gas_token_addr" "$destination_addr" "$initial_receiver_balance" "$amount" if [ $status -eq 0 ]; then break fi diff --git a/test/helpers/common.bash b/test/helpers/common.bash index 11b5fb85..c9e825c1 100644 --- a/test/helpers/common.bash +++ b/test/helpers/common.bash @@ -294,21 +294,24 @@ function check_balances() { fi } -function verify_native_token_balance() { - local rpc_url="$1" # RPC URL - local account="$2" # account address - local initial_balance="$3" # initial balance in Ether (decimal) - local ether_amount="$4" # amount to be added (in Ether, decimal) - - # Convert initial balance and amount to wei (no decimals) - local initial_balance_wei=$(cast --to-wei "$initial_balance") +function verify_balance() { + local rpc_url="$1" # RPC URL + local gas_token_addr="$2" # gas token contract address + local account="$3" # account address + local initial_balance_wei="$4" # initial balance in Wei (decimal) + local ether_amount="$5" # amount to be added (in Ether, decimal) # Trim 'ether' from ether_amount if it exists ether_amount=$(echo "$ether_amount" | sed 's/ether//') local amount_wei=$(cast --to-wei "$ether_amount") # Get final balance in wei (after the operation) - local final_balance_wei=$(cast balance "$account" --rpc-url "$rpc_url" | awk '{print $1}') + local final_balance_wei + if [[ $gas_token_addr == "0x0000000000000000000000000000000000000000" ]]; then + final_balance_wei=$(cast balance "$account" --rpc-url "$rpc_url" | awk '{print $1}') + else + final_balance_wei=$(cast call --rpc-url "$rpc_url" "$gas_token_addr" "$balance_of_fn_sig" "$destination_addr" | awk '{print $1}') + fi echo "Final balance of $account in $rpc_url: $final_balance_wei wei" >&3 # Calculate expected final balance (initial_balance + amount) diff --git a/test/helpers/lxly-bridge-test.bash b/test/helpers/lxly-bridge-test.bash index 1c57fafb..03a2b66b 100644 --- a/test/helpers/lxly-bridge-test.bash +++ b/test/helpers/lxly-bridge-test.bash @@ -1,24 +1,26 @@ #!/usr/bin/env bash # Error code reference https://hackmd.io/WwahVBZERJKdfK3BbKxzQQ function deposit() { - if [[ $token_addr == "0x0000000000000000000000000000000000000000" ]]; then + local token_addr="$1" + local rpc_url="$2" + if [[ $token_addr == $native_token_addr ]]; then echo "The ETH balance for sender "$sender_addr":" >&3 - cast balance -e --rpc-url $l1_rpc_url $sender_addr >&3 + cast balance -e --rpc-url $rpc_url $sender_addr >&3 else echo "The "$token_addr" token balance for sender "$sender_addr":" >&3 - balance_wei=$(cast call --rpc-url "$l1_rpc_url" "$token_addr" "$balance_of_fn_sig" "$sender_addr") + balance_wei=$(cast call --rpc-url "$rpc_url" "$token_addr" "$balance_of_fn_sig" "$sender_addr") echo "$(cast --from-wei "$balance_wei")" >&3 fi - echo "Attempting to deposit $amount [wei] to $destination_addr, token $token_addr (sender=$sender_addr, network id=$destination_net, rpc url=$l1_rpc_url)" >&3 + echo "Attempting to deposit $amount [wei] to $destination_addr, token $token_addr (sender=$sender_addr, network id=$destination_net, rpc url=$rpc_url)" >&3 if [[ $dry_run == "true" ]]; then cast calldata $bridge_sig $destination_net $destination_addr $amount $token_addr $is_forced $meta_bytes else if [[ $token_addr == "0x0000000000000000000000000000000000000000" ]]; then - cast send --legacy --private-key $sender_private_key --value $amount --rpc-url $l1_rpc_url $bridge_addr $bridge_sig $destination_net $destination_addr $amount $token_addr $is_forced $meta_bytes + cast send --legacy --private-key $sender_private_key --value $amount --rpc-url $rpc_url $bridge_addr $bridge_sig $destination_net $destination_addr $amount $token_addr $is_forced $meta_bytes else - cast send --legacy --private-key $sender_private_key --rpc-url $l1_rpc_url $bridge_addr $bridge_sig $destination_net $destination_addr $amount $token_addr $is_forced $meta_bytes + cast send --legacy --private-key $sender_private_key --rpc-url $rpc_url $bridge_addr $bridge_sig $destination_net $destination_addr $amount $token_addr $is_forced $meta_bytes fi fi } @@ -68,7 +70,6 @@ function claim() { if [[ $dry_run == "true" ]]; then cast calldata $claim_sig "$in_merkle_proof" "$in_rollup_merkle_proof" $in_global_index $in_main_exit_root $in_rollup_exit_root $in_orig_net $in_orig_addr $in_dest_net $in_dest_addr $in_amount $in_metadata - cast call --rpc-url $destination_rpc_url $bridge_addr $claim_sig "$in_merkle_proof" "$in_rollup_merkle_proof" $in_global_index $in_main_exit_root $in_rollup_exit_root $in_orig_net $in_orig_addr $in_dest_net $in_dest_addr $in_amount $in_metadata else local comp_gas_price=$(bc -l <<< "$gas_price * 1.5" | sed 's/\..*//') if [[ $? -ne 0 ]]; then @@ -105,29 +106,3 @@ function wait_for_claim() { sleep "$claim_frequency" done } - -function withdrawal() { - if [[ $token_addr == "0x0000000000000000000000000000000000000000" ]]; then - echo "The ETH balance for sender "$sender_addr":" >&3 - cast balance -e --rpc-url $l2_rpc_url $sender_addr >&3 - - echo "The ETH balance for receiver "$destination_addr ":" >&3 - cast balance -e --rpc-url $l1_rpc_url $destination_addr >&3 - else - echo "The "$token_addr" token balance for sender "$sender_addr":" >&3 - balance_wei=$(cast call --rpc-url "$l2_rpc_url" "$token_addr" "$balance_of_fn_sig" "$sender_addr") - echo "$(cast --from-wei "$balance_wei")" >&3 - fi - - echo "Attempting to withdraw $amount [wei] to $destination_addr, token $token_addr (sender=$sender_addr, network id=$l1_rpc_network_id, rpc url=$l2_rpc_url)" >&3 - - if [[ $dry_run == "true" ]]; then - cast calldata $bridge_sig $l1_rpc_network_id $destination_addr $amount $token_addr $is_forced $meta_bytes - else - if [[ $token_addr == "0x0000000000000000000000000000000000000000" ]]; then - cast send --legacy --private-key $sender_private_key --value $amount --rpc-url $l2_rpc_url $bridge_addr $bridge_sig $l1_rpc_network_id $destination_addr $amount $token_addr $is_forced $meta_bytes - else - cast send --legacy --private-key $sender_private_key --rpc-url $l2_rpc_url $bridge_addr $bridge_sig $destination_net $l1_rpc_network_id $amount $token_addr $is_forced $meta_bytes - fi - fi -} From c86e3b8e3a2b0a5ed18c76b80165e0c65a90cc50 Mon Sep 17 00:00:00 2001 From: Victor Castell <0x@vcastellm.xyz> Date: Mon, 21 Oct 2024 21:02:02 +0000 Subject: [PATCH 39/84] refactor: fixes --- test/bridge-e2e.bats | 11 +++++------ test/helpers/lxly-bridge-test.bash | 7 +++++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/test/bridge-e2e.bats b/test/bridge-e2e.bats index b6c76b9f..d6755bce 100644 --- a/test/bridge-e2e.bats +++ b/test/bridge-e2e.bats @@ -13,10 +13,10 @@ setup() { bridge_default_address=$(echo "$combined_json_output" | jq -r .polygonZkEVMBridgeAddress) BRIDGE_ADDRESS=$bridge_default_address fi - echo "Bridge address=$BRIDGE_ADDRESS" >&3 readonly sender_private_key=${SENDER_PRIVATE_KEY:-"12d7de8621a77640c9241b2595ba78ce443d05e94090365ab3bb5e19df82c625"} + readonly sender_addr="$(cast wallet address --private-key $sender_private_key)" destination_net=${DESTINATION_NET:-"1"} destination_addr=${DESTINATION_ADDRESS:-"0x0bb7AA0b4FdC2D2862c088424260e99ed6299148"} ether_value=${ETHER_VALUE:-"0.0200000054"} @@ -42,7 +42,6 @@ setup() { readonly bridge_api_url=${BRIDGE_API_URL:-"$(kurtosis port print $enclave zkevm-bridge-service-001 rpc)"} readonly dry_run=${DRY_RUN:-"false"} - readonly sender_addr="$(cast wallet address --private-key $sender_private_key)" readonly l1_rpc_network_id=$(cast call --rpc-url $l1_rpc_url $bridge_addr 'networkID() (uint32)') readonly l2_rpc_network_id=$(cast call --rpc-url $l2_rpc_url $bridge_addr 'networkID() (uint32)') gas_price=$(cast gas-price --rpc-url "$l2_rpc_url") @@ -133,12 +132,12 @@ setup() { echo "Gas token addr $gas_token_addr, L1 RPC: $l1_rpc_url" >&3 # Validate that the native token of receiver on L1 has increased by the bridge tokens amount - local initial_receiver_balance=$(cast call --rpc-url "$l1_rpc_url" "$gas_token_addr" "$balance_of_fn_sig" "$destination_addr" 2>&1) - echo "Receiver balance of gas token on L1 $initial_receiver_balance" >&3 + local initial_receiver_balance=$(cast call --rpc-url "$l1_rpc_url" "$gas_token_addr" "$balance_of_fn_sig" "$destination_addr" | awk '{print $1}') assert_success + echo "Receiver balance of gas token on L1 $initial_receiver_balance" >&3 destination_net=$l1_rpc_network_id - run deposit "$gas_token_addr" "$l2_rpc_url" + run deposit "$native_token_addr" "$l2_rpc_url" assert_success # Claim withdrawals (settle them on the L1) @@ -148,7 +147,7 @@ setup() { run wait_for_claim "$timeout" "$claim_frequency" "$l1_rpc_url" assert_success - run verify_gas_token_balance "$l1_rpc_url" "$gas_token_addr" "$destination_addr" "$initial_receiver_balance" "$amount" + run verify_balance "$l1_rpc_url" "$gas_token_addr" "$destination_addr" "$initial_receiver_balance" "$ether_value" if [ $status -eq 0 ]; then break fi diff --git a/test/helpers/lxly-bridge-test.bash b/test/helpers/lxly-bridge-test.bash index 03a2b66b..f723c277 100644 --- a/test/helpers/lxly-bridge-test.bash +++ b/test/helpers/lxly-bridge-test.bash @@ -3,12 +3,13 @@ function deposit() { local token_addr="$1" local rpc_url="$2" - if [[ $token_addr == $native_token_addr ]]; then + if [[ $token_addr == "0x0000000000000000000000000000000000000000" ]]; then echo "The ETH balance for sender "$sender_addr":" >&3 cast balance -e --rpc-url $rpc_url $sender_addr >&3 else echo "The "$token_addr" token balance for sender "$sender_addr":" >&3 - balance_wei=$(cast call --rpc-url "$rpc_url" "$token_addr" "$balance_of_fn_sig" "$sender_addr") + echo "cast call --rpc-url $rpc_url $token_addr \"$balance_of_fn_sig\" $sender_addr" >&3 + balance_wei=$(cast call --rpc-url "$rpc_url" "$token_addr" "$balance_of_fn_sig" "$sender_addr" | awk '{print $1}') echo "$(cast --from-wei "$balance_wei")" >&3 fi @@ -18,8 +19,10 @@ function deposit() { cast calldata $bridge_sig $destination_net $destination_addr $amount $token_addr $is_forced $meta_bytes else if [[ $token_addr == "0x0000000000000000000000000000000000000000" ]]; then + echo "cast send --legacy --private-key $sender_private_key --value $amount --rpc-url $rpc_url $bridge_addr $bridge_sig $destination_net $destination_addr $amount $token_addr $is_forced $meta_bytes" >&3 cast send --legacy --private-key $sender_private_key --value $amount --rpc-url $rpc_url $bridge_addr $bridge_sig $destination_net $destination_addr $amount $token_addr $is_forced $meta_bytes else + echo "cast send --legacy --private-key $sender_private_key --rpc-url $rpc_url $bridge_addr \"$bridge_sig\" $destination_net $destination_addr $amount $token_addr $is_forced $meta_bytes" cast send --legacy --private-key $sender_private_key --rpc-url $rpc_url $bridge_addr $bridge_sig $destination_net $destination_addr $amount $token_addr $is_forced $meta_bytes fi fi From f4e16e33a616147efbec493fbba24049d74ea1b5 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Tue, 22 Oct 2024 09:11:59 +0200 Subject: [PATCH 40/84] fix: comments --- agglayer/client.go | 4 +-- agglayer/types.go | 9 ++--- aggsender/aggsender.go | 10 +++--- aggsender/aggsender_test.go | 65 ++++++++++++++++++++----------------- aggsender/config.go | 24 ++++++++++---- aggsender/types/types.go | 3 +- config/default.go | 3 +- 7 files changed, 68 insertions(+), 50 deletions(-) diff --git a/agglayer/client.go b/agglayer/client.go index b1ac680f..132c2716 100644 --- a/agglayer/client.go +++ b/agglayer/client.go @@ -97,7 +97,7 @@ func (c *AggLayerClient) SendCertificate(certificate *SignedCertificate) (common } if response.Error != nil { - return common.Hash{}, fmt.Errorf("%v %v", response.Error.Code, response.Error.Message) + return common.Hash{}, fmt.Errorf("%d %s", response.Error.Code, response.Error.Message) } var result types.ArgHash @@ -117,7 +117,7 @@ func (c *AggLayerClient) GetCertificateHeader(certificateHash common.Hash) (*Cer } if response.Error != nil { - return nil, fmt.Errorf("%v %v", response.Error.Code, response.Error.Message) + return nil, fmt.Errorf("%d %s", response.Error.Code, response.Error.Message) } var result *CertificateHeader diff --git a/agglayer/types.go b/agglayer/types.go index 93d5ebfb..36fa15be 100644 --- a/agglayer/types.go +++ b/agglayer/types.go @@ -31,11 +31,11 @@ func (l LeafType) Uint8() uint8 { } const ( - LeafTypeAsset LeafType = 0 - LeafTypeMessage LeafType = 1 + LeafTypeAsset LeafType = iota + LeafTypeMessage ) -// Certificate is the data structure that will be sent to the aggLayer +// Certificate is the data structure that will be sent to the agglayer type Certificate struct { NetworkID uint32 `json:"network_id"` Height uint64 `json:"height"` @@ -178,5 +178,6 @@ type CertificateHeader struct { } func (c CertificateHeader) String() string { - return fmt.Sprintf("Height: %d, CertificateID: %s", c.Height, c.CertificateID.String()) + return fmt.Sprintf("Height: %d, CertificateID: %s, NewLocalExitRoot: %s", + c.Height, c.CertificateID.String(), c.NewLocalExitRoot.String()) } diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index f7d5610c..11604ada 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -23,9 +23,6 @@ import ( "github.com/ethereum/go-ethereum/crypto" ) -// defines number of blocks before epoch ending to send a certificate -const numOfBlocksBeforeEpochEnding = 2 - var errNoBridgesAndClaims = errors.New("no bridges and claims to build certificate") // L1InfoTreeSyncer is an interface defining functions that an L1InfoTreeSyncer should implement @@ -94,7 +91,7 @@ func New( return nil, err } - sequencerPrivateKey, err := cdkcommon.NewKeyFromKeystore(cfg.SequencerPrivateKey) + sequencerPrivateKey, err := cdkcommon.NewKeyFromKeystore(cfg.AggsenderPrivateKey) if err != nil { return nil, err } @@ -213,7 +210,8 @@ func (a *AggSender) sendCertificate(ctx context.Context) error { a.lastL1CertificateBlock = l1Block a.lock.Unlock() - a.log.Infof("certificate: %s sent successfully for block: %d to block: %d", certificateHash, fromBlock, toBlock) + a.log.Infof("certificate: %s sent successfully for range of l2 blocks (from block: %d, to block: %d)", + certificateHash, fromBlock, toBlock) return nil } @@ -531,7 +529,7 @@ func (a *AggSender) shouldSendCertificate(block uint64) bool { lastL1BlockSeen := a.lastL1CertificateBlock a.lock.Unlock() - shouldSend := lastL1BlockSeen+a.cfg.EpochSize-numOfBlocksBeforeEpochEnding <= block + shouldSend := lastL1BlockSeen+a.cfg.EpochSize-a.cfg.BlocksBeforeEpochEnding <= block return shouldSend } diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go index 87448e16..b5f4c64c 100644 --- a/aggsender/aggsender_test.go +++ b/aggsender/aggsender_test.go @@ -776,43 +776,49 @@ func TestShouldSendCertificate(t *testing.T) { t.Parallel() tests := []struct { - name string - block uint64 - epochSize uint64 - lastL1CertificateBlock uint64 - expectedResult bool + name string + block uint64 + epochSize uint64 + blocksBeforeEpochEnding uint64 + lastL1CertificateBlock uint64 + expectedResult bool }{ { - name: "Should send certificate", - block: 8, - epochSize: 10, - expectedResult: true, + name: "Should send certificate", + block: 8, + epochSize: 10, + blocksBeforeEpochEnding: 2, + expectedResult: true, }, { - name: "Should send certificate - another case", - block: 9, - epochSize: 10, - expectedResult: true, + name: "Should send certificate - another case", + block: 9, + epochSize: 10, + blocksBeforeEpochEnding: 1, + expectedResult: true, }, { - name: "Should not send certificate", - block: 25, - epochSize: 10, - lastL1CertificateBlock: 18, - expectedResult: false, + name: "Should not send certificate", + block: 25, + epochSize: 10, + lastL1CertificateBlock: 18, + blocksBeforeEpochEnding: 2, + expectedResult: false, }, { - name: "Should not send certificate at zero block", - block: 0, - epochSize: 1, - expectedResult: false, + name: "Should not send certificate at zero block", + block: 0, + epochSize: 1, + blocksBeforeEpochEnding: 2, + expectedResult: false, }, { - name: "Should send certificate with large epoch size", - block: 1998, - epochSize: 1000, - lastL1CertificateBlock: 998, - expectedResult: true, + name: "Should send certificate with large epoch size", + block: 1998, + epochSize: 1000, + lastL1CertificateBlock: 998, + blocksBeforeEpochEnding: 2, + expectedResult: true, }, } @@ -824,7 +830,8 @@ func TestShouldSendCertificate(t *testing.T) { aggSender := &AggSender{ cfg: Config{ - EpochSize: tt.epochSize, + EpochSize: tt.epochSize, + BlocksBeforeEpochEnding: tt.blocksBeforeEpochEnding, }, lastL1CertificateBlock: tt.lastL1CertificateBlock, } @@ -1004,7 +1011,7 @@ func TestSendCertificate(t *testing.T) { var ( aggsender = &AggSender{ log: log.WithFields("aggsender", 1), - cfg: Config{EpochSize: 10}, + cfg: Config{EpochSize: 10, BlocksBeforeEpochEnding: 2}, sequencerKey: cfg.sequencerKey, } mockL1Client *EthClientMock diff --git a/aggsender/config.go b/aggsender/config.go index 95cc859d..246924a9 100644 --- a/aggsender/config.go +++ b/aggsender/config.go @@ -6,11 +6,21 @@ import ( // Config is the configuration for the AggSender type Config struct { - DBPath string `mapstructure:"DBPath"` - AggLayerURL string `mapstructure:"AggLayerURL"` - BlockGetInterval types.Duration `mapstructure:"BlockGetInterval"` - CheckSettledInterval types.Duration `mapstructure:"CheckSettledInterval"` - SequencerPrivateKey types.KeystoreFileConfig `mapstructure:"SequencerPrivateKey"` - URLRPCL2 string `mapstructure:"URLRPCL2"` - EpochSize uint64 `mapstructure:"EpochSize"` + // DBPath is the path of the sqlite db on which the AggSender will store the data + DBPath string `mapstructure:"DBPath"` + // AggLayerURL is the URL of the AggLayer + AggLayerURL string `mapstructure:"AggLayerURL"` + // BlockGetInterval is the interval at which the AggSender will get the blocks from L1 + BlockGetInterval types.Duration `mapstructure:"BlockGetInterval"` + // CheckSettledInterval is the interval at which the AggSender will check if the blocks are settled + CheckSettledInterval types.Duration `mapstructure:"CheckSettledInterval"` + // AggsenderPrivateKey is the private key which is used to sign certificates + AggsenderPrivateKey types.KeystoreFileConfig `mapstructure:"AggsenderPrivateKey"` + // URLRPCL2 is the URL of the L2 RPC node + URLRPCL2 string `mapstructure:"URLRPCL2"` + // EpochSize is the size of the epoch on L1 (configured on agglayer) in blocks + EpochSize uint64 `mapstructure:"EpochSize"` + // BlocksBeforeEpochEnding indicates how many blocks before the epoch ending + // the AggSender should send the certificate + BlocksBeforeEpochEnding uint64 `mapstructure:"BlocksBeforeEpochEnding"` } diff --git a/aggsender/types/types.go b/aggsender/types/types.go index 8d47a5f2..0281581c 100644 --- a/aggsender/types/types.go +++ b/aggsender/types/types.go @@ -17,5 +17,6 @@ type CertificateInfo struct { } func (c CertificateInfo) String() string { - return fmt.Sprintf("Height: %d, CertificateID: %s", c.Height, c.CertificateID.String()) + return fmt.Sprintf("Height: %d, CertificateID: %s, FromBlock: %d, ToBlock: %d, NewLocalExitRoot: %s", + c.Height, c.CertificateID.String(), c.FromBlock, c.ToBlock, c.NewLocalExitRoot.String()) } diff --git a/config/default.go b/config/default.go index 27d97801..bf75d8b2 100644 --- a/config/default.go +++ b/config/default.go @@ -341,9 +341,10 @@ GlobalExitRootManagerAddr = "{{L1Config.polygonZkEVMGlobalExitRootAddress}}" [AggSender] DBPath = "{{PathRWData}}/aggsender.sqlite" AggLayerURL = "{{AggLayerURL}}" -SequencerPrivateKey = {Path = "{{SequencerPrivateKeyPath}}", Password = "{{SequencerPrivateKeyPassword}}"} +AggsenderPrivateKey = {Path = "{{SequencerPrivateKeyPath}}", Password = "{{SequencerPrivateKeyPassword}}"} BlockGetInterval = "2s" URLRPCL2="{{L2URL}}" CheckSettledInterval = "2s" EpochSize = 10 +BlocksBeforeEpochEnding = 2 ` From 313cf7fccc42354520a70b5bdddaff22374d41b5 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Tue, 22 Oct 2024 09:31:20 +0200 Subject: [PATCH 41/84] fix: mocks to mocks folder --- aggsender/aggsender.go | 48 +++---------------- aggsender/aggsender_test.go | 36 +++++++------- .../{db => mocks}/mock_aggsender_storage.go | 2 +- aggsender/{ => mocks}/mock_eth_client.go | 2 +- .../{ => mocks}/mock_l1infotree_syncer.go | 2 +- aggsender/{ => mocks}/mock_l2bridge_syncer.go | 2 +- aggsender/{ => mocks}/mock_logger.go | 2 +- aggsender/types/types.go | 38 +++++++++++++++ test/Makefile | 10 ++-- 9 files changed, 73 insertions(+), 69 deletions(-) rename aggsender/{db => mocks}/mock_aggsender_storage.go (99%) rename aggsender/{ => mocks}/mock_eth_client.go (99%) rename aggsender/{ => mocks}/mock_l1infotree_syncer.go (99%) rename aggsender/{ => mocks}/mock_l2bridge_syncer.go (99%) rename aggsender/{ => mocks}/mock_logger.go (98%) diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index 11604ada..bebc18c1 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -5,7 +5,6 @@ import ( "crypto/ecdsa" "errors" "fmt" - "math/big" "sync" "time" @@ -14,10 +13,8 @@ import ( aggsendertypes "github.com/0xPolygon/cdk/aggsender/types" "github.com/0xPolygon/cdk/bridgesync" cdkcommon "github.com/0xPolygon/cdk/common" - "github.com/0xPolygon/cdk/etherman" "github.com/0xPolygon/cdk/l1infotreesync" "github.com/0xPolygon/cdk/log" - treeTypes "github.com/0xPolygon/cdk/tree/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" @@ -25,45 +22,14 @@ import ( var errNoBridgesAndClaims = errors.New("no bridges and claims to build certificate") -// 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) -} - -// L2BridgeSyncer is an interface defining functions that an L2BridgeSyncer should implement -type L2BridgeSyncer interface { - GetBlockByLER(ctx context.Context, ler common.Hash) (uint64, error) - GetExitRootByIndex(ctx context.Context, index uint32) (treeTypes.Root, error) - GetBridges(ctx context.Context, fromBlock, toBlock uint64) ([]bridgesync.Bridge, error) - GetClaims(ctx context.Context, fromBlock, toBlock uint64) ([]bridgesync.Claim, error) - OriginNetwork() uint32 - BlockFinality() etherman.BlockNumberFinality -} - -// EthClient is an interface defining functions that an EthClient should implement -type EthClient interface { - BlockNumber(ctx context.Context) (uint64, error) - HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) -} - -// Logger is an interface that defines the methods to log messages -type Logger interface { - Info(args ...interface{}) - Infof(format string, args ...interface{}) - Error(args ...interface{}) - Errorf(format string, args ...interface{}) -} - // AggSender is a component that will send certificates to the aggLayer type AggSender struct { - log Logger + log aggsendertypes.Logger - l2Syncer L2BridgeSyncer - l2Client EthClient - l1infoTreeSyncer L1InfoTreeSyncer - l1Client EthClient + l2Syncer aggsendertypes.L2BridgeSyncer + l2Client aggsendertypes.EthClient + l1infoTreeSyncer aggsendertypes.L1InfoTreeSyncer + l1Client aggsendertypes.EthClient storage db.AggSenderStorage aggLayerClient agglayer.AgglayerClientInterface @@ -82,10 +48,10 @@ func New( logger *log.Logger, cfg Config, aggLayerClient agglayer.AgglayerClientInterface, - l1Client EthClient, + l1Client aggsendertypes.EthClient, l1InfoTreeSyncer *l1infotreesync.L1InfoTreeSync, l2Syncer *bridgesync.BridgeSync, - l2Client EthClient) (*AggSender, error) { + l2Client aggsendertypes.EthClient) (*AggSender, error) { storage, err := db.NewAggSenderSQLStorage(logger, cfg.DBPath) if err != nil { return nil, err diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go index b5f4c64c..46cc948d 100644 --- a/aggsender/aggsender_test.go +++ b/aggsender/aggsender_test.go @@ -10,7 +10,7 @@ import ( "time" "github.com/0xPolygon/cdk/agglayer" - "github.com/0xPolygon/cdk/aggsender/db" + "github.com/0xPolygon/cdk/aggsender/mocks" aggsendertypes "github.com/0xPolygon/cdk/aggsender/types" "github.com/0xPolygon/cdk/bridgesync" "github.com/0xPolygon/cdk/config/types" @@ -340,7 +340,7 @@ func TestGetImportedBridgeExits(t *testing.T) { mockProof := generateTestProof(t) - mockL1InfoTreeSyncer := NewL1InfoTreeSyncerMock(t) + mockL1InfoTreeSyncer := mocks.NewL1InfoTreeSyncerMock(t) mockL1InfoTreeSyncer.On("GetInfoByGlobalExitRoot", mock.Anything).Return(&l1infotreesync.L1InfoTreeLeaf{ L1InfoTreeIndex: 1, Timestamp: 123456789, @@ -560,8 +560,8 @@ func TestGetImportedBridgeExits(t *testing.T) { } func TestBuildCertificate(t *testing.T) { - mockL2BridgeSyncer := NewL2BridgeSyncerMock(t) - mockL1InfoTreeSyncer := NewL1InfoTreeSyncerMock(t) + mockL2BridgeSyncer := mocks.NewL2BridgeSyncerMock(t) + mockL1InfoTreeSyncer := mocks.NewL1InfoTreeSyncerMock(t) mockProof := generateTestProof(t) tests := []struct { @@ -926,9 +926,9 @@ func TestCheckIfCertificatesAreSettled(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - mockStorage := db.NewAggSenderStorageMock(t) + mockStorage := mocks.NewAggSenderStorageMock(t) mockAggLayerClient := agglayer.NewAgglayerClientMock(t) - mockLogger := NewLoggerMock(t) + mockLogger := mocks.NewLoggerMock(t) mockStorage.On("GetCertificatesByStatus", mock.Anything, []agglayer.CertificateStatus{agglayer.Pending}).Return(tt.pendingCertificates, tt.getFromDBError) for certID, header := range tt.certificateHeaders { @@ -1006,24 +1006,24 @@ func TestSendCertificate(t *testing.T) { expectedError string } - setupTest := func(cfg testCfg) (*AggSender, *db.AggSenderStorageMock, *EthClientMock, *L2BridgeSyncerMock, - *EthClientMock, *agglayer.AgglayerClientMock, *L1InfoTreeSyncerMock) { + setupTest := func(cfg testCfg) (*AggSender, *mocks.AggSenderStorageMock, *mocks.EthClientMock, *mocks.L2BridgeSyncerMock, + *mocks.EthClientMock, *agglayer.AgglayerClientMock, *mocks.L1InfoTreeSyncerMock) { var ( aggsender = &AggSender{ log: log.WithFields("aggsender", 1), cfg: Config{EpochSize: 10, BlocksBeforeEpochEnding: 2}, sequencerKey: cfg.sequencerKey, } - mockL1Client *EthClientMock - mockStorage *db.AggSenderStorageMock - mockL2Syncer *L2BridgeSyncerMock - mockL2Client *EthClientMock + mockL1Client *mocks.EthClientMock + mockStorage *mocks.AggSenderStorageMock + mockL2Syncer *mocks.L2BridgeSyncerMock + mockL2Client *mocks.EthClientMock mockAggLayerClient *agglayer.AgglayerClientMock - mockL1InfoTreeSyncer *L1InfoTreeSyncerMock + mockL1InfoTreeSyncer *mocks.L1InfoTreeSyncerMock ) if cfg.l1BlockNumber != nil { - mockL1Client = NewEthClientMock(t) + mockL1Client = mocks.NewEthClientMock(t) mockL1Client.On("BlockNumber", mock.Anything).Return(cfg.l1BlockNumber...).Once() aggsender.l1Client = mockL1Client @@ -1031,7 +1031,7 @@ func TestSendCertificate(t *testing.T) { if cfg.getLastSentCertificate != nil || cfg.deleteCertificate != nil || cfg.getCertificateByHeight != nil || cfg.saveLastSentCertificate != nil { - mockStorage = db.NewAggSenderStorageMock(t) + mockStorage = mocks.NewAggSenderStorageMock(t) mockStorage.On("GetLastSentCertificate", mock.Anything).Return(cfg.getLastSentCertificate...).Once() if cfg.deleteCertificate != nil { @@ -1051,7 +1051,7 @@ func TestSendCertificate(t *testing.T) { if cfg.l2BlockFinality != nil || cfg.getBlockByLER != nil || cfg.originNetwork != nil || cfg.getBridges != nil || cfg.getClaims != nil || cfg.getInfoByGlobalExitRoot != nil { - mockL2Syncer = NewL2BridgeSyncerMock(t) + mockL2Syncer = mocks.NewL2BridgeSyncerMock(t) mockL2Syncer.On("BlockFinality").Return(cfg.l2BlockFinality...).Once() if cfg.getBlockByLER != nil { @@ -1078,7 +1078,7 @@ func TestSendCertificate(t *testing.T) { } if cfg.l2HeaderByNumber != nil { - mockL2Client = NewEthClientMock(t) + mockL2Client = mocks.NewEthClientMock(t) mockL2Client.On("HeaderByNumber", mock.Anything, mock.Anything).Return(cfg.l2HeaderByNumber...).Once() aggsender.l2Client = mockL2Client @@ -1096,7 +1096,7 @@ func TestSendCertificate(t *testing.T) { } if cfg.getInfoByGlobalExitRoot != nil { - mockL1InfoTreeSyncer = NewL1InfoTreeSyncerMock(t) + mockL1InfoTreeSyncer = mocks.NewL1InfoTreeSyncerMock(t) mockL1InfoTreeSyncer.On("GetInfoByGlobalExitRoot", mock.Anything).Return(cfg.getInfoByGlobalExitRoot...).Once() aggsender.l1infoTreeSyncer = mockL1InfoTreeSyncer diff --git a/aggsender/db/mock_aggsender_storage.go b/aggsender/mocks/mock_aggsender_storage.go similarity index 99% rename from aggsender/db/mock_aggsender_storage.go rename to aggsender/mocks/mock_aggsender_storage.go index 02342a80..9c6eb9a8 100644 --- a/aggsender/db/mock_aggsender_storage.go +++ b/aggsender/mocks/mock_aggsender_storage.go @@ -1,6 +1,6 @@ // Code generated by mockery v2.45.0. DO NOT EDIT. -package db +package mocks import ( agglayer "github.com/0xPolygon/cdk/agglayer" diff --git a/aggsender/mock_eth_client.go b/aggsender/mocks/mock_eth_client.go similarity index 99% rename from aggsender/mock_eth_client.go rename to aggsender/mocks/mock_eth_client.go index 6742c054..92a6dad2 100644 --- a/aggsender/mock_eth_client.go +++ b/aggsender/mocks/mock_eth_client.go @@ -1,6 +1,6 @@ // Code generated by mockery v2.45.0. DO NOT EDIT. -package aggsender +package mocks import ( context "context" diff --git a/aggsender/mock_l1infotree_syncer.go b/aggsender/mocks/mock_l1infotree_syncer.go similarity index 99% rename from aggsender/mock_l1infotree_syncer.go rename to aggsender/mocks/mock_l1infotree_syncer.go index 9e036c54..47d22064 100644 --- a/aggsender/mock_l1infotree_syncer.go +++ b/aggsender/mocks/mock_l1infotree_syncer.go @@ -1,6 +1,6 @@ // Code generated by mockery v2.45.0. DO NOT EDIT. -package aggsender +package mocks import ( context "context" diff --git a/aggsender/mock_l2bridge_syncer.go b/aggsender/mocks/mock_l2bridge_syncer.go similarity index 99% rename from aggsender/mock_l2bridge_syncer.go rename to aggsender/mocks/mock_l2bridge_syncer.go index 397b1dc8..bde74b27 100644 --- a/aggsender/mock_l2bridge_syncer.go +++ b/aggsender/mocks/mock_l2bridge_syncer.go @@ -1,6 +1,6 @@ // Code generated by mockery v2.45.0. DO NOT EDIT. -package aggsender +package mocks import ( bridgesync "github.com/0xPolygon/cdk/bridgesync" diff --git a/aggsender/mock_logger.go b/aggsender/mocks/mock_logger.go similarity index 98% rename from aggsender/mock_logger.go rename to aggsender/mocks/mock_logger.go index 6f00145d..553ec510 100644 --- a/aggsender/mock_logger.go +++ b/aggsender/mocks/mock_logger.go @@ -1,6 +1,6 @@ // Code generated by mockery v2.45.0. DO NOT EDIT. -package aggsender +package mocks import mock "github.com/stretchr/testify/mock" diff --git a/aggsender/types/types.go b/aggsender/types/types.go index 0281581c..36227f60 100644 --- a/aggsender/types/types.go +++ b/aggsender/types/types.go @@ -1,12 +1,50 @@ package types import ( + "context" "fmt" + "math/big" "github.com/0xPolygon/cdk/agglayer" + "github.com/0xPolygon/cdk/bridgesync" + "github.com/0xPolygon/cdk/etherman" + "github.com/0xPolygon/cdk/l1infotreesync" + treeTypes "github.com/0xPolygon/cdk/tree/types" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" ) +// 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) +} + +// L2BridgeSyncer is an interface defining functions that an L2BridgeSyncer should implement +type L2BridgeSyncer interface { + GetBlockByLER(ctx context.Context, ler common.Hash) (uint64, error) + GetExitRootByIndex(ctx context.Context, index uint32) (treeTypes.Root, error) + GetBridges(ctx context.Context, fromBlock, toBlock uint64) ([]bridgesync.Bridge, error) + GetClaims(ctx context.Context, fromBlock, toBlock uint64) ([]bridgesync.Claim, error) + OriginNetwork() uint32 + BlockFinality() etherman.BlockNumberFinality +} + +// EthClient is an interface defining functions that an EthClient should implement +type EthClient interface { + BlockNumber(ctx context.Context) (uint64, error) + HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) +} + +// Logger is an interface that defines the methods to log messages +type Logger interface { + Info(args ...interface{}) + Infof(format string, args ...interface{}) + Error(args ...interface{}) + Errorf(format string, args ...interface{}) +} + type CertificateInfo struct { Height uint64 `meddler:"height"` CertificateID common.Hash `meddler:"certificate_id"` diff --git a/test/Makefile b/test/Makefile index f25f4f6e..4e5c5820 100644 --- a/test/Makefile +++ b/test/Makefile @@ -60,11 +60,11 @@ generate-mocks-aggregator: ## Generates mocks for aggregator, using mockery tool .PHONY: generate-mocks-aggsender generate-mocks-aggsender: ## Generates mocks for aggsender, using mockery tool - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=L1InfoTreeSyncer --dir=../aggsender --output=../aggsender --outpkg=aggsender --inpackage --structname=L1InfoTreeSyncerMock --filename=mock_l1infotree_syncer.go - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=L2BridgeSyncer --dir=../aggsender --output=../aggsender --outpkg=aggsender --inpackage --structname=L2BridgeSyncerMock --filename=mock_l2bridge_syncer.go - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=Logger --dir=../aggsender --output=../aggsender --outpkg=aggsender --inpackage --structname=LoggerMock --filename=mock_logger.go - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=AggSenderStorage --dir=../aggsender/db --output=../aggsender/db --outpkg=db --inpackage --structname=AggSenderStorageMock --filename=mock_aggsender_storage.go - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=EthClient --dir=../aggsender --output=../aggsender --outpkg=aggsender --inpackage --structname=EthClientMock --filename=mock_eth_client.go + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=L1InfoTreeSyncer --dir=../aggsender --output=../aggsender/mocks --outpkg=mocks --structname=L1InfoTreeSyncerMock --filename=mock_l1infotree_syncer.go + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=L2BridgeSyncer --dir=../aggsender --output=../aggsender/mocks --outpkg=mocks --structname=L2BridgeSyncerMock --filename=mock_l2bridge_syncer.go + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=Logger --dir=../aggsender --output=../aggsender/mocks --outpkg=mocks --structname=LoggerMock --filename=mock_logger.go + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=AggSenderStorage --dir=../aggsender/db --output=../aggsender/mocks --outpkg=mocks --structname=AggSenderStorageMock --filename=mock_aggsender_storage.go + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=EthClient --dir=../aggsender --output=../aggsender/mocks --outpkg=mocks --structname=EthClientMock --filename=mock_eth_client.go .PHONY: generate-mocks-agglayer generate-mocks-agglayer: ## Generates mocks for agglayer, using mockery tool From 9a76640e50bfb084dd05bc9d08c16366a4bfd046 Mon Sep 17 00:00:00 2001 From: Victor Castell <0x@vcastellm.xyz> Date: Tue, 22 Oct 2024 08:03:33 +0000 Subject: [PATCH 42/84] ci: adjust timeout --- test/bridge-e2e.bats | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/bridge-e2e.bats b/test/bridge-e2e.bats index d6755bce..ab29eced 100644 --- a/test/bridge-e2e.bats +++ b/test/bridge-e2e.bats @@ -141,7 +141,7 @@ setup() { assert_success # Claim withdrawals (settle them on the L1) - timeout="240" + timeout="340" claim_frequency="10" destination_net=$l1_rpc_network_id run wait_for_claim "$timeout" "$claim_frequency" "$l1_rpc_url" From 3805e8a678a796b54044800284cdcb3bd75fff9d Mon Sep 17 00:00:00 2001 From: Victor Castell <0x@vcastellm.xyz> Date: Tue, 22 Oct 2024 08:49:17 +0000 Subject: [PATCH 43/84] ci: timeout and pless --- test/bridge-e2e.bats | 2 +- test/combinations/fork9-cdk-validium.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/bridge-e2e.bats b/test/bridge-e2e.bats index ab29eced..fb50e002 100644 --- a/test/bridge-e2e.bats +++ b/test/bridge-e2e.bats @@ -141,7 +141,7 @@ setup() { assert_success # Claim withdrawals (settle them on the L1) - timeout="340" + timeout="540" claim_frequency="10" destination_net=$l1_rpc_network_id run wait_for_claim "$timeout" "$claim_frequency" "$l1_rpc_url" diff --git a/test/combinations/fork9-cdk-validium.yml b/test/combinations/fork9-cdk-validium.yml index f60fec9c..191993cc 100644 --- a/test/combinations/fork9-cdk-validium.yml +++ b/test/combinations/fork9-cdk-validium.yml @@ -6,7 +6,7 @@ args: cdk_validium_node_image: 0xpolygon/cdk-validium-node:0.7.0-cdk cdk_node_image: cdk zkevm_use_gas_token_contract: true - additional_services: - - pless_zkevm_node + # additional_services: + # - pless_zkevm_node data_availability_mode: cdk-validium sequencer_type: erigon From 76e2de667cfaea8c6f10a3200ee6013d40bd3e1f Mon Sep 17 00:00:00 2001 From: joanestebanr <129153821+joanestebanr@users.noreply.github.com> Date: Tue, 22 Oct 2024 10:53:19 +0200 Subject: [PATCH 44/84] fix: raise condition on L2block beetween aggsender and l2bridgesync --- aggsender/aggsender.go | 31 ++- aggsender/aggsender_test.go | 5 +- aggsender/mocks/mock_aggsender_storage.go | 183 +++++++++++++++- aggsender/mocks/mock_eth_client.go | 81 ++++++- aggsender/mocks/mock_l1infotree_syncer.go | 80 ++++++- aggsender/mocks/mock_l2bridge_syncer.go | 250 +++++++++++++++++++++- aggsender/mocks/mock_logger.go | 152 ++++++++++++- aggsender/types/types.go | 1 + bridgesync/processor.go | 2 +- test/Makefile | 10 +- 10 files changed, 758 insertions(+), 37 deletions(-) diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index bebc18c1..d1f74377 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -116,19 +116,23 @@ func (a *AggSender) sendCertificate(ctx context.Context) error { if err != nil { return fmt.Errorf("error getting block from l2: %w", err) } + lasL2BlockSynced, err := a.l2Syncer.GetLastProcessedBlock(ctx) + if err != nil { + return fmt.Errorf("error getting last processed block from l2: %w", err) + } + log.Debugf("lastL2Block: %d, lasL2BlockSynced: %d", lastL2Block.Number.Uint64(), lasL2BlockSynced) previousLocalExitRoot, previousHeight, lastCertificateBlock, err := a.getLastSentCertificateData(ctx) if err != nil { return err } - - if lastL2Block.Number.Uint64() <= lastCertificateBlock { - a.log.Info("no new blocks to send a certificate") + fromBlock := lastCertificateBlock + 1 + toBlock, err := chooseToL2Block(lastL2Block.Number.Uint64(), lasL2BlockSynced, lastCertificateBlock) + if err != nil { + a.log.Errorf("no new blocks to send a certificate: %s", err.Error()) return nil } - - fromBlock := lastCertificateBlock + 1 - toBlock := lastL2Block.Number.Uint64() + log.Infof("lastL2Block: %d, lasL2BlockSynced: %d choose range:[%d,%d]", lastL2Block.Number.Uint64(), lasL2BlockSynced, fromBlock, toBlock) bridges, err := a.l2Syncer.GetBridges(ctx, fromBlock, toBlock) if err != nil { @@ -182,6 +186,21 @@ func (a *AggSender) sendCertificate(ctx context.Context) error { return nil } +func chooseToL2Block(lastL2Block, lasL2BlockSynced, lastCertificateBlock uint64) (uint64, error) { + if lastL2Block <= lastCertificateBlock { + return lastCertificateBlock, fmt.Errorf("lastL2Block: %d (into RPC) is less than or equal to lastCertificateBlock: %d. So we can't send new cert", lastL2Block, lastCertificateBlock) + } + if lasL2BlockSynced <= lastCertificateBlock { + return lastCertificateBlock, fmt.Errorf("lasL2BlockSynced: %d (into L2 Syncer) is less than or equal to lastCertificateBlock: %d. So we can't send new cert", lastL2Block, lastCertificateBlock) + } + if lasL2BlockSynced <= lastL2Block { + // If lasL2BlockSynced is less than or equal to lastL2Block, means that syncer is not on top of block but + // we can use it to send a new certificate + return lasL2BlockSynced, nil + } + return lastL2Block, nil +} + // buildCertificate builds a certificate from the bridge events func (a *AggSender) buildCertificate(ctx context.Context, bridges []bridgesync.Bridge, diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go index 46cc948d..b081f3e9 100644 --- a/aggsender/aggsender_test.go +++ b/aggsender/aggsender_test.go @@ -1080,7 +1080,10 @@ func TestSendCertificate(t *testing.T) { if cfg.l2HeaderByNumber != nil { mockL2Client = mocks.NewEthClientMock(t) mockL2Client.On("HeaderByNumber", mock.Anything, mock.Anything).Return(cfg.l2HeaderByNumber...).Once() - + if cfg.l2HeaderByNumber[0] != nil { + hdr := cfg.l2HeaderByNumber[0].(*gethTypes.Header) + mockL2Syncer.On("GetLastProcessedBlock", mock.Anything).Return(hdr.Number.Uint64(), nil).Once() + } aggsender.l2Client = mockL2Client } diff --git a/aggsender/mocks/mock_aggsender_storage.go b/aggsender/mocks/mock_aggsender_storage.go index 9c6eb9a8..a5f193fc 100644 --- a/aggsender/mocks/mock_aggsender_storage.go +++ b/aggsender/mocks/mock_aggsender_storage.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.45.0. DO NOT EDIT. +// Code generated by mockery. DO NOT EDIT. package mocks @@ -18,6 +18,14 @@ type AggSenderStorageMock struct { mock.Mock } +type AggSenderStorageMock_Expecter struct { + mock *mock.Mock +} + +func (_m *AggSenderStorageMock) EXPECT() *AggSenderStorageMock_Expecter { + return &AggSenderStorageMock_Expecter{mock: &_m.Mock} +} + // DeleteCertificate provides a mock function with given fields: ctx, certificateID func (_m *AggSenderStorageMock) DeleteCertificate(ctx context.Context, certificateID common.Hash) error { ret := _m.Called(ctx, certificateID) @@ -36,6 +44,35 @@ func (_m *AggSenderStorageMock) DeleteCertificate(ctx context.Context, certifica return r0 } +// AggSenderStorageMock_DeleteCertificate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteCertificate' +type AggSenderStorageMock_DeleteCertificate_Call struct { + *mock.Call +} + +// DeleteCertificate is a helper method to define mock.On call +// - ctx context.Context +// - certificateID common.Hash +func (_e *AggSenderStorageMock_Expecter) DeleteCertificate(ctx interface{}, certificateID interface{}) *AggSenderStorageMock_DeleteCertificate_Call { + return &AggSenderStorageMock_DeleteCertificate_Call{Call: _e.mock.On("DeleteCertificate", ctx, certificateID)} +} + +func (_c *AggSenderStorageMock_DeleteCertificate_Call) Run(run func(ctx context.Context, certificateID common.Hash)) *AggSenderStorageMock_DeleteCertificate_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(common.Hash)) + }) + return _c +} + +func (_c *AggSenderStorageMock_DeleteCertificate_Call) Return(_a0 error) *AggSenderStorageMock_DeleteCertificate_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *AggSenderStorageMock_DeleteCertificate_Call) RunAndReturn(run func(context.Context, common.Hash) error) *AggSenderStorageMock_DeleteCertificate_Call { + _c.Call.Return(run) + return _c +} + // GetCertificateByHeight provides a mock function with given fields: ctx, height func (_m *AggSenderStorageMock) GetCertificateByHeight(ctx context.Context, height uint64) (types.CertificateInfo, error) { ret := _m.Called(ctx, height) @@ -64,6 +101,35 @@ func (_m *AggSenderStorageMock) GetCertificateByHeight(ctx context.Context, heig return r0, r1 } +// AggSenderStorageMock_GetCertificateByHeight_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetCertificateByHeight' +type AggSenderStorageMock_GetCertificateByHeight_Call struct { + *mock.Call +} + +// GetCertificateByHeight is a helper method to define mock.On call +// - ctx context.Context +// - height uint64 +func (_e *AggSenderStorageMock_Expecter) GetCertificateByHeight(ctx interface{}, height interface{}) *AggSenderStorageMock_GetCertificateByHeight_Call { + return &AggSenderStorageMock_GetCertificateByHeight_Call{Call: _e.mock.On("GetCertificateByHeight", ctx, height)} +} + +func (_c *AggSenderStorageMock_GetCertificateByHeight_Call) Run(run func(ctx context.Context, height uint64)) *AggSenderStorageMock_GetCertificateByHeight_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64)) + }) + return _c +} + +func (_c *AggSenderStorageMock_GetCertificateByHeight_Call) Return(_a0 types.CertificateInfo, _a1 error) *AggSenderStorageMock_GetCertificateByHeight_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *AggSenderStorageMock_GetCertificateByHeight_Call) RunAndReturn(run func(context.Context, uint64) (types.CertificateInfo, error)) *AggSenderStorageMock_GetCertificateByHeight_Call { + _c.Call.Return(run) + return _c +} + // GetCertificatesByStatus provides a mock function with given fields: ctx, status func (_m *AggSenderStorageMock) GetCertificatesByStatus(ctx context.Context, status []agglayer.CertificateStatus) ([]*types.CertificateInfo, error) { ret := _m.Called(ctx, status) @@ -94,6 +160,35 @@ func (_m *AggSenderStorageMock) GetCertificatesByStatus(ctx context.Context, sta return r0, r1 } +// AggSenderStorageMock_GetCertificatesByStatus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetCertificatesByStatus' +type AggSenderStorageMock_GetCertificatesByStatus_Call struct { + *mock.Call +} + +// GetCertificatesByStatus is a helper method to define mock.On call +// - ctx context.Context +// - status []agglayer.CertificateStatus +func (_e *AggSenderStorageMock_Expecter) GetCertificatesByStatus(ctx interface{}, status interface{}) *AggSenderStorageMock_GetCertificatesByStatus_Call { + return &AggSenderStorageMock_GetCertificatesByStatus_Call{Call: _e.mock.On("GetCertificatesByStatus", ctx, status)} +} + +func (_c *AggSenderStorageMock_GetCertificatesByStatus_Call) Run(run func(ctx context.Context, status []agglayer.CertificateStatus)) *AggSenderStorageMock_GetCertificatesByStatus_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].([]agglayer.CertificateStatus)) + }) + return _c +} + +func (_c *AggSenderStorageMock_GetCertificatesByStatus_Call) Return(_a0 []*types.CertificateInfo, _a1 error) *AggSenderStorageMock_GetCertificatesByStatus_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *AggSenderStorageMock_GetCertificatesByStatus_Call) RunAndReturn(run func(context.Context, []agglayer.CertificateStatus) ([]*types.CertificateInfo, error)) *AggSenderStorageMock_GetCertificatesByStatus_Call { + _c.Call.Return(run) + return _c +} + // GetLastSentCertificate provides a mock function with given fields: ctx func (_m *AggSenderStorageMock) GetLastSentCertificate(ctx context.Context) (types.CertificateInfo, error) { ret := _m.Called(ctx) @@ -122,6 +217,34 @@ func (_m *AggSenderStorageMock) GetLastSentCertificate(ctx context.Context) (typ return r0, r1 } +// AggSenderStorageMock_GetLastSentCertificate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLastSentCertificate' +type AggSenderStorageMock_GetLastSentCertificate_Call struct { + *mock.Call +} + +// GetLastSentCertificate is a helper method to define mock.On call +// - ctx context.Context +func (_e *AggSenderStorageMock_Expecter) GetLastSentCertificate(ctx interface{}) *AggSenderStorageMock_GetLastSentCertificate_Call { + return &AggSenderStorageMock_GetLastSentCertificate_Call{Call: _e.mock.On("GetLastSentCertificate", ctx)} +} + +func (_c *AggSenderStorageMock_GetLastSentCertificate_Call) Run(run func(ctx context.Context)) *AggSenderStorageMock_GetLastSentCertificate_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *AggSenderStorageMock_GetLastSentCertificate_Call) Return(_a0 types.CertificateInfo, _a1 error) *AggSenderStorageMock_GetLastSentCertificate_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *AggSenderStorageMock_GetLastSentCertificate_Call) RunAndReturn(run func(context.Context) (types.CertificateInfo, error)) *AggSenderStorageMock_GetLastSentCertificate_Call { + _c.Call.Return(run) + return _c +} + // SaveLastSentCertificate provides a mock function with given fields: ctx, certificate func (_m *AggSenderStorageMock) SaveLastSentCertificate(ctx context.Context, certificate types.CertificateInfo) error { ret := _m.Called(ctx, certificate) @@ -140,6 +263,35 @@ func (_m *AggSenderStorageMock) SaveLastSentCertificate(ctx context.Context, cer return r0 } +// AggSenderStorageMock_SaveLastSentCertificate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SaveLastSentCertificate' +type AggSenderStorageMock_SaveLastSentCertificate_Call struct { + *mock.Call +} + +// SaveLastSentCertificate is a helper method to define mock.On call +// - ctx context.Context +// - certificate types.CertificateInfo +func (_e *AggSenderStorageMock_Expecter) SaveLastSentCertificate(ctx interface{}, certificate interface{}) *AggSenderStorageMock_SaveLastSentCertificate_Call { + return &AggSenderStorageMock_SaveLastSentCertificate_Call{Call: _e.mock.On("SaveLastSentCertificate", ctx, certificate)} +} + +func (_c *AggSenderStorageMock_SaveLastSentCertificate_Call) Run(run func(ctx context.Context, certificate types.CertificateInfo)) *AggSenderStorageMock_SaveLastSentCertificate_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(types.CertificateInfo)) + }) + return _c +} + +func (_c *AggSenderStorageMock_SaveLastSentCertificate_Call) Return(_a0 error) *AggSenderStorageMock_SaveLastSentCertificate_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *AggSenderStorageMock_SaveLastSentCertificate_Call) RunAndReturn(run func(context.Context, types.CertificateInfo) error) *AggSenderStorageMock_SaveLastSentCertificate_Call { + _c.Call.Return(run) + return _c +} + // UpdateCertificateStatus provides a mock function with given fields: ctx, certificate func (_m *AggSenderStorageMock) UpdateCertificateStatus(ctx context.Context, certificate types.CertificateInfo) error { ret := _m.Called(ctx, certificate) @@ -158,6 +310,35 @@ func (_m *AggSenderStorageMock) UpdateCertificateStatus(ctx context.Context, cer return r0 } +// AggSenderStorageMock_UpdateCertificateStatus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateCertificateStatus' +type AggSenderStorageMock_UpdateCertificateStatus_Call struct { + *mock.Call +} + +// UpdateCertificateStatus is a helper method to define mock.On call +// - ctx context.Context +// - certificate types.CertificateInfo +func (_e *AggSenderStorageMock_Expecter) UpdateCertificateStatus(ctx interface{}, certificate interface{}) *AggSenderStorageMock_UpdateCertificateStatus_Call { + return &AggSenderStorageMock_UpdateCertificateStatus_Call{Call: _e.mock.On("UpdateCertificateStatus", ctx, certificate)} +} + +func (_c *AggSenderStorageMock_UpdateCertificateStatus_Call) Run(run func(ctx context.Context, certificate types.CertificateInfo)) *AggSenderStorageMock_UpdateCertificateStatus_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(types.CertificateInfo)) + }) + return _c +} + +func (_c *AggSenderStorageMock_UpdateCertificateStatus_Call) Return(_a0 error) *AggSenderStorageMock_UpdateCertificateStatus_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *AggSenderStorageMock_UpdateCertificateStatus_Call) RunAndReturn(run func(context.Context, types.CertificateInfo) error) *AggSenderStorageMock_UpdateCertificateStatus_Call { + _c.Call.Return(run) + return _c +} + // NewAggSenderStorageMock creates a new instance of AggSenderStorageMock. 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 NewAggSenderStorageMock(t interface { diff --git a/aggsender/mocks/mock_eth_client.go b/aggsender/mocks/mock_eth_client.go index 92a6dad2..ebf618bf 100644 --- a/aggsender/mocks/mock_eth_client.go +++ b/aggsender/mocks/mock_eth_client.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.45.0. DO NOT EDIT. +// Code generated by mockery. DO NOT EDIT. package mocks @@ -6,9 +6,9 @@ import ( context "context" big "math/big" - mock "github.com/stretchr/testify/mock" + coretypes "github.com/ethereum/go-ethereum/core/types" - types "github.com/ethereum/go-ethereum/core/types" + mock "github.com/stretchr/testify/mock" ) // EthClientMock is an autogenerated mock type for the EthClient type @@ -16,6 +16,14 @@ type EthClientMock struct { mock.Mock } +type EthClientMock_Expecter struct { + mock *mock.Mock +} + +func (_m *EthClientMock) EXPECT() *EthClientMock_Expecter { + return &EthClientMock_Expecter{mock: &_m.Mock} +} + // BlockNumber provides a mock function with given fields: ctx func (_m *EthClientMock) BlockNumber(ctx context.Context) (uint64, error) { ret := _m.Called(ctx) @@ -44,24 +52,52 @@ func (_m *EthClientMock) BlockNumber(ctx context.Context) (uint64, error) { return r0, r1 } +// EthClientMock_BlockNumber_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BlockNumber' +type EthClientMock_BlockNumber_Call struct { + *mock.Call +} + +// BlockNumber is a helper method to define mock.On call +// - ctx context.Context +func (_e *EthClientMock_Expecter) BlockNumber(ctx interface{}) *EthClientMock_BlockNumber_Call { + return &EthClientMock_BlockNumber_Call{Call: _e.mock.On("BlockNumber", ctx)} +} + +func (_c *EthClientMock_BlockNumber_Call) Run(run func(ctx context.Context)) *EthClientMock_BlockNumber_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *EthClientMock_BlockNumber_Call) Return(_a0 uint64, _a1 error) *EthClientMock_BlockNumber_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EthClientMock_BlockNumber_Call) RunAndReturn(run func(context.Context) (uint64, error)) *EthClientMock_BlockNumber_Call { + _c.Call.Return(run) + return _c +} + // HeaderByNumber provides a mock function with given fields: ctx, number -func (_m *EthClientMock) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { +func (_m *EthClientMock) HeaderByNumber(ctx context.Context, number *big.Int) (*coretypes.Header, error) { ret := _m.Called(ctx, number) if len(ret) == 0 { panic("no return value specified for HeaderByNumber") } - var r0 *types.Header + var r0 *coretypes.Header var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *big.Int) (*types.Header, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, *big.Int) (*coretypes.Header, error)); ok { return rf(ctx, number) } - if rf, ok := ret.Get(0).(func(context.Context, *big.Int) *types.Header); ok { + if rf, ok := ret.Get(0).(func(context.Context, *big.Int) *coretypes.Header); ok { r0 = rf(ctx, number) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(*types.Header) + r0 = ret.Get(0).(*coretypes.Header) } } @@ -74,6 +110,35 @@ func (_m *EthClientMock) HeaderByNumber(ctx context.Context, number *big.Int) (* return r0, r1 } +// EthClientMock_HeaderByNumber_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'HeaderByNumber' +type EthClientMock_HeaderByNumber_Call struct { + *mock.Call +} + +// HeaderByNumber is a helper method to define mock.On call +// - ctx context.Context +// - number *big.Int +func (_e *EthClientMock_Expecter) HeaderByNumber(ctx interface{}, number interface{}) *EthClientMock_HeaderByNumber_Call { + return &EthClientMock_HeaderByNumber_Call{Call: _e.mock.On("HeaderByNumber", ctx, number)} +} + +func (_c *EthClientMock_HeaderByNumber_Call) Run(run func(ctx context.Context, number *big.Int)) *EthClientMock_HeaderByNumber_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*big.Int)) + }) + return _c +} + +func (_c *EthClientMock_HeaderByNumber_Call) Return(_a0 *coretypes.Header, _a1 error) *EthClientMock_HeaderByNumber_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EthClientMock_HeaderByNumber_Call) RunAndReturn(run func(context.Context, *big.Int) (*coretypes.Header, error)) *EthClientMock_HeaderByNumber_Call { + _c.Call.Return(run) + return _c +} + // NewEthClientMock creates a new instance of EthClientMock. 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 NewEthClientMock(t interface { diff --git a/aggsender/mocks/mock_l1infotree_syncer.go b/aggsender/mocks/mock_l1infotree_syncer.go index 47d22064..3fe26bd2 100644 --- a/aggsender/mocks/mock_l1infotree_syncer.go +++ b/aggsender/mocks/mock_l1infotree_syncer.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.45.0. DO NOT EDIT. +// Code generated by mockery. DO NOT EDIT. package mocks @@ -11,7 +11,7 @@ import ( mock "github.com/stretchr/testify/mock" - types "github.com/0xPolygon/cdk/tree/types" + treetypes "github.com/0xPolygon/cdk/tree/types" ) // L1InfoTreeSyncerMock is an autogenerated mock type for the L1InfoTreeSyncer type @@ -19,6 +19,14 @@ type L1InfoTreeSyncerMock struct { mock.Mock } +type L1InfoTreeSyncerMock_Expecter struct { + mock *mock.Mock +} + +func (_m *L1InfoTreeSyncerMock) EXPECT() *L1InfoTreeSyncerMock_Expecter { + return &L1InfoTreeSyncerMock_Expecter{mock: &_m.Mock} +} + // GetInfoByGlobalExitRoot provides a mock function with given fields: globalExitRoot func (_m *L1InfoTreeSyncerMock) GetInfoByGlobalExitRoot(globalExitRoot common.Hash) (*l1infotreesync.L1InfoTreeLeaf, error) { ret := _m.Called(globalExitRoot) @@ -49,24 +57,52 @@ func (_m *L1InfoTreeSyncerMock) GetInfoByGlobalExitRoot(globalExitRoot common.Ha return r0, r1 } +// L1InfoTreeSyncerMock_GetInfoByGlobalExitRoot_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetInfoByGlobalExitRoot' +type L1InfoTreeSyncerMock_GetInfoByGlobalExitRoot_Call struct { + *mock.Call +} + +// GetInfoByGlobalExitRoot is a helper method to define mock.On call +// - globalExitRoot common.Hash +func (_e *L1InfoTreeSyncerMock_Expecter) GetInfoByGlobalExitRoot(globalExitRoot interface{}) *L1InfoTreeSyncerMock_GetInfoByGlobalExitRoot_Call { + return &L1InfoTreeSyncerMock_GetInfoByGlobalExitRoot_Call{Call: _e.mock.On("GetInfoByGlobalExitRoot", globalExitRoot)} +} + +func (_c *L1InfoTreeSyncerMock_GetInfoByGlobalExitRoot_Call) Run(run func(globalExitRoot common.Hash)) *L1InfoTreeSyncerMock_GetInfoByGlobalExitRoot_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(common.Hash)) + }) + return _c +} + +func (_c *L1InfoTreeSyncerMock_GetInfoByGlobalExitRoot_Call) Return(_a0 *l1infotreesync.L1InfoTreeLeaf, _a1 error) *L1InfoTreeSyncerMock_GetInfoByGlobalExitRoot_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *L1InfoTreeSyncerMock_GetInfoByGlobalExitRoot_Call) RunAndReturn(run func(common.Hash) (*l1infotreesync.L1InfoTreeLeaf, error)) *L1InfoTreeSyncerMock_GetInfoByGlobalExitRoot_Call { + _c.Call.Return(run) + return _c +} + // GetL1InfoTreeMerkleProofFromIndexToRoot provides a mock function with given fields: ctx, index, root -func (_m *L1InfoTreeSyncerMock) GetL1InfoTreeMerkleProofFromIndexToRoot(ctx context.Context, index uint32, root common.Hash) (types.Proof, error) { +func (_m *L1InfoTreeSyncerMock) GetL1InfoTreeMerkleProofFromIndexToRoot(ctx context.Context, index uint32, root common.Hash) (treetypes.Proof, error) { ret := _m.Called(ctx, index, root) if len(ret) == 0 { panic("no return value specified for GetL1InfoTreeMerkleProofFromIndexToRoot") } - var r0 types.Proof + var r0 treetypes.Proof var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, common.Hash) (types.Proof, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, uint32, common.Hash) (treetypes.Proof, error)); ok { return rf(ctx, index, root) } - if rf, ok := ret.Get(0).(func(context.Context, uint32, common.Hash) types.Proof); ok { + if rf, ok := ret.Get(0).(func(context.Context, uint32, common.Hash) treetypes.Proof); ok { r0 = rf(ctx, index, root) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(types.Proof) + r0 = ret.Get(0).(treetypes.Proof) } } @@ -79,6 +115,36 @@ func (_m *L1InfoTreeSyncerMock) GetL1InfoTreeMerkleProofFromIndexToRoot(ctx cont return r0, r1 } +// L1InfoTreeSyncerMock_GetL1InfoTreeMerkleProofFromIndexToRoot_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetL1InfoTreeMerkleProofFromIndexToRoot' +type L1InfoTreeSyncerMock_GetL1InfoTreeMerkleProofFromIndexToRoot_Call struct { + *mock.Call +} + +// GetL1InfoTreeMerkleProofFromIndexToRoot is a helper method to define mock.On call +// - ctx context.Context +// - index uint32 +// - root common.Hash +func (_e *L1InfoTreeSyncerMock_Expecter) GetL1InfoTreeMerkleProofFromIndexToRoot(ctx interface{}, index interface{}, root interface{}) *L1InfoTreeSyncerMock_GetL1InfoTreeMerkleProofFromIndexToRoot_Call { + return &L1InfoTreeSyncerMock_GetL1InfoTreeMerkleProofFromIndexToRoot_Call{Call: _e.mock.On("GetL1InfoTreeMerkleProofFromIndexToRoot", ctx, index, root)} +} + +func (_c *L1InfoTreeSyncerMock_GetL1InfoTreeMerkleProofFromIndexToRoot_Call) Run(run func(ctx context.Context, index uint32, root common.Hash)) *L1InfoTreeSyncerMock_GetL1InfoTreeMerkleProofFromIndexToRoot_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint32), args[2].(common.Hash)) + }) + return _c +} + +func (_c *L1InfoTreeSyncerMock_GetL1InfoTreeMerkleProofFromIndexToRoot_Call) Return(_a0 treetypes.Proof, _a1 error) *L1InfoTreeSyncerMock_GetL1InfoTreeMerkleProofFromIndexToRoot_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *L1InfoTreeSyncerMock_GetL1InfoTreeMerkleProofFromIndexToRoot_Call) RunAndReturn(run func(context.Context, uint32, common.Hash) (treetypes.Proof, error)) *L1InfoTreeSyncerMock_GetL1InfoTreeMerkleProofFromIndexToRoot_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 { diff --git a/aggsender/mocks/mock_l2bridge_syncer.go b/aggsender/mocks/mock_l2bridge_syncer.go index bde74b27..7d87e230 100644 --- a/aggsender/mocks/mock_l2bridge_syncer.go +++ b/aggsender/mocks/mock_l2bridge_syncer.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.45.0. DO NOT EDIT. +// Code generated by mockery. DO NOT EDIT. package mocks @@ -12,7 +12,7 @@ import ( mock "github.com/stretchr/testify/mock" - types "github.com/0xPolygon/cdk/tree/types" + treetypes "github.com/0xPolygon/cdk/tree/types" ) // L2BridgeSyncerMock is an autogenerated mock type for the L2BridgeSyncer type @@ -20,6 +20,14 @@ type L2BridgeSyncerMock struct { mock.Mock } +type L2BridgeSyncerMock_Expecter struct { + mock *mock.Mock +} + +func (_m *L2BridgeSyncerMock) EXPECT() *L2BridgeSyncerMock_Expecter { + return &L2BridgeSyncerMock_Expecter{mock: &_m.Mock} +} + // BlockFinality provides a mock function with given fields: func (_m *L2BridgeSyncerMock) BlockFinality() etherman.BlockNumberFinality { ret := _m.Called() @@ -38,6 +46,33 @@ func (_m *L2BridgeSyncerMock) BlockFinality() etherman.BlockNumberFinality { return r0 } +// L2BridgeSyncerMock_BlockFinality_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BlockFinality' +type L2BridgeSyncerMock_BlockFinality_Call struct { + *mock.Call +} + +// BlockFinality is a helper method to define mock.On call +func (_e *L2BridgeSyncerMock_Expecter) BlockFinality() *L2BridgeSyncerMock_BlockFinality_Call { + return &L2BridgeSyncerMock_BlockFinality_Call{Call: _e.mock.On("BlockFinality")} +} + +func (_c *L2BridgeSyncerMock_BlockFinality_Call) Run(run func()) *L2BridgeSyncerMock_BlockFinality_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *L2BridgeSyncerMock_BlockFinality_Call) Return(_a0 etherman.BlockNumberFinality) *L2BridgeSyncerMock_BlockFinality_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *L2BridgeSyncerMock_BlockFinality_Call) RunAndReturn(run func() etherman.BlockNumberFinality) *L2BridgeSyncerMock_BlockFinality_Call { + _c.Call.Return(run) + return _c +} + // GetBlockByLER provides a mock function with given fields: ctx, ler func (_m *L2BridgeSyncerMock) GetBlockByLER(ctx context.Context, ler common.Hash) (uint64, error) { ret := _m.Called(ctx, ler) @@ -66,6 +101,35 @@ func (_m *L2BridgeSyncerMock) GetBlockByLER(ctx context.Context, ler common.Hash return r0, r1 } +// L2BridgeSyncerMock_GetBlockByLER_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetBlockByLER' +type L2BridgeSyncerMock_GetBlockByLER_Call struct { + *mock.Call +} + +// GetBlockByLER is a helper method to define mock.On call +// - ctx context.Context +// - ler common.Hash +func (_e *L2BridgeSyncerMock_Expecter) GetBlockByLER(ctx interface{}, ler interface{}) *L2BridgeSyncerMock_GetBlockByLER_Call { + return &L2BridgeSyncerMock_GetBlockByLER_Call{Call: _e.mock.On("GetBlockByLER", ctx, ler)} +} + +func (_c *L2BridgeSyncerMock_GetBlockByLER_Call) Run(run func(ctx context.Context, ler common.Hash)) *L2BridgeSyncerMock_GetBlockByLER_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(common.Hash)) + }) + return _c +} + +func (_c *L2BridgeSyncerMock_GetBlockByLER_Call) Return(_a0 uint64, _a1 error) *L2BridgeSyncerMock_GetBlockByLER_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *L2BridgeSyncerMock_GetBlockByLER_Call) RunAndReturn(run func(context.Context, common.Hash) (uint64, error)) *L2BridgeSyncerMock_GetBlockByLER_Call { + _c.Call.Return(run) + return _c +} + // GetBridges provides a mock function with given fields: ctx, fromBlock, toBlock func (_m *L2BridgeSyncerMock) GetBridges(ctx context.Context, fromBlock uint64, toBlock uint64) ([]bridgesync.Bridge, error) { ret := _m.Called(ctx, fromBlock, toBlock) @@ -96,6 +160,36 @@ func (_m *L2BridgeSyncerMock) GetBridges(ctx context.Context, fromBlock uint64, return r0, r1 } +// L2BridgeSyncerMock_GetBridges_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetBridges' +type L2BridgeSyncerMock_GetBridges_Call struct { + *mock.Call +} + +// GetBridges is a helper method to define mock.On call +// - ctx context.Context +// - fromBlock uint64 +// - toBlock uint64 +func (_e *L2BridgeSyncerMock_Expecter) GetBridges(ctx interface{}, fromBlock interface{}, toBlock interface{}) *L2BridgeSyncerMock_GetBridges_Call { + return &L2BridgeSyncerMock_GetBridges_Call{Call: _e.mock.On("GetBridges", ctx, fromBlock, toBlock)} +} + +func (_c *L2BridgeSyncerMock_GetBridges_Call) Run(run func(ctx context.Context, fromBlock uint64, toBlock uint64)) *L2BridgeSyncerMock_GetBridges_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64), args[2].(uint64)) + }) + return _c +} + +func (_c *L2BridgeSyncerMock_GetBridges_Call) Return(_a0 []bridgesync.Bridge, _a1 error) *L2BridgeSyncerMock_GetBridges_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *L2BridgeSyncerMock_GetBridges_Call) RunAndReturn(run func(context.Context, uint64, uint64) ([]bridgesync.Bridge, error)) *L2BridgeSyncerMock_GetBridges_Call { + _c.Call.Return(run) + return _c +} + // GetClaims provides a mock function with given fields: ctx, fromBlock, toBlock func (_m *L2BridgeSyncerMock) GetClaims(ctx context.Context, fromBlock uint64, toBlock uint64) ([]bridgesync.Claim, error) { ret := _m.Called(ctx, fromBlock, toBlock) @@ -126,23 +220,53 @@ func (_m *L2BridgeSyncerMock) GetClaims(ctx context.Context, fromBlock uint64, t return r0, r1 } +// L2BridgeSyncerMock_GetClaims_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetClaims' +type L2BridgeSyncerMock_GetClaims_Call struct { + *mock.Call +} + +// GetClaims is a helper method to define mock.On call +// - ctx context.Context +// - fromBlock uint64 +// - toBlock uint64 +func (_e *L2BridgeSyncerMock_Expecter) GetClaims(ctx interface{}, fromBlock interface{}, toBlock interface{}) *L2BridgeSyncerMock_GetClaims_Call { + return &L2BridgeSyncerMock_GetClaims_Call{Call: _e.mock.On("GetClaims", ctx, fromBlock, toBlock)} +} + +func (_c *L2BridgeSyncerMock_GetClaims_Call) Run(run func(ctx context.Context, fromBlock uint64, toBlock uint64)) *L2BridgeSyncerMock_GetClaims_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64), args[2].(uint64)) + }) + return _c +} + +func (_c *L2BridgeSyncerMock_GetClaims_Call) Return(_a0 []bridgesync.Claim, _a1 error) *L2BridgeSyncerMock_GetClaims_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *L2BridgeSyncerMock_GetClaims_Call) RunAndReturn(run func(context.Context, uint64, uint64) ([]bridgesync.Claim, error)) *L2BridgeSyncerMock_GetClaims_Call { + _c.Call.Return(run) + return _c +} + // GetExitRootByIndex provides a mock function with given fields: ctx, index -func (_m *L2BridgeSyncerMock) GetExitRootByIndex(ctx context.Context, index uint32) (types.Root, error) { +func (_m *L2BridgeSyncerMock) GetExitRootByIndex(ctx context.Context, index uint32) (treetypes.Root, error) { ret := _m.Called(ctx, index) if len(ret) == 0 { panic("no return value specified for GetExitRootByIndex") } - var r0 types.Root + var r0 treetypes.Root var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) (types.Root, error)); ok { + 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) types.Root); ok { + if rf, ok := ret.Get(0).(func(context.Context, uint32) treetypes.Root); ok { r0 = rf(ctx, index) } else { - r0 = ret.Get(0).(types.Root) + r0 = ret.Get(0).(treetypes.Root) } if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { @@ -154,6 +278,91 @@ func (_m *L2BridgeSyncerMock) GetExitRootByIndex(ctx context.Context, index uint return r0, r1 } +// L2BridgeSyncerMock_GetExitRootByIndex_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetExitRootByIndex' +type L2BridgeSyncerMock_GetExitRootByIndex_Call struct { + *mock.Call +} + +// GetExitRootByIndex is a helper method to define mock.On call +// - ctx context.Context +// - index uint32 +func (_e *L2BridgeSyncerMock_Expecter) GetExitRootByIndex(ctx interface{}, index interface{}) *L2BridgeSyncerMock_GetExitRootByIndex_Call { + return &L2BridgeSyncerMock_GetExitRootByIndex_Call{Call: _e.mock.On("GetExitRootByIndex", ctx, index)} +} + +func (_c *L2BridgeSyncerMock_GetExitRootByIndex_Call) Run(run func(ctx context.Context, index uint32)) *L2BridgeSyncerMock_GetExitRootByIndex_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint32)) + }) + return _c +} + +func (_c *L2BridgeSyncerMock_GetExitRootByIndex_Call) Return(_a0 treetypes.Root, _a1 error) *L2BridgeSyncerMock_GetExitRootByIndex_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *L2BridgeSyncerMock_GetExitRootByIndex_Call) RunAndReturn(run func(context.Context, uint32) (treetypes.Root, error)) *L2BridgeSyncerMock_GetExitRootByIndex_Call { + _c.Call.Return(run) + return _c +} + +// GetLastProcessedBlock provides a mock function with given fields: ctx +func (_m *L2BridgeSyncerMock) GetLastProcessedBlock(ctx context.Context) (uint64, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetLastProcessedBlock") + } + + var r0 uint64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (uint64, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) uint64); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(uint64) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// L2BridgeSyncerMock_GetLastProcessedBlock_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLastProcessedBlock' +type L2BridgeSyncerMock_GetLastProcessedBlock_Call struct { + *mock.Call +} + +// GetLastProcessedBlock is a helper method to define mock.On call +// - ctx context.Context +func (_e *L2BridgeSyncerMock_Expecter) GetLastProcessedBlock(ctx interface{}) *L2BridgeSyncerMock_GetLastProcessedBlock_Call { + return &L2BridgeSyncerMock_GetLastProcessedBlock_Call{Call: _e.mock.On("GetLastProcessedBlock", ctx)} +} + +func (_c *L2BridgeSyncerMock_GetLastProcessedBlock_Call) Run(run func(ctx context.Context)) *L2BridgeSyncerMock_GetLastProcessedBlock_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *L2BridgeSyncerMock_GetLastProcessedBlock_Call) Return(_a0 uint64, _a1 error) *L2BridgeSyncerMock_GetLastProcessedBlock_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *L2BridgeSyncerMock_GetLastProcessedBlock_Call) RunAndReturn(run func(context.Context) (uint64, error)) *L2BridgeSyncerMock_GetLastProcessedBlock_Call { + _c.Call.Return(run) + return _c +} + // OriginNetwork provides a mock function with given fields: func (_m *L2BridgeSyncerMock) OriginNetwork() uint32 { ret := _m.Called() @@ -172,6 +381,33 @@ func (_m *L2BridgeSyncerMock) OriginNetwork() uint32 { return r0 } +// L2BridgeSyncerMock_OriginNetwork_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OriginNetwork' +type L2BridgeSyncerMock_OriginNetwork_Call struct { + *mock.Call +} + +// OriginNetwork is a helper method to define mock.On call +func (_e *L2BridgeSyncerMock_Expecter) OriginNetwork() *L2BridgeSyncerMock_OriginNetwork_Call { + return &L2BridgeSyncerMock_OriginNetwork_Call{Call: _e.mock.On("OriginNetwork")} +} + +func (_c *L2BridgeSyncerMock_OriginNetwork_Call) Run(run func()) *L2BridgeSyncerMock_OriginNetwork_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *L2BridgeSyncerMock_OriginNetwork_Call) Return(_a0 uint32) *L2BridgeSyncerMock_OriginNetwork_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *L2BridgeSyncerMock_OriginNetwork_Call) RunAndReturn(run func() uint32) *L2BridgeSyncerMock_OriginNetwork_Call { + _c.Call.Return(run) + return _c +} + // NewL2BridgeSyncerMock creates a new instance of L2BridgeSyncerMock. 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 NewL2BridgeSyncerMock(t interface { diff --git a/aggsender/mocks/mock_logger.go b/aggsender/mocks/mock_logger.go index 553ec510..b058d2a7 100644 --- a/aggsender/mocks/mock_logger.go +++ b/aggsender/mocks/mock_logger.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.45.0. DO NOT EDIT. +// Code generated by mockery. DO NOT EDIT. package mocks @@ -9,6 +9,14 @@ type LoggerMock struct { mock.Mock } +type LoggerMock_Expecter struct { + mock *mock.Mock +} + +func (_m *LoggerMock) EXPECT() *LoggerMock_Expecter { + return &LoggerMock_Expecter{mock: &_m.Mock} +} + // Error provides a mock function with given fields: args func (_m *LoggerMock) Error(args ...interface{}) { var _ca []interface{} @@ -16,6 +24,41 @@ func (_m *LoggerMock) Error(args ...interface{}) { _m.Called(_ca...) } +// LoggerMock_Error_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Error' +type LoggerMock_Error_Call struct { + *mock.Call +} + +// Error is a helper method to define mock.On call +// - args ...interface{} +func (_e *LoggerMock_Expecter) Error(args ...interface{}) *LoggerMock_Error_Call { + return &LoggerMock_Error_Call{Call: _e.mock.On("Error", + append([]interface{}{}, args...)...)} +} + +func (_c *LoggerMock_Error_Call) Run(run func(args ...interface{})) *LoggerMock_Error_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-0) + for i, a := range args[0:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(variadicArgs...) + }) + return _c +} + +func (_c *LoggerMock_Error_Call) Return() *LoggerMock_Error_Call { + _c.Call.Return() + return _c +} + +func (_c *LoggerMock_Error_Call) RunAndReturn(run func(...interface{})) *LoggerMock_Error_Call { + _c.Call.Return(run) + return _c +} + // Errorf provides a mock function with given fields: format, args func (_m *LoggerMock) Errorf(format string, args ...interface{}) { var _ca []interface{} @@ -24,6 +67,42 @@ func (_m *LoggerMock) Errorf(format string, args ...interface{}) { _m.Called(_ca...) } +// LoggerMock_Errorf_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Errorf' +type LoggerMock_Errorf_Call struct { + *mock.Call +} + +// Errorf is a helper method to define mock.On call +// - format string +// - args ...interface{} +func (_e *LoggerMock_Expecter) Errorf(format interface{}, args ...interface{}) *LoggerMock_Errorf_Call { + return &LoggerMock_Errorf_Call{Call: _e.mock.On("Errorf", + append([]interface{}{format}, args...)...)} +} + +func (_c *LoggerMock_Errorf_Call) Run(run func(format string, args ...interface{})) *LoggerMock_Errorf_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-1) + for i, a := range args[1:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(args[0].(string), variadicArgs...) + }) + return _c +} + +func (_c *LoggerMock_Errorf_Call) Return() *LoggerMock_Errorf_Call { + _c.Call.Return() + return _c +} + +func (_c *LoggerMock_Errorf_Call) RunAndReturn(run func(string, ...interface{})) *LoggerMock_Errorf_Call { + _c.Call.Return(run) + return _c +} + // Info provides a mock function with given fields: args func (_m *LoggerMock) Info(args ...interface{}) { var _ca []interface{} @@ -31,6 +110,41 @@ func (_m *LoggerMock) Info(args ...interface{}) { _m.Called(_ca...) } +// LoggerMock_Info_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Info' +type LoggerMock_Info_Call struct { + *mock.Call +} + +// Info is a helper method to define mock.On call +// - args ...interface{} +func (_e *LoggerMock_Expecter) Info(args ...interface{}) *LoggerMock_Info_Call { + return &LoggerMock_Info_Call{Call: _e.mock.On("Info", + append([]interface{}{}, args...)...)} +} + +func (_c *LoggerMock_Info_Call) Run(run func(args ...interface{})) *LoggerMock_Info_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-0) + for i, a := range args[0:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(variadicArgs...) + }) + return _c +} + +func (_c *LoggerMock_Info_Call) Return() *LoggerMock_Info_Call { + _c.Call.Return() + return _c +} + +func (_c *LoggerMock_Info_Call) RunAndReturn(run func(...interface{})) *LoggerMock_Info_Call { + _c.Call.Return(run) + return _c +} + // Infof provides a mock function with given fields: format, args func (_m *LoggerMock) Infof(format string, args ...interface{}) { var _ca []interface{} @@ -39,6 +153,42 @@ func (_m *LoggerMock) Infof(format string, args ...interface{}) { _m.Called(_ca...) } +// LoggerMock_Infof_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Infof' +type LoggerMock_Infof_Call struct { + *mock.Call +} + +// Infof is a helper method to define mock.On call +// - format string +// - args ...interface{} +func (_e *LoggerMock_Expecter) Infof(format interface{}, args ...interface{}) *LoggerMock_Infof_Call { + return &LoggerMock_Infof_Call{Call: _e.mock.On("Infof", + append([]interface{}{format}, args...)...)} +} + +func (_c *LoggerMock_Infof_Call) Run(run func(format string, args ...interface{})) *LoggerMock_Infof_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-1) + for i, a := range args[1:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(args[0].(string), variadicArgs...) + }) + return _c +} + +func (_c *LoggerMock_Infof_Call) Return() *LoggerMock_Infof_Call { + _c.Call.Return() + return _c +} + +func (_c *LoggerMock_Infof_Call) RunAndReturn(run func(string, ...interface{})) *LoggerMock_Infof_Call { + _c.Call.Return(run) + return _c +} + // NewLoggerMock creates a new instance of LoggerMock. 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 NewLoggerMock(t interface { diff --git a/aggsender/types/types.go b/aggsender/types/types.go index 36227f60..423bfe73 100644 --- a/aggsender/types/types.go +++ b/aggsender/types/types.go @@ -29,6 +29,7 @@ type L2BridgeSyncer interface { GetClaims(ctx context.Context, fromBlock, toBlock uint64) ([]bridgesync.Claim, error) OriginNetwork() uint32 BlockFinality() etherman.BlockNumberFinality + GetLastProcessedBlock(ctx context.Context) (uint64, error) } // EthClient is an interface defining functions that an EthClient should implement diff --git a/bridgesync/processor.go b/bridgesync/processor.go index cf279c41..87c8a327 100644 --- a/bridgesync/processor.go +++ b/bridgesync/processor.go @@ -201,7 +201,7 @@ func (p *processor) isBlockProcessed(tx db.Querier, blockNum uint64) error { return err } if lpb < blockNum { - return ErrBlockNotProcessed + return fmt.Errorf("block %d not processed, last %d. Err:%w", blockNum, lpb, ErrBlockNotProcessed) } return nil } diff --git a/test/Makefile b/test/Makefile index 4e5c5820..96ede079 100644 --- a/test/Makefile +++ b/test/Makefile @@ -60,11 +60,11 @@ generate-mocks-aggregator: ## Generates mocks for aggregator, using mockery tool .PHONY: generate-mocks-aggsender generate-mocks-aggsender: ## Generates mocks for aggsender, using mockery tool - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=L1InfoTreeSyncer --dir=../aggsender --output=../aggsender/mocks --outpkg=mocks --structname=L1InfoTreeSyncerMock --filename=mock_l1infotree_syncer.go - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=L2BridgeSyncer --dir=../aggsender --output=../aggsender/mocks --outpkg=mocks --structname=L2BridgeSyncerMock --filename=mock_l2bridge_syncer.go - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=Logger --dir=../aggsender --output=../aggsender/mocks --outpkg=mocks --structname=LoggerMock --filename=mock_logger.go - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=AggSenderStorage --dir=../aggsender/db --output=../aggsender/mocks --outpkg=mocks --structname=AggSenderStorageMock --filename=mock_aggsender_storage.go - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=EthClient --dir=../aggsender --output=../aggsender/mocks --outpkg=mocks --structname=EthClientMock --filename=mock_eth_client.go + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=L1InfoTreeSyncer --dir=../aggsender/types --output=../aggsender/mocks --outpkg=mocks --structname=L1InfoTreeSyncerMock --filename=mock_l1infotree_syncer.go ${COMMON_MOCKERY_PARAMS} + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=L2BridgeSyncer --dir=../aggsender/types --output=../aggsender/mocks --outpkg=mocks --structname=L2BridgeSyncerMock --filename=mock_l2bridge_syncer.go ${COMMON_MOCKERY_PARAMS} + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=Logger --dir=../aggsender/types --output=../aggsender/mocks --outpkg=mocks --structname=LoggerMock --filename=mock_logger.go ${COMMON_MOCKERY_PARAMS} + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=AggSenderStorage --dir=../aggsender/db --output=../aggsender/mocks --outpkg=mocks --structname=AggSenderStorageMock --filename=mock_aggsender_storage.go ${COMMON_MOCKERY_PARAMS} + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=EthClient --dir=../aggsender/types --output=../aggsender/mocks --outpkg=mocks --structname=EthClientMock --filename=mock_eth_client.go ${COMMON_MOCKERY_PARAMS} .PHONY: generate-mocks-agglayer generate-mocks-agglayer: ## Generates mocks for agglayer, using mockery tool From 57f943b9e1556c4788b3b908c9fec8870f22d9a6 Mon Sep 17 00:00:00 2001 From: joanestebanr <129153821+joanestebanr@users.noreply.github.com> Date: Tue, 22 Oct 2024 11:06:19 +0200 Subject: [PATCH 45/84] fix: lint --- aggsender/aggsender.go | 9 ++++++--- aggsender/aggsender_test.go | 6 ++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index d1f74377..33fb4ddd 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -132,7 +132,8 @@ func (a *AggSender) sendCertificate(ctx context.Context) error { a.log.Errorf("no new blocks to send a certificate: %s", err.Error()) return nil } - log.Infof("lastL2Block: %d, lasL2BlockSynced: %d choose range:[%d,%d]", lastL2Block.Number.Uint64(), lasL2BlockSynced, fromBlock, toBlock) + a.log.Infof("lastL2Block: %d, lasL2BlockSynced: %d choose range:[%d,%d]", + lastL2Block.Number.Uint64(), lasL2BlockSynced, fromBlock, toBlock) bridges, err := a.l2Syncer.GetBridges(ctx, fromBlock, toBlock) if err != nil { @@ -188,10 +189,12 @@ func (a *AggSender) sendCertificate(ctx context.Context) error { func chooseToL2Block(lastL2Block, lasL2BlockSynced, lastCertificateBlock uint64) (uint64, error) { if lastL2Block <= lastCertificateBlock { - return lastCertificateBlock, fmt.Errorf("lastL2Block: %d (into RPC) is less than or equal to lastCertificateBlock: %d. So we can't send new cert", lastL2Block, lastCertificateBlock) + return lastCertificateBlock, fmt.Errorf("lastL2Block: %d (into RPC) is <= to lastCertificateBlock: %d", + lastL2Block, lastCertificateBlock) } if lasL2BlockSynced <= lastCertificateBlock { - return lastCertificateBlock, fmt.Errorf("lasL2BlockSynced: %d (into L2 Syncer) is less than or equal to lastCertificateBlock: %d. So we can't send new cert", lastL2Block, lastCertificateBlock) + return lastCertificateBlock, fmt.Errorf("lasL2BlockSynced: %d (into L2 Syncer) is <= to lastCertificateBlock: %d", + lastL2Block, lastCertificateBlock) } if lasL2BlockSynced <= lastL2Block { // If lasL2BlockSynced is less than or equal to lastL2Block, means that syncer is not on top of block but diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go index b081f3e9..456ffb5a 100644 --- a/aggsender/aggsender_test.go +++ b/aggsender/aggsender_test.go @@ -1081,8 +1081,10 @@ func TestSendCertificate(t *testing.T) { mockL2Client = mocks.NewEthClientMock(t) mockL2Client.On("HeaderByNumber", mock.Anything, mock.Anything).Return(cfg.l2HeaderByNumber...).Once() if cfg.l2HeaderByNumber[0] != nil { - hdr := cfg.l2HeaderByNumber[0].(*gethTypes.Header) - mockL2Syncer.On("GetLastProcessedBlock", mock.Anything).Return(hdr.Number.Uint64(), nil).Once() + hdr, ok := cfg.l2HeaderByNumber[0].(*gethTypes.Header) + if ok { + mockL2Syncer.On("GetLastProcessedBlock", mock.Anything).Return(hdr.Number.Uint64(), nil).Once() + } } aggsender.l2Client = mockL2Client } From 7029ba5f3579144d608c2bbba5cbe9c3e15d4228 Mon Sep 17 00:00:00 2001 From: Victor Castell <0x@vcastellm.xyz> Date: Tue, 22 Oct 2024 09:35:16 +0000 Subject: [PATCH 46/84] refactor: apply feedback --- test/bridge-e2e.bats | 9 ++++----- test/helpers/common.bash | 6 +++--- test/helpers/lxly-bridge-test.bash | 4 +++- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/test/bridge-e2e.bats b/test/bridge-e2e.bats index fb50e002..646066e8 100644 --- a/test/bridge-e2e.bats +++ b/test/bridge-e2e.bats @@ -21,7 +21,7 @@ setup() { destination_addr=${DESTINATION_ADDRESS:-"0x0bb7AA0b4FdC2D2862c088424260e99ed6299148"} ether_value=${ETHER_VALUE:-"0.0200000054"} amount=$(cast to-wei $ether_value ether) - readonly native_token_addr=${TOKEN_ADDRESS:-"0x0000000000000000000000000000000000000000"} + readonly native_token_addr=${NATIVE_TOKEN_ADDRESS:-"0x0000000000000000000000000000000000000000"} if [[ -n "$GAS_TOKEN_ADDR" ]]; then echo "Using provided GAS_TOKEN_ADDR: $GAS_TOKEN_ADDR" >&3 gas_token_addr="$GAS_TOKEN_ADDR" @@ -35,7 +35,6 @@ setup() { fi readonly is_forced=${IS_FORCED:-"true"} readonly bridge_addr=$BRIDGE_ADDRESS - readonly bridge_sig='bridgeAsset(uint32,address,uint256,address,bool,bytes)' readonly meta_bytes=${META_BYTES:-"0x"} readonly l1_rpc_url=${L1_ETH_RPC_URL:-"$(kurtosis port print $enclave el-1-geth-lighthouse rpc)"} @@ -53,7 +52,7 @@ setup() { echo "Initial receiver balance of native token on L2 $initial_receiver_balance" >&3 echo "Running LxLy deposit" >&3 - run deposit "$native_token_addr" "$l1_rpc_url" + run bridgeAsset "$native_token_addr" "$l1_rpc_url" assert_success echo "Running LxLy claim" >&3 @@ -113,7 +112,7 @@ setup() { destination_addr=$receiver destination_net=$l2_rpc_network_id amount=$wei_amount - run deposit "$gas_token_addr" "$l1_rpc_url" + run bridgeAsset "$gas_token_addr" "$l1_rpc_url" assert_success # Claim deposits (settle them on the L2) @@ -137,7 +136,7 @@ setup() { echo "Receiver balance of gas token on L1 $initial_receiver_balance" >&3 destination_net=$l1_rpc_network_id - run deposit "$native_token_addr" "$l2_rpc_url" + run bridgeAsset "$native_token_addr" "$l2_rpc_url" assert_success # Claim withdrawals (settle them on the L1) diff --git a/test/helpers/common.bash b/test/helpers/common.bash index c9e825c1..3ba729ff 100644 --- a/test/helpers/common.bash +++ b/test/helpers/common.bash @@ -296,7 +296,7 @@ function check_balances() { function verify_balance() { local rpc_url="$1" # RPC URL - local gas_token_addr="$2" # gas token contract address + local token_addr="$2" # gas token contract address local account="$3" # account address local initial_balance_wei="$4" # initial balance in Wei (decimal) local ether_amount="$5" # amount to be added (in Ether, decimal) @@ -307,10 +307,10 @@ function verify_balance() { # Get final balance in wei (after the operation) local final_balance_wei - if [[ $gas_token_addr == "0x0000000000000000000000000000000000000000" ]]; then + if [[ $token_addr == "0x0000000000000000000000000000000000000000" ]]; then final_balance_wei=$(cast balance "$account" --rpc-url "$rpc_url" | awk '{print $1}') else - final_balance_wei=$(cast call --rpc-url "$rpc_url" "$gas_token_addr" "$balance_of_fn_sig" "$destination_addr" | awk '{print $1}') + final_balance_wei=$(cast call --rpc-url "$rpc_url" "$token_addr" "$balance_of_fn_sig" "$destination_addr" | awk '{print $1}') fi echo "Final balance of $account in $rpc_url: $final_balance_wei wei" >&3 diff --git a/test/helpers/lxly-bridge-test.bash b/test/helpers/lxly-bridge-test.bash index f723c277..7b3cb008 100644 --- a/test/helpers/lxly-bridge-test.bash +++ b/test/helpers/lxly-bridge-test.bash @@ -1,8 +1,10 @@ #!/usr/bin/env bash # Error code reference https://hackmd.io/WwahVBZERJKdfK3BbKxzQQ -function deposit() { +function bridgeAsset() { local token_addr="$1" local rpc_url="$2" + readonly bridge_sig='bridgeAsset(uint32,address,uint256,address,bool,bytes)' + if [[ $token_addr == "0x0000000000000000000000000000000000000000" ]]; then echo "The ETH balance for sender "$sender_addr":" >&3 cast balance -e --rpc-url $rpc_url $sender_addr >&3 From d7400037029838eda4df7652a9c1abb15435c426 Mon Sep 17 00:00:00 2001 From: Victor Castell <0x@vcastellm.xyz> Date: Tue, 22 Oct 2024 10:24:43 +0000 Subject: [PATCH 47/84] ci: bump prover --- test/combinations/fork9-cdk-validium.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/combinations/fork9-cdk-validium.yml b/test/combinations/fork9-cdk-validium.yml index 191993cc..29af42fa 100644 --- a/test/combinations/fork9-cdk-validium.yml +++ b/test/combinations/fork9-cdk-validium.yml @@ -1,6 +1,6 @@ args: zkevm_contracts_image: leovct/zkevm-contracts:v6.0.0-rc.1-fork.9 - zkevm_prover_image: hermeznetwork/zkevm-prover:v6.0.4 + zkevm_prover_image: hermeznetwork/zkevm-prover:v6.0.6 cdk_erigon_node_image: hermeznetwork/cdk-erigon:v2.1.0 zkevm_node_image: hermeznetwork/zkevm-node:v0.7.3-RC1 cdk_validium_node_image: 0xpolygon/cdk-validium-node:0.7.0-cdk From 716d7e50dd1d79e40b633b6b5ad09f092a29e036 Mon Sep 17 00:00:00 2001 From: joanestebanr <129153821+joanestebanr@users.noreply.github.com> Date: Tue, 22 Oct 2024 15:13:37 +0200 Subject: [PATCH 48/84] fix: error l1infotreesync.GetInfoByGlobalExitRoot --- l1infotreesync/processor.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/l1infotreesync/processor.go b/l1infotreesync/processor.go index e7115a60..2cd6190c 100644 --- a/l1infotreesync/processor.go +++ b/l1infotreesync/processor.go @@ -402,7 +402,7 @@ func (p *processor) GetInfoByGlobalExitRoot(ger common.Hash) (*L1InfoTreeLeaf, e SELECT * FROM l1info_leaf WHERE global_exit_root = $1 LIMIT 1; - `, ger.Hex()) + `, ger.String()) return info, db.ReturnErrNotFound(err) } From 3bc59b349612e549d463a4b95901b9222ad6daaa Mon Sep 17 00:00:00 2001 From: joanestebanr <129153821+joanestebanr@users.noreply.github.com> Date: Tue, 22 Oct 2024 15:48:15 +0200 Subject: [PATCH 49/84] fix: getRHTNode bug --- tree/tree.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tree/tree.go b/tree/tree.go index ffcc2c54..0a9176f4 100644 --- a/tree/tree.go +++ b/tree/tree.go @@ -123,7 +123,7 @@ func (t *Tree) getRHTNode(tx db.Querier, nodeHash common.Hash) (*types.TreeNode, err := meddler.QueryRow( tx, node, fmt.Sprintf(`select * from %s where hash = $1`, t.rhtTable), - nodeHash.Hex(), + nodeHash.String(), ) if err != nil { if errors.Is(err, sql.ErrNoRows) { From 4aeeae53adbc4ec20480b8590b4bc45b8af8adcf Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Tue, 22 Oct 2024 16:03:22 +0200 Subject: [PATCH 50/84] fix: unit test --- bridgesync/processor.go | 6 +++--- bridgesync/processor_test.go | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/bridgesync/processor.go b/bridgesync/processor.go index 87c8a327..dbd11a1d 100644 --- a/bridgesync/processor.go +++ b/bridgesync/processor.go @@ -26,8 +26,8 @@ const ( ) var ( - // ErrBlockNotProcessed indicates that the given block(s) have not been processed yet. - ErrBlockNotProcessed = errors.New("given block(s) have not been processed yet") + // errBlockNotProcessedFormat indicates that the given block(s) have not been processed yet. + errBlockNotProcessedFormat = fmt.Sprintf("block %%d not processed, last processed: %%d") ) // Bridge is the representation of a bridge event @@ -201,7 +201,7 @@ func (p *processor) isBlockProcessed(tx db.Querier, blockNum uint64) error { return err } if lpb < blockNum { - return fmt.Errorf("block %d not processed, last %d. Err:%w", blockNum, lpb, ErrBlockNotProcessed) + return fmt.Errorf(errBlockNotProcessedFormat, blockNum, lpb) } return nil } diff --git a/bridgesync/processor_test.go b/bridgesync/processor_test.go index 11d1ea7b..c4ed607d 100644 --- a/bridgesync/processor_test.go +++ b/bridgesync/processor_test.go @@ -64,7 +64,7 @@ func TestProceessor(t *testing.T) { fromBlock: 0, toBlock: 2, expectedClaims: nil, - expectedErr: ErrBlockNotProcessed, + expectedErr: fmt.Errorf(errBlockNotProcessedFormat, 2, 0), }, &getBridges{ p: p, @@ -73,7 +73,7 @@ func TestProceessor(t *testing.T) { fromBlock: 0, toBlock: 2, expectedBridges: nil, - expectedErr: ErrBlockNotProcessed, + expectedErr: fmt.Errorf(errBlockNotProcessedFormat, 2, 0), }, &processBlockAction{ p: p, @@ -96,7 +96,7 @@ func TestProceessor(t *testing.T) { fromBlock: 0, toBlock: 2, expectedClaims: nil, - expectedErr: ErrBlockNotProcessed, + expectedErr: fmt.Errorf(errBlockNotProcessedFormat, 2, 1), }, &getBridges{ p: p, @@ -105,7 +105,7 @@ func TestProceessor(t *testing.T) { fromBlock: 0, toBlock: 2, expectedBridges: nil, - expectedErr: ErrBlockNotProcessed, + expectedErr: fmt.Errorf(errBlockNotProcessedFormat, 2, 1), }, &getClaims{ p: p, @@ -139,7 +139,7 @@ func TestProceessor(t *testing.T) { fromBlock: 0, toBlock: 2, expectedClaims: nil, - expectedErr: ErrBlockNotProcessed, + expectedErr: fmt.Errorf(errBlockNotProcessedFormat, 2, 0), }, &getBridges{ p: p, @@ -148,7 +148,7 @@ func TestProceessor(t *testing.T) { fromBlock: 0, toBlock: 2, expectedBridges: nil, - expectedErr: ErrBlockNotProcessed, + expectedErr: fmt.Errorf(errBlockNotProcessedFormat, 2, 0), }, &processBlockAction{ p: p, From 213139e64421b00f43e6726822379f0519c82407 Mon Sep 17 00:00:00 2001 From: Victor Castell <0x@vcastellm.xyz> Date: Tue, 22 Oct 2024 16:05:51 +0000 Subject: [PATCH 51/84] ci: try something --- test/bridge-e2e.bats | 6 +++--- test/combinations/fork11-rollup.yml | 2 -- test/combinations/fork9-cdk-validium.yml | 4 ++-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/test/bridge-e2e.bats b/test/bridge-e2e.bats index 646066e8..8bd166ac 100644 --- a/test/bridge-e2e.bats +++ b/test/bridge-e2e.bats @@ -130,7 +130,6 @@ setup() { echo "Running LxLy withdrawal" >&3 echo "Gas token addr $gas_token_addr, L1 RPC: $l1_rpc_url" >&3 - # Validate that the native token of receiver on L1 has increased by the bridge tokens amount local initial_receiver_balance=$(cast call --rpc-url "$l1_rpc_url" "$gas_token_addr" "$balance_of_fn_sig" "$destination_addr" | awk '{print $1}') assert_success echo "Receiver balance of gas token on L1 $initial_receiver_balance" >&3 @@ -140,12 +139,13 @@ setup() { assert_success # Claim withdrawals (settle them on the L1) - timeout="540" + timeout="120" claim_frequency="10" destination_net=$l1_rpc_network_id run wait_for_claim "$timeout" "$claim_frequency" "$l1_rpc_url" - assert_success + # assert_success + # Validate that the token of receiver on L1 has increased by the bridge tokens amount run verify_balance "$l1_rpc_url" "$gas_token_addr" "$destination_addr" "$initial_receiver_balance" "$ether_value" if [ $status -eq 0 ]; then break diff --git a/test/combinations/fork11-rollup.yml b/test/combinations/fork11-rollup.yml index 653adc9d..1afd8f79 100644 --- a/test/combinations/fork11-rollup.yml +++ b/test/combinations/fork11-rollup.yml @@ -5,7 +5,5 @@ args: zkevm_node_image: hermeznetwork/zkevm-node:v0.7.0-fork11-RC1 cdk_node_image: cdk zkevm_use_gas_token_contract: true - additional_services: - - pless_zkevm_node data_availability_mode: rollup sequencer_type: erigon diff --git a/test/combinations/fork9-cdk-validium.yml b/test/combinations/fork9-cdk-validium.yml index 29af42fa..c28b2c49 100644 --- a/test/combinations/fork9-cdk-validium.yml +++ b/test/combinations/fork9-cdk-validium.yml @@ -6,7 +6,7 @@ args: cdk_validium_node_image: 0xpolygon/cdk-validium-node:0.7.0-cdk cdk_node_image: cdk zkevm_use_gas_token_contract: true - # additional_services: - # - pless_zkevm_node + additional_services: + - pless_zkevm_node data_availability_mode: cdk-validium sequencer_type: erigon From 899210ca86124319d35aa1acec5cdee80159673a Mon Sep 17 00:00:00 2001 From: Victor Castell <0x@vcastellm.xyz> Date: Tue, 22 Oct 2024 16:55:11 +0000 Subject: [PATCH 52/84] ci: try --- test/bridge-e2e.bats | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/bridge-e2e.bats b/test/bridge-e2e.bats index 8bd166ac..67b0d413 100644 --- a/test/bridge-e2e.bats +++ b/test/bridge-e2e.bats @@ -139,7 +139,7 @@ setup() { assert_success # Claim withdrawals (settle them on the L1) - timeout="120" + timeout="360" claim_frequency="10" destination_net=$l1_rpc_network_id run wait_for_claim "$timeout" "$claim_frequency" "$l1_rpc_url" From 2098a6b209cd352c79139051acb7bcc482e1a98e Mon Sep 17 00:00:00 2001 From: Victor Castell <0x@vcastellm.xyz> Date: Wed, 23 Oct 2024 07:37:19 +0000 Subject: [PATCH 53/84] ci: peek at bridge file --- test/helpers/lxly-bridge-test.bash | 1 + 1 file changed, 1 insertion(+) diff --git a/test/helpers/lxly-bridge-test.bash b/test/helpers/lxly-bridge-test.bash index 7b3cb008..bb441bbd 100644 --- a/test/helpers/lxly-bridge-test.bash +++ b/test/helpers/lxly-bridge-test.bash @@ -37,6 +37,7 @@ function claim() { readonly claimable_deposit_file=$(mktemp) echo "Getting full list of deposits" >&3 curl -s "$bridge_api_url/bridges/$destination_addr?limit=100&offset=0" | jq '.' | tee $bridge_deposit_file + cat $bridge_deposit_file >&3 echo "Looking for claimable deposits" >&3 jq '[.deposits[] | select(.ready_for_claim == true and .claim_tx_hash == "" and .dest_net == '$destination_net')]' $bridge_deposit_file | tee $claimable_deposit_file From f90062e95b8654707a2052bf3b9fd26a3e0b77f4 Mon Sep 17 00:00:00 2001 From: Victor Castell <0x@vcastellm.xyz> Date: Wed, 23 Oct 2024 08:49:07 +0000 Subject: [PATCH 54/84] ci: upload logs --- .github/workflows/test-e2e.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index 787d2301..fe422513 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -81,3 +81,23 @@ jobs: env: KURTOSIS_FOLDER: ${{ github.workspace }}/kurtosis-cdk BATS_LIB_PATH: /usr/lib/ + + - name: Prepare logs + working-directory: ./kurtosis-cdk + if: failure() + run: | + mkdir -p ci_logs + cd ci_logs + kurtosis service logs cdk cdk-erigon-node-001 --all > cdk-erigon-node-001.log + kurtosis service logs cdk cdk-erigon-sequencer-001 --all > cdk-erigon-sequencer-001.log + kurtosis service logs cdk zkevm-agglayer-001 --all > zkevm-agglayer-001.log + kurtosis service logs cdk zkevm-prover-001 --all > zkevm-prover-001.log + kurtosis service logs cdk cdk-node-001 --all > cdk-node-001.log + kurtosis service logs cdk zkevm-bridge-service-001 --all > zkevm-bridge-service-001.log + + - name: Upload logs + if: failure() + uses: actions/upload-artifact@v3 + with: + name: logs_${{ github.run_id }} + path: ./kurtosis-cdk/ci_logs From 9b2aeade30adf6d3badfcd083ace1d2f74e00d8b Mon Sep 17 00:00:00 2001 From: joanestebanr <129153821+joanestebanr@users.noreply.github.com> Date: Wed, 23 Oct 2024 11:00:23 +0200 Subject: [PATCH 55/84] fix: add to remove previous data --- scripts/local_config | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/local_config b/scripts/local_config index 1d5aa751..d8eba1a4 100755 --- a/scripts/local_config +++ b/scripts/local_config @@ -78,7 +78,7 @@ function export_key_from_toml_file_or_fatal(){ local _KEY="$4" local _VALUE=$(get_value_from_toml_file $_FILE $_SECTION $_KEY) if [ -z "$_VALUE" ]; then - log_fatal "$FUNCNAME: key $_KEY not found in section $_SECTION" + log_fatal "$FUNCNAME: key $_KEY not found in section $_SECTION in file $_FILE" fi export $_EXPORTED_VAR_NAME="$_VALUE" log_debug "$_EXPORTED_VAR_NAME=${!_EXPORTED_VAR_NAME} \t\t\t# file:$_FILE section:$_SECTION key:$_KEY" @@ -348,6 +348,7 @@ echo "- Stop cdk-node:" echo " kurtosis service stop cdk-v1 cdk-node-001" echo " " echo "- Add next configuration to vscode launch.json" +echo " -----------------------------------------------------------" cat << EOF { "name": "Debug cdk", @@ -367,3 +368,7 @@ cat << EOF "-components", "aggsender", EOF +echo " -----------------------------------------------------------" +echo " " +echo " - rembember to clean previous execution data: " +echo " rm -Rf ${path_rw_data}/*" \ No newline at end of file From 04f427aa3a0dfec03130fd8c3954787afc9b78be Mon Sep 17 00:00:00 2001 From: joanestebanr <129153821+joanestebanr@users.noreply.github.com> Date: Wed, 23 Oct 2024 11:02:20 +0200 Subject: [PATCH 56/84] fix: sqlite extesion for default db --- config/default.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/config/default.go b/config/default.go index bf75d8b2..fe7005c3 100644 --- a/config/default.go +++ b/config/default.go @@ -268,7 +268,7 @@ WriteTimeout = "2s" MaxRequestsPerIPAndSecond = 10 [ClaimSponsor] -DBPath = "/{{PathRWData}}/claimsopnsor" +DBPath = "/{{PathRWData}}/claimsopnsor.sqlite" Enabled = true SenderAddr = "0xfa3b44587990f97ba8b6ba7e230a5f0e95d14b3d" BridgeAddrL2 = "0xB7098a13a48EcE087d3DA15b2D28eCE0f89819B8" @@ -300,7 +300,7 @@ GasOffset = 0 HTTPHeaders = [] [BridgeL1Sync] -DBPath = "{{PathRWData}}/bridgel1sync" +DBPath = "{{PathRWData}}/bridgel1sync.sqlite" BlockFinality = "LatestBlock" InitialBlockNum = 0 BridgeAddr = "{{polygonBridgeAddr}}" @@ -310,7 +310,7 @@ MaxRetryAttemptsAfterError = -1 WaitForNewBlocksPeriod = "3s" [BridgeL2Sync] -DBPath = "{{PathRWData}}/bridgel2sync" +DBPath = "{{PathRWData}}/bridgel2sync.sqlite" BlockFinality = "LatestBlock" InitialBlockNum = 0 BridgeAddr = "{{polygonBridgeAddr}}" @@ -321,7 +321,7 @@ WaitForNewBlocksPeriod = "3s" [LastGERSync] # MDBX database path -DBPath = "{{PathRWData}}/lastgersync" +DBPath = "{{PathRWData}}/lastgersync.sqlite" BlockFinality = "LatestBlock" InitialBlockNum = 0 GlobalExitRootL2Addr = "{{L2Config.GlobalExitRootAddr}}" From ab08783694e0412b073fb9d9c4184229a73effa1 Mon Sep 17 00:00:00 2001 From: joanestebanr <129153821+joanestebanr@users.noreply.github.com> Date: Wed, 23 Oct 2024 11:08:58 +0200 Subject: [PATCH 57/84] fix: allow to generate proof of partial tree --- tree/tree.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tree/tree.go b/tree/tree.go index 0a9176f4..6abb9e3d 100644 --- a/tree/tree.go +++ b/tree/tree.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/0xPolygon/cdk/db" + "github.com/0xPolygon/cdk/log" "github.com/0xPolygon/cdk/tree/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -113,7 +114,8 @@ func (t *Tree) GetProof(ctx context.Context, index uint32, root common.Hash) (ty return types.Proof{}, err } if isErrNotFound { - return types.Proof{}, db.ErrNotFound + // TODO: Validate it. It returns a proof of a tree with missing leafs + log.Warnf("getSiblings returned proof with zero hashes for index %d and root %s", index, root.String()) } return siblings, nil } From df721ecca5dd141e832b1cd31e433bf43e6232db Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Thu, 24 Oct 2024 09:21:43 +0200 Subject: [PATCH 58/84] fix: remove execution clients --- aggsender/aggsender.go | 91 +++---------- aggsender/aggsender_test.go | 247 ++++++++++-------------------------- cmd/run.go | 6 +- 3 files changed, 85 insertions(+), 259 deletions(-) diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index 33fb4ddd..bc4e115b 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -5,7 +5,6 @@ import ( "crypto/ecdsa" "errors" "fmt" - "sync" "time" "github.com/0xPolygon/cdk/agglayer" @@ -16,7 +15,6 @@ import ( "github.com/0xPolygon/cdk/l1infotreesync" "github.com/0xPolygon/cdk/log" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" ) @@ -27,9 +25,7 @@ type AggSender struct { log aggsendertypes.Logger l2Syncer aggsendertypes.L2BridgeSyncer - l2Client aggsendertypes.EthClient l1infoTreeSyncer aggsendertypes.L1InfoTreeSyncer - l1Client aggsendertypes.EthClient storage db.AggSenderStorage aggLayerClient agglayer.AgglayerClientInterface @@ -37,9 +33,6 @@ type AggSender struct { cfg Config sequencerKey *ecdsa.PrivateKey - - lock sync.Mutex - lastL1CertificateBlock uint64 } // New returns a new AggSender @@ -48,10 +41,8 @@ func New( logger *log.Logger, cfg Config, aggLayerClient agglayer.AgglayerClientInterface, - l1Client aggsendertypes.EthClient, l1InfoTreeSyncer *l1infotreesync.L1InfoTreeSync, - l2Syncer *bridgesync.BridgeSync, - l2Client aggsendertypes.EthClient) (*AggSender, error) { + l2Syncer *bridgesync.BridgeSync) (*AggSender, error) { storage, err := db.NewAggSenderSQLStorage(logger, cfg.DBPath) if err != nil { return nil, err @@ -67,8 +58,6 @@ func New( log: logger, storage: storage, l2Syncer: l2Syncer, - l2Client: l2Client, - l1Client: l1Client, aggLayerClient: aggLayerClient, l1infoTreeSyncer: l1InfoTreeSyncer, sequencerKey: sequencerPrivateKey, @@ -102,38 +91,34 @@ func (a *AggSender) sendCertificates(ctx context.Context) { func (a *AggSender) sendCertificate(ctx context.Context) error { a.log.Infof("trying to send a new certificate...") - l1Block, err := a.l1Client.BlockNumber(ctx) + shouldSend, err := a.shouldSendCertificate(ctx) if err != nil { - return fmt.Errorf("error getting l1 block number: %w", err) + return err } - if !a.shouldSendCertificate(l1Block) { - a.log.Infof("block %d on L1 not near epoch ending, so we don't send a certificate", l1Block) + if !shouldSend { + a.log.Infof("waiting for pending certificates to be settled") return nil } - lastL2Block, err := a.getLastL2Block(ctx) - if err != nil { - return fmt.Errorf("error getting block from l2: %w", err) - } lasL2BlockSynced, err := a.l2Syncer.GetLastProcessedBlock(ctx) if err != nil { return fmt.Errorf("error getting last processed block from l2: %w", err) } - log.Debugf("lastL2Block: %d, lasL2BlockSynced: %d", lastL2Block.Number.Uint64(), lasL2BlockSynced) previousLocalExitRoot, previousHeight, lastCertificateBlock, err := a.getLastSentCertificateData(ctx) if err != nil { return err } - fromBlock := lastCertificateBlock + 1 - toBlock, err := chooseToL2Block(lastL2Block.Number.Uint64(), lasL2BlockSynced, lastCertificateBlock) - if err != nil { - a.log.Errorf("no new blocks to send a certificate: %s", err.Error()) + + if lastCertificateBlock >= lasL2BlockSynced { + a.log.Infof("no new blocks to send a certificate, last certificate block: %d, last L2 block: %d", + lastCertificateBlock, lasL2BlockSynced) return nil } - a.log.Infof("lastL2Block: %d, lasL2BlockSynced: %d choose range:[%d,%d]", - lastL2Block.Number.Uint64(), lasL2BlockSynced, fromBlock, toBlock) + + fromBlock := lastCertificateBlock + 1 + toBlock := lasL2BlockSynced bridges, err := a.l2Syncer.GetBridges(ctx, fromBlock, toBlock) if err != nil { @@ -177,33 +162,12 @@ func (a *AggSender) sendCertificate(ctx context.Context) error { return fmt.Errorf("error saving last sent certificate in db: %w", err) } - a.lock.Lock() - a.lastL1CertificateBlock = l1Block - a.lock.Unlock() - a.log.Infof("certificate: %s sent successfully for range of l2 blocks (from block: %d, to block: %d)", certificateHash, fromBlock, toBlock) return nil } -func chooseToL2Block(lastL2Block, lasL2BlockSynced, lastCertificateBlock uint64) (uint64, error) { - if lastL2Block <= lastCertificateBlock { - return lastCertificateBlock, fmt.Errorf("lastL2Block: %d (into RPC) is <= to lastCertificateBlock: %d", - lastL2Block, lastCertificateBlock) - } - if lasL2BlockSynced <= lastCertificateBlock { - return lastCertificateBlock, fmt.Errorf("lasL2BlockSynced: %d (into L2 Syncer) is <= to lastCertificateBlock: %d", - lastL2Block, lastCertificateBlock) - } - if lasL2BlockSynced <= lastL2Block { - // If lasL2BlockSynced is less than or equal to lastL2Block, means that syncer is not on top of block but - // we can use it to send a new certificate - return lasL2BlockSynced, nil - } - return lastL2Block, nil -} - // buildCertificate builds a certificate from the bridge events func (a *AggSender) buildCertificate(ctx context.Context, bridges []bridgesync.Bridge, @@ -294,17 +258,6 @@ func (a *AggSender) getLastSentCertificateData(ctx context.Context) (common.Hash return previousLocalExitRoot, previousHeight, lastCertificateBlock, nil } -// getLastL2Block gets the last L2 block based on the finality configured for l2 bridge syncer -func (a *AggSender) getLastL2Block(ctx context.Context) (*types.Header, error) { - finality := a.l2Syncer.BlockFinality() - blockFinality, err := finality.ToBlockNum() - if err != nil { - return nil, fmt.Errorf("error getting block finality: %w", err) - } - - return a.l2Client.HeaderByNumber(ctx, blockFinality) -} - // convertClaimToImportedBridgeExit converts a claim to an ImportedBridgeExit object func (a *AggSender) convertClaimToImportedBridgeExit(claim bridgesync.Claim) (*agglayer.ImportedBridgeExit, error) { leafType := agglayer.LeafTypeAsset @@ -505,19 +458,13 @@ func (a *AggSender) checkPendingCertificatesStatus(ctx context.Context) { } } -// shouldSendCertificate checks if a certificate should be sent at given L1 block -// we send certificates at two blocks before the epoch ending so we get most of the -// bridges and claims in that epoch -func (a *AggSender) shouldSendCertificate(block uint64) bool { - if block == 0 { - return false +// shouldSendCertificate checks if a certificate should be sent at given time +// if we have pending certificates, then we wait until they are settled +func (a *AggSender) shouldSendCertificate(ctx context.Context) (bool, error) { + pendingCertificates, err := a.storage.GetCertificatesByStatus(ctx, []agglayer.CertificateStatus{agglayer.Pending}) + if err != nil { + return false, fmt.Errorf("error getting pending certificates: %w", err) } - a.lock.Lock() - lastL1BlockSeen := a.lastL1CertificateBlock - a.lock.Unlock() - - shouldSend := lastL1BlockSeen+a.cfg.EpochSize-a.cfg.BlocksBeforeEpochEnding <= block - - return shouldSend + return len(pendingCertificates) == 0, nil } diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go index 456ffb5a..db8ec9a7 100644 --- a/aggsender/aggsender_test.go +++ b/aggsender/aggsender_test.go @@ -14,12 +14,10 @@ import ( aggsendertypes "github.com/0xPolygon/cdk/aggsender/types" "github.com/0xPolygon/cdk/bridgesync" "github.com/0xPolygon/cdk/config/types" - "github.com/0xPolygon/cdk/etherman" "github.com/0xPolygon/cdk/l1infotreesync" "github.com/0xPolygon/cdk/log" treeTypes "github.com/0xPolygon/cdk/tree/types" "github.com/ethereum/go-ethereum/common" - gethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -133,6 +131,7 @@ func TestConvertClaimToImportedBridgeExit(t *testing.T) { }) } } + func TestGetBridgeExits(t *testing.T) { t.Parallel() @@ -772,75 +771,6 @@ func generateTestProof(t *testing.T) treeTypes.Proof { return proof } -func TestShouldSendCertificate(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - block uint64 - epochSize uint64 - blocksBeforeEpochEnding uint64 - lastL1CertificateBlock uint64 - expectedResult bool - }{ - { - name: "Should send certificate", - block: 8, - epochSize: 10, - blocksBeforeEpochEnding: 2, - expectedResult: true, - }, - { - name: "Should send certificate - another case", - block: 9, - epochSize: 10, - blocksBeforeEpochEnding: 1, - expectedResult: true, - }, - { - name: "Should not send certificate", - block: 25, - epochSize: 10, - lastL1CertificateBlock: 18, - blocksBeforeEpochEnding: 2, - expectedResult: false, - }, - { - name: "Should not send certificate at zero block", - block: 0, - epochSize: 1, - blocksBeforeEpochEnding: 2, - expectedResult: false, - }, - { - name: "Should send certificate with large epoch size", - block: 1998, - epochSize: 1000, - lastL1CertificateBlock: 998, - blocksBeforeEpochEnding: 2, - expectedResult: true, - }, - } - - for _, tt := range tests { - tt := tt - - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - aggSender := &AggSender{ - cfg: Config{ - EpochSize: tt.epochSize, - BlocksBeforeEpochEnding: tt.blocksBeforeEpochEnding, - }, - lastL1CertificateBlock: tt.lastL1CertificateBlock, - } - result := aggSender.shouldSendCertificate(tt.block) - require.Equal(t, tt.expectedResult, result) - }) - } -} - func TestCheckIfCertificatesAreSettled(t *testing.T) { t.Parallel() @@ -988,10 +918,9 @@ func TestSendCertificate(t *testing.T) { type testCfg struct { name string sequencerKey *ecdsa.PrivateKey - l1BlockNumber []interface{} + shouldSendCertificate []interface{} getLastSentCertificate []interface{} - l2BlockFinality []interface{} - l2HeaderByNumber []interface{} + lastL2BlockProcessed []interface{} getCertificateHeader []interface{} deleteCertificate []interface{} getCertificateByHeight []interface{} @@ -1006,33 +935,31 @@ func TestSendCertificate(t *testing.T) { expectedError string } - setupTest := func(cfg testCfg) (*AggSender, *mocks.AggSenderStorageMock, *mocks.EthClientMock, *mocks.L2BridgeSyncerMock, - *mocks.EthClientMock, *agglayer.AgglayerClientMock, *mocks.L1InfoTreeSyncerMock) { + setupTest := func(cfg testCfg) (*AggSender, *mocks.AggSenderStorageMock, *mocks.L2BridgeSyncerMock, + *agglayer.AgglayerClientMock, *mocks.L1InfoTreeSyncerMock) { var ( aggsender = &AggSender{ log: log.WithFields("aggsender", 1), cfg: Config{EpochSize: 10, BlocksBeforeEpochEnding: 2}, sequencerKey: cfg.sequencerKey, } - mockL1Client *mocks.EthClientMock mockStorage *mocks.AggSenderStorageMock mockL2Syncer *mocks.L2BridgeSyncerMock - mockL2Client *mocks.EthClientMock mockAggLayerClient *agglayer.AgglayerClientMock mockL1InfoTreeSyncer *mocks.L1InfoTreeSyncerMock ) - if cfg.l1BlockNumber != nil { - mockL1Client = mocks.NewEthClientMock(t) - mockL1Client.On("BlockNumber", mock.Anything).Return(cfg.l1BlockNumber...).Once() - - aggsender.l1Client = mockL1Client - } - - if cfg.getLastSentCertificate != nil || cfg.deleteCertificate != nil || + if cfg.shouldSendCertificate != nil || cfg.getLastSentCertificate != nil || cfg.deleteCertificate != nil || cfg.getCertificateByHeight != nil || cfg.saveLastSentCertificate != nil { mockStorage = mocks.NewAggSenderStorageMock(t) - mockStorage.On("GetLastSentCertificate", mock.Anything).Return(cfg.getLastSentCertificate...).Once() + mockStorage.On("GetCertificatesByStatus", mock.Anything, []agglayer.CertificateStatus{agglayer.Pending}). + Return(cfg.shouldSendCertificate...).Once() + + aggsender.storage = mockStorage + + if cfg.getLastSentCertificate != nil { + mockStorage.On("GetLastSentCertificate", mock.Anything).Return(cfg.getLastSentCertificate...).Once() + } if cfg.deleteCertificate != nil { mockStorage.On("DeleteCertificate", mock.Anything, mock.Anything).Return(cfg.deleteCertificate...).Once() @@ -1045,14 +972,14 @@ func TestSendCertificate(t *testing.T) { if cfg.saveLastSentCertificate != nil { mockStorage.On("SaveLastSentCertificate", mock.Anything, mock.Anything).Return(cfg.saveLastSentCertificate...).Once() } - - aggsender.storage = mockStorage } - if cfg.l2BlockFinality != nil || cfg.getBlockByLER != nil || cfg.originNetwork != nil || + if cfg.lastL2BlockProcessed != nil || + cfg.getBlockByLER != nil || cfg.originNetwork != nil || cfg.getBridges != nil || cfg.getClaims != nil || cfg.getInfoByGlobalExitRoot != nil { mockL2Syncer = mocks.NewL2BridgeSyncerMock(t) - mockL2Syncer.On("BlockFinality").Return(cfg.l2BlockFinality...).Once() + + mockL2Syncer.On("GetLastProcessedBlock", mock.Anything).Return(cfg.lastL2BlockProcessed...).Once() if cfg.getBlockByLER != nil { mockL2Syncer.On("GetBlockByLER", mock.Anything, mock.Anything).Return(cfg.getBlockByLER...).Once() @@ -1077,18 +1004,6 @@ func TestSendCertificate(t *testing.T) { aggsender.l2Syncer = mockL2Syncer } - if cfg.l2HeaderByNumber != nil { - mockL2Client = mocks.NewEthClientMock(t) - mockL2Client.On("HeaderByNumber", mock.Anything, mock.Anything).Return(cfg.l2HeaderByNumber...).Once() - if cfg.l2HeaderByNumber[0] != nil { - hdr, ok := cfg.l2HeaderByNumber[0].(*gethTypes.Header) - if ok { - mockL2Syncer.On("GetLastProcessedBlock", mock.Anything).Return(hdr.Number.Uint64(), nil).Once() - } - } - aggsender.l2Client = mockL2Client - } - if cfg.getCertificateHeader != nil || cfg.sendCertificate != nil { mockAggLayerClient = agglayer.NewAgglayerClientMock(t) mockAggLayerClient.On("GetCertificateHeader", mock.Anything).Return(cfg.getCertificateHeader...).Once() @@ -1107,43 +1022,32 @@ func TestSendCertificate(t *testing.T) { aggsender.l1infoTreeSyncer = mockL1InfoTreeSyncer } - return aggsender, mockStorage, mockL1Client, mockL2Syncer, mockL2Client, mockAggLayerClient, mockL1InfoTreeSyncer + return aggsender, mockStorage, mockL2Syncer, mockAggLayerClient, mockL1InfoTreeSyncer } tests := []testCfg{ { - name: "error getting L1 block", - l1BlockNumber: []interface{}{uint64(0), errors.New("error getting block")}, - expectedError: "error getting block", + name: "error getting pending certificates", + shouldSendCertificate: []interface{}{nil, errors.New("error getting pending")}, + expectedError: "error getting pending", }, { - name: "should not send certificate", - l1BlockNumber: []interface{}{uint64(1), nil}, + name: "should not send certificate", + shouldSendCertificate: []interface{}{[]*aggsendertypes.CertificateInfo{ + {Status: agglayer.Pending}, + }, nil}, }, { name: "error getting last sent certificate", - l1BlockNumber: []interface{}{uint64(8), nil}, - l2BlockFinality: []interface{}{etherman.FinalizedBlock}, - l2HeaderByNumber: []interface{}{&gethTypes.Header{Number: big.NewInt(8)}, nil}, + shouldSendCertificate: []interface{}{[]*aggsendertypes.CertificateInfo{}, nil}, + lastL2BlockProcessed: []interface{}{uint64(8), nil}, getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{}, errors.New("error getting last sent certificate")}, expectedError: "error getting last sent certificate", }, { - name: "error getting block finality", - l1BlockNumber: []interface{}{uint64(8), nil}, - l2BlockFinality: []interface{}{etherman.BlockNumberFinality("invalid")}, - expectedError: "error getting block finality", - }, - { - name: "error getting last l2 block", - l1BlockNumber: []interface{}{uint64(8), nil}, - l2BlockFinality: []interface{}{etherman.FinalizedBlock}, - l2HeaderByNumber: []interface{}{nil, errors.New("error getting block")}, - expectedError: "error getting block from l2", - }, - { - name: "error getting last certificate header", - l1BlockNumber: []interface{}{uint64(18), nil}, + name: "error getting last certificate header", + shouldSendCertificate: []interface{}{[]*aggsendertypes.CertificateInfo{}, nil}, + lastL2BlockProcessed: []interface{}{uint64(8), nil}, getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ Height: 1, CertificateID: common.HexToHash("0x1"), @@ -1151,14 +1055,13 @@ func TestSendCertificate(t *testing.T) { FromBlock: 1, ToBlock: 10, }, nil}, - l2BlockFinality: []interface{}{etherman.FinalizedBlock}, - l2HeaderByNumber: []interface{}{&gethTypes.Header{Number: big.NewInt(20)}, nil}, getCertificateHeader: []interface{}{nil, errors.New("error getting certificate header")}, expectedError: "error getting certificate", }, { - name: "error deleting in error certificate", - l1BlockNumber: []interface{}{uint64(18), nil}, + name: "error deleting in error certificate", + shouldSendCertificate: []interface{}{[]*aggsendertypes.CertificateInfo{}, nil}, + lastL2BlockProcessed: []interface{}{uint64(20), nil}, getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ Height: 1, CertificateID: common.HexToHash("0x1"), @@ -1166,8 +1069,6 @@ func TestSendCertificate(t *testing.T) { FromBlock: 1, ToBlock: 10, }, nil}, - l2BlockFinality: []interface{}{etherman.FinalizedBlock}, - l2HeaderByNumber: []interface{}{&gethTypes.Header{Number: big.NewInt(20)}, nil}, getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ Status: agglayer.InError, }, nil}, @@ -1175,8 +1076,9 @@ func TestSendCertificate(t *testing.T) { expectedError: "error deleting certificate", }, { - name: "error getting certificate by height", - l1BlockNumber: []interface{}{uint64(28), nil}, + name: "error getting certificate by height", + shouldSendCertificate: []interface{}{[]*aggsendertypes.CertificateInfo{}, nil}, + lastL2BlockProcessed: []interface{}{uint64(29), nil}, getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ Height: 11, CertificateID: common.HexToHash("0x1"), @@ -1184,8 +1086,6 @@ func TestSendCertificate(t *testing.T) { FromBlock: 19, ToBlock: 28, }, nil}, - l2BlockFinality: []interface{}{etherman.FinalizedBlock}, - l2HeaderByNumber: []interface{}{&gethTypes.Header{Number: big.NewInt(29)}, nil}, getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ Status: agglayer.InError, }, nil}, @@ -1194,8 +1094,9 @@ func TestSendCertificate(t *testing.T) { expectedError: "error getting certificate by height", }, { - name: "error getting block by LER", - l1BlockNumber: []interface{}{uint64(38), nil}, + name: "error getting block by LER", + shouldSendCertificate: []interface{}{[]*aggsendertypes.CertificateInfo{}, nil}, + lastL2BlockProcessed: []interface{}{uint64(38), nil}, getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ Height: 21, CertificateID: common.HexToHash("0x11"), @@ -1203,8 +1104,6 @@ func TestSendCertificate(t *testing.T) { FromBlock: 29, ToBlock: 38, }, nil}, - l2BlockFinality: []interface{}{etherman.PendingBlock}, - l2HeaderByNumber: []interface{}{&gethTypes.Header{Number: big.NewInt(38)}, nil}, getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ Status: agglayer.InError, }, nil}, @@ -1217,8 +1116,9 @@ func TestSendCertificate(t *testing.T) { expectedError: "error getting block by LER", }, { - name: "no new blocks to send certificate", - l1BlockNumber: []interface{}{uint64(38), nil}, + name: "no new blocks to send certificate", + shouldSendCertificate: []interface{}{[]*aggsendertypes.CertificateInfo{}, nil}, + lastL2BlockProcessed: []interface{}{uint64(41), nil}, getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ Height: 41, CertificateID: common.HexToHash("0x111"), @@ -1226,16 +1126,15 @@ func TestSendCertificate(t *testing.T) { FromBlock: 31, ToBlock: 40, }, nil}, - l2BlockFinality: []interface{}{etherman.FinalizedBlock}, - l2HeaderByNumber: []interface{}{&gethTypes.Header{Number: big.NewInt(41)}, nil}, getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ Status: agglayer.Settled, }, nil}, getBlockByLER: []interface{}{uint64(41), nil}, }, { - name: "get bridges error", - l1BlockNumber: []interface{}{uint64(98), nil}, + name: "get bridges error", + shouldSendCertificate: []interface{}{[]*aggsendertypes.CertificateInfo{}, nil}, + lastL2BlockProcessed: []interface{}{uint64(59), nil}, getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ Height: 50, CertificateID: common.HexToHash("0x1111"), @@ -1243,8 +1142,6 @@ func TestSendCertificate(t *testing.T) { FromBlock: 40, ToBlock: 49, }, nil}, - l2BlockFinality: []interface{}{etherman.FinalizedBlock}, - l2HeaderByNumber: []interface{}{&gethTypes.Header{Number: big.NewInt(59)}, nil}, getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ Status: agglayer.Settled, CertificateID: common.HexToHash("0x1110"), @@ -1255,8 +1152,9 @@ func TestSendCertificate(t *testing.T) { expectedError: "error getting bridges", }, { - name: "no bridges", - l1BlockNumber: []interface{}{uint64(198), nil}, + name: "no bridges", + shouldSendCertificate: []interface{}{[]*aggsendertypes.CertificateInfo{}, nil}, + lastL2BlockProcessed: []interface{}{uint64(69), nil}, getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ Height: 60, CertificateID: common.HexToHash("0x11111"), @@ -1264,8 +1162,6 @@ func TestSendCertificate(t *testing.T) { FromBlock: 50, ToBlock: 59, }, nil}, - l2BlockFinality: []interface{}{etherman.PendingBlock}, - l2HeaderByNumber: []interface{}{&gethTypes.Header{Number: big.NewInt(69)}, nil}, getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ Status: agglayer.Settled, CertificateID: common.HexToHash("0x1110"), @@ -1275,8 +1171,9 @@ func TestSendCertificate(t *testing.T) { getBridges: []interface{}{[]bridgesync.Bridge{}, nil}, }, { - name: "get claims error", - l1BlockNumber: []interface{}{uint64(158), nil}, + name: "get claims error", + shouldSendCertificate: []interface{}{[]*aggsendertypes.CertificateInfo{}, nil}, + lastL2BlockProcessed: []interface{}{uint64(79), nil}, getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ Height: 70, CertificateID: common.HexToHash("0x121111"), @@ -1284,8 +1181,6 @@ func TestSendCertificate(t *testing.T) { FromBlock: 60, ToBlock: 69, }, nil}, - l2BlockFinality: []interface{}{etherman.FinalizedBlock}, - l2HeaderByNumber: []interface{}{&gethTypes.Header{Number: big.NewInt(79)}, nil}, getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ Status: agglayer.Settled, CertificateID: common.HexToHash("0x1110"), @@ -1304,8 +1199,9 @@ func TestSendCertificate(t *testing.T) { expectedError: "error getting claims", }, { - name: "error building certificate", - l1BlockNumber: []interface{}{uint64(148), nil}, + name: "error building certificate", + shouldSendCertificate: []interface{}{[]*aggsendertypes.CertificateInfo{}, nil}, + lastL2BlockProcessed: []interface{}{uint64(89), nil}, getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ Height: 80, CertificateID: common.HexToHash("0x1321111"), @@ -1313,8 +1209,6 @@ func TestSendCertificate(t *testing.T) { FromBlock: 70, ToBlock: 79, }, nil}, - l2BlockFinality: []interface{}{etherman.PendingBlock}, - l2HeaderByNumber: []interface{}{&gethTypes.Header{Number: big.NewInt(89)}, nil}, getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ Status: agglayer.Settled, CertificateID: common.HexToHash("0x1110"), @@ -1338,8 +1232,9 @@ func TestSendCertificate(t *testing.T) { expectedError: "error building certificate", }, { - name: "send certificate error", - l1BlockNumber: []interface{}{uint64(138), nil}, + name: "send certificate error", + shouldSendCertificate: []interface{}{[]*aggsendertypes.CertificateInfo{}, nil}, + lastL2BlockProcessed: []interface{}{uint64(99), nil}, getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ Height: 90, CertificateID: common.HexToHash("0x1121111"), @@ -1347,8 +1242,6 @@ func TestSendCertificate(t *testing.T) { FromBlock: 80, ToBlock: 89, }, nil}, - l2BlockFinality: []interface{}{etherman.FinalizedBlock}, - l2HeaderByNumber: []interface{}{&gethTypes.Header{Number: big.NewInt(99)}, nil}, getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ Status: agglayer.Settled, CertificateID: common.HexToHash("0x1110"), @@ -1372,8 +1265,9 @@ func TestSendCertificate(t *testing.T) { expectedError: "error sending certificate", }, { - name: "store last sent certificate error", - l1BlockNumber: []interface{}{uint64(128), nil}, + name: "store last sent certificate error", + shouldSendCertificate: []interface{}{[]*aggsendertypes.CertificateInfo{}, nil}, + lastL2BlockProcessed: []interface{}{uint64(109), nil}, getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ Height: 100, CertificateID: common.HexToHash("0x11121111"), @@ -1381,8 +1275,6 @@ func TestSendCertificate(t *testing.T) { FromBlock: 90, ToBlock: 99, }, nil}, - l2BlockFinality: []interface{}{etherman.FinalizedBlock}, - l2HeaderByNumber: []interface{}{&gethTypes.Header{Number: big.NewInt(109)}, nil}, getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ Status: agglayer.Settled, CertificateID: common.HexToHash("0x11110"), @@ -1407,8 +1299,9 @@ func TestSendCertificate(t *testing.T) { expectedError: "error saving last sent certificate in db", }, { - name: "successful sending of certificate", - l1BlockNumber: []interface{}{uint64(178), nil}, + name: "successful sending of certificate", + shouldSendCertificate: []interface{}{[]*aggsendertypes.CertificateInfo{}, nil}, + lastL2BlockProcessed: []interface{}{uint64(119), nil}, getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ Height: 110, CertificateID: common.HexToHash("0x12121111"), @@ -1416,8 +1309,6 @@ func TestSendCertificate(t *testing.T) { FromBlock: 100, ToBlock: 109, }, nil}, - l2BlockFinality: []interface{}{etherman.FinalizedBlock}, - l2HeaderByNumber: []interface{}{&gethTypes.Header{Number: big.NewInt(119)}, nil}, getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ Status: agglayer.Settled, CertificateID: common.HexToHash("0x11110"), @@ -1448,7 +1339,7 @@ func TestSendCertificate(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - aggsender, mockStorage, mockL1Client, mockL2Syncer, mockL2Client, + aggsender, mockStorage, mockL2Syncer, mockAggLayerClient, mockL1InfoTreeSyncer := setupTest(tt) err := aggsender.sendCertificate(context.Background()) @@ -1463,18 +1354,10 @@ func TestSendCertificate(t *testing.T) { mockStorage.AssertExpectations(t) } - if mockL1Client != nil { - mockL1Client.AssertExpectations(t) - } - if mockL2Syncer != nil { mockL2Syncer.AssertExpectations(t) } - if mockL2Client != nil { - mockL2Client.AssertExpectations(t) - } - if mockAggLayerClient != nil { mockAggLayerClient.AssertExpectations(t) } diff --git a/cmd/run.go b/cmd/run.go index cf2453d9..94fd3425 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -125,10 +125,8 @@ func start(cliCtx *cli.Context) error { aggsender, err := createAggSender( cliCtx.Context, c.AggSender, - l1Client, l1InfoTreeSync, l2BridgeSync, - l2Client, ) if err != nil { log.Fatal(err) @@ -146,15 +144,13 @@ func start(cliCtx *cli.Context) error { func createAggSender( ctx context.Context, cfg aggsender.Config, - l1Client bridgesync.EthClienter, l1InfoTreeSync *l1infotreesync.L1InfoTreeSync, l2Syncer *bridgesync.BridgeSync, - l2Client bridgesync.EthClienter, ) (*aggsender.AggSender, error) { logger := log.WithFields("module", cdkcommon.AGGSENDER) agglayerClient := agglayer.NewAggLayerClient(cfg.AggLayerURL) - return aggsender.New(ctx, logger, cfg, agglayerClient, l1Client, l1InfoTreeSync, l2Syncer, l2Client) + return aggsender.New(ctx, logger, cfg, agglayerClient, l1InfoTreeSync, l2Syncer) } func createAggregator(ctx context.Context, c config.Config, runMigrations bool) *aggregator.Aggregator { From 21680d828f80c61e923a2ce5d340d00c02dc3088 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Thu, 24 Oct 2024 09:27:16 +0200 Subject: [PATCH 59/84] fix: identantion and primary key --- aggsender/db/migrations/0001.sql | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/aggsender/db/migrations/0001.sql b/aggsender/db/migrations/0001.sql index 28b17098..3ed7f997 100644 --- a/aggsender/db/migrations/0001.sql +++ b/aggsender/db/migrations/0001.sql @@ -3,12 +3,10 @@ DROP TABLE IF EXISTS certificate_info; -- +migrate Up CREATE TABLE certificate_info ( - height INTEGER NOT NULL, - certificate_id VARCHAR NOT NULL, + height INTEGER NOT NULL, + certificate_id VARCHAR NOT NULL PRIMARY KEY, status INTEGER NOT NULL, new_local_exit_root VARCHAR NOT NULL, from_block INTEGER NOT NULL, - to_block INTEGER NOT NULL, - - PRIMARY KEY (height, certificate_id) + to_block INTEGER NOT NULL ); \ No newline at end of file From d1e805ab5c3c288cc34814072c66e19732028c9f Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Thu, 24 Oct 2024 09:37:39 +0200 Subject: [PATCH 60/84] fix: last certificate block --- aggsender/aggsender.go | 6 +--- aggsender/aggsender_test.go | 56 +++++++------------------------------ 2 files changed, 11 insertions(+), 51 deletions(-) diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index bc4e115b..f31becc0 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -248,11 +248,7 @@ func (a *AggSender) getLastSentCertificateData(ctx context.Context) (common.Hash previousHeight = lastSentCertificateHeader.Height } - lastCertificateBlock, err = a.l2Syncer.GetBlockByLER(ctx, previousLocalExitRoot) - if err != nil { - return common.Hash{}, 0, 0, fmt.Errorf("error getting block by LER %s: %w", - lastSentCertificate.CertificateID, err) - } + lastCertificateBlock = lastSentCertificate.ToBlock } return previousLocalExitRoot, previousHeight, lastCertificateBlock, nil diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go index db8ec9a7..3e911a27 100644 --- a/aggsender/aggsender_test.go +++ b/aggsender/aggsender_test.go @@ -924,7 +924,6 @@ func TestSendCertificate(t *testing.T) { getCertificateHeader []interface{} deleteCertificate []interface{} getCertificateByHeight []interface{} - getBlockByLER []interface{} getBridges []interface{} getClaims []interface{} getInfoByGlobalExitRoot []interface{} @@ -974,17 +973,12 @@ func TestSendCertificate(t *testing.T) { } } - if cfg.lastL2BlockProcessed != nil || - cfg.getBlockByLER != nil || cfg.originNetwork != nil || + if cfg.lastL2BlockProcessed != nil || cfg.originNetwork != nil || cfg.getBridges != nil || cfg.getClaims != nil || cfg.getInfoByGlobalExitRoot != nil { mockL2Syncer = mocks.NewL2BridgeSyncerMock(t) mockL2Syncer.On("GetLastProcessedBlock", mock.Anything).Return(cfg.lastL2BlockProcessed...).Once() - if cfg.getBlockByLER != nil { - mockL2Syncer.On("GetBlockByLER", mock.Anything, mock.Anything).Return(cfg.getBlockByLER...).Once() - } - if cfg.getBridges != nil { mockL2Syncer.On("GetBridges", mock.Anything, mock.Anything, mock.Anything).Return(cfg.getBridges...).Once() } @@ -1093,28 +1087,6 @@ func TestSendCertificate(t *testing.T) { getCertificateByHeight: []interface{}{aggsendertypes.CertificateInfo{}, errors.New("error getting certificate by height")}, expectedError: "error getting certificate by height", }, - { - name: "error getting block by LER", - shouldSendCertificate: []interface{}{[]*aggsendertypes.CertificateInfo{}, nil}, - lastL2BlockProcessed: []interface{}{uint64(38), nil}, - getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ - Height: 21, - CertificateID: common.HexToHash("0x11"), - NewLocalExitRoot: common.HexToHash("0x1223"), - FromBlock: 29, - ToBlock: 38, - }, nil}, - getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ - Status: agglayer.InError, - }, nil}, - deleteCertificate: []interface{}{nil}, - getCertificateByHeight: []interface{}{aggsendertypes.CertificateInfo{ - NewLocalExitRoot: common.HexToHash("0x1222"), - Height: 20, - }, nil}, - getBlockByLER: []interface{}{uint64(0), errors.New("error getting block by LER")}, - expectedError: "error getting block by LER", - }, { name: "no new blocks to send certificate", shouldSendCertificate: []interface{}{[]*aggsendertypes.CertificateInfo{}, nil}, @@ -1124,12 +1096,11 @@ func TestSendCertificate(t *testing.T) { CertificateID: common.HexToHash("0x111"), NewLocalExitRoot: common.HexToHash("0x13223"), FromBlock: 31, - ToBlock: 40, + ToBlock: 41, }, nil}, getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ Status: agglayer.Settled, }, nil}, - getBlockByLER: []interface{}{uint64(41), nil}, }, { name: "get bridges error", @@ -1140,14 +1111,13 @@ func TestSendCertificate(t *testing.T) { CertificateID: common.HexToHash("0x1111"), NewLocalExitRoot: common.HexToHash("0x132233"), FromBlock: 40, - ToBlock: 49, + ToBlock: 41, }, nil}, getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ Status: agglayer.Settled, CertificateID: common.HexToHash("0x1110"), Height: 49, }, nil}, - getBlockByLER: []interface{}{uint64(41), nil}, getBridges: []interface{}{nil, errors.New("error getting bridges")}, expectedError: "error getting bridges", }, @@ -1160,15 +1130,14 @@ func TestSendCertificate(t *testing.T) { CertificateID: common.HexToHash("0x11111"), NewLocalExitRoot: common.HexToHash("0x1322233"), FromBlock: 50, - ToBlock: 59, + ToBlock: 51, }, nil}, getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ Status: agglayer.Settled, CertificateID: common.HexToHash("0x1110"), Height: 59, }, nil}, - getBlockByLER: []interface{}{uint64(51), nil}, - getBridges: []interface{}{[]bridgesync.Bridge{}, nil}, + getBridges: []interface{}{[]bridgesync.Bridge{}, nil}, }, { name: "get claims error", @@ -1179,14 +1148,13 @@ func TestSendCertificate(t *testing.T) { CertificateID: common.HexToHash("0x121111"), NewLocalExitRoot: common.HexToHash("0x13122233"), FromBlock: 60, - ToBlock: 69, + ToBlock: 61, }, nil}, getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ Status: agglayer.Settled, CertificateID: common.HexToHash("0x1110"), Height: 69, }, nil}, - getBlockByLER: []interface{}{uint64(61), nil}, getBridges: []interface{}{[]bridgesync.Bridge{ { BlockNum: 61, @@ -1207,14 +1175,13 @@ func TestSendCertificate(t *testing.T) { CertificateID: common.HexToHash("0x1321111"), NewLocalExitRoot: common.HexToHash("0x131122233"), FromBlock: 70, - ToBlock: 79, + ToBlock: 71, }, nil}, getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ Status: agglayer.Settled, CertificateID: common.HexToHash("0x1110"), Height: 79, }, nil}, - getBlockByLER: []interface{}{uint64(71), nil}, getBridges: []interface{}{[]bridgesync.Bridge{ { BlockNum: 71, @@ -1240,14 +1207,13 @@ func TestSendCertificate(t *testing.T) { CertificateID: common.HexToHash("0x1121111"), NewLocalExitRoot: common.HexToHash("0x111122211"), FromBlock: 80, - ToBlock: 89, + ToBlock: 81, }, nil}, getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ Status: agglayer.Settled, CertificateID: common.HexToHash("0x1110"), Height: 89, }, nil}, - getBlockByLER: []interface{}{uint64(81), nil}, getBridges: []interface{}{[]bridgesync.Bridge{ { BlockNum: 81, @@ -1273,14 +1239,13 @@ func TestSendCertificate(t *testing.T) { CertificateID: common.HexToHash("0x11121111"), NewLocalExitRoot: common.HexToHash("0x1211122211"), FromBlock: 90, - ToBlock: 99, + ToBlock: 91, }, nil}, getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ Status: agglayer.Settled, CertificateID: common.HexToHash("0x11110"), Height: 99, }, nil}, - getBlockByLER: []interface{}{uint64(91), nil}, getBridges: []interface{}{[]bridgesync.Bridge{ { BlockNum: 91, @@ -1307,14 +1272,13 @@ func TestSendCertificate(t *testing.T) { CertificateID: common.HexToHash("0x12121111"), NewLocalExitRoot: common.HexToHash("0x1221122211"), FromBlock: 100, - ToBlock: 109, + ToBlock: 101, }, nil}, getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ Status: agglayer.Settled, CertificateID: common.HexToHash("0x11110"), Height: 109, }, nil}, - getBlockByLER: []interface{}{uint64(91), nil}, getBridges: []interface{}{[]bridgesync.Bridge{ { BlockNum: 101, From 335b15683579f711848d5c3f51de1ab7f26a3a75 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Thu, 24 Oct 2024 16:03:11 +0200 Subject: [PATCH 61/84] fix: lastCertificateBlock --- aggsender/aggsender.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index f31becc0..20b077d9 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -243,12 +243,12 @@ func (a *AggSender) getLastSentCertificateData(ctx context.Context) (common.Hash previousLocalExitRoot = lastValidCertificate.NewLocalExitRoot previousHeight = lastValidCertificate.Height + lastCertificateBlock = lastValidCertificate.ToBlock } else { previousLocalExitRoot = lastSentCertificateHeader.NewLocalExitRoot previousHeight = lastSentCertificateHeader.Height + lastCertificateBlock = lastSentCertificate.ToBlock } - - lastCertificateBlock = lastSentCertificate.ToBlock } return previousLocalExitRoot, previousHeight, lastCertificateBlock, nil From a7c53ff9788c8fdabe661fb9beb05c6f081158ee Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Thu, 24 Oct 2024 16:02:21 +0200 Subject: [PATCH 62/84] fix: certificate json format and new hash calculation --- agglayer/types.go | 161 +++++++++++++++++--- aggsender/aggsender.go | 53 +++++-- aggsender/aggsender_test.go | 285 +++++++++++++++++++++--------------- 3 files changed, 349 insertions(+), 150 deletions(-) diff --git a/agglayer/types.go b/agglayer/types.go index 36fa15be..b56e3e5e 100644 --- a/agglayer/types.go +++ b/agglayer/types.go @@ -1,6 +1,7 @@ package agglayer import ( + "encoding/json" "fmt" "math/big" @@ -47,18 +48,40 @@ type Certificate struct { // Hash returns a hash that uniquely identifies the certificate func (c *Certificate) Hash() common.Hash { + bridgeExitsHashes := make([][]byte, len(c.BridgeExits)) + for i, bridgeExit := range c.BridgeExits { + bridgeExitsHashes[i] = bridgeExit.Hash().Bytes() + } + + importedBridgeExitsHashes := make([][]byte, len(c.ImportedBridgeExits)) + for i, importedBridgeExit := range c.ImportedBridgeExits { + importedBridgeExitsHashes[i] = importedBridgeExit.Hash().Bytes() + } + + bridgeExitsPart := crypto.Keccak256(bridgeExitsHashes...) + importedBridgeExitsPart := crypto.Keccak256(importedBridgeExitsHashes...) + return crypto.Keccak256Hash( cdkcommon.Uint32ToBytes(c.NetworkID), cdkcommon.Uint64ToBytes(c.Height), c.PrevLocalExitRoot.Bytes(), c.NewLocalExitRoot.Bytes(), + bridgeExitsPart, + importedBridgeExitsPart, ) } // SignedCertificate is the struct that contains the certificate and the signature of the signer type SignedCertificate struct { *Certificate - Signature []byte `json:"signature"` + Signature *Signature `json:"signature"` +} + +// Signature is the data structure that will hold the signature of the given certificate +type Signature struct { + R common.Hash `json:"r"` + S common.Hash `json:"s"` + OddParity bool `json:"odd_y_parity"` } // TokenInfo encapsulates the information to uniquely identify a token on the origin network. @@ -74,12 +97,17 @@ type GlobalIndex struct { LeafIndex uint32 `json:"leaf_index"` } +func (g *GlobalIndex) Hash() common.Hash { + return crypto.Keccak256Hash( + bridgesync.GenerateGlobalIndex(g.MainnetFlag, g.RollupIndex, g.LeafIndex).Bytes()) +} + // BridgeExit represents a token bridge exit type BridgeExit struct { LeafType LeafType `json:"leaf_type"` TokenInfo *TokenInfo `json:"token_info"` - DestinationNetwork uint32 `json:"destination_network"` - DestinationAddress common.Address `json:"destination_address"` + DestinationNetwork uint32 `json:"dest_network"` + DestinationAddress common.Address `json:"dest_address"` Amount *big.Int `json:"amount"` Metadata []byte `json:"metadata"` } @@ -101,69 +129,160 @@ func (c *BridgeExit) Hash() common.Hash { ) } +// MerkleProof represents an inclusion proof of a leaf in a Merkle tree type MerkleProof struct { Root common.Hash `json:"root"` Proof [types.DefaultHeight]common.Hash `json:"proof"` } +// MarshalJSON is the implementation of the json.Marshaler interface +func (m *MerkleProof) MarshalJSON() ([]byte, error) { + return json.Marshal(&struct { + Root common.Hash `json:"root"` + Proof map[string][types.DefaultHeight]common.Hash `json:"proof"` + }{ + Root: m.Root, + Proof: map[string][types.DefaultHeight]common.Hash{ + "siblings": m.Proof, + }, + }) +} + +// Hash returns the hash of the Merkle proof struct +func (m *MerkleProof) Hash() common.Hash { + proofsAsSingleSlice := make([]byte, 0) + + for _, proof := range m.Proof { + proofsAsSingleSlice = append(proofsAsSingleSlice, proof.Bytes()...) + } + + return crypto.Keccak256Hash( + m.Root.Bytes(), + proofsAsSingleSlice, + ) +} + +// L1InfoTreeLeafInner represents the inner part of the L1 info tree leaf type L1InfoTreeLeafInner struct { GlobalExitRoot common.Hash `json:"global_exit_root"` BlockHash common.Hash `json:"block_hash"` Timestamp uint64 `json:"timestamp"` } -func (l L1InfoTreeLeafInner) Hash() common.Hash { - return crypto.Keccak256Hash(l.GlobalExitRoot.Bytes(), l.BlockHash.Bytes(), cdkcommon.Uint64ToBytes(l.Timestamp)) +// Hash returns the hash of the L1InfoTreeLeafInner struct +func (l *L1InfoTreeLeafInner) Hash() common.Hash { + return crypto.Keccak256Hash( + l.GlobalExitRoot.Bytes(), + l.BlockHash.Bytes(), + cdkcommon.Uint64ToBytes(l.Timestamp), + ) } +// L1InfoTreeLeaf represents the leaf of the L1 info tree type L1InfoTreeLeaf struct { - L1InfoTreeIndex uint32 `json:"l1_info_tree_index"` - RollupExitRoot common.Hash `json:"rer"` - MainnetExitRoot common.Hash `json:"mer"` - Inner L1InfoTreeLeafInner `json:"inner"` + L1InfoTreeIndex uint32 `json:"l1_info_tree_index"` + RollupExitRoot common.Hash `json:"rer"` + MainnetExitRoot common.Hash `json:"mer"` + Inner *L1InfoTreeLeafInner `json:"inner"` } -func (l L1InfoTreeLeaf) Hash() common.Hash { +// Hash returns the hash of the L1InfoTreeLeaf struct +func (l *L1InfoTreeLeaf) Hash() common.Hash { return l.Inner.Hash() } +// Claim is the interface that will be implemented by the different types of claims type Claim interface { Type() string + Hash() common.Hash + MarshalJSON() ([]byte, error) } +// ClaimFromMainnnet represents a claim originating from the mainnet type ClaimFromMainnnet struct { - ProofLeafMER MerkleProof `json:"proof_leaf_mer"` - ProofGERToL1Root MerkleProof `json:"proof_ger_l1root"` - L1Leaf L1InfoTreeLeaf `json:"l1_leaf"` + ProofLeafMER *MerkleProof `json:"proof_leaf_mer"` + ProofGERToL1Root *MerkleProof `json:"proof_ger_l1root"` + L1Leaf *L1InfoTreeLeaf `json:"l1_leaf"` } +// Type is the implementation of Claim interface func (c ClaimFromMainnnet) Type() string { return "Mainnet" } +// MarshalJSON is the implementation of Claim interface +func (c *ClaimFromMainnnet) MarshalJSON() ([]byte, error) { + return json.Marshal(&struct { + Child map[string]interface{} `json:"Mainnet"` + }{ + Child: map[string]interface{}{ + "proof_leaf_mer": c.ProofLeafMER, + "proof_ger_l1root": c.ProofGERToL1Root, + "l1_leaf": c.L1Leaf, + }, + }) +} + +// Hash is the implementation of Claim interface +func (c *ClaimFromMainnnet) Hash() common.Hash { + return crypto.Keccak256Hash( + c.ProofLeafMER.Hash().Bytes(), + c.ProofGERToL1Root.Hash().Bytes(), + c.L1Leaf.Hash().Bytes(), + ) +} + +// ClaimFromRollup represents a claim originating from a rollup type ClaimFromRollup struct { - ProofLeafLER MerkleProof `json:"proof_leaf_ler"` - ProofLERToRER MerkleProof `json:"proof_ler_rer"` - ProofGERToL1Root MerkleProof `json:"proof_ger_l1root"` - L1Leaf L1InfoTreeLeaf `json:"l1_leaf"` + ProofLeafLER *MerkleProof `json:"proof_leaf_ler"` + ProofLERToRER *MerkleProof `json:"proof_ler_rer"` + ProofGERToL1Root *MerkleProof `json:"proof_ger_l1root"` + L1Leaf *L1InfoTreeLeaf `json:"l1_leaf"` } +// Type is the implementation of Claim interface func (c ClaimFromRollup) Type() string { return "Rollup" } +// MarshalJSON is the implementation of Claim interface +func (c *ClaimFromRollup) MarshalJSON() ([]byte, error) { + return json.Marshal(&struct { + Child map[string]interface{} `json:"Rollup"` + }{ + Child: map[string]interface{}{ + "proof_leaf_ler": c.ProofLeafLER, + "proof_ler_rer": c.ProofLERToRER, + "proof_ger_l1root": c.ProofGERToL1Root, + "l1_leaf": c.L1Leaf, + }, + }) +} + +// Hash is the implementation of Claim interface +func (c *ClaimFromRollup) Hash() common.Hash { + return crypto.Keccak256Hash( + c.ProofLeafLER.Hash().Bytes(), + c.ProofLERToRER.Hash().Bytes(), + c.ProofGERToL1Root.Hash().Bytes(), + c.L1Leaf.Hash().Bytes(), + ) +} + // ImportedBridgeExit represents a token bridge exit originating on another network but claimed on the current network. type ImportedBridgeExit struct { BridgeExit *BridgeExit `json:"bridge_exit"` - ClaimData Claim `json:"claim"` + ClaimData Claim `json:"claim_data"` GlobalIndex *GlobalIndex `json:"global_index"` } // Hash returns a hash that uniquely identifies the imported bridge exit func (c *ImportedBridgeExit) Hash() common.Hash { - globalIndexBig := bridgesync.GenerateGlobalIndex(c.GlobalIndex.MainnetFlag, - c.GlobalIndex.RollupIndex, c.GlobalIndex.LeafIndex) - return crypto.Keccak256Hash(globalIndexBig.Bytes()) + return crypto.Keccak256Hash( + c.BridgeExit.Hash().Bytes(), + c.ClaimData.Hash().Bytes(), + c.GlobalIndex.Hash().Bytes(), + ) } // CertificateHeader is the structure returned by the interop_getCertificateHeader RPC call diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index 20b077d9..933f772e 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -18,7 +18,12 @@ import ( "github.com/ethereum/go-ethereum/crypto" ) -var errNoBridgesAndClaims = errors.New("no bridges and claims to build certificate") +const signatureSize = 65 + +var ( + errNoBridgesAndClaims = errors.New("no bridges and claims to build certificate") + errInvalidSignatureSize = errors.New("invalid signature size") +) // AggSender is a component that will send certificates to the aggLayer type AggSender struct { @@ -344,49 +349,50 @@ func (a *AggSender) getImportedBridgeExits(ctx context.Context, for i, ibe := range importedBridgeExits { gerToL1Proof, err := a.l1infoTreeSyncer.GetL1InfoTreeMerkleProofFromIndexToRoot(ctx, ibe.GlobalIndex.LeafIndex, ger) if err != nil { - return nil, fmt.Errorf("error getting L1 Info tree merkle proof: %w", err) + return nil, fmt.Errorf("error getting L1 Info tree merkle proof for leaf index: %d. GER: %s. Error: %w", + ibe.GlobalIndex.LeafIndex, ger, err) } claim := claims[i] if ibe.GlobalIndex.MainnetFlag { ibe.ClaimData = &agglayer.ClaimFromMainnnet{ - L1Leaf: agglayer.L1InfoTreeLeaf{ + L1Leaf: &agglayer.L1InfoTreeLeaf{ L1InfoTreeIndex: ibe.GlobalIndex.LeafIndex, RollupExitRoot: claims[i].RollupExitRoot, MainnetExitRoot: claims[i].MainnetExitRoot, - Inner: agglayer.L1InfoTreeLeafInner{ + Inner: &agglayer.L1InfoTreeLeafInner{ GlobalExitRoot: ger, Timestamp: timestamp, BlockHash: blockHash, }, }, - ProofLeafMER: agglayer.MerkleProof{ + ProofLeafMER: &agglayer.MerkleProof{ Root: claim.MainnetExitRoot, Proof: claim.ProofLocalExitRoot, }, - ProofGERToL1Root: agglayer.MerkleProof{ + ProofGERToL1Root: &agglayer.MerkleProof{ Root: ger, Proof: gerToL1Proof, }, } } else { ibe.ClaimData = &agglayer.ClaimFromRollup{ - L1Leaf: agglayer.L1InfoTreeLeaf{ + L1Leaf: &agglayer.L1InfoTreeLeaf{ L1InfoTreeIndex: ibe.GlobalIndex.LeafIndex, RollupExitRoot: claim.RollupExitRoot, MainnetExitRoot: claim.MainnetExitRoot, - Inner: agglayer.L1InfoTreeLeafInner{ + Inner: &agglayer.L1InfoTreeLeafInner{ GlobalExitRoot: ger, Timestamp: timestamp, BlockHash: blockHash, }, }, - ProofLeafLER: agglayer.MerkleProof{ + ProofLeafLER: &agglayer.MerkleProof{ Root: claim.MainnetExitRoot, Proof: claim.ProofLocalExitRoot, }, - ProofLERToRER: agglayer.MerkleProof{}, - ProofGERToL1Root: agglayer.MerkleProof{ + ProofLERToRER: &agglayer.MerkleProof{}, + ProofGERToL1Root: &agglayer.MerkleProof{ Root: ger, Proof: gerToL1Proof, }, @@ -406,9 +412,18 @@ func (a *AggSender) signCertificate(certificate *agglayer.Certificate) (*agglaye return nil, err } + r, s, isOddParity, err := extractSignatureData(sig) + if err != nil { + return nil, err + } + return &agglayer.SignedCertificate{ Certificate: certificate, - Signature: sig, + Signature: &agglayer.Signature{ + R: r, + S: s, + OddParity: isOddParity, + }, }, nil } @@ -464,3 +479,17 @@ func (a *AggSender) shouldSendCertificate(ctx context.Context) (bool, error) { return len(pendingCertificates) == 0, nil } + +// extractSignatureData extracts the R, S, and V from a 65-byte signature +func extractSignatureData(signature []byte) (r, s common.Hash, isOddParity bool, err error) { + if len(signature) != signatureSize { + err = errInvalidSignatureSize + return + } + + r = common.BytesToHash(signature[:32]) // First 32 bytes are R + s = common.BytesToHash(signature[32:64]) // Next 32 bytes are S + isOddParity = signature[64]%2 == 1 // Last byte is V + + return +} diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go index 3e911a27..b0162e49 100644 --- a/aggsender/aggsender_test.go +++ b/aggsender/aggsender_test.go @@ -3,9 +3,11 @@ package aggsender import ( "context" "crypto/ecdsa" + "encoding/json" "errors" "fmt" "math/big" + "os" "testing" "time" @@ -235,104 +237,6 @@ func TestGetBridgeExits(t *testing.T) { } } -func TestSignCertificate(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - certificate *agglayer.Certificate - sequencerKey *ecdsa.PrivateKey - expectedError bool - }{ - { - name: "Valid certificate", - certificate: &agglayer.Certificate{ - NetworkID: 1, - PrevLocalExitRoot: common.HexToHash("0x123"), - NewLocalExitRoot: common.HexToHash("0x456"), - BridgeExits: []*agglayer.BridgeExit{ - { - LeafType: agglayer.LeafTypeAsset, - TokenInfo: &agglayer.TokenInfo{ - OriginNetwork: 1, - OriginTokenAddress: common.HexToAddress("0x789"), - }, - DestinationNetwork: 2, - DestinationAddress: common.HexToAddress("0xabc"), - Amount: big.NewInt(100), - Metadata: []byte("metadata"), - }, - }, - ImportedBridgeExits: []*agglayer.ImportedBridgeExit{}, - Height: 1, - }, - sequencerKey: func() *ecdsa.PrivateKey { - key, _ := crypto.GenerateKey() - return key - }(), - expectedError: false, - }, - { - name: "Invalid certificate", - certificate: &agglayer.Certificate{ - NetworkID: 1, - PrevLocalExitRoot: common.HexToHash("0x123"), - NewLocalExitRoot: common.HexToHash("0x456"), - BridgeExits: []*agglayer.BridgeExit{}, - ImportedBridgeExits: []*agglayer.ImportedBridgeExit{ - { - BridgeExit: &agglayer.BridgeExit{ - LeafType: agglayer.LeafTypeAsset, - TokenInfo: &agglayer.TokenInfo{ - OriginNetwork: 1, - OriginTokenAddress: common.HexToAddress("0x789"), - }, - DestinationNetwork: 2, - DestinationAddress: common.HexToAddress("0xabc"), - Amount: big.NewInt(100), - Metadata: []byte("metadata"), - }, - GlobalIndex: &agglayer.GlobalIndex{ - MainnetFlag: false, - RollupIndex: 0, - LeafIndex: 1, - }, - }, - }, - Height: 1, - }, - sequencerKey: func() *ecdsa.PrivateKey { - key, _ := crypto.GenerateKey() - return key - }(), - expectedError: false, - }, - } - - for _, tt := range tests { - tt := tt - - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - aggSender := &AggSender{ - sequencerKey: tt.sequencerKey, - } - signedCert, err := aggSender.signCertificate(tt.certificate) - - if tt.expectedError { - require.Error(t, err) - require.Nil(t, signedCert) - } else { - require.NoError(t, err) - require.NotNil(t, signedCert) - require.Equal(t, tt.certificate, signedCert.Certificate) - require.NotEmpty(t, signedCert.Signature) - } - }) - } -} - //nolint:dupl func TestGetImportedBridgeExits(t *testing.T) { t.Parallel() @@ -392,22 +296,22 @@ func TestGetImportedBridgeExits(t *testing.T) { LeafIndex: 1, }, ClaimData: &agglayer.ClaimFromRollup{ - L1Leaf: agglayer.L1InfoTreeLeaf{ + L1Leaf: &agglayer.L1InfoTreeLeaf{ L1InfoTreeIndex: 1, RollupExitRoot: common.HexToHash("0xaaab"), MainnetExitRoot: common.HexToHash("0xbbba"), - Inner: agglayer.L1InfoTreeLeafInner{ + Inner: &agglayer.L1InfoTreeLeafInner{ GlobalExitRoot: common.HexToHash("0x7891"), Timestamp: 123456789, BlockHash: common.HexToHash("0xabc"), }, }, - ProofLeafLER: agglayer.MerkleProof{ + ProofLeafLER: &agglayer.MerkleProof{ Root: common.HexToHash("0xbbba"), Proof: mockProof, }, - ProofLERToRER: agglayer.MerkleProof{}, - ProofGERToL1Root: agglayer.MerkleProof{ + ProofLERToRER: &agglayer.MerkleProof{}, + ProofGERToL1Root: &agglayer.MerkleProof{ Root: common.HexToHash("0x7891"), Proof: mockProof, }, @@ -467,22 +371,22 @@ func TestGetImportedBridgeExits(t *testing.T) { LeafIndex: 1, }, ClaimData: &agglayer.ClaimFromRollup{ - L1Leaf: agglayer.L1InfoTreeLeaf{ + L1Leaf: &agglayer.L1InfoTreeLeaf{ L1InfoTreeIndex: 1, RollupExitRoot: common.HexToHash("0xaaa"), MainnetExitRoot: common.HexToHash("0xbbb"), - Inner: agglayer.L1InfoTreeLeafInner{ + Inner: &agglayer.L1InfoTreeLeafInner{ GlobalExitRoot: common.HexToHash("0x789"), Timestamp: 123456789, BlockHash: common.HexToHash("0xabc"), }, }, - ProofLeafLER: agglayer.MerkleProof{ + ProofLeafLER: &agglayer.MerkleProof{ Root: common.HexToHash("0xbbb"), Proof: mockProof, }, - ProofLERToRER: agglayer.MerkleProof{}, - ProofGERToL1Root: agglayer.MerkleProof{ + ProofLERToRER: &agglayer.MerkleProof{}, + ProofGERToL1Root: &agglayer.MerkleProof{ Root: common.HexToHash("0x789"), Proof: mockProof, }, @@ -506,21 +410,21 @@ func TestGetImportedBridgeExits(t *testing.T) { LeafIndex: 2, }, ClaimData: &agglayer.ClaimFromMainnnet{ - L1Leaf: agglayer.L1InfoTreeLeaf{ + L1Leaf: &agglayer.L1InfoTreeLeaf{ L1InfoTreeIndex: 2, RollupExitRoot: common.HexToHash("0xbbb"), MainnetExitRoot: common.HexToHash("0xccc"), - Inner: agglayer.L1InfoTreeLeafInner{ + Inner: &agglayer.L1InfoTreeLeafInner{ GlobalExitRoot: common.HexToHash("0x789"), Timestamp: 123456789, BlockHash: common.HexToHash("0xabc"), }, }, - ProofLeafMER: agglayer.MerkleProof{ + ProofLeafMER: &agglayer.MerkleProof{ Root: common.HexToHash("0xccc"), Proof: mockProof, }, - ProofGERToL1Root: agglayer.MerkleProof{ + ProofGERToL1Root: &agglayer.MerkleProof{ Root: common.HexToHash("0x789"), Proof: mockProof, }, @@ -641,22 +545,22 @@ func TestBuildCertificate(t *testing.T) { LeafIndex: 1, }, ClaimData: &agglayer.ClaimFromRollup{ - L1Leaf: agglayer.L1InfoTreeLeaf{ + L1Leaf: &agglayer.L1InfoTreeLeaf{ L1InfoTreeIndex: 1, RollupExitRoot: common.HexToHash("0xaaab"), MainnetExitRoot: common.HexToHash("0xbbba"), - Inner: agglayer.L1InfoTreeLeafInner{ + Inner: &agglayer.L1InfoTreeLeafInner{ GlobalExitRoot: common.HexToHash("0x7891"), Timestamp: 123456789, BlockHash: common.HexToHash("0xabc"), }, }, - ProofLeafLER: agglayer.MerkleProof{ + ProofLeafLER: &agglayer.MerkleProof{ Root: common.HexToHash("0xbbba"), Proof: mockProof, }, - ProofLERToRER: agglayer.MerkleProof{}, - ProofGERToL1Root: agglayer.MerkleProof{ + ProofLERToRER: &agglayer.MerkleProof{}, + ProofGERToL1Root: &agglayer.MerkleProof{ Root: common.HexToHash("0x7891"), Proof: mockProof, }, @@ -1332,3 +1236,150 @@ func TestSendCertificate(t *testing.T) { }) } } + +func TestExtractSignatureData(t *testing.T) { + t.Parallel() + + testR := common.HexToHash("0x1") + testV := common.HexToHash("0x2") + + tests := []struct { + name string + signature []byte + expectedR common.Hash + expectedS common.Hash + expectedOddParity bool + expectedError error + }{ + { + name: "Valid signature - odd parity", + signature: append(append(testR.Bytes(), testV.Bytes()...), 1), + expectedR: testR, + expectedS: testV, + expectedOddParity: true, + expectedError: nil, + }, + { + name: "Valid signature - even parity", + signature: append(append(testR.Bytes(), testV.Bytes()...), 2), + expectedR: testR, + expectedS: testV, + expectedOddParity: false, + expectedError: nil, + }, + { + name: "Invalid signature size", + signature: make([]byte, 64), // Invalid size + expectedError: errInvalidSignatureSize, + }, + } + + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + r, s, isOddParity, err := extractSignatureData(tt.signature) + + if tt.expectedError != nil { + require.Error(t, err) + require.Equal(t, tt.expectedError, err) + } else { + require.NoError(t, err) + require.Equal(t, tt.expectedR, r) + require.Equal(t, tt.expectedS, s) + require.Equal(t, tt.expectedOddParity, isOddParity) + } + }) + } +} + +func TestBre(t *testing.T) { + //agglayerClient := agglayer.AggLayerClient{} + + key, err := crypto.GenerateKey() + require.NoError(t, err) + + signature, err := crypto.Sign(common.HexToHash("0x1").Bytes(), key) + require.NoError(t, err) + + r, s, v, err := extractSignatureData(signature) + require.NoError(t, err) + + certificate := &agglayer.SignedCertificate{ + Certificate: &agglayer.Certificate{ + NetworkID: 1, + Height: 1, + PrevLocalExitRoot: common.HexToHash("0x1"), + NewLocalExitRoot: common.HexToHash("0x2"), + BridgeExits: []*agglayer.BridgeExit{ + { + LeafType: agglayer.LeafTypeAsset, + TokenInfo: &agglayer.TokenInfo{ + OriginNetwork: 1, + OriginTokenAddress: common.HexToAddress("0x11"), + }, + DestinationNetwork: 2, + DestinationAddress: common.HexToAddress("0x22"), + Amount: big.NewInt(100), + Metadata: []byte("metadata"), + }, + }, + ImportedBridgeExits: []*agglayer.ImportedBridgeExit{ + { + GlobalIndex: &agglayer.GlobalIndex{ + MainnetFlag: false, + RollupIndex: 1, + LeafIndex: 11, + }, + BridgeExit: &agglayer.BridgeExit{ + LeafType: agglayer.LeafTypeAsset, + TokenInfo: &agglayer.TokenInfo{ + OriginNetwork: 1, + OriginTokenAddress: common.HexToAddress("0x11"), + }, + DestinationNetwork: 2, + DestinationAddress: common.HexToAddress("0x22"), + Amount: big.NewInt(100), + Metadata: []byte("metadata"), + }, + ClaimData: &agglayer.ClaimFromMainnnet{ + ProofLeafMER: &agglayer.MerkleProof{ + Root: common.HexToHash("0x1"), + Proof: [32]common.Hash{}, + }, + ProofGERToL1Root: &agglayer.MerkleProof{ + Root: common.HexToHash("0x3"), + Proof: [32]common.Hash{}, + }, + L1Leaf: &agglayer.L1InfoTreeLeaf{ + L1InfoTreeIndex: 1, + RollupExitRoot: common.HexToHash("0x4"), + MainnetExitRoot: common.HexToHash("0x5"), + Inner: &agglayer.L1InfoTreeLeafInner{ + GlobalExitRoot: common.HexToHash("0x6"), + BlockHash: common.HexToHash("0x7"), + Timestamp: 1231, + }, + }, + }, + }, + }, + }, + Signature: &agglayer.Signature{ + R: r, + S: s, + OddParity: v, + }, + } + + file, err := os.Create("test.json") + require.NoError(t, err) + + defer file.Close() + + encoder := json.NewEncoder(file) + encoder.SetIndent("", " ") + require.NoError(t, encoder.Encode(certificate)) +} From 948a59e18f159c87343274ad16005cfd2e5f59a7 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Fri, 25 Oct 2024 08:25:14 +0200 Subject: [PATCH 63/84] fix: remove unnecessary fields from config --- aggsender/aggsender_test.go | 2 +- aggsender/config.go | 5 ----- config/default.go | 2 -- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go index b0162e49..38de7512 100644 --- a/aggsender/aggsender_test.go +++ b/aggsender/aggsender_test.go @@ -843,7 +843,7 @@ func TestSendCertificate(t *testing.T) { var ( aggsender = &AggSender{ log: log.WithFields("aggsender", 1), - cfg: Config{EpochSize: 10, BlocksBeforeEpochEnding: 2}, + cfg: Config{}, sequencerKey: cfg.sequencerKey, } mockStorage *mocks.AggSenderStorageMock diff --git a/aggsender/config.go b/aggsender/config.go index 246924a9..2d88569d 100644 --- a/aggsender/config.go +++ b/aggsender/config.go @@ -18,9 +18,4 @@ type Config struct { AggsenderPrivateKey types.KeystoreFileConfig `mapstructure:"AggsenderPrivateKey"` // URLRPCL2 is the URL of the L2 RPC node URLRPCL2 string `mapstructure:"URLRPCL2"` - // EpochSize is the size of the epoch on L1 (configured on agglayer) in blocks - EpochSize uint64 `mapstructure:"EpochSize"` - // BlocksBeforeEpochEnding indicates how many blocks before the epoch ending - // the AggSender should send the certificate - BlocksBeforeEpochEnding uint64 `mapstructure:"BlocksBeforeEpochEnding"` } diff --git a/config/default.go b/config/default.go index fe7005c3..3e2def8b 100644 --- a/config/default.go +++ b/config/default.go @@ -345,6 +345,4 @@ AggsenderPrivateKey = {Path = "{{SequencerPrivateKeyPath}}", Password = "{{Seque BlockGetInterval = "2s" URLRPCL2="{{L2URL}}" CheckSettledInterval = "2s" -EpochSize = 10 -BlocksBeforeEpochEnding = 2 ` From 3ea7d7d3951e9ce4f7ab5e37cfab3bc29bf311ad Mon Sep 17 00:00:00 2001 From: joanestebanr <129153821+joanestebanr@users.noreply.github.com> Date: Fri, 25 Oct 2024 10:19:25 +0200 Subject: [PATCH 64/84] fix: lint --- aggsender/aggsender.go | 2 +- aggsender/aggsender_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index 933f772e..7f487a8a 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -489,7 +489,7 @@ func extractSignatureData(signature []byte) (r, s common.Hash, isOddParity bool, r = common.BytesToHash(signature[:32]) // First 32 bytes are R s = common.BytesToHash(signature[32:64]) // Next 32 bytes are S - isOddParity = signature[64]%2 == 1 // Last byte is V + isOddParity = signature[64]%2 == 1 //nolint:mnd // Last byte is V return } diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go index 38de7512..ae675625 100644 --- a/aggsender/aggsender_test.go +++ b/aggsender/aggsender_test.go @@ -1295,8 +1295,8 @@ func TestExtractSignatureData(t *testing.T) { } } -func TestBre(t *testing.T) { - //agglayerClient := agglayer.AggLayerClient{} +func TestExploratoryGenerateCert(t *testing.T) { + t.Skip() key, err := crypto.GenerateKey() require.NoError(t, err) From 9616ae1c2b2f7a2d476fc8ab17fd2bb74e02e8d8 Mon Sep 17 00:00:00 2001 From: Joan Esteban <129153821+joanestebanr@users.noreply.github.com> Date: Fri, 25 Oct 2024 10:54:59 +0200 Subject: [PATCH 65/84] feat: allow bridgeAsset with forceUpdateGlobalExitRoot=false (#140) --- aggsender/aggsender.go | 5 +- aggsender/aggsender_test.go | 4 +- aggsender/mocks/mock_l2bridge_syncer.go | 22 +- aggsender/mocks/mock_logger.go | 86 ++ aggsender/types/types.go | 4 +- bridgesync/bridge_contract.go | 37 + bridgesync/bridge_contract_test.go | 13 + bridgesync/bridgesync.go | 21 +- bridgesync/bridgesync_test.go | 84 ++ bridgesync/e2e_test.go | 4 +- bridgesync/mocks/bridge_contractor.go | 93 ++ bridgesync/mocks/eth_clienter.go | 1136 +++++++++++++++++++++++ bridgesync/mocks/reorg_detector.go | 147 +++ bridgesync/processor.go | 40 +- bridgesync/processor_test.go | 4 +- claimsponsor/e2e_test.go | 5 +- cmd/run.go | 12 + test/Makefile | 8 +- 18 files changed, 1694 insertions(+), 31 deletions(-) create mode 100644 bridgesync/bridge_contract.go create mode 100644 bridgesync/bridge_contract_test.go create mode 100644 bridgesync/bridgesync_test.go create mode 100644 bridgesync/mocks/bridge_contractor.go create mode 100644 bridgesync/mocks/eth_clienter.go create mode 100644 bridgesync/mocks/reorg_detector.go diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index 7f487a8a..77fd81ae 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -125,7 +125,7 @@ func (a *AggSender) sendCertificate(ctx context.Context) error { fromBlock := lastCertificateBlock + 1 toBlock := lasL2BlockSynced - bridges, err := a.l2Syncer.GetBridges(ctx, fromBlock, toBlock) + bridges, err := a.l2Syncer.GetBridgesPublished(ctx, fromBlock, toBlock) if err != nil { return fmt.Errorf("error getting bridges: %w", err) } @@ -325,7 +325,8 @@ func (a *AggSender) getImportedBridgeExits(ctx context.Context, blockHash common.Hash ) - for _, claim := range claims { + for i, claim := range claims { + a.log.Debugf("claim[%d]: destAddr: %s GER:%s", i, claim.DestinationAddress.String(), claim.GlobalExitRoot.String()) info, err := a.l1infoTreeSyncer.GetInfoByGlobalExitRoot(claim.GlobalExitRoot) if err != nil { return nil, fmt.Errorf("error getting info by global exit root: %w", err) diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go index ae675625..51071171 100644 --- a/aggsender/aggsender_test.go +++ b/aggsender/aggsender_test.go @@ -448,6 +448,7 @@ func TestGetImportedBridgeExits(t *testing.T) { aggSender := &AggSender{ l1infoTreeSyncer: mockL1InfoTreeSyncer, + log: log.WithFields("test", "unittest"), } exits, err := aggSender.getImportedBridgeExits(context.Background(), tt.claims) @@ -649,6 +650,7 @@ func TestBuildCertificate(t *testing.T) { 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) @@ -884,7 +886,7 @@ func TestSendCertificate(t *testing.T) { mockL2Syncer.On("GetLastProcessedBlock", mock.Anything).Return(cfg.lastL2BlockProcessed...).Once() if cfg.getBridges != nil { - mockL2Syncer.On("GetBridges", mock.Anything, mock.Anything, mock.Anything).Return(cfg.getBridges...).Once() + mockL2Syncer.On("GetBridgesPublished", mock.Anything, mock.Anything, mock.Anything).Return(cfg.getBridges...).Once() } if cfg.getClaims != nil { diff --git a/aggsender/mocks/mock_l2bridge_syncer.go b/aggsender/mocks/mock_l2bridge_syncer.go index 7d87e230..725184c3 100644 --- a/aggsender/mocks/mock_l2bridge_syncer.go +++ b/aggsender/mocks/mock_l2bridge_syncer.go @@ -130,12 +130,12 @@ func (_c *L2BridgeSyncerMock_GetBlockByLER_Call) RunAndReturn(run func(context.C return _c } -// GetBridges provides a mock function with given fields: ctx, fromBlock, toBlock -func (_m *L2BridgeSyncerMock) GetBridges(ctx context.Context, fromBlock uint64, toBlock uint64) ([]bridgesync.Bridge, error) { +// GetBridgesPublished provides a mock function with given fields: ctx, fromBlock, toBlock +func (_m *L2BridgeSyncerMock) GetBridgesPublished(ctx context.Context, fromBlock uint64, toBlock uint64) ([]bridgesync.Bridge, error) { ret := _m.Called(ctx, fromBlock, toBlock) if len(ret) == 0 { - panic("no return value specified for GetBridges") + panic("no return value specified for GetBridgesPublished") } var r0 []bridgesync.Bridge @@ -160,32 +160,32 @@ func (_m *L2BridgeSyncerMock) GetBridges(ctx context.Context, fromBlock uint64, return r0, r1 } -// L2BridgeSyncerMock_GetBridges_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetBridges' -type L2BridgeSyncerMock_GetBridges_Call struct { +// L2BridgeSyncerMock_GetBridgesPublished_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetBridgesPublished' +type L2BridgeSyncerMock_GetBridgesPublished_Call struct { *mock.Call } -// GetBridges is a helper method to define mock.On call +// GetBridgesPublished is a helper method to define mock.On call // - ctx context.Context // - fromBlock uint64 // - toBlock uint64 -func (_e *L2BridgeSyncerMock_Expecter) GetBridges(ctx interface{}, fromBlock interface{}, toBlock interface{}) *L2BridgeSyncerMock_GetBridges_Call { - return &L2BridgeSyncerMock_GetBridges_Call{Call: _e.mock.On("GetBridges", ctx, fromBlock, toBlock)} +func (_e *L2BridgeSyncerMock_Expecter) GetBridgesPublished(ctx interface{}, fromBlock interface{}, toBlock interface{}) *L2BridgeSyncerMock_GetBridgesPublished_Call { + return &L2BridgeSyncerMock_GetBridgesPublished_Call{Call: _e.mock.On("GetBridgesPublished", ctx, fromBlock, toBlock)} } -func (_c *L2BridgeSyncerMock_GetBridges_Call) Run(run func(ctx context.Context, fromBlock uint64, toBlock uint64)) *L2BridgeSyncerMock_GetBridges_Call { +func (_c *L2BridgeSyncerMock_GetBridgesPublished_Call) Run(run func(ctx context.Context, fromBlock uint64, toBlock uint64)) *L2BridgeSyncerMock_GetBridgesPublished_Call { _c.Call.Run(func(args mock.Arguments) { run(args[0].(context.Context), args[1].(uint64), args[2].(uint64)) }) return _c } -func (_c *L2BridgeSyncerMock_GetBridges_Call) Return(_a0 []bridgesync.Bridge, _a1 error) *L2BridgeSyncerMock_GetBridges_Call { +func (_c *L2BridgeSyncerMock_GetBridgesPublished_Call) Return(_a0 []bridgesync.Bridge, _a1 error) *L2BridgeSyncerMock_GetBridgesPublished_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *L2BridgeSyncerMock_GetBridges_Call) RunAndReturn(run func(context.Context, uint64, uint64) ([]bridgesync.Bridge, error)) *L2BridgeSyncerMock_GetBridges_Call { +func (_c *L2BridgeSyncerMock_GetBridgesPublished_Call) RunAndReturn(run func(context.Context, uint64, uint64) ([]bridgesync.Bridge, error)) *L2BridgeSyncerMock_GetBridgesPublished_Call { _c.Call.Return(run) return _c } diff --git a/aggsender/mocks/mock_logger.go b/aggsender/mocks/mock_logger.go index b058d2a7..5b0eb4e9 100644 --- a/aggsender/mocks/mock_logger.go +++ b/aggsender/mocks/mock_logger.go @@ -17,6 +17,92 @@ func (_m *LoggerMock) EXPECT() *LoggerMock_Expecter { return &LoggerMock_Expecter{mock: &_m.Mock} } +// Debug provides a mock function with given fields: args +func (_m *LoggerMock) Debug(args ...interface{}) { + var _ca []interface{} + _ca = append(_ca, args...) + _m.Called(_ca...) +} + +// LoggerMock_Debug_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Debug' +type LoggerMock_Debug_Call struct { + *mock.Call +} + +// Debug is a helper method to define mock.On call +// - args ...interface{} +func (_e *LoggerMock_Expecter) Debug(args ...interface{}) *LoggerMock_Debug_Call { + return &LoggerMock_Debug_Call{Call: _e.mock.On("Debug", + append([]interface{}{}, args...)...)} +} + +func (_c *LoggerMock_Debug_Call) Run(run func(args ...interface{})) *LoggerMock_Debug_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-0) + for i, a := range args[0:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(variadicArgs...) + }) + return _c +} + +func (_c *LoggerMock_Debug_Call) Return() *LoggerMock_Debug_Call { + _c.Call.Return() + return _c +} + +func (_c *LoggerMock_Debug_Call) RunAndReturn(run func(...interface{})) *LoggerMock_Debug_Call { + _c.Call.Return(run) + return _c +} + +// Debugf provides a mock function with given fields: format, args +func (_m *LoggerMock) Debugf(format string, args ...interface{}) { + var _ca []interface{} + _ca = append(_ca, format) + _ca = append(_ca, args...) + _m.Called(_ca...) +} + +// LoggerMock_Debugf_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Debugf' +type LoggerMock_Debugf_Call struct { + *mock.Call +} + +// Debugf is a helper method to define mock.On call +// - format string +// - args ...interface{} +func (_e *LoggerMock_Expecter) Debugf(format interface{}, args ...interface{}) *LoggerMock_Debugf_Call { + return &LoggerMock_Debugf_Call{Call: _e.mock.On("Debugf", + append([]interface{}{format}, args...)...)} +} + +func (_c *LoggerMock_Debugf_Call) Run(run func(format string, args ...interface{})) *LoggerMock_Debugf_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-1) + for i, a := range args[1:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(args[0].(string), variadicArgs...) + }) + return _c +} + +func (_c *LoggerMock_Debugf_Call) Return() *LoggerMock_Debugf_Call { + _c.Call.Return() + return _c +} + +func (_c *LoggerMock_Debugf_Call) RunAndReturn(run func(string, ...interface{})) *LoggerMock_Debugf_Call { + _c.Call.Return(run) + return _c +} + // Error provides a mock function with given fields: args func (_m *LoggerMock) Error(args ...interface{}) { var _ca []interface{} diff --git a/aggsender/types/types.go b/aggsender/types/types.go index 423bfe73..18d3011b 100644 --- a/aggsender/types/types.go +++ b/aggsender/types/types.go @@ -25,7 +25,7 @@ type L1InfoTreeSyncer interface { type L2BridgeSyncer interface { GetBlockByLER(ctx context.Context, ler common.Hash) (uint64, error) GetExitRootByIndex(ctx context.Context, index uint32) (treeTypes.Root, error) - GetBridges(ctx context.Context, fromBlock, toBlock uint64) ([]bridgesync.Bridge, error) + GetBridgesPublished(ctx context.Context, fromBlock, toBlock uint64) ([]bridgesync.Bridge, error) GetClaims(ctx context.Context, fromBlock, toBlock uint64) ([]bridgesync.Claim, error) OriginNetwork() uint32 BlockFinality() etherman.BlockNumberFinality @@ -44,6 +44,8 @@ type Logger interface { Infof(format string, args ...interface{}) Error(args ...interface{}) Errorf(format string, args ...interface{}) + Debug(args ...interface{}) + Debugf(format string, args ...interface{}) } type CertificateInfo struct { diff --git a/bridgesync/bridge_contract.go b/bridgesync/bridge_contract.go new file mode 100644 index 00000000..3e19cec2 --- /dev/null +++ b/bridgesync/bridge_contract.go @@ -0,0 +1,37 @@ +package bridgesync + +import ( + "context" + "fmt" + "math/big" + + "github.com/0xPolygon/cdk-contracts-tooling/contracts/banana/polygonzkevmbridgev2" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" +) + +type BridgeContract struct { + bridgeAddr common.Address + Contract *polygonzkevmbridgev2.Polygonzkevmbridgev2 +} + +func NewBridgeContract(bridgeAddr common.Address, backend bind.ContractBackend) (*BridgeContract, error) { + contract, err := polygonzkevmbridgev2.NewPolygonzkevmbridgev2(bridgeAddr, backend) + if err != nil { + return nil, fmt.Errorf("failed to instantiate bridge contract at addr %s. Err:%w", bridgeAddr.String(), err) + } + + return &BridgeContract{ + bridgeAddr: bridgeAddr, + Contract: contract, + }, nil +} + +// Returns LastUpdatedDepositCount for a specific blockNumber +func (b *BridgeContract) LastUpdatedDepositCount(ctx context.Context, blockNumber uint64) (uint32, error) { + opts := &bind.CallOpts{ + Context: ctx, + BlockNumber: new(big.Int).SetUint64(blockNumber), + } + return b.Contract.LastUpdatedDepositCount(opts) +} diff --git a/bridgesync/bridge_contract_test.go b/bridgesync/bridge_contract_test.go new file mode 100644 index 00000000..753904ff --- /dev/null +++ b/bridgesync/bridge_contract_test.go @@ -0,0 +1,13 @@ +package bridgesync + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func TestLastUpdatedDepositCount(t *testing.T) { + _, err := NewBridgeContract(common.Address{}, nil) + require.NoError(t, err) +} diff --git a/bridgesync/bridgesync.go b/bridgesync/bridgesync.go index 95c2f371..4c4b4e61 100644 --- a/bridgesync/bridgesync.go +++ b/bridgesync/bridgesync.go @@ -16,6 +16,10 @@ const ( downloadBufferSize = 1000 ) +type ReorgDetector interface { + sync.ReorgDetector +} + // BridgeSync manages the state of the exit tree for the bridge contract by processing Ethereum blockchain events. type BridgeSync struct { processor *processor @@ -32,13 +36,14 @@ func NewL1( bridge common.Address, syncBlockChunkSize uint64, blockFinalityType etherman.BlockNumberFinality, - rd sync.ReorgDetector, + rd ReorgDetector, ethClient EthClienter, initialBlock uint64, waitForNewBlocksPeriod time.Duration, retryAfterErrorPeriod time.Duration, maxRetryAttemptsAfterError int, originNetwork uint32, + bridgeContract BridgeContractor, ) (*BridgeSync, error) { return newBridgeSync( ctx, @@ -55,6 +60,7 @@ func NewL1( maxRetryAttemptsAfterError, originNetwork, false, + bridgeContract, ) } @@ -65,13 +71,14 @@ func NewL2( bridge common.Address, syncBlockChunkSize uint64, blockFinalityType etherman.BlockNumberFinality, - rd sync.ReorgDetector, + rd ReorgDetector, ethClient EthClienter, initialBlock uint64, waitForNewBlocksPeriod time.Duration, retryAfterErrorPeriod time.Duration, maxRetryAttemptsAfterError int, originNetwork uint32, + bridgeContract BridgeContractor, ) (*BridgeSync, error) { return newBridgeSync( ctx, @@ -88,6 +95,7 @@ func NewL2( maxRetryAttemptsAfterError, originNetwork, true, + bridgeContract, ) } @@ -97,7 +105,7 @@ func newBridgeSync( bridge common.Address, syncBlockChunkSize uint64, blockFinalityType etherman.BlockNumberFinality, - rd sync.ReorgDetector, + rd ReorgDetector, ethClient EthClienter, initialBlock uint64, l1OrL2ID string, @@ -106,8 +114,9 @@ func newBridgeSync( maxRetryAttemptsAfterError int, originNetwork uint32, syncFullClaims bool, + bridgeContract BridgeContractor, ) (*BridgeSync, error) { - processor, err := newProcessor(dbPath, l1OrL2ID) + processor, err := newProcessor(dbPath, l1OrL2ID, bridgeContract) if err != nil { return nil, err } @@ -182,6 +191,10 @@ func (s *BridgeSync) GetBridges(ctx context.Context, fromBlock, toBlock uint64) return s.processor.GetBridges(ctx, fromBlock, toBlock) } +func (s *BridgeSync) GetBridgesPublished(ctx context.Context, fromBlock, toBlock uint64) ([]Bridge, error) { + return s.processor.GetBridgesPublished(ctx, fromBlock, toBlock) +} + func (s *BridgeSync) GetProof(ctx context.Context, depositCount uint32, localExitRoot common.Hash) (tree.Proof, error) { return s.processor.exitTree.GetProof(ctx, depositCount, localExitRoot) } diff --git a/bridgesync/bridgesync_test.go b/bridgesync/bridgesync_test.go new file mode 100644 index 00000000..125d58dd --- /dev/null +++ b/bridgesync/bridgesync_test.go @@ -0,0 +1,84 @@ +package bridgesync_test + +import ( + "context" + "testing" + "time" + + "github.com/0xPolygon/cdk/bridgesync" + mocksbridgesync "github.com/0xPolygon/cdk/bridgesync/mocks" + "github.com/0xPolygon/cdk/etherman" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +// Mock implementations for the interfaces +type MockEthClienter struct { + mock.Mock +} + +type MockBridgeContractor struct { + mock.Mock +} + +func TestNewLx(t *testing.T) { + ctx := context.Background() + dbPath := "test_db_path" + bridge := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") + syncBlockChunkSize := uint64(100) + blockFinalityType := etherman.SafeBlock + initialBlock := uint64(0) + waitForNewBlocksPeriod := time.Second * 10 + retryAfterErrorPeriod := time.Second * 5 + maxRetryAttemptsAfterError := 3 + originNetwork := uint32(1) + + mockEthClient := mocksbridgesync.NewEthClienter(t) + mockBridgeContract := mocksbridgesync.NewBridgeContractor(t) + mockReorgDetector := mocksbridgesync.NewReorgDetector(t) + + mockReorgDetector.EXPECT().Subscribe(mock.Anything).Return(nil, nil) + + bridgeSync, err := bridgesync.NewL1( + ctx, + dbPath, + bridge, + syncBlockChunkSize, + blockFinalityType, + mockReorgDetector, + mockEthClient, + initialBlock, + waitForNewBlocksPeriod, + retryAfterErrorPeriod, + maxRetryAttemptsAfterError, + originNetwork, + mockBridgeContract, + ) + + assert.NoError(t, err) + assert.NotNil(t, bridgeSync) + assert.Equal(t, originNetwork, bridgeSync.OriginNetwork()) + assert.Equal(t, blockFinalityType, bridgeSync.BlockFinality()) + + bridgeSyncL2, err := bridgesync.NewL2( + ctx, + dbPath, + bridge, + syncBlockChunkSize, + blockFinalityType, + mockReorgDetector, + mockEthClient, + initialBlock, + waitForNewBlocksPeriod, + retryAfterErrorPeriod, + maxRetryAttemptsAfterError, + originNetwork, + mockBridgeContract, + ) + + assert.NoError(t, err) + assert.NotNil(t, bridgeSync) + assert.Equal(t, originNetwork, bridgeSyncL2.OriginNetwork()) + assert.Equal(t, blockFinalityType, bridgeSyncL2.BlockFinality()) +} diff --git a/bridgesync/e2e_test.go b/bridgesync/e2e_test.go index a8868ce1..f096b7a5 100644 --- a/bridgesync/e2e_test.go +++ b/bridgesync/e2e_test.go @@ -29,7 +29,9 @@ func TestBridgeEventE2E(t *testing.T) { go rd.Start(ctx) //nolint:errcheck testClient := helpers.TestClient{ClientRenamed: client.Client()} - syncer, err := bridgesync.NewL1(ctx, dbPathSyncer, setup.EBZkevmBridgeAddr, 10, etherman.LatestBlock, rd, testClient, 0, time.Millisecond*10, 0, 0, 1) + bridgeContract, err := bridgesync.NewBridgeContract(setup.EBZkevmBridgeAddr, testClient) + require.NoError(t, err) + syncer, err := bridgesync.NewL1(ctx, dbPathSyncer, setup.EBZkevmBridgeAddr, 10, etherman.LatestBlock, rd, testClient, 0, time.Millisecond*10, 0, 0, 1, bridgeContract) require.NoError(t, err) go syncer.Start(ctx) diff --git a/bridgesync/mocks/bridge_contractor.go b/bridgesync/mocks/bridge_contractor.go new file mode 100644 index 00000000..fd559850 --- /dev/null +++ b/bridgesync/mocks/bridge_contractor.go @@ -0,0 +1,93 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks_bridgesync + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" +) + +// BridgeContractor is an autogenerated mock type for the BridgeContractor type +type BridgeContractor struct { + mock.Mock +} + +type BridgeContractor_Expecter struct { + mock *mock.Mock +} + +func (_m *BridgeContractor) EXPECT() *BridgeContractor_Expecter { + return &BridgeContractor_Expecter{mock: &_m.Mock} +} + +// LastUpdatedDepositCount provides a mock function with given fields: ctx, BlockNumber +func (_m *BridgeContractor) LastUpdatedDepositCount(ctx context.Context, BlockNumber uint64) (uint32, error) { + ret := _m.Called(ctx, BlockNumber) + + if len(ret) == 0 { + panic("no return value specified for LastUpdatedDepositCount") + } + + var r0 uint32 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64) (uint32, error)); ok { + return rf(ctx, BlockNumber) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64) uint32); ok { + r0 = rf(ctx, BlockNumber) + } else { + r0 = ret.Get(0).(uint32) + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64) error); ok { + r1 = rf(ctx, BlockNumber) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// BridgeContractor_LastUpdatedDepositCount_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LastUpdatedDepositCount' +type BridgeContractor_LastUpdatedDepositCount_Call struct { + *mock.Call +} + +// LastUpdatedDepositCount is a helper method to define mock.On call +// - ctx context.Context +// - BlockNumber uint64 +func (_e *BridgeContractor_Expecter) LastUpdatedDepositCount(ctx interface{}, BlockNumber interface{}) *BridgeContractor_LastUpdatedDepositCount_Call { + return &BridgeContractor_LastUpdatedDepositCount_Call{Call: _e.mock.On("LastUpdatedDepositCount", ctx, BlockNumber)} +} + +func (_c *BridgeContractor_LastUpdatedDepositCount_Call) Run(run func(ctx context.Context, BlockNumber uint64)) *BridgeContractor_LastUpdatedDepositCount_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64)) + }) + return _c +} + +func (_c *BridgeContractor_LastUpdatedDepositCount_Call) Return(_a0 uint32, _a1 error) *BridgeContractor_LastUpdatedDepositCount_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *BridgeContractor_LastUpdatedDepositCount_Call) RunAndReturn(run func(context.Context, uint64) (uint32, error)) *BridgeContractor_LastUpdatedDepositCount_Call { + _c.Call.Return(run) + return _c +} + +// NewBridgeContractor creates a new instance of BridgeContractor. 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 NewBridgeContractor(t interface { + mock.TestingT + Cleanup(func()) +}) *BridgeContractor { + mock := &BridgeContractor{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/bridgesync/mocks/eth_clienter.go b/bridgesync/mocks/eth_clienter.go new file mode 100644 index 00000000..3d208e45 --- /dev/null +++ b/bridgesync/mocks/eth_clienter.go @@ -0,0 +1,1136 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks_bridgesync + +import ( + big "math/big" + + common "github.com/ethereum/go-ethereum/common" + + context "context" + + ethereum "github.com/ethereum/go-ethereum" + + mock "github.com/stretchr/testify/mock" + + rpc "github.com/ethereum/go-ethereum/rpc" + + types "github.com/ethereum/go-ethereum/core/types" +) + +// EthClienter is an autogenerated mock type for the EthClienter type +type EthClienter struct { + mock.Mock +} + +type EthClienter_Expecter struct { + mock *mock.Mock +} + +func (_m *EthClienter) EXPECT() *EthClienter_Expecter { + return &EthClienter_Expecter{mock: &_m.Mock} +} + +// BlockByHash provides a mock function with given fields: ctx, hash +func (_m *EthClienter) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { + ret := _m.Called(ctx, hash) + + if len(ret) == 0 { + panic("no return value specified for BlockByHash") + } + + var r0 *types.Block + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Hash) (*types.Block, error)); ok { + return rf(ctx, hash) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Hash) *types.Block); ok { + r0 = rf(ctx, hash) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Block) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Hash) error); ok { + r1 = rf(ctx, hash) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EthClienter_BlockByHash_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BlockByHash' +type EthClienter_BlockByHash_Call struct { + *mock.Call +} + +// BlockByHash is a helper method to define mock.On call +// - ctx context.Context +// - hash common.Hash +func (_e *EthClienter_Expecter) BlockByHash(ctx interface{}, hash interface{}) *EthClienter_BlockByHash_Call { + return &EthClienter_BlockByHash_Call{Call: _e.mock.On("BlockByHash", ctx, hash)} +} + +func (_c *EthClienter_BlockByHash_Call) Run(run func(ctx context.Context, hash common.Hash)) *EthClienter_BlockByHash_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(common.Hash)) + }) + return _c +} + +func (_c *EthClienter_BlockByHash_Call) Return(_a0 *types.Block, _a1 error) *EthClienter_BlockByHash_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EthClienter_BlockByHash_Call) RunAndReturn(run func(context.Context, common.Hash) (*types.Block, error)) *EthClienter_BlockByHash_Call { + _c.Call.Return(run) + return _c +} + +// BlockByNumber provides a mock function with given fields: ctx, number +func (_m *EthClienter) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) { + ret := _m.Called(ctx, number) + + if len(ret) == 0 { + panic("no return value specified for BlockByNumber") + } + + var r0 *types.Block + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *big.Int) (*types.Block, error)); ok { + return rf(ctx, number) + } + if rf, ok := ret.Get(0).(func(context.Context, *big.Int) *types.Block); ok { + r0 = rf(ctx, number) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Block) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *big.Int) error); ok { + r1 = rf(ctx, number) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EthClienter_BlockByNumber_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BlockByNumber' +type EthClienter_BlockByNumber_Call struct { + *mock.Call +} + +// BlockByNumber is a helper method to define mock.On call +// - ctx context.Context +// - number *big.Int +func (_e *EthClienter_Expecter) BlockByNumber(ctx interface{}, number interface{}) *EthClienter_BlockByNumber_Call { + return &EthClienter_BlockByNumber_Call{Call: _e.mock.On("BlockByNumber", ctx, number)} +} + +func (_c *EthClienter_BlockByNumber_Call) Run(run func(ctx context.Context, number *big.Int)) *EthClienter_BlockByNumber_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*big.Int)) + }) + return _c +} + +func (_c *EthClienter_BlockByNumber_Call) Return(_a0 *types.Block, _a1 error) *EthClienter_BlockByNumber_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EthClienter_BlockByNumber_Call) RunAndReturn(run func(context.Context, *big.Int) (*types.Block, error)) *EthClienter_BlockByNumber_Call { + _c.Call.Return(run) + return _c +} + +// BlockNumber provides a mock function with given fields: ctx +func (_m *EthClienter) BlockNumber(ctx context.Context) (uint64, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for BlockNumber") + } + + var r0 uint64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (uint64, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) uint64); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(uint64) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EthClienter_BlockNumber_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BlockNumber' +type EthClienter_BlockNumber_Call struct { + *mock.Call +} + +// BlockNumber is a helper method to define mock.On call +// - ctx context.Context +func (_e *EthClienter_Expecter) BlockNumber(ctx interface{}) *EthClienter_BlockNumber_Call { + return &EthClienter_BlockNumber_Call{Call: _e.mock.On("BlockNumber", ctx)} +} + +func (_c *EthClienter_BlockNumber_Call) Run(run func(ctx context.Context)) *EthClienter_BlockNumber_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *EthClienter_BlockNumber_Call) Return(_a0 uint64, _a1 error) *EthClienter_BlockNumber_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EthClienter_BlockNumber_Call) RunAndReturn(run func(context.Context) (uint64, error)) *EthClienter_BlockNumber_Call { + _c.Call.Return(run) + return _c +} + +// CallContract provides a mock function with given fields: ctx, call, blockNumber +func (_m *EthClienter) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { + ret := _m.Called(ctx, call, blockNumber) + + if len(ret) == 0 { + panic("no return value specified for CallContract") + } + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, ethereum.CallMsg, *big.Int) ([]byte, error)); ok { + return rf(ctx, call, blockNumber) + } + if rf, ok := ret.Get(0).(func(context.Context, ethereum.CallMsg, *big.Int) []byte); ok { + r0 = rf(ctx, call, blockNumber) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, ethereum.CallMsg, *big.Int) error); ok { + r1 = rf(ctx, call, blockNumber) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EthClienter_CallContract_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CallContract' +type EthClienter_CallContract_Call struct { + *mock.Call +} + +// CallContract is a helper method to define mock.On call +// - ctx context.Context +// - call ethereum.CallMsg +// - blockNumber *big.Int +func (_e *EthClienter_Expecter) CallContract(ctx interface{}, call interface{}, blockNumber interface{}) *EthClienter_CallContract_Call { + return &EthClienter_CallContract_Call{Call: _e.mock.On("CallContract", ctx, call, blockNumber)} +} + +func (_c *EthClienter_CallContract_Call) Run(run func(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int)) *EthClienter_CallContract_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(ethereum.CallMsg), args[2].(*big.Int)) + }) + return _c +} + +func (_c *EthClienter_CallContract_Call) Return(_a0 []byte, _a1 error) *EthClienter_CallContract_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EthClienter_CallContract_Call) RunAndReturn(run func(context.Context, ethereum.CallMsg, *big.Int) ([]byte, error)) *EthClienter_CallContract_Call { + _c.Call.Return(run) + return _c +} + +// Client provides a mock function with given fields: +func (_m *EthClienter) Client() *rpc.Client { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Client") + } + + var r0 *rpc.Client + if rf, ok := ret.Get(0).(func() *rpc.Client); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*rpc.Client) + } + } + + return r0 +} + +// EthClienter_Client_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Client' +type EthClienter_Client_Call struct { + *mock.Call +} + +// Client is a helper method to define mock.On call +func (_e *EthClienter_Expecter) Client() *EthClienter_Client_Call { + return &EthClienter_Client_Call{Call: _e.mock.On("Client")} +} + +func (_c *EthClienter_Client_Call) Run(run func()) *EthClienter_Client_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *EthClienter_Client_Call) Return(_a0 *rpc.Client) *EthClienter_Client_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *EthClienter_Client_Call) RunAndReturn(run func() *rpc.Client) *EthClienter_Client_Call { + _c.Call.Return(run) + return _c +} + +// CodeAt provides a mock function with given fields: ctx, contract, blockNumber +func (_m *EthClienter) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) { + ret := _m.Called(ctx, contract, blockNumber) + + if len(ret) == 0 { + panic("no return value specified for CodeAt") + } + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Address, *big.Int) ([]byte, error)); ok { + return rf(ctx, contract, blockNumber) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Address, *big.Int) []byte); ok { + r0 = rf(ctx, contract, blockNumber) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Address, *big.Int) error); ok { + r1 = rf(ctx, contract, blockNumber) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EthClienter_CodeAt_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CodeAt' +type EthClienter_CodeAt_Call struct { + *mock.Call +} + +// CodeAt is a helper method to define mock.On call +// - ctx context.Context +// - contract common.Address +// - blockNumber *big.Int +func (_e *EthClienter_Expecter) CodeAt(ctx interface{}, contract interface{}, blockNumber interface{}) *EthClienter_CodeAt_Call { + return &EthClienter_CodeAt_Call{Call: _e.mock.On("CodeAt", ctx, contract, blockNumber)} +} + +func (_c *EthClienter_CodeAt_Call) Run(run func(ctx context.Context, contract common.Address, blockNumber *big.Int)) *EthClienter_CodeAt_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(common.Address), args[2].(*big.Int)) + }) + return _c +} + +func (_c *EthClienter_CodeAt_Call) Return(_a0 []byte, _a1 error) *EthClienter_CodeAt_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EthClienter_CodeAt_Call) RunAndReturn(run func(context.Context, common.Address, *big.Int) ([]byte, error)) *EthClienter_CodeAt_Call { + _c.Call.Return(run) + return _c +} + +// EstimateGas provides a mock function with given fields: ctx, call +func (_m *EthClienter) EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint64, error) { + ret := _m.Called(ctx, call) + + if len(ret) == 0 { + panic("no return value specified for EstimateGas") + } + + var r0 uint64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, ethereum.CallMsg) (uint64, error)); ok { + return rf(ctx, call) + } + if rf, ok := ret.Get(0).(func(context.Context, ethereum.CallMsg) uint64); ok { + r0 = rf(ctx, call) + } else { + r0 = ret.Get(0).(uint64) + } + + if rf, ok := ret.Get(1).(func(context.Context, ethereum.CallMsg) error); ok { + r1 = rf(ctx, call) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EthClienter_EstimateGas_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EstimateGas' +type EthClienter_EstimateGas_Call struct { + *mock.Call +} + +// EstimateGas is a helper method to define mock.On call +// - ctx context.Context +// - call ethereum.CallMsg +func (_e *EthClienter_Expecter) EstimateGas(ctx interface{}, call interface{}) *EthClienter_EstimateGas_Call { + return &EthClienter_EstimateGas_Call{Call: _e.mock.On("EstimateGas", ctx, call)} +} + +func (_c *EthClienter_EstimateGas_Call) Run(run func(ctx context.Context, call ethereum.CallMsg)) *EthClienter_EstimateGas_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(ethereum.CallMsg)) + }) + return _c +} + +func (_c *EthClienter_EstimateGas_Call) Return(_a0 uint64, _a1 error) *EthClienter_EstimateGas_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EthClienter_EstimateGas_Call) RunAndReturn(run func(context.Context, ethereum.CallMsg) (uint64, error)) *EthClienter_EstimateGas_Call { + _c.Call.Return(run) + return _c +} + +// FilterLogs provides a mock function with given fields: ctx, q +func (_m *EthClienter) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) { + ret := _m.Called(ctx, q) + + if len(ret) == 0 { + panic("no return value specified for FilterLogs") + } + + var r0 []types.Log + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, ethereum.FilterQuery) ([]types.Log, error)); ok { + return rf(ctx, q) + } + if rf, ok := ret.Get(0).(func(context.Context, ethereum.FilterQuery) []types.Log); ok { + r0 = rf(ctx, q) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]types.Log) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, ethereum.FilterQuery) error); ok { + r1 = rf(ctx, q) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EthClienter_FilterLogs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FilterLogs' +type EthClienter_FilterLogs_Call struct { + *mock.Call +} + +// FilterLogs is a helper method to define mock.On call +// - ctx context.Context +// - q ethereum.FilterQuery +func (_e *EthClienter_Expecter) FilterLogs(ctx interface{}, q interface{}) *EthClienter_FilterLogs_Call { + return &EthClienter_FilterLogs_Call{Call: _e.mock.On("FilterLogs", ctx, q)} +} + +func (_c *EthClienter_FilterLogs_Call) Run(run func(ctx context.Context, q ethereum.FilterQuery)) *EthClienter_FilterLogs_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(ethereum.FilterQuery)) + }) + return _c +} + +func (_c *EthClienter_FilterLogs_Call) Return(_a0 []types.Log, _a1 error) *EthClienter_FilterLogs_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EthClienter_FilterLogs_Call) RunAndReturn(run func(context.Context, ethereum.FilterQuery) ([]types.Log, error)) *EthClienter_FilterLogs_Call { + _c.Call.Return(run) + return _c +} + +// HeaderByHash provides a mock function with given fields: ctx, hash +func (_m *EthClienter) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { + ret := _m.Called(ctx, hash) + + if len(ret) == 0 { + panic("no return value specified for HeaderByHash") + } + + var r0 *types.Header + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Hash) (*types.Header, error)); ok { + return rf(ctx, hash) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Hash) *types.Header); ok { + r0 = rf(ctx, hash) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Header) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Hash) error); ok { + r1 = rf(ctx, hash) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EthClienter_HeaderByHash_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'HeaderByHash' +type EthClienter_HeaderByHash_Call struct { + *mock.Call +} + +// HeaderByHash is a helper method to define mock.On call +// - ctx context.Context +// - hash common.Hash +func (_e *EthClienter_Expecter) HeaderByHash(ctx interface{}, hash interface{}) *EthClienter_HeaderByHash_Call { + return &EthClienter_HeaderByHash_Call{Call: _e.mock.On("HeaderByHash", ctx, hash)} +} + +func (_c *EthClienter_HeaderByHash_Call) Run(run func(ctx context.Context, hash common.Hash)) *EthClienter_HeaderByHash_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(common.Hash)) + }) + return _c +} + +func (_c *EthClienter_HeaderByHash_Call) Return(_a0 *types.Header, _a1 error) *EthClienter_HeaderByHash_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EthClienter_HeaderByHash_Call) RunAndReturn(run func(context.Context, common.Hash) (*types.Header, error)) *EthClienter_HeaderByHash_Call { + _c.Call.Return(run) + return _c +} + +// HeaderByNumber provides a mock function with given fields: ctx, number +func (_m *EthClienter) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { + ret := _m.Called(ctx, number) + + if len(ret) == 0 { + panic("no return value specified for HeaderByNumber") + } + + var r0 *types.Header + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *big.Int) (*types.Header, error)); ok { + return rf(ctx, number) + } + if rf, ok := ret.Get(0).(func(context.Context, *big.Int) *types.Header); ok { + r0 = rf(ctx, number) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Header) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *big.Int) error); ok { + r1 = rf(ctx, number) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EthClienter_HeaderByNumber_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'HeaderByNumber' +type EthClienter_HeaderByNumber_Call struct { + *mock.Call +} + +// HeaderByNumber is a helper method to define mock.On call +// - ctx context.Context +// - number *big.Int +func (_e *EthClienter_Expecter) HeaderByNumber(ctx interface{}, number interface{}) *EthClienter_HeaderByNumber_Call { + return &EthClienter_HeaderByNumber_Call{Call: _e.mock.On("HeaderByNumber", ctx, number)} +} + +func (_c *EthClienter_HeaderByNumber_Call) Run(run func(ctx context.Context, number *big.Int)) *EthClienter_HeaderByNumber_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*big.Int)) + }) + return _c +} + +func (_c *EthClienter_HeaderByNumber_Call) Return(_a0 *types.Header, _a1 error) *EthClienter_HeaderByNumber_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EthClienter_HeaderByNumber_Call) RunAndReturn(run func(context.Context, *big.Int) (*types.Header, error)) *EthClienter_HeaderByNumber_Call { + _c.Call.Return(run) + return _c +} + +// PendingCodeAt provides a mock function with given fields: ctx, account +func (_m *EthClienter) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) { + ret := _m.Called(ctx, account) + + if len(ret) == 0 { + panic("no return value specified for PendingCodeAt") + } + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Address) ([]byte, error)); ok { + return rf(ctx, account) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Address) []byte); ok { + r0 = rf(ctx, account) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Address) error); ok { + r1 = rf(ctx, account) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EthClienter_PendingCodeAt_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PendingCodeAt' +type EthClienter_PendingCodeAt_Call struct { + *mock.Call +} + +// PendingCodeAt is a helper method to define mock.On call +// - ctx context.Context +// - account common.Address +func (_e *EthClienter_Expecter) PendingCodeAt(ctx interface{}, account interface{}) *EthClienter_PendingCodeAt_Call { + return &EthClienter_PendingCodeAt_Call{Call: _e.mock.On("PendingCodeAt", ctx, account)} +} + +func (_c *EthClienter_PendingCodeAt_Call) Run(run func(ctx context.Context, account common.Address)) *EthClienter_PendingCodeAt_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(common.Address)) + }) + return _c +} + +func (_c *EthClienter_PendingCodeAt_Call) Return(_a0 []byte, _a1 error) *EthClienter_PendingCodeAt_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EthClienter_PendingCodeAt_Call) RunAndReturn(run func(context.Context, common.Address) ([]byte, error)) *EthClienter_PendingCodeAt_Call { + _c.Call.Return(run) + return _c +} + +// PendingNonceAt provides a mock function with given fields: ctx, account +func (_m *EthClienter) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { + ret := _m.Called(ctx, account) + + if len(ret) == 0 { + panic("no return value specified for PendingNonceAt") + } + + var r0 uint64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Address) (uint64, error)); ok { + return rf(ctx, account) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Address) uint64); ok { + r0 = rf(ctx, account) + } else { + r0 = ret.Get(0).(uint64) + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Address) error); ok { + r1 = rf(ctx, account) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EthClienter_PendingNonceAt_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PendingNonceAt' +type EthClienter_PendingNonceAt_Call struct { + *mock.Call +} + +// PendingNonceAt is a helper method to define mock.On call +// - ctx context.Context +// - account common.Address +func (_e *EthClienter_Expecter) PendingNonceAt(ctx interface{}, account interface{}) *EthClienter_PendingNonceAt_Call { + return &EthClienter_PendingNonceAt_Call{Call: _e.mock.On("PendingNonceAt", ctx, account)} +} + +func (_c *EthClienter_PendingNonceAt_Call) Run(run func(ctx context.Context, account common.Address)) *EthClienter_PendingNonceAt_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(common.Address)) + }) + return _c +} + +func (_c *EthClienter_PendingNonceAt_Call) Return(_a0 uint64, _a1 error) *EthClienter_PendingNonceAt_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EthClienter_PendingNonceAt_Call) RunAndReturn(run func(context.Context, common.Address) (uint64, error)) *EthClienter_PendingNonceAt_Call { + _c.Call.Return(run) + return _c +} + +// SendTransaction provides a mock function with given fields: ctx, tx +func (_m *EthClienter) SendTransaction(ctx context.Context, tx *types.Transaction) error { + ret := _m.Called(ctx, tx) + + if len(ret) == 0 { + panic("no return value specified for SendTransaction") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *types.Transaction) error); ok { + r0 = rf(ctx, tx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// EthClienter_SendTransaction_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendTransaction' +type EthClienter_SendTransaction_Call struct { + *mock.Call +} + +// SendTransaction is a helper method to define mock.On call +// - ctx context.Context +// - tx *types.Transaction +func (_e *EthClienter_Expecter) SendTransaction(ctx interface{}, tx interface{}) *EthClienter_SendTransaction_Call { + return &EthClienter_SendTransaction_Call{Call: _e.mock.On("SendTransaction", ctx, tx)} +} + +func (_c *EthClienter_SendTransaction_Call) Run(run func(ctx context.Context, tx *types.Transaction)) *EthClienter_SendTransaction_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*types.Transaction)) + }) + return _c +} + +func (_c *EthClienter_SendTransaction_Call) Return(_a0 error) *EthClienter_SendTransaction_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *EthClienter_SendTransaction_Call) RunAndReturn(run func(context.Context, *types.Transaction) error) *EthClienter_SendTransaction_Call { + _c.Call.Return(run) + return _c +} + +// SubscribeFilterLogs provides a mock function with given fields: ctx, q, ch +func (_m *EthClienter) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { + ret := _m.Called(ctx, q, ch) + + if len(ret) == 0 { + panic("no return value specified for SubscribeFilterLogs") + } + + var r0 ethereum.Subscription + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, ethereum.FilterQuery, chan<- types.Log) (ethereum.Subscription, error)); ok { + return rf(ctx, q, ch) + } + if rf, ok := ret.Get(0).(func(context.Context, ethereum.FilterQuery, chan<- types.Log) ethereum.Subscription); ok { + r0 = rf(ctx, q, ch) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(ethereum.Subscription) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, ethereum.FilterQuery, chan<- types.Log) error); ok { + r1 = rf(ctx, q, ch) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EthClienter_SubscribeFilterLogs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SubscribeFilterLogs' +type EthClienter_SubscribeFilterLogs_Call struct { + *mock.Call +} + +// SubscribeFilterLogs is a helper method to define mock.On call +// - ctx context.Context +// - q ethereum.FilterQuery +// - ch chan<- types.Log +func (_e *EthClienter_Expecter) SubscribeFilterLogs(ctx interface{}, q interface{}, ch interface{}) *EthClienter_SubscribeFilterLogs_Call { + return &EthClienter_SubscribeFilterLogs_Call{Call: _e.mock.On("SubscribeFilterLogs", ctx, q, ch)} +} + +func (_c *EthClienter_SubscribeFilterLogs_Call) Run(run func(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log)) *EthClienter_SubscribeFilterLogs_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(ethereum.FilterQuery), args[2].(chan<- types.Log)) + }) + return _c +} + +func (_c *EthClienter_SubscribeFilterLogs_Call) Return(_a0 ethereum.Subscription, _a1 error) *EthClienter_SubscribeFilterLogs_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EthClienter_SubscribeFilterLogs_Call) RunAndReturn(run func(context.Context, ethereum.FilterQuery, chan<- types.Log) (ethereum.Subscription, error)) *EthClienter_SubscribeFilterLogs_Call { + _c.Call.Return(run) + return _c +} + +// SubscribeNewHead provides a mock function with given fields: ctx, ch +func (_m *EthClienter) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) { + ret := _m.Called(ctx, ch) + + if len(ret) == 0 { + panic("no return value specified for SubscribeNewHead") + } + + var r0 ethereum.Subscription + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, chan<- *types.Header) (ethereum.Subscription, error)); ok { + return rf(ctx, ch) + } + if rf, ok := ret.Get(0).(func(context.Context, chan<- *types.Header) ethereum.Subscription); ok { + r0 = rf(ctx, ch) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(ethereum.Subscription) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, chan<- *types.Header) error); ok { + r1 = rf(ctx, ch) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EthClienter_SubscribeNewHead_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SubscribeNewHead' +type EthClienter_SubscribeNewHead_Call struct { + *mock.Call +} + +// SubscribeNewHead is a helper method to define mock.On call +// - ctx context.Context +// - ch chan<- *types.Header +func (_e *EthClienter_Expecter) SubscribeNewHead(ctx interface{}, ch interface{}) *EthClienter_SubscribeNewHead_Call { + return &EthClienter_SubscribeNewHead_Call{Call: _e.mock.On("SubscribeNewHead", ctx, ch)} +} + +func (_c *EthClienter_SubscribeNewHead_Call) Run(run func(ctx context.Context, ch chan<- *types.Header)) *EthClienter_SubscribeNewHead_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(chan<- *types.Header)) + }) + return _c +} + +func (_c *EthClienter_SubscribeNewHead_Call) Return(_a0 ethereum.Subscription, _a1 error) *EthClienter_SubscribeNewHead_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EthClienter_SubscribeNewHead_Call) RunAndReturn(run func(context.Context, chan<- *types.Header) (ethereum.Subscription, error)) *EthClienter_SubscribeNewHead_Call { + _c.Call.Return(run) + return _c +} + +// SuggestGasPrice provides a mock function with given fields: ctx +func (_m *EthClienter) SuggestGasPrice(ctx context.Context) (*big.Int, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for SuggestGasPrice") + } + + var r0 *big.Int + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (*big.Int, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) *big.Int); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*big.Int) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EthClienter_SuggestGasPrice_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SuggestGasPrice' +type EthClienter_SuggestGasPrice_Call struct { + *mock.Call +} + +// SuggestGasPrice is a helper method to define mock.On call +// - ctx context.Context +func (_e *EthClienter_Expecter) SuggestGasPrice(ctx interface{}) *EthClienter_SuggestGasPrice_Call { + return &EthClienter_SuggestGasPrice_Call{Call: _e.mock.On("SuggestGasPrice", ctx)} +} + +func (_c *EthClienter_SuggestGasPrice_Call) Run(run func(ctx context.Context)) *EthClienter_SuggestGasPrice_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *EthClienter_SuggestGasPrice_Call) Return(_a0 *big.Int, _a1 error) *EthClienter_SuggestGasPrice_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EthClienter_SuggestGasPrice_Call) RunAndReturn(run func(context.Context) (*big.Int, error)) *EthClienter_SuggestGasPrice_Call { + _c.Call.Return(run) + return _c +} + +// SuggestGasTipCap provides a mock function with given fields: ctx +func (_m *EthClienter) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for SuggestGasTipCap") + } + + var r0 *big.Int + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (*big.Int, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) *big.Int); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*big.Int) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EthClienter_SuggestGasTipCap_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SuggestGasTipCap' +type EthClienter_SuggestGasTipCap_Call struct { + *mock.Call +} + +// SuggestGasTipCap is a helper method to define mock.On call +// - ctx context.Context +func (_e *EthClienter_Expecter) SuggestGasTipCap(ctx interface{}) *EthClienter_SuggestGasTipCap_Call { + return &EthClienter_SuggestGasTipCap_Call{Call: _e.mock.On("SuggestGasTipCap", ctx)} +} + +func (_c *EthClienter_SuggestGasTipCap_Call) Run(run func(ctx context.Context)) *EthClienter_SuggestGasTipCap_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *EthClienter_SuggestGasTipCap_Call) Return(_a0 *big.Int, _a1 error) *EthClienter_SuggestGasTipCap_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EthClienter_SuggestGasTipCap_Call) RunAndReturn(run func(context.Context) (*big.Int, error)) *EthClienter_SuggestGasTipCap_Call { + _c.Call.Return(run) + return _c +} + +// TransactionCount provides a mock function with given fields: ctx, blockHash +func (_m *EthClienter) TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error) { + ret := _m.Called(ctx, blockHash) + + if len(ret) == 0 { + panic("no return value specified for TransactionCount") + } + + var r0 uint + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Hash) (uint, error)); ok { + return rf(ctx, blockHash) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Hash) uint); ok { + r0 = rf(ctx, blockHash) + } else { + r0 = ret.Get(0).(uint) + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Hash) error); ok { + r1 = rf(ctx, blockHash) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EthClienter_TransactionCount_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'TransactionCount' +type EthClienter_TransactionCount_Call struct { + *mock.Call +} + +// TransactionCount is a helper method to define mock.On call +// - ctx context.Context +// - blockHash common.Hash +func (_e *EthClienter_Expecter) TransactionCount(ctx interface{}, blockHash interface{}) *EthClienter_TransactionCount_Call { + return &EthClienter_TransactionCount_Call{Call: _e.mock.On("TransactionCount", ctx, blockHash)} +} + +func (_c *EthClienter_TransactionCount_Call) Run(run func(ctx context.Context, blockHash common.Hash)) *EthClienter_TransactionCount_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(common.Hash)) + }) + return _c +} + +func (_c *EthClienter_TransactionCount_Call) Return(_a0 uint, _a1 error) *EthClienter_TransactionCount_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EthClienter_TransactionCount_Call) RunAndReturn(run func(context.Context, common.Hash) (uint, error)) *EthClienter_TransactionCount_Call { + _c.Call.Return(run) + return _c +} + +// TransactionInBlock provides a mock function with given fields: ctx, blockHash, index +func (_m *EthClienter) TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error) { + ret := _m.Called(ctx, blockHash, index) + + if len(ret) == 0 { + panic("no return value specified for TransactionInBlock") + } + + var r0 *types.Transaction + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Hash, uint) (*types.Transaction, error)); ok { + return rf(ctx, blockHash, index) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Hash, uint) *types.Transaction); ok { + r0 = rf(ctx, blockHash, index) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Transaction) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Hash, uint) error); ok { + r1 = rf(ctx, blockHash, index) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EthClienter_TransactionInBlock_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'TransactionInBlock' +type EthClienter_TransactionInBlock_Call struct { + *mock.Call +} + +// TransactionInBlock is a helper method to define mock.On call +// - ctx context.Context +// - blockHash common.Hash +// - index uint +func (_e *EthClienter_Expecter) TransactionInBlock(ctx interface{}, blockHash interface{}, index interface{}) *EthClienter_TransactionInBlock_Call { + return &EthClienter_TransactionInBlock_Call{Call: _e.mock.On("TransactionInBlock", ctx, blockHash, index)} +} + +func (_c *EthClienter_TransactionInBlock_Call) Run(run func(ctx context.Context, blockHash common.Hash, index uint)) *EthClienter_TransactionInBlock_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(common.Hash), args[2].(uint)) + }) + return _c +} + +func (_c *EthClienter_TransactionInBlock_Call) Return(_a0 *types.Transaction, _a1 error) *EthClienter_TransactionInBlock_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EthClienter_TransactionInBlock_Call) RunAndReturn(run func(context.Context, common.Hash, uint) (*types.Transaction, error)) *EthClienter_TransactionInBlock_Call { + _c.Call.Return(run) + return _c +} + +// NewEthClienter creates a new instance of EthClienter. 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 NewEthClienter(t interface { + mock.TestingT + Cleanup(func()) +}) *EthClienter { + mock := &EthClienter{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/bridgesync/mocks/reorg_detector.go b/bridgesync/mocks/reorg_detector.go new file mode 100644 index 00000000..d24f4b83 --- /dev/null +++ b/bridgesync/mocks/reorg_detector.go @@ -0,0 +1,147 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks_bridgesync + +import ( + context "context" + + common "github.com/ethereum/go-ethereum/common" + + mock "github.com/stretchr/testify/mock" + + reorgdetector "github.com/0xPolygon/cdk/reorgdetector" +) + +// ReorgDetector is an autogenerated mock type for the ReorgDetector type +type ReorgDetector struct { + mock.Mock +} + +type ReorgDetector_Expecter struct { + mock *mock.Mock +} + +func (_m *ReorgDetector) EXPECT() *ReorgDetector_Expecter { + return &ReorgDetector_Expecter{mock: &_m.Mock} +} + +// AddBlockToTrack provides a mock function with given fields: ctx, id, blockNum, blockHash +func (_m *ReorgDetector) AddBlockToTrack(ctx context.Context, id string, blockNum uint64, blockHash common.Hash) error { + ret := _m.Called(ctx, id, blockNum, blockHash) + + if len(ret) == 0 { + panic("no return value specified for AddBlockToTrack") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, uint64, common.Hash) error); ok { + r0 = rf(ctx, id, blockNum, blockHash) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ReorgDetector_AddBlockToTrack_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddBlockToTrack' +type ReorgDetector_AddBlockToTrack_Call struct { + *mock.Call +} + +// AddBlockToTrack is a helper method to define mock.On call +// - ctx context.Context +// - id string +// - blockNum uint64 +// - blockHash common.Hash +func (_e *ReorgDetector_Expecter) AddBlockToTrack(ctx interface{}, id interface{}, blockNum interface{}, blockHash interface{}) *ReorgDetector_AddBlockToTrack_Call { + return &ReorgDetector_AddBlockToTrack_Call{Call: _e.mock.On("AddBlockToTrack", ctx, id, blockNum, blockHash)} +} + +func (_c *ReorgDetector_AddBlockToTrack_Call) Run(run func(ctx context.Context, id string, blockNum uint64, blockHash common.Hash)) *ReorgDetector_AddBlockToTrack_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(uint64), args[3].(common.Hash)) + }) + return _c +} + +func (_c *ReorgDetector_AddBlockToTrack_Call) Return(_a0 error) *ReorgDetector_AddBlockToTrack_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *ReorgDetector_AddBlockToTrack_Call) RunAndReturn(run func(context.Context, string, uint64, common.Hash) error) *ReorgDetector_AddBlockToTrack_Call { + _c.Call.Return(run) + return _c +} + +// Subscribe provides a mock function with given fields: id +func (_m *ReorgDetector) Subscribe(id string) (*reorgdetector.Subscription, error) { + ret := _m.Called(id) + + if len(ret) == 0 { + panic("no return value specified for Subscribe") + } + + var r0 *reorgdetector.Subscription + var r1 error + if rf, ok := ret.Get(0).(func(string) (*reorgdetector.Subscription, error)); ok { + return rf(id) + } + if rf, ok := ret.Get(0).(func(string) *reorgdetector.Subscription); ok { + r0 = rf(id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*reorgdetector.Subscription) + } + } + + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ReorgDetector_Subscribe_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Subscribe' +type ReorgDetector_Subscribe_Call struct { + *mock.Call +} + +// Subscribe is a helper method to define mock.On call +// - id string +func (_e *ReorgDetector_Expecter) Subscribe(id interface{}) *ReorgDetector_Subscribe_Call { + return &ReorgDetector_Subscribe_Call{Call: _e.mock.On("Subscribe", id)} +} + +func (_c *ReorgDetector_Subscribe_Call) Run(run func(id string)) *ReorgDetector_Subscribe_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *ReorgDetector_Subscribe_Call) Return(_a0 *reorgdetector.Subscription, _a1 error) *ReorgDetector_Subscribe_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ReorgDetector_Subscribe_Call) RunAndReturn(run func(string) (*reorgdetector.Subscription, error)) *ReorgDetector_Subscribe_Call { + _c.Call.Return(run) + return _c +} + +// NewReorgDetector creates a new instance of ReorgDetector. 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 NewReorgDetector(t interface { + mock.TestingT + Cleanup(func()) +}) *ReorgDetector { + mock := &ReorgDetector{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/bridgesync/processor.go b/bridgesync/processor.go index dbd11a1d..e0a0dae7 100644 --- a/bridgesync/processor.go +++ b/bridgesync/processor.go @@ -98,13 +98,18 @@ type Event struct { Claim *Claim } +type BridgeContractor interface { + LastUpdatedDepositCount(ctx context.Context, BlockNumber uint64) (uint32, error) +} + type processor struct { - db *sql.DB - exitTree *tree.AppendOnlyTree - log *log.Logger + db *sql.DB + exitTree *tree.AppendOnlyTree + log *log.Logger + bridgeContract BridgeContractor } -func newProcessor(dbPath, loggerPrefix string) (*processor, error) { +func newProcessor(dbPath, loggerPrefix string, bridgeContract BridgeContractor) (*processor, error) { err := migrations.RunMigrations(dbPath) if err != nil { return nil, err @@ -116,11 +121,32 @@ func newProcessor(dbPath, loggerPrefix string) (*processor, error) { logger := log.WithFields("bridge-syncer", loggerPrefix) exitTree := tree.NewAppendOnlyTree(db, "") return &processor{ - db: db, - exitTree: exitTree, - log: logger, + db: db, + exitTree: exitTree, + log: logger, + bridgeContract: bridgeContract, }, nil } +func (p *processor) GetBridgesPublished( + ctx context.Context, fromBlock, toBlock uint64, +) ([]Bridge, error) { + allBridges, err := p.GetBridges(ctx, fromBlock, toBlock) + if err != nil { + return nil, err + } + lastCount, er := p.bridgeContract.LastUpdatedDepositCount(ctx, toBlock) + if er != nil { + return nil, er + } + p.log.Debugf("last updated deposit count: %d in block %d. Num bridges: %d", lastCount, toBlock, len(allBridges)) + var bridges []Bridge + for _, bridge := range allBridges { + if bridge.DepositCount <= lastCount { + bridges = append(bridges, bridge) + } + } + return bridges, nil +} func (p *processor) GetBridges( ctx context.Context, fromBlock, toBlock uint64, diff --git a/bridgesync/processor_test.go b/bridgesync/processor_test.go index c4ed607d..c4f447bc 100644 --- a/bridgesync/processor_test.go +++ b/bridgesync/processor_test.go @@ -34,7 +34,7 @@ func TestProceessor(t *testing.T) { log.Debugf("sqlite path: %s", path) err := migrationsBridge.RunMigrations(path) require.NoError(t, err) - p, err := newProcessor(path, "foo") + p, err := newProcessor(path, "foo", nil) require.NoError(t, err) actions := []processAction{ // processed: ~ @@ -687,7 +687,7 @@ func TestInsertAndGetClaim(t *testing.T) { log.Debugf("sqlite path: %s", path) err := migrationsBridge.RunMigrations(path) require.NoError(t, err) - p, err := newProcessor(path, "foo") + p, err := newProcessor(path, "foo", nil) require.NoError(t, err) tx, err := p.db.BeginTx(context.Background(), nil) diff --git a/claimsponsor/e2e_test.go b/claimsponsor/e2e_test.go index 426d7b3e..7c4c3062 100644 --- a/claimsponsor/e2e_test.go +++ b/claimsponsor/e2e_test.go @@ -26,7 +26,10 @@ func TestE2EL1toEVML2(t *testing.T) { env := aggoraclehelpers.SetupAggoracleWithEVMChain(t) dbPathBridgeSyncL1 := path.Join(t.TempDir(), "file::memory:?cache=shared") testClient := helpers.TestClient{ClientRenamed: env.L1Client.Client()} - bridgeSyncL1, err := bridgesync.NewL1(ctx, dbPathBridgeSyncL1, env.BridgeL1Addr, 10, etherman.LatestBlock, env.ReorgDetector, testClient, 0, time.Millisecond*10, 0, 0, 1) + bridgeContract, err := bridgesync.NewBridgeContract(env.BridgeL1Addr, testClient) + require.NoError(t, err) + + bridgeSyncL1, err := bridgesync.NewL1(ctx, dbPathBridgeSyncL1, env.BridgeL1Addr, 10, etherman.LatestBlock, env.ReorgDetector, testClient, 0, time.Millisecond*10, 0, 0, 1, bridgeContract) require.NoError(t, err) go bridgeSyncL1.Start(ctx) diff --git a/cmd/run.go b/cmd/run.go index 94fd3425..9d9e9625 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -692,6 +692,10 @@ func runBridgeSyncL1IfNeeded( if !isNeeded([]string{cdkcommon.RPC}, components) { return nil } + bridgeContract, err := bridgesync.NewBridgeContract(cfg.BridgeAddr, l1Client) + if err != nil { + log.Fatalf("error creating L1 bridge contract: %s", err) + } bridgeSyncL1, err := bridgesync.NewL1( ctx, cfg.DBPath, @@ -705,6 +709,7 @@ func runBridgeSyncL1IfNeeded( cfg.RetryAfterErrorPeriod.Duration, cfg.MaxRetryAttemptsAfterError, cfg.OriginNetwork, + bridgeContract, ) if err != nil { log.Fatalf("error creating bridgeSyncL1: %s", err) @@ -724,6 +729,12 @@ func runBridgeSyncL2IfNeeded( if !isNeeded([]string{cdkcommon.RPC, cdkcommon.AGGSENDER}, components) { return nil } + + bridgeContract, err := bridgesync.NewBridgeContract(cfg.BridgeAddr, l2Client) + if err != nil { + log.Fatalf("error creating L2 bridge contract: %s", err) + } + bridgeSyncL2, err := bridgesync.NewL2( ctx, cfg.DBPath, @@ -737,6 +748,7 @@ func runBridgeSyncL2IfNeeded( cfg.RetryAfterErrorPeriod.Duration, cfg.MaxRetryAttemptsAfterError, cfg.OriginNetwork, + bridgeContract, ) if err != nil { log.Fatalf("error creating bridgeSyncL2: %s", err) diff --git a/test/Makefile b/test/Makefile index 96ede079..c966d050 100644 --- a/test/Makefile +++ b/test/Makefile @@ -2,7 +2,7 @@ generate-mocks: generate-mocks-bridgesync generate-mocks-reorgdetector generate-mocks-sequencesender \ generate-mocks-da generate-mocks-l1infotreesync generate-mocks-helpers \ generate-mocks-sync generate-mocks-l1infotreesync generate-mocks-aggregator \ - generate-mocks-aggsender generate-mocks-agglayer + generate-mocks-aggsender generate-mocks-agglayer generate-mocks-bridgesync .PHONY: generate-mocks-bridgesync generate-mocks-bridgesync: ## Generates mocks for bridgesync, using mockery tool @@ -70,6 +70,12 @@ generate-mocks-aggsender: ## Generates mocks for aggsender, using mockery tool generate-mocks-agglayer: ## Generates mocks for agglayer, using mockery tool export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=AgglayerClientInterface --dir=../agglayer --output=../agglayer --outpkg=agglayer --inpackage --structname=AgglayerClientMock --filename=mock_agglayer_client.go +.PHONY: generate-mocks-bridgesync +generate-mocks-bridgesync: ## Generates mocks for bridgesync, using mockery tool + rm -Rf ../bridgesync/mocks + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --all --case snake --dir ../bridgesync --output ../bridgesync/mocks --outpkg mocks_bridgesync ${COMMON_MOCKERY_PARAMS} + + .PHONY: test-e2e-fork9-validium test-e2e-fork9-validium: stop ./run-e2e.sh fork9 cdk-validium From d47cb0153d0b0944f9564d2761caec9626eef2b4 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Fri, 25 Oct 2024 11:27:29 +0200 Subject: [PATCH 66/84] feat: GetBridgesPublished unit test --- bridgesync/processor_test.go | 117 +++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/bridgesync/processor_test.go b/bridgesync/processor_test.go index c4f447bc..17152651 100644 --- a/bridgesync/processor_test.go +++ b/bridgesync/processor_test.go @@ -724,3 +724,120 @@ func TestInsertAndGetClaim(t *testing.T) { require.Len(t, claims, 1) require.Equal(t, testClaim, &claims[0]) } + +type mockBridgeContract struct { + lastUpdatedDepositCount uint32 + err error +} + +func (m *mockBridgeContract) LastUpdatedDepositCount(ctx context.Context, blockNumber uint64) (uint32, error) { + return m.lastUpdatedDepositCount, m.err +} + +func TestGetBridgesPublished(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + fromBlock uint64 + toBlock uint64 + bridges []Bridge + lastUpdatedDepositCount uint32 + expectedBridges []Bridge + expectedError error + }{ + { + name: "no bridges", + fromBlock: 1, + toBlock: 10, + bridges: []Bridge{}, + lastUpdatedDepositCount: 0, + expectedBridges: nil, + expectedError: nil, + }, + { + name: "bridges within deposit count", + fromBlock: 1, + toBlock: 10, + bridges: []Bridge{ + {DepositCount: 1, BlockNum: 1, Amount: big.NewInt(1)}, + {DepositCount: 2, BlockNum: 2, Amount: big.NewInt(1)}, + }, + lastUpdatedDepositCount: 2, + expectedBridges: []Bridge{ + {DepositCount: 1, BlockNum: 1, Amount: big.NewInt(1)}, + {DepositCount: 2, BlockNum: 2, Amount: big.NewInt(1)}, + }, + expectedError: nil, + }, + { + name: "bridges exceeding deposit count", + fromBlock: 1, + toBlock: 10, + bridges: []Bridge{ + {DepositCount: 1, BlockNum: 1, Amount: big.NewInt(1)}, + {DepositCount: 2, BlockNum: 2, Amount: big.NewInt(1)}, + {DepositCount: 3, BlockNum: 3, Amount: big.NewInt(1)}, + }, + lastUpdatedDepositCount: 2, + expectedBridges: []Bridge{ + {DepositCount: 1, BlockNum: 1, Amount: big.NewInt(1)}, + {DepositCount: 2, BlockNum: 2, Amount: big.NewInt(1)}, + }, + expectedError: nil, + }, + { + name: "error fetching last updated deposit count", + fromBlock: 1, + toBlock: 10, + bridges: []Bridge{ + {DepositCount: 1, BlockNum: 1, Amount: big.NewInt(1)}, + }, + lastUpdatedDepositCount: 0, + expectedBridges: nil, + expectedError: errors.New("mock error"), + }, + } + + for _, tc := range testCases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + mockBridgeContract := &mockBridgeContract{ + lastUpdatedDepositCount: tc.lastUpdatedDepositCount, + err: tc.expectedError, + } + + path := path.Join(t.TempDir(), "file::memory:?cache=shared") + require.NoError(t, migrationsBridge.RunMigrations(path)) + p, err := newProcessor(path, "foo", mockBridgeContract) + require.NoError(t, err) + + tx, err := p.db.BeginTx(context.Background(), nil) + require.NoError(t, err) + + for i := tc.fromBlock; i <= tc.toBlock; i++ { + _, err = tx.Exec(`INSERT INTO block (num) VALUES ($1)`, i) + require.NoError(t, err) + } + + for _, bridge := range tc.bridges { + require.NoError(t, meddler.Insert(tx, "bridge", &bridge)) + } + + require.NoError(t, tx.Commit()) + + ctx := context.Background() + bridges, err := p.GetBridgesPublished(ctx, tc.fromBlock, tc.toBlock) + + if tc.expectedError != nil { + require.Equal(t, tc.expectedError, err) + } else { + require.NoError(t, err) + require.Equal(t, tc.expectedBridges, bridges) + } + }) + } +} From d3665710dac9a71a41a1a545fd85474e577274db Mon Sep 17 00:00:00 2001 From: joanestebanr <129153821+joanestebanr@users.noreply.github.com> Date: Fri, 25 Oct 2024 12:37:07 +0200 Subject: [PATCH 67/84] feat: fix Certificate format --- agglayer/types.go | 40 ++++++++++++++++++++++++++++++++++++++++ aggsender/aggsender.go | 17 +++++++++++++++++ config/default.go | 2 ++ 3 files changed, 59 insertions(+) diff --git a/agglayer/types.go b/agglayer/types.go index b56e3e5e..3d703b4a 100644 --- a/agglayer/types.go +++ b/agglayer/types.go @@ -31,6 +31,10 @@ func (l LeafType) Uint8() uint8 { return uint8(l) } +func (l LeafType) String() string { + return [...]string{"Transfer", "Message"}[l] +} + const ( LeafTypeAsset LeafType = iota LeafTypeMessage @@ -135,6 +139,42 @@ type MerkleProof struct { Proof [types.DefaultHeight]common.Hash `json:"proof"` } +func (m Certificate) MarshalJSON() ([]byte, error) { + return json.Marshal(&struct { + NetworkID uint32 `json:"network_id"` + Height uint64 `json:"height"` + PrevLocalExitRoot [32]byte `json:"prev_local_exit_root"` + NewLocalExitRoot [32]byte `json:"new_local_exit_root"` + BridgeExits []*BridgeExit `json:"bridge_exits"` + ImportedBridgeExits []*ImportedBridgeExit `json:"imported_bridge_exits"` + }{ + NetworkID: m.NetworkID, + Height: m.Height, + PrevLocalExitRoot: m.PrevLocalExitRoot, + NewLocalExitRoot: m.NewLocalExitRoot, + BridgeExits: m.BridgeExits, + ImportedBridgeExits: m.ImportedBridgeExits, + }) +} + +func (m BridgeExit) MarshalJSON() ([]byte, error) { + return json.Marshal(&struct { + LeafType LeafType `json:"leaf_type"` + TokenInfo *TokenInfo `json:"token_info"` + DestinationNetwork uint32 `json:"dest_network"` + DestinationAddress common.Address `json:"dest_address"` + Amount *big.Int `json:"amount"` + Metadata []byte `json:"metadata"` + }{ + LeafType: m.LeafType.String(), + TokenInfo: m.TokenInfo, + DestinationNetwork: m.DestinationNetwork, + DestinationAddress: m.DestinationAddress, + Amount: m.Amount, + Metadata: m.Metadata, + }) +} + // MarshalJSON is the implementation of the json.Marshaler interface func (m *MerkleProof) MarshalJSON() ([]byte, error) { return json.Marshal(&struct { diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index 77fd81ae..655a4a24 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -3,8 +3,10 @@ package aggsender import ( "context" "crypto/ecdsa" + "encoding/json" "errors" "fmt" + "io/ioutil" "time" "github.com/0xPolygon/cdk/agglayer" @@ -151,11 +153,13 @@ func (a *AggSender) sendCertificate(ctx context.Context) error { if err != nil { return fmt.Errorf("error signing certificate: %w", err) } + a.saveCertificate(signedCertificate) certificateHash, err := a.aggLayerClient.SendCertificate(signedCertificate) if err != nil { return fmt.Errorf("error sending certificate: %w", err) } + log.Infof("certificate send: Height: %d hash: %s", signedCertificate.Height, certificateHash.String()) if err := a.storage.SaveLastSentCertificate(ctx, aggsendertypes.CertificateInfo{ Height: certificate.Height, @@ -172,6 +176,19 @@ func (a *AggSender) sendCertificate(ctx context.Context) error { return nil } +func (a *AggSender) saveCertificate(signedCertificate *agglayer.SignedCertificate) { + fn := "/tmp/certificate.json" + a.log.Infof("saving certificate in db") + jsonData, err := json.Marshal(signedCertificate) + if err != nil { + a.log.Errorf("error marshalling certificate: %w", err) + } + // write json data to file + err = ioutil.WriteFile(fn, jsonData, 0644) + if err != nil { + a.log.Errorf("error writing certificate to file: %w", err) + } +} // buildCertificate builds a certificate from the bridge events func (a *AggSender) buildCertificate(ctx context.Context, diff --git a/config/default.go b/config/default.go index 3e2def8b..f07b3f17 100644 --- a/config/default.go +++ b/config/default.go @@ -308,6 +308,7 @@ SyncBlockChunkSize = 100 RetryAfterErrorPeriod = "1s" MaxRetryAttemptsAfterError = -1 WaitForNewBlocksPeriod = "3s" +OriginNetwork=0 [BridgeL2Sync] DBPath = "{{PathRWData}}/bridgel2sync.sqlite" @@ -318,6 +319,7 @@ SyncBlockChunkSize = 100 RetryAfterErrorPeriod = "1s" MaxRetryAttemptsAfterError = -1 WaitForNewBlocksPeriod = "3s" +OriginNetwork=1 [LastGERSync] # MDBX database path From 87ebc7c57ffd255f795e896f8cb2ad4b9bb13cc2 Mon Sep 17 00:00:00 2001 From: joanestebanr <129153821+joanestebanr@users.noreply.github.com> Date: Fri, 25 Oct 2024 13:00:48 +0200 Subject: [PATCH 68/84] fix: format of certificate --- agglayer/types.go | 39 +++++++++++++++++++++---- agglayer/types_test.go | 66 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 5 deletions(-) create mode 100644 agglayer/types_test.go diff --git a/agglayer/types.go b/agglayer/types.go index 3d703b4a..530ae168 100644 --- a/agglayer/types.go +++ b/agglayer/types.go @@ -139,6 +139,26 @@ type MerkleProof struct { Proof [types.DefaultHeight]common.Hash `json:"proof"` } +func (m SignedCertificate) MarshalJSON() ([]byte, error) { + return json.Marshal(&struct { + NetworkID uint32 `json:"network_id"` + Height uint64 `json:"height"` + PrevLocalExitRoot [32]byte `json:"prev_local_exit_root"` + NewLocalExitRoot [32]byte `json:"new_local_exit_root"` + BridgeExits []*BridgeExit `json:"bridge_exits"` + ImportedBridgeExits []*ImportedBridgeExit `json:"imported_bridge_exits"` + Signature *Signature `json:"signature"` + }{ + NetworkID: m.NetworkID, + Height: m.Height, + PrevLocalExitRoot: m.PrevLocalExitRoot, + NewLocalExitRoot: m.NewLocalExitRoot, + BridgeExits: m.BridgeExits, + ImportedBridgeExits: m.ImportedBridgeExits, + Signature: m.Signature, + }) +} + func (m Certificate) MarshalJSON() ([]byte, error) { return json.Marshal(&struct { NetworkID uint32 `json:"network_id"` @@ -157,21 +177,30 @@ func (m Certificate) MarshalJSON() ([]byte, error) { }) } +func bytesToUints(data []byte) []uint { + uints := make([]uint, len(data)) + for i, b := range data { + uints[i] = uint(b) + } + return uints +} + func (m BridgeExit) MarshalJSON() ([]byte, error) { return json.Marshal(&struct { - LeafType LeafType `json:"leaf_type"` + LeafType string `json:"leaf_type"` TokenInfo *TokenInfo `json:"token_info"` DestinationNetwork uint32 `json:"dest_network"` DestinationAddress common.Address `json:"dest_address"` - Amount *big.Int `json:"amount"` - Metadata []byte `json:"metadata"` + Amount string `json:"amount"` + Metadata []uint `json:"metadata"` }{ LeafType: m.LeafType.String(), TokenInfo: m.TokenInfo, DestinationNetwork: m.DestinationNetwork, DestinationAddress: m.DestinationAddress, - Amount: m.Amount, - Metadata: m.Metadata, + Amount: m.Amount.String(), + // If []byte is marshaled as a string base64 encoded + Metadata: bytesToUints(m.Metadata), }) } diff --git a/agglayer/types_test.go b/agglayer/types_test.go new file mode 100644 index 00000000..1df1f20f --- /dev/null +++ b/agglayer/types_test.go @@ -0,0 +1,66 @@ +package agglayer + +import ( + "encoding/json" + "math/big" + "testing" + + "github.com/0xPolygon/cdk/log" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +const ( + expectedSignedCertificateEmptyMetadataJSON = `{"network_id":1,"height":1,"prev_local_exit_root":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"new_local_exit_root":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"bridge_exits":[{"leaf_type":"Transfer","token_info":null,"dest_network":0,"dest_address":"0x0000000000000000000000000000000000000000","amount":"1","metadata":[]}],"imported_bridge_exits":[{"bridge_exit":{"leaf_type":"Transfer","token_info":null,"dest_network":0,"dest_address":"0x0000000000000000000000000000000000000000","amount":"1","metadata":[]},"claim_data":null,"global_index":{"mainnet_flag":false,"rollup_index":1,"leaf_index":1}}],"signature":{"r":"0x0000000000000000000000000000000000000000000000000000000000000000","s":"0x0000000000000000000000000000000000000000000000000000000000000000","odd_y_parity":false}}` + expectedSignedCertificateyMetadataJSON = `{"network_id":1,"height":1,"prev_local_exit_root":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"new_local_exit_root":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"bridge_exits":[{"leaf_type":"Transfer","token_info":null,"dest_network":0,"dest_address":"0x0000000000000000000000000000000000000000","amount":"1","metadata":[1,2,3]}],"imported_bridge_exits":[{"bridge_exit":{"leaf_type":"Transfer","token_info":null,"dest_network":0,"dest_address":"0x0000000000000000000000000000000000000000","amount":"1","metadata":[]},"claim_data":null,"global_index":{"mainnet_flag":false,"rollup_index":1,"leaf_index":1}}],"signature":{"r":"0x0000000000000000000000000000000000000000000000000000000000000000","s":"0x0000000000000000000000000000000000000000000000000000000000000000","odd_y_parity":false}}` +) + +func TestMarshalJSON(t *testing.T) { + cert := SignedCertificate{ + Certificate: &Certificate{ + NetworkID: 1, + Height: 1, + PrevLocalExitRoot: common.Hash{}, + NewLocalExitRoot: common.Hash{}, + BridgeExits: []*BridgeExit{ + { + LeafType: LeafTypeAsset, + DestinationAddress: common.Address{}, + Amount: big.NewInt(1), + }, + }, + ImportedBridgeExits: []*ImportedBridgeExit{ + { + BridgeExit: &BridgeExit{ + LeafType: LeafTypeAsset, + DestinationAddress: common.Address{}, + Amount: big.NewInt(1), + Metadata: []byte{}, + }, + ClaimData: nil, + GlobalIndex: &GlobalIndex{ + MainnetFlag: false, + RollupIndex: 1, + LeafIndex: 1, + }, + }, + }, + }, + + Signature: &Signature{ + R: common.Hash{}, + S: common.Hash{}, + OddParity: false, + }, + } + data, err := json.Marshal(cert) + require.NoError(t, err) + log.Info(string(data)) + require.Equal(t, expectedSignedCertificateEmptyMetadataJSON, string(data)) + + cert.BridgeExits[0].Metadata = []byte{1, 2, 3} + data, err = json.Marshal(cert) + require.NoError(t, err) + log.Info(string(data)) + require.Equal(t, expectedSignedCertificateyMetadataJSON, string(data)) +} From 1834b98a90af577e376ac95d8418182f0d3505c0 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Fri, 25 Oct 2024 14:21:12 +0200 Subject: [PATCH 69/84] fix: claims json format --- agglayer/types.go | 133 ++++++++++++++++-------------------- aggsender/aggsender_test.go | 2 +- 2 files changed, 58 insertions(+), 77 deletions(-) diff --git a/agglayer/types.go b/agglayer/types.go index 530ae168..a48f0b0f 100644 --- a/agglayer/types.go +++ b/agglayer/types.go @@ -44,8 +44,8 @@ const ( type Certificate struct { NetworkID uint32 `json:"network_id"` Height uint64 `json:"height"` - PrevLocalExitRoot common.Hash `json:"prev_local_exit_root"` - NewLocalExitRoot common.Hash `json:"new_local_exit_root"` + PrevLocalExitRoot [32]byte `json:"prev_local_exit_root"` + NewLocalExitRoot [32]byte `json:"new_local_exit_root"` BridgeExits []*BridgeExit `json:"bridge_exits"` ImportedBridgeExits []*ImportedBridgeExit `json:"imported_bridge_exits"` } @@ -68,8 +68,8 @@ func (c *Certificate) Hash() common.Hash { return crypto.Keccak256Hash( cdkcommon.Uint32ToBytes(c.NetworkID), cdkcommon.Uint64ToBytes(c.Height), - c.PrevLocalExitRoot.Bytes(), - c.NewLocalExitRoot.Bytes(), + c.PrevLocalExitRoot[:], + c.NewLocalExitRoot[:], bridgeExitsPart, importedBridgeExitsPart, ) @@ -117,66 +117,42 @@ type BridgeExit struct { } // Hash returns a hash that uniquely identifies the bridge exit -func (c *BridgeExit) Hash() common.Hash { - if c.Amount == nil { - c.Amount = big.NewInt(0) +func (b *BridgeExit) Hash() common.Hash { + if b.Amount == nil { + b.Amount = big.NewInt(0) } return crypto.Keccak256Hash( - []byte{c.LeafType.Uint8()}, - cdkcommon.Uint32ToBytes(c.TokenInfo.OriginNetwork), - c.TokenInfo.OriginTokenAddress.Bytes(), - cdkcommon.Uint32ToBytes(c.DestinationNetwork), - c.DestinationAddress.Bytes(), - c.Amount.Bytes(), - crypto.Keccak256(c.Metadata), + []byte{b.LeafType.Uint8()}, + cdkcommon.Uint32ToBytes(b.TokenInfo.OriginNetwork), + b.TokenInfo.OriginTokenAddress.Bytes(), + cdkcommon.Uint32ToBytes(b.DestinationNetwork), + b.DestinationAddress.Bytes(), + b.Amount.Bytes(), + crypto.Keccak256(b.Metadata), ) } -// MerkleProof represents an inclusion proof of a leaf in a Merkle tree -type MerkleProof struct { - Root common.Hash `json:"root"` - Proof [types.DefaultHeight]common.Hash `json:"proof"` -} - -func (m SignedCertificate) MarshalJSON() ([]byte, error) { - return json.Marshal(&struct { - NetworkID uint32 `json:"network_id"` - Height uint64 `json:"height"` - PrevLocalExitRoot [32]byte `json:"prev_local_exit_root"` - NewLocalExitRoot [32]byte `json:"new_local_exit_root"` - BridgeExits []*BridgeExit `json:"bridge_exits"` - ImportedBridgeExits []*ImportedBridgeExit `json:"imported_bridge_exits"` - Signature *Signature `json:"signature"` - }{ - NetworkID: m.NetworkID, - Height: m.Height, - PrevLocalExitRoot: m.PrevLocalExitRoot, - NewLocalExitRoot: m.NewLocalExitRoot, - BridgeExits: m.BridgeExits, - ImportedBridgeExits: m.ImportedBridgeExits, - Signature: m.Signature, - }) -} - -func (m Certificate) MarshalJSON() ([]byte, error) { +// MarshalJSON is the implementation of the json.Marshaler interface +func (b *BridgeExit) MarshalJSON() ([]byte, error) { return json.Marshal(&struct { - NetworkID uint32 `json:"network_id"` - Height uint64 `json:"height"` - PrevLocalExitRoot [32]byte `json:"prev_local_exit_root"` - NewLocalExitRoot [32]byte `json:"new_local_exit_root"` - BridgeExits []*BridgeExit `json:"bridge_exits"` - ImportedBridgeExits []*ImportedBridgeExit `json:"imported_bridge_exits"` + LeafType string `json:"leaf_type"` + TokenInfo *TokenInfo `json:"token_info"` + DestinationNetwork uint32 `json:"dest_network"` + DestinationAddress common.Address `json:"dest_address"` + Amount *big.Int `json:"amount"` + Metadata []uint `json:"metadata"` }{ - NetworkID: m.NetworkID, - Height: m.Height, - PrevLocalExitRoot: m.PrevLocalExitRoot, - NewLocalExitRoot: m.NewLocalExitRoot, - BridgeExits: m.BridgeExits, - ImportedBridgeExits: m.ImportedBridgeExits, + LeafType: b.LeafType.String(), + TokenInfo: b.TokenInfo, + DestinationNetwork: b.DestinationNetwork, + DestinationAddress: b.DestinationAddress, + Amount: b.Amount, + Metadata: bytesToUints(b.Metadata), }) } +// bytesToUints converts a byte slice to a slice of uints func bytesToUints(data []byte) []uint { uints := make([]uint, len(data)) for i, b := range data { @@ -185,34 +161,26 @@ func bytesToUints(data []byte) []uint { return uints } -func (m BridgeExit) MarshalJSON() ([]byte, error) { - return json.Marshal(&struct { - LeafType string `json:"leaf_type"` - TokenInfo *TokenInfo `json:"token_info"` - DestinationNetwork uint32 `json:"dest_network"` - DestinationAddress common.Address `json:"dest_address"` - Amount string `json:"amount"` - Metadata []uint `json:"metadata"` - }{ - LeafType: m.LeafType.String(), - TokenInfo: m.TokenInfo, - DestinationNetwork: m.DestinationNetwork, - DestinationAddress: m.DestinationAddress, - Amount: m.Amount.String(), - // If []byte is marshaled as a string base64 encoded - Metadata: bytesToUints(m.Metadata), - }) +// MerkleProof represents an inclusion proof of a leaf in a Merkle tree +type MerkleProof struct { + Root common.Hash `json:"root"` + Proof [types.DefaultHeight]common.Hash `json:"proof"` } // MarshalJSON is the implementation of the json.Marshaler interface func (m *MerkleProof) MarshalJSON() ([]byte, error) { + proofsAsBytes := [types.DefaultHeight][types.DefaultHeight]byte{} + for i, proof := range m.Proof { + proofsAsBytes[i] = proof + } + return json.Marshal(&struct { - Root common.Hash `json:"root"` - Proof map[string][types.DefaultHeight]common.Hash `json:"proof"` + Root [types.DefaultHeight]byte `json:"root"` + Proof map[string][types.DefaultHeight][types.DefaultHeight]byte `json:"proof"` }{ Root: m.Root, - Proof: map[string][types.DefaultHeight]common.Hash{ - "siblings": m.Proof, + Proof: map[string][types.DefaultHeight][types.DefaultHeight]byte{ + "siblings": proofsAsBytes, }, }) } @@ -247,11 +215,24 @@ func (l *L1InfoTreeLeafInner) Hash() common.Hash { ) } +// MarshalJSON is the implementation of the json.Marshaler interface +func (l *L1InfoTreeLeafInner) MarshalJSON() ([]byte, error) { + return json.Marshal(&struct { + GlobalExitRoot [types.DefaultHeight]byte `json:"global_exit_root"` + BlockHash [types.DefaultHeight]byte `json:"block_hash"` + Timestamp uint64 `json:"timestamp"` + }{ + GlobalExitRoot: l.GlobalExitRoot, + BlockHash: l.BlockHash, + Timestamp: l.Timestamp, + }) +} + // L1InfoTreeLeaf represents the leaf of the L1 info tree type L1InfoTreeLeaf struct { L1InfoTreeIndex uint32 `json:"l1_info_tree_index"` - RollupExitRoot common.Hash `json:"rer"` - MainnetExitRoot common.Hash `json:"mer"` + RollupExitRoot [32]byte `json:"rer"` + MainnetExitRoot [32]byte `json:"mer"` Inner *L1InfoTreeLeafInner `json:"inner"` } diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go index 51071171..7f91f777 100644 --- a/aggsender/aggsender_test.go +++ b/aggsender/aggsender_test.go @@ -1298,7 +1298,7 @@ func TestExtractSignatureData(t *testing.T) { } func TestExploratoryGenerateCert(t *testing.T) { - t.Skip() + t.Skip("This test is only for exploratory purposes, to generate json format of the certificate") key, err := crypto.GenerateKey() require.NoError(t, err) From 505a02ce31665a46efc77b0968dfa247a3a9c310 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Fri, 25 Oct 2024 14:39:34 +0200 Subject: [PATCH 70/84] fix: remove saveCertificate to json function --- agglayer/types.go | 4 ++-- aggsender/aggsender.go | 16 ---------------- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/agglayer/types.go b/agglayer/types.go index a48f0b0f..548e889e 100644 --- a/agglayer/types.go +++ b/agglayer/types.go @@ -140,14 +140,14 @@ func (b *BridgeExit) MarshalJSON() ([]byte, error) { TokenInfo *TokenInfo `json:"token_info"` DestinationNetwork uint32 `json:"dest_network"` DestinationAddress common.Address `json:"dest_address"` - Amount *big.Int `json:"amount"` + Amount string `json:"amount"` Metadata []uint `json:"metadata"` }{ LeafType: b.LeafType.String(), TokenInfo: b.TokenInfo, DestinationNetwork: b.DestinationNetwork, DestinationAddress: b.DestinationAddress, - Amount: b.Amount, + Amount: b.Amount.String(), Metadata: bytesToUints(b.Metadata), }) } diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index 655a4a24..b1f8c500 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -3,10 +3,8 @@ package aggsender import ( "context" "crypto/ecdsa" - "encoding/json" "errors" "fmt" - "io/ioutil" "time" "github.com/0xPolygon/cdk/agglayer" @@ -153,7 +151,6 @@ func (a *AggSender) sendCertificate(ctx context.Context) error { if err != nil { return fmt.Errorf("error signing certificate: %w", err) } - a.saveCertificate(signedCertificate) certificateHash, err := a.aggLayerClient.SendCertificate(signedCertificate) if err != nil { @@ -176,19 +173,6 @@ func (a *AggSender) sendCertificate(ctx context.Context) error { return nil } -func (a *AggSender) saveCertificate(signedCertificate *agglayer.SignedCertificate) { - fn := "/tmp/certificate.json" - a.log.Infof("saving certificate in db") - jsonData, err := json.Marshal(signedCertificate) - if err != nil { - a.log.Errorf("error marshalling certificate: %w", err) - } - // write json data to file - err = ioutil.WriteFile(fn, jsonData, 0644) - if err != nil { - a.log.Errorf("error writing certificate to file: %w", err) - } -} // buildCertificate builds a certificate from the bridge events func (a *AggSender) buildCertificate(ctx context.Context, From 68f067ce56fd19da0a7197b21108645b451e78fc Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Fri, 25 Oct 2024 15:02:04 +0200 Subject: [PATCH 71/84] fix: certificate status unmarshal --- agglayer/types.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/agglayer/types.go b/agglayer/types.go index 548e889e..60a54a56 100644 --- a/agglayer/types.go +++ b/agglayer/types.go @@ -25,6 +25,28 @@ func (c CertificateStatus) String() string { return [...]string{"Pending", "InError", "Settled"}[c] } +// UnmarshalJSON is the implementation of the json.Unmarshaler interface +func (c CertificateStatus) UnmarshalJSON(data []byte) error { + var status string + err := json.Unmarshal(data, &status) + if err != nil { + return err + } + + switch status { + case "Pending": + c = Pending + case "InError": + c = InError + case "Settled": + c = Settled + default: + return fmt.Errorf("invalid status: %s", status) + } + + return nil +} + type LeafType uint8 func (l LeafType) Uint8() uint8 { From d42e71b4b24fce920cb71e572606415feb82d8a2 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Mon, 28 Oct 2024 09:24:27 +0100 Subject: [PATCH 72/84] fix: first certificate height and LER --- aggsender/aggsender.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index b1f8c500..f686ec0f 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -23,6 +23,8 @@ const signatureSize = 65 var ( errNoBridgesAndClaims = errors.New("no bridges and claims to build certificate") errInvalidSignatureSize = errors.New("invalid signature size") + + zeroLER = common.HexToHash("0x27ae5ba08d7291c96c8cbddcc148bf48a6d68c7974b94356f53754ef6171d757") ) // AggSender is a component that will send certificates to the aggLayer @@ -199,13 +201,21 @@ func (a *AggSender) buildCertificate(ctx context.Context, return nil, fmt.Errorf("error getting exit root by index: %d. Error: %w", depositCount, err) } + height := lastHeight + 1 + previousLER := previousExitRoot + if previousExitRoot == (common.Hash{}) { + // meaning this is the first certificate + height = 0 + previousLER = zeroLER + } + return &agglayer.Certificate{ NetworkID: a.l2Syncer.OriginNetwork(), - PrevLocalExitRoot: previousExitRoot, + PrevLocalExitRoot: previousLER, NewLocalExitRoot: exitRoot.Hash, BridgeExits: bridgeExits, ImportedBridgeExits: importedBridgeExits, - Height: lastHeight + 1, + Height: height, }, nil } From 424fb8743ebbbdd8d5ef7dafe5a2c769297027ac Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Mon, 28 Oct 2024 11:49:13 +0100 Subject: [PATCH 73/84] fix: proofs --- agglayer/types.go | 29 ++- aggsender/aggsender.go | 126 ++++++----- aggsender/aggsender_test.go | 406 ++++++++++++++++++------------------ 3 files changed, 283 insertions(+), 278 deletions(-) diff --git a/agglayer/types.go b/agglayer/types.go index 60a54a56..e8bdb254 100644 --- a/agglayer/types.go +++ b/agglayer/types.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "math/big" + "strings" "github.com/0xPolygon/cdk/bridgesync" cdkcommon "github.com/0xPolygon/cdk/common" @@ -16,30 +17,42 @@ type CertificateStatus int const ( Pending CertificateStatus = iota + Proven + Candidate InError Settled ) // String representation of the enum func (c CertificateStatus) String() string { - return [...]string{"Pending", "InError", "Settled"}[c] + return [...]string{"Pending", "Proven", "Candidate", "InError", "Settled"}[c] } // UnmarshalJSON is the implementation of the json.Unmarshaler interface -func (c CertificateStatus) UnmarshalJSON(data []byte) error { +func (c *CertificateStatus) UnmarshalJSON(data []byte) error { + dataStr := string(data) + var status string - err := json.Unmarshal(data, &status) - if err != nil { - return err + if strings.Contains(dataStr, "InError") { + status = "InError" + } else { + err := json.Unmarshal(data, &status) + if err != nil { + return err + } } switch status { case "Pending": - c = Pending + *c = Pending case "InError": - c = InError + *c = InError + case "Proven": + *c = Proven + case "Candidate": + *c = Candidate case "Settled": - c = Settled + *c = Settled default: return fmt.Errorf("invalid status: %s", status) } diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index f686ec0f..80b5acbd 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -3,12 +3,14 @@ package aggsender import ( "context" "crypto/ecdsa" + "encoding/json" "errors" "fmt" "time" "github.com/0xPolygon/cdk/agglayer" "github.com/0xPolygon/cdk/aggsender/db" + "github.com/0xPolygon/cdk/aggsender/types" aggsendertypes "github.com/0xPolygon/cdk/aggsender/types" "github.com/0xPolygon/cdk/bridgesync" cdkcommon "github.com/0xPolygon/cdk/common" @@ -113,18 +115,25 @@ func (a *AggSender) sendCertificate(ctx context.Context) error { return fmt.Errorf("error getting last processed block from l2: %w", err) } - previousLocalExitRoot, previousHeight, lastCertificateBlock, err := a.getLastSentCertificateData(ctx) + lastSentCertificateInfo, err := a.getLastSentCertificateData(ctx) if err != nil { return err } - if lastCertificateBlock >= lasL2BlockSynced { + previousToBlock := lastSentCertificateInfo.ToBlock + if lastSentCertificateInfo.Status == agglayer.InError { + // if the last certificate was in error, we need to resend it + // from the block before the error + previousToBlock = lastSentCertificateInfo.FromBlock - 1 + } + + if previousToBlock >= lasL2BlockSynced { a.log.Infof("no new blocks to send a certificate, last certificate block: %d, last L2 block: %d", - lastCertificateBlock, lasL2BlockSynced) + previousToBlock, lasL2BlockSynced) return nil } - fromBlock := lastCertificateBlock + 1 + fromBlock := previousToBlock + 1 toBlock := lasL2BlockSynced bridges, err := a.l2Syncer.GetBridgesPublished(ctx, fromBlock, toBlock) @@ -144,7 +153,7 @@ func (a *AggSender) sendCertificate(ctx context.Context) error { a.log.Infof("building certificate for block: %d to block: %d", fromBlock, toBlock) - certificate, err := a.buildCertificate(ctx, bridges, claims, previousLocalExitRoot, previousHeight) + certificate, err := a.buildCertificate(ctx, bridges, claims, lastSentCertificateInfo) if err != nil { return fmt.Errorf("error building certificate: %w", err) } @@ -154,6 +163,8 @@ func (a *AggSender) sendCertificate(ctx context.Context) error { return fmt.Errorf("error signing certificate: %w", err) } + a.logJSONCertificate(signedCertificate) + certificateHash, err := a.aggLayerClient.SendCertificate(signedCertificate) if err != nil { return fmt.Errorf("error sending certificate: %w", err) @@ -176,11 +187,23 @@ func (a *AggSender) sendCertificate(ctx context.Context) error { return nil } +// logJSONCertificate logs the certificate in JSON format to the logs +func (a *AggSender) logJSONCertificate(certificate *agglayer.SignedCertificate) { + raw, err := json.Marshal(certificate) + if err != nil { + a.log.Errorf("error marshalling certificate: %w", err) + return + } + + a.log.Debug("JSON certificate:") + a.log.Debug(string(raw)) +} + // buildCertificate builds a certificate from the bridge events func (a *AggSender) buildCertificate(ctx context.Context, bridges []bridgesync.Bridge, claims []bridgesync.Claim, - previousExitRoot common.Hash, lastHeight uint64) (*agglayer.Certificate, error) { + lastSentCertificateInfo types.CertificateInfo) (*agglayer.Certificate, error) { if len(bridges) == 0 && len(claims) == 0 { return nil, errNoBridgesAndClaims } @@ -201,9 +224,9 @@ func (a *AggSender) buildCertificate(ctx context.Context, return nil, fmt.Errorf("error getting exit root by index: %d. Error: %w", depositCount, err) } - height := lastHeight + 1 - previousLER := previousExitRoot - if previousExitRoot == (common.Hash{}) { + height := lastSentCertificateInfo.Height + 1 + previousLER := lastSentCertificateInfo.NewLocalExitRoot + if lastSentCertificateInfo.NewLocalExitRoot == (common.Hash{}) { // meaning this is the first certificate height = 0 previousLER = zeroLER @@ -221,53 +244,13 @@ func (a *AggSender) buildCertificate(ctx context.Context, // getLastSentCertificateData gets the previous local exit root, previous certificate height // and last certificate block sent (gotten from the last sent certificate in db) -func (a *AggSender) getLastSentCertificateData(ctx context.Context) (common.Hash, uint64, uint64, error) { +func (a *AggSender) getLastSentCertificateData(ctx context.Context) (types.CertificateInfo, error) { lastSentCertificate, err := a.storage.GetLastSentCertificate(ctx) if err != nil { - return common.Hash{}, 0, 0, fmt.Errorf("error getting last sent certificate: %w", err) + return types.CertificateInfo{}, fmt.Errorf("error getting last sent certificate: %w", err) } - a.log.Infof("last sent certificate: %s", lastSentCertificate.String()) - - var ( - previousLocalExitRoot common.Hash - previousHeight uint64 - lastCertificateBlock uint64 - ) - - if lastSentCertificate.CertificateID != (common.Hash{}) { - // we have sent a certificate before, get the last certificate header - lastSentCertificateHeader, err := a.aggLayerClient.GetCertificateHeader(lastSentCertificate.CertificateID) - if err != nil { - return common.Hash{}, 0, 0, fmt.Errorf("error getting certificate %s header: %w", - lastSentCertificate.CertificateID, err) - } - - if lastSentCertificateHeader.Status == agglayer.InError { - // last sent certificate had errors, we need to remove it from the db - // and build a new certificate from that block - if err := a.storage.DeleteCertificate(ctx, lastSentCertificateHeader.CertificateID); err != nil { - return common.Hash{}, 0, 0, fmt.Errorf("error deleting certificate %s: %w", - lastSentCertificate.CertificateID, err) - } - - lastValidCertificate, err := a.storage.GetCertificateByHeight(ctx, lastSentCertificateHeader.Height-1) - if err != nil { - return common.Hash{}, 0, 0, fmt.Errorf("error getting certificate by height %d: %w", - lastSentCertificateHeader.Height, err) - } - - previousLocalExitRoot = lastValidCertificate.NewLocalExitRoot - previousHeight = lastValidCertificate.Height - lastCertificateBlock = lastValidCertificate.ToBlock - } else { - previousLocalExitRoot = lastSentCertificateHeader.NewLocalExitRoot - previousHeight = lastSentCertificateHeader.Height - lastCertificateBlock = lastSentCertificate.ToBlock - } - } - - return previousLocalExitRoot, previousHeight, lastCertificateBlock, nil + return lastSentCertificate, nil } // convertClaimToImportedBridgeExit converts a claim to an ImportedBridgeExit object @@ -334,9 +317,11 @@ func (a *AggSender) getImportedBridgeExits(ctx context.Context, ger common.Hash timestamp uint64 blockHash common.Hash + greatestClaimIndex = 0 ) - for i, claim := range claims { + l1Infos := make([]*l1infotreesync.L1InfoTreeLeaf, 0, len(claims)) + for i, claim := range claims[:] { a.log.Debugf("claim[%d]: destAddr: %s GER:%s", i, claim.DestinationAddress.String(), claim.GlobalExitRoot.String()) info, err := a.l1infoTreeSyncer.GetInfoByGlobalExitRoot(claim.GlobalExitRoot) if err != nil { @@ -348,6 +333,7 @@ func (a *AggSender) getImportedBridgeExits(ctx context.Context, ger = claim.GlobalExitRoot timestamp = info.Timestamp blockHash = info.PreviousBlockHash + greatestClaimIndex = i } importedBridgeExit, err := a.convertClaimToImportedBridgeExit(claim) @@ -356,22 +342,25 @@ func (a *AggSender) getImportedBridgeExits(ctx context.Context, } importedBridgeExits = append(importedBridgeExits, importedBridgeExit) + l1Infos = append(l1Infos, info) } for i, ibe := range importedBridgeExits { - gerToL1Proof, err := a.l1infoTreeSyncer.GetL1InfoTreeMerkleProofFromIndexToRoot(ctx, ibe.GlobalIndex.LeafIndex, ger) + l1Info := l1Infos[i] + + gerToL1Proof, err := a.l1infoTreeSyncer.GetL1InfoTreeMerkleProofFromIndexToRoot(ctx, l1Info.L1InfoTreeIndex, ger) if err != nil { return nil, fmt.Errorf("error getting L1 Info tree merkle proof for leaf index: %d. GER: %s. Error: %w", - ibe.GlobalIndex.LeafIndex, ger, err) + l1Info.L1InfoTreeIndex, ger, err) } claim := claims[i] if ibe.GlobalIndex.MainnetFlag { ibe.ClaimData = &agglayer.ClaimFromMainnnet{ L1Leaf: &agglayer.L1InfoTreeLeaf{ - L1InfoTreeIndex: ibe.GlobalIndex.LeafIndex, - RollupExitRoot: claims[i].RollupExitRoot, - MainnetExitRoot: claims[i].MainnetExitRoot, + L1InfoTreeIndex: greatestL1InfoTreeIndex, + RollupExitRoot: claims[greatestClaimIndex].RollupExitRoot, + MainnetExitRoot: claims[greatestClaimIndex].MainnetExitRoot, Inner: &agglayer.L1InfoTreeLeafInner{ GlobalExitRoot: ger, Timestamp: timestamp, @@ -379,8 +368,8 @@ func (a *AggSender) getImportedBridgeExits(ctx context.Context, }, }, ProofLeafMER: &agglayer.MerkleProof{ - Root: claim.MainnetExitRoot, - Proof: claim.ProofLocalExitRoot, + Root: claims[greatestClaimIndex].MainnetExitRoot, + Proof: claims[greatestClaimIndex].ProofLocalExitRoot, }, ProofGERToL1Root: &agglayer.MerkleProof{ Root: ger, @@ -390,20 +379,23 @@ func (a *AggSender) getImportedBridgeExits(ctx context.Context, } else { ibe.ClaimData = &agglayer.ClaimFromRollup{ L1Leaf: &agglayer.L1InfoTreeLeaf{ - L1InfoTreeIndex: ibe.GlobalIndex.LeafIndex, - RollupExitRoot: claim.RollupExitRoot, - MainnetExitRoot: claim.MainnetExitRoot, + L1InfoTreeIndex: greatestL1InfoTreeIndex, + RollupExitRoot: claims[greatestClaimIndex].RollupExitRoot, + MainnetExitRoot: claims[greatestClaimIndex].MainnetExitRoot, Inner: &agglayer.L1InfoTreeLeafInner{ - GlobalExitRoot: ger, + GlobalExitRoot: claim.GlobalExitRoot, Timestamp: timestamp, BlockHash: blockHash, }, }, ProofLeafLER: &agglayer.MerkleProof{ - Root: claim.MainnetExitRoot, - Proof: claim.ProofLocalExitRoot, + Root: claims[greatestClaimIndex].MainnetExitRoot, + Proof: claims[greatestClaimIndex].ProofLocalExitRoot, + }, + ProofLERToRER: &agglayer.MerkleProof{ + Root: claims[greatestClaimIndex].RollupExitRoot, + Proof: claims[greatestClaimIndex].ProofRollupExitRoot, }, - ProofLERToRER: &agglayer.MerkleProof{}, ProofGERToL1Root: &agglayer.MerkleProof{ Root: ger, Proof: gerToL1Proof, diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go index 7f91f777..58b85ced 100644 --- a/aggsender/aggsender_test.go +++ b/aggsender/aggsender_test.go @@ -411,11 +411,11 @@ func TestGetImportedBridgeExits(t *testing.T) { }, ClaimData: &agglayer.ClaimFromMainnnet{ L1Leaf: &agglayer.L1InfoTreeLeaf{ - L1InfoTreeIndex: 2, + L1InfoTreeIndex: 1, RollupExitRoot: common.HexToHash("0xbbb"), MainnetExitRoot: common.HexToHash("0xccc"), Inner: &agglayer.L1InfoTreeLeafInner{ - GlobalExitRoot: common.HexToHash("0x789"), + GlobalExitRoot: common.HexToHash("0xdef"), Timestamp: 123456789, BlockHash: common.HexToHash("0xabc"), }, @@ -463,207 +463,207 @@ 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 +// 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 generateTestProof(t *testing.T) treeTypes.Proof { t.Helper() From b6b80fdc080b29085ef2e6fffe8a2073888e8f8d Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Mon, 28 Oct 2024 12:52:38 +0100 Subject: [PATCH 74/84] fix: get last sent certificate --- aggsender/aggsender.go | 13 +------------ aggsender/db/aggsender_db_storage.go | 5 ++--- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index 80b5acbd..128f62a0 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -115,7 +115,7 @@ func (a *AggSender) sendCertificate(ctx context.Context) error { return fmt.Errorf("error getting last processed block from l2: %w", err) } - lastSentCertificateInfo, err := a.getLastSentCertificateData(ctx) + lastSentCertificateInfo, err := a.storage.GetLastSentCertificate(ctx) if err != nil { return err } @@ -242,17 +242,6 @@ func (a *AggSender) buildCertificate(ctx context.Context, }, nil } -// getLastSentCertificateData gets the previous local exit root, previous certificate height -// and last certificate block sent (gotten from the last sent certificate in db) -func (a *AggSender) getLastSentCertificateData(ctx context.Context) (types.CertificateInfo, error) { - lastSentCertificate, err := a.storage.GetLastSentCertificate(ctx) - if err != nil { - return types.CertificateInfo{}, fmt.Errorf("error getting last sent certificate: %w", err) - } - - return lastSentCertificate, nil -} - // convertClaimToImportedBridgeExit converts a claim to an ImportedBridgeExit object func (a *AggSender) convertClaimToImportedBridgeExit(claim bridgesync.Claim) (*agglayer.ImportedBridgeExit, error) { leafType := agglayer.LeafTypeAsset diff --git a/aggsender/db/aggsender_db_storage.go b/aggsender/db/aggsender_db_storage.go index 57a2d3a7..25b31392 100644 --- a/aggsender/db/aggsender_db_storage.go +++ b/aggsender/db/aggsender_db_storage.go @@ -99,12 +99,11 @@ func (a *AggSenderSQLStorage) GetCertificateByHeight(ctx context.Context, return certificateInfo, nil } -// GetLastSentCertificate returns the last certificate sent to the aggLayer that is still Pending +// GetLastSentCertificate returns the last certificate sent to the aggLayer func (a *AggSenderSQLStorage) GetLastSentCertificate(ctx context.Context) (types.CertificateInfo, error) { var certificateInfo types.CertificateInfo if err := meddler.QueryRow(a.db, &certificateInfo, - "SELECT * FROM certificate_info WHERE status = $1 ORDER BY height DESC LIMIT 1;", - agglayer.Pending); err != nil { + "SELECT * FROM certificate_info ORDER BY height DESC LIMIT 1;"); err != nil { return types.CertificateInfo{}, getSelectQueryError(0, err) } From 80c968257c4e6e757e0443f1c5341a9d57e7dbcd Mon Sep 17 00:00:00 2001 From: joanestebanr <129153821+joanestebanr@users.noreply.github.com> Date: Mon, 28 Oct 2024 14:50:19 +0100 Subject: [PATCH 75/84] feat: add saveCertificate to file --- aggsender/aggsender.go | 18 ++++++++++++++++++ aggsender/aggsender_test.go | 9 +++++++++ 2 files changed, 27 insertions(+) diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index 128f62a0..b172a324 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "os" "time" "github.com/0xPolygon/cdk/agglayer" @@ -164,6 +165,7 @@ func (a *AggSender) sendCertificate(ctx context.Context) error { } a.logJSONCertificate(signedCertificate) + a.saveCertificate(signedCertificate) certificateHash, err := a.aggLayerClient.SendCertificate(signedCertificate) if err != nil { @@ -186,6 +188,22 @@ func (a *AggSender) sendCertificate(ctx context.Context) error { return nil } +func (a *AggSender) saveCertificate(signedCertificate *agglayer.SignedCertificate) { + if signedCertificate == nil { + return + } + fn := fmt.Sprintf("/tmp/certificate_%04d.json", signedCertificate.Height) + a.log.Infof("saving certificate to file: %s", fn) + jsonData, err := json.Marshal(signedCertificate) + if err != nil { + a.log.Errorf("error marshalling certificate: %w", err) + } + // write json data to file + err = os.WriteFile(fn, jsonData, 0644) + if err != nil { + a.log.Errorf("error writing certificate to file: %w", err) + } +} // logJSONCertificate logs the certificate in JSON format to the logs func (a *AggSender) logJSONCertificate(certificate *agglayer.SignedCertificate) { diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go index 58b85ced..fad0dc9c 100644 --- a/aggsender/aggsender_test.go +++ b/aggsender/aggsender_test.go @@ -25,6 +25,15 @@ import ( "github.com/stretchr/testify/require" ) +func TestExploratoryGetCertificateHeader(t *testing.T) { + t.Skip("This test is exploratory and should be skipped") + aggLayerClient := agglayer.NewAggLayerClient("http://localhost:32795") + certificateID := common.HexToHash("0xf153e75e24591432ac5deafaeaafba3fec0fd851261c86051b9c0d540b38c369") + certificateHeader, err := aggLayerClient.GetCertificateHeader(certificateID) + require.NoError(t, err) + fmt.Print(certificateHeader) +} + func TestConvertClaimToImportedBridgeExit(t *testing.T) { t.Parallel() From f26c7ada74957fddaedadfd36b601ce2b033350c Mon Sep 17 00:00:00 2001 From: joanestebanr <129153821+joanestebanr@users.noreply.github.com> Date: Mon, 28 Oct 2024 16:33:52 +0100 Subject: [PATCH 76/84] PR: fix PR comments --- scripts/local_config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/local_config b/scripts/local_config index d8eba1a4..d1a47b2c 100755 --- a/scripts/local_config +++ b/scripts/local_config @@ -33,7 +33,7 @@ function get_value_from_toml_file(){ local _TMP_FILE=$(mktemp) cat $_FILE > $_TMP_FILE # Maybe the file doesnt end with a new line so we added just in case - echo " " >> $_TMP_FILE + echo >> $_TMP_FILE while read -r _LINE; do # Clean up line from spaces and tabs _LINE=$(echo $_LINE | tr -d '[:space:]') From 73188555ba2181303c9936c085fc0921997f7721 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Mon, 28 Oct 2024 20:52:02 +0100 Subject: [PATCH 77/84] fix: comments and get imported bridge exits --- aggsender/aggsender.go | 111 ++++++---------- aggsender/aggsender_test.go | 207 ++++++++--------------------- aggsender/config.go | 6 +- config/default.go | 3 +- test/helpers/lxly-bridge-test.bash | 2 - 5 files changed, 102 insertions(+), 227 deletions(-) diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index b172a324..b7165946 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -11,7 +11,6 @@ import ( "github.com/0xPolygon/cdk/agglayer" "github.com/0xPolygon/cdk/aggsender/db" - "github.com/0xPolygon/cdk/aggsender/types" aggsendertypes "github.com/0xPolygon/cdk/aggsender/types" "github.com/0xPolygon/cdk/bridgesync" cdkcommon "github.com/0xPolygon/cdk/common" @@ -53,7 +52,7 @@ func New( aggLayerClient agglayer.AgglayerClientInterface, l1InfoTreeSyncer *l1infotreesync.L1InfoTreeSync, l2Syncer *bridgesync.BridgeSync) (*AggSender, error) { - storage, err := db.NewAggSenderSQLStorage(logger, cfg.DBPath) + storage, err := db.NewAggSenderSQLStorage(logger, cfg.StoragePath) if err != nil { return nil, err } @@ -164,8 +163,7 @@ func (a *AggSender) sendCertificate(ctx context.Context) error { return fmt.Errorf("error signing certificate: %w", err) } - a.logJSONCertificate(signedCertificate) - a.saveCertificate(signedCertificate) + a.saveCertificateToFile(signedCertificate) certificateHash, err := a.aggLayerClient.SendCertificate(signedCertificate) if err != nil { @@ -188,40 +186,30 @@ func (a *AggSender) sendCertificate(ctx context.Context) error { return nil } -func (a *AggSender) saveCertificate(signedCertificate *agglayer.SignedCertificate) { - if signedCertificate == nil { + +// saveCertificate saves the certificate to a tmp file +func (a *AggSender) saveCertificateToFile(signedCertificate *agglayer.SignedCertificate) { + if signedCertificate == nil || !a.cfg.SaveCertificatesToFiles { return } + fn := fmt.Sprintf("/tmp/certificate_%04d.json", signedCertificate.Height) a.log.Infof("saving certificate to file: %s", fn) jsonData, err := json.Marshal(signedCertificate) if err != nil { a.log.Errorf("error marshalling certificate: %w", err) } - // write json data to file - err = os.WriteFile(fn, jsonData, 0644) - if err != nil { - a.log.Errorf("error writing certificate to file: %w", err) - } -} -// logJSONCertificate logs the certificate in JSON format to the logs -func (a *AggSender) logJSONCertificate(certificate *agglayer.SignedCertificate) { - raw, err := json.Marshal(certificate) - if err != nil { - a.log.Errorf("error marshalling certificate: %w", err) - return + if err = os.WriteFile(fn, jsonData, 0644); err != nil { + a.log.Errorf("error writing certificate to file: %w", err) } - - a.log.Debug("JSON certificate:") - a.log.Debug(string(raw)) } // buildCertificate builds a certificate from the bridge events func (a *AggSender) buildCertificate(ctx context.Context, bridges []bridgesync.Bridge, claims []bridgesync.Claim, - lastSentCertificateInfo types.CertificateInfo) (*agglayer.Certificate, error) { + lastSentCertificateInfo aggsendertypes.CertificateInfo) (*agglayer.Certificate, error) { if len(bridges) == 0 && len(claims) == 0 { return nil, errNoBridgesAndClaims } @@ -318,93 +306,72 @@ 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) { - var ( - importedBridgeExits = make([]*agglayer.ImportedBridgeExit, 0, len(claims)) - greatestL1InfoTreeIndex = uint32(0) - ger common.Hash - timestamp uint64 - blockHash common.Hash - greatestClaimIndex = 0 - ) - - l1Infos := make([]*l1infotreesync.L1InfoTreeLeaf, 0, len(claims)) - for i, claim := range claims[:] { + 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()) - info, err := a.l1infoTreeSyncer.GetInfoByGlobalExitRoot(claim.GlobalExitRoot) + l1Info, err := a.l1infoTreeSyncer.GetInfoByGlobalExitRoot(claim.GlobalExitRoot) if err != nil { return nil, fmt.Errorf("error getting info by global exit root: %w", err) } - if greatestL1InfoTreeIndex < info.L1InfoTreeIndex { - greatestL1InfoTreeIndex = info.L1InfoTreeIndex - ger = claim.GlobalExitRoot - timestamp = info.Timestamp - blockHash = info.PreviousBlockHash - greatestClaimIndex = i - } - - importedBridgeExit, err := a.convertClaimToImportedBridgeExit(claim) + ibe, err := a.convertClaimToImportedBridgeExit(claim) if err != nil { return nil, fmt.Errorf("error converting claim to imported bridge exit: %w", err) } - importedBridgeExits = append(importedBridgeExits, importedBridgeExit) - l1Infos = append(l1Infos, info) - } - - for i, ibe := range importedBridgeExits { - l1Info := l1Infos[i] + importedBridgeExits = append(importedBridgeExits, ibe) - gerToL1Proof, err := a.l1infoTreeSyncer.GetL1InfoTreeMerkleProofFromIndexToRoot(ctx, l1Info.L1InfoTreeIndex, ger) + gerToL1Proof, err := a.l1infoTreeSyncer.GetL1InfoTreeMerkleProofFromIndexToRoot(ctx, l1Info.L1InfoTreeIndex, l1Info.GlobalExitRoot) if err != nil { return nil, fmt.Errorf("error getting L1 Info tree merkle proof for leaf index: %d. GER: %s. Error: %w", - l1Info.L1InfoTreeIndex, ger, err) + l1Info.L1InfoTreeIndex, l1Info.GlobalExitRoot, err) } claim := claims[i] if ibe.GlobalIndex.MainnetFlag { ibe.ClaimData = &agglayer.ClaimFromMainnnet{ L1Leaf: &agglayer.L1InfoTreeLeaf{ - L1InfoTreeIndex: greatestL1InfoTreeIndex, - RollupExitRoot: claims[greatestClaimIndex].RollupExitRoot, - MainnetExitRoot: claims[greatestClaimIndex].MainnetExitRoot, + L1InfoTreeIndex: l1Info.L1InfoTreeIndex, + RollupExitRoot: claim.RollupExitRoot, + MainnetExitRoot: claim.MainnetExitRoot, Inner: &agglayer.L1InfoTreeLeafInner{ - GlobalExitRoot: ger, - Timestamp: timestamp, - BlockHash: blockHash, + GlobalExitRoot: l1Info.GlobalExitRoot, + Timestamp: l1Info.Timestamp, + BlockHash: l1Info.PreviousBlockHash, }, }, ProofLeafMER: &agglayer.MerkleProof{ - Root: claims[greatestClaimIndex].MainnetExitRoot, - Proof: claims[greatestClaimIndex].ProofLocalExitRoot, + Root: claim.MainnetExitRoot, + Proof: claim.ProofLocalExitRoot, }, ProofGERToL1Root: &agglayer.MerkleProof{ - Root: ger, + Root: l1Info.GlobalExitRoot, Proof: gerToL1Proof, }, } } else { ibe.ClaimData = &agglayer.ClaimFromRollup{ L1Leaf: &agglayer.L1InfoTreeLeaf{ - L1InfoTreeIndex: greatestL1InfoTreeIndex, - RollupExitRoot: claims[greatestClaimIndex].RollupExitRoot, - MainnetExitRoot: claims[greatestClaimIndex].MainnetExitRoot, + L1InfoTreeIndex: l1Info.L1InfoTreeIndex, + RollupExitRoot: claim.RollupExitRoot, + MainnetExitRoot: claim.MainnetExitRoot, Inner: &agglayer.L1InfoTreeLeafInner{ - GlobalExitRoot: claim.GlobalExitRoot, - Timestamp: timestamp, - BlockHash: blockHash, + GlobalExitRoot: l1Info.GlobalExitRoot, + Timestamp: l1Info.Timestamp, + BlockHash: l1Info.PreviousBlockHash, }, }, ProofLeafLER: &agglayer.MerkleProof{ - Root: claims[greatestClaimIndex].MainnetExitRoot, - Proof: claims[greatestClaimIndex].ProofLocalExitRoot, + Root: claim.MainnetExitRoot, + Proof: claim.ProofLocalExitRoot, }, ProofLERToRER: &agglayer.MerkleProof{ - Root: claims[greatestClaimIndex].RollupExitRoot, - Proof: claims[greatestClaimIndex].ProofRollupExitRoot, + Root: claim.RollupExitRoot, + Proof: claim.ProofRollupExitRoot, }, ProofGERToL1Root: &agglayer.MerkleProof{ - Root: ger, + Root: l1Info.GlobalExitRoot, Proof: gerToL1Proof, }, } @@ -467,7 +434,7 @@ func (a *AggSender) checkPendingCertificatesStatus(ctx context.Context) { continue } - if certificateHeader.Status == agglayer.Settled || certificateHeader.Status == agglayer.InError { + if certificateHeader.Status != agglayer.Pending { certificate.Status = certificateHeader.Status a.log.Infof("certificate %s changed status to %s", certificateHeader.String(), certificate.Status) diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go index fad0dc9c..dd4e3901 100644 --- a/aggsender/aggsender_test.go +++ b/aggsender/aggsender_test.go @@ -251,12 +251,12 @@ func TestGetImportedBridgeExits(t *testing.T) { t.Parallel() mockProof := generateTestProof(t) - mockL1InfoTreeSyncer := mocks.NewL1InfoTreeSyncerMock(t) mockL1InfoTreeSyncer.On("GetInfoByGlobalExitRoot", mock.Anything).Return(&l1infotreesync.L1InfoTreeLeaf{ L1InfoTreeIndex: 1, Timestamp: 123456789, PreviousBlockHash: common.HexToHash("0xabc"), + GlobalExitRoot: common.HexToHash("0x7891"), }, nil) mockL1InfoTreeSyncer.On("GetL1InfoTreeMerkleProofFromIndexToRoot", mock.Anything, mock.Anything, mock.Anything).Return(mockProof, nil) @@ -271,18 +271,19 @@ func TestGetImportedBridgeExits(t *testing.T) { name: "Single claim", 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, + IsMessage: false, + OriginNetwork: 1, + OriginAddress: common.HexToAddress("0x1234"), + DestinationNetwork: 2, + DestinationAddress: common.HexToAddress("0x4567"), + Amount: big.NewInt(111), + Metadata: []byte("metadata1"), + GlobalIndex: bridgesync.GenerateGlobalIndex(false, 1, 1), + GlobalExitRoot: common.HexToHash("0x7891"), + RollupExitRoot: common.HexToHash("0xaaab"), + MainnetExitRoot: common.HexToHash("0xbbba"), + ProofLocalExitRoot: mockProof, + ProofRollupExitRoot: mockProof, }, }, expectedError: false, @@ -301,7 +302,7 @@ func TestGetImportedBridgeExits(t *testing.T) { }, GlobalIndex: &agglayer.GlobalIndex{ MainnetFlag: false, - RollupIndex: 0, + RollupIndex: 1, LeafIndex: 1, }, ClaimData: &agglayer.ClaimFromRollup{ @@ -319,7 +320,10 @@ func TestGetImportedBridgeExits(t *testing.T) { Root: common.HexToHash("0xbbba"), Proof: mockProof, }, - ProofLERToRER: &agglayer.MerkleProof{}, + ProofLERToRER: &agglayer.MerkleProof{ + Root: common.HexToHash("0xaaab"), + Proof: mockProof, + }, ProofGERToL1Root: &agglayer.MerkleProof{ Root: common.HexToHash("0x7891"), Proof: mockProof, @@ -332,32 +336,34 @@ func TestGetImportedBridgeExits(t *testing.T) { name: "Multiple claims", claims: []bridgesync.Claim{ { - IsMessage: false, - OriginNetwork: 1, - OriginAddress: common.HexToAddress("0x123"), - DestinationNetwork: 2, - DestinationAddress: common.HexToAddress("0x456"), - Amount: big.NewInt(100), - Metadata: []byte("metadata"), - GlobalIndex: big.NewInt(1), - GlobalExitRoot: common.HexToHash("0x789"), - RollupExitRoot: common.HexToHash("0xaaa"), - MainnetExitRoot: common.HexToHash("0xbbb"), - ProofLocalExitRoot: mockProof, + IsMessage: false, + OriginNetwork: 1, + OriginAddress: common.HexToAddress("0x123"), + DestinationNetwork: 2, + DestinationAddress: common.HexToAddress("0x456"), + Amount: big.NewInt(100), + Metadata: []byte("metadata"), + GlobalIndex: big.NewInt(1), + GlobalExitRoot: common.HexToHash("0x7891"), + RollupExitRoot: common.HexToHash("0xaaa"), + MainnetExitRoot: common.HexToHash("0xbbb"), + ProofLocalExitRoot: mockProof, + ProofRollupExitRoot: mockProof, }, { - IsMessage: true, - OriginNetwork: 3, - OriginAddress: common.HexToAddress("0x789"), - DestinationNetwork: 4, - DestinationAddress: common.HexToAddress("0xabc"), - Amount: big.NewInt(200), - Metadata: []byte("data"), - GlobalIndex: bridgesync.GenerateGlobalIndex(true, 0, 2), - GlobalExitRoot: common.HexToHash("0xdef"), - RollupExitRoot: common.HexToHash("0xbbb"), - MainnetExitRoot: common.HexToHash("0xccc"), - ProofLocalExitRoot: mockProof, + IsMessage: true, + OriginNetwork: 3, + OriginAddress: common.HexToAddress("0x789"), + DestinationNetwork: 4, + DestinationAddress: common.HexToAddress("0xabc"), + Amount: big.NewInt(200), + Metadata: []byte("data"), + GlobalIndex: bridgesync.GenerateGlobalIndex(true, 0, 2), + GlobalExitRoot: common.HexToHash("0x7891"), + RollupExitRoot: common.HexToHash("0xbbb"), + MainnetExitRoot: common.HexToHash("0xccc"), + ProofLocalExitRoot: mockProof, + ProofRollupExitRoot: mockProof, }, }, expectedError: false, @@ -385,7 +391,7 @@ func TestGetImportedBridgeExits(t *testing.T) { RollupExitRoot: common.HexToHash("0xaaa"), MainnetExitRoot: common.HexToHash("0xbbb"), Inner: &agglayer.L1InfoTreeLeafInner{ - GlobalExitRoot: common.HexToHash("0x789"), + GlobalExitRoot: common.HexToHash("0x7891"), Timestamp: 123456789, BlockHash: common.HexToHash("0xabc"), }, @@ -394,9 +400,12 @@ func TestGetImportedBridgeExits(t *testing.T) { Root: common.HexToHash("0xbbb"), Proof: mockProof, }, - ProofLERToRER: &agglayer.MerkleProof{}, + ProofLERToRER: &agglayer.MerkleProof{ + Root: common.HexToHash("0xaaa"), + Proof: mockProof, + }, ProofGERToL1Root: &agglayer.MerkleProof{ - Root: common.HexToHash("0x789"), + Root: common.HexToHash("0x7891"), Proof: mockProof, }, }, @@ -424,7 +433,7 @@ func TestGetImportedBridgeExits(t *testing.T) { RollupExitRoot: common.HexToHash("0xbbb"), MainnetExitRoot: common.HexToHash("0xccc"), Inner: &agglayer.L1InfoTreeLeafInner{ - GlobalExitRoot: common.HexToHash("0xdef"), + GlobalExitRoot: common.HexToHash("0x7891"), Timestamp: 123456789, BlockHash: common.HexToHash("0xabc"), }, @@ -434,7 +443,7 @@ func TestGetImportedBridgeExits(t *testing.T) { Proof: mockProof, }, ProofGERToL1Root: &agglayer.MerkleProof{ - Root: common.HexToHash("0x789"), + Root: common.HexToHash("0x7891"), Proof: mockProof, }, }, @@ -836,9 +845,6 @@ func TestSendCertificate(t *testing.T) { shouldSendCertificate []interface{} getLastSentCertificate []interface{} lastL2BlockProcessed []interface{} - getCertificateHeader []interface{} - deleteCertificate []interface{} - getCertificateByHeight []interface{} getBridges []interface{} getClaims []interface{} getInfoByGlobalExitRoot []interface{} @@ -863,8 +869,8 @@ func TestSendCertificate(t *testing.T) { mockL1InfoTreeSyncer *mocks.L1InfoTreeSyncerMock ) - if cfg.shouldSendCertificate != nil || cfg.getLastSentCertificate != nil || cfg.deleteCertificate != nil || - cfg.getCertificateByHeight != nil || cfg.saveLastSentCertificate != nil { + if cfg.shouldSendCertificate != nil || cfg.getLastSentCertificate != nil || + cfg.saveLastSentCertificate != nil { mockStorage = mocks.NewAggSenderStorageMock(t) mockStorage.On("GetCertificatesByStatus", mock.Anything, []agglayer.CertificateStatus{agglayer.Pending}). Return(cfg.shouldSendCertificate...).Once() @@ -875,14 +881,6 @@ func TestSendCertificate(t *testing.T) { mockStorage.On("GetLastSentCertificate", mock.Anything).Return(cfg.getLastSentCertificate...).Once() } - if cfg.deleteCertificate != nil { - mockStorage.On("DeleteCertificate", mock.Anything, mock.Anything).Return(cfg.deleteCertificate...).Once() - } - - if cfg.getCertificateByHeight != nil { - mockStorage.On("GetCertificateByHeight", mock.Anything, mock.Anything).Return(cfg.getCertificateByHeight...).Once() - } - if cfg.saveLastSentCertificate != nil { mockStorage.On("SaveLastSentCertificate", mock.Anything, mock.Anything).Return(cfg.saveLastSentCertificate...).Once() } @@ -913,13 +911,9 @@ func TestSendCertificate(t *testing.T) { aggsender.l2Syncer = mockL2Syncer } - if cfg.getCertificateHeader != nil || cfg.sendCertificate != nil { + if cfg.sendCertificate != nil { mockAggLayerClient = agglayer.NewAgglayerClientMock(t) - mockAggLayerClient.On("GetCertificateHeader", mock.Anything).Return(cfg.getCertificateHeader...).Once() - - if cfg.sendCertificate != nil { - mockAggLayerClient.On("SendCertificate", mock.Anything).Return(cfg.sendCertificate...).Once() - } + mockAggLayerClient.On("SendCertificate", mock.Anything).Return(cfg.sendCertificate...).Once() aggsender.aggLayerClient = mockAggLayerClient } @@ -953,55 +947,6 @@ func TestSendCertificate(t *testing.T) { getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{}, errors.New("error getting last sent certificate")}, expectedError: "error getting last sent certificate", }, - { - name: "error getting last certificate header", - shouldSendCertificate: []interface{}{[]*aggsendertypes.CertificateInfo{}, nil}, - lastL2BlockProcessed: []interface{}{uint64(8), nil}, - getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ - Height: 1, - CertificateID: common.HexToHash("0x1"), - NewLocalExitRoot: common.HexToHash("0x123"), - FromBlock: 1, - ToBlock: 10, - }, nil}, - getCertificateHeader: []interface{}{nil, errors.New("error getting certificate header")}, - expectedError: "error getting certificate", - }, - { - name: "error deleting in error certificate", - shouldSendCertificate: []interface{}{[]*aggsendertypes.CertificateInfo{}, nil}, - lastL2BlockProcessed: []interface{}{uint64(20), nil}, - getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ - Height: 1, - CertificateID: common.HexToHash("0x1"), - NewLocalExitRoot: common.HexToHash("0x123"), - FromBlock: 1, - ToBlock: 10, - }, nil}, - getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ - Status: agglayer.InError, - }, nil}, - deleteCertificate: []interface{}{errors.New("error deleting certificate")}, - expectedError: "error deleting certificate", - }, - { - name: "error getting certificate by height", - shouldSendCertificate: []interface{}{[]*aggsendertypes.CertificateInfo{}, nil}, - lastL2BlockProcessed: []interface{}{uint64(29), nil}, - getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ - Height: 11, - CertificateID: common.HexToHash("0x1"), - NewLocalExitRoot: common.HexToHash("0x123"), - FromBlock: 19, - ToBlock: 28, - }, nil}, - getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ - Status: agglayer.InError, - }, nil}, - deleteCertificate: []interface{}{nil}, - getCertificateByHeight: []interface{}{aggsendertypes.CertificateInfo{}, errors.New("error getting certificate by height")}, - expectedError: "error getting certificate by height", - }, { name: "no new blocks to send certificate", shouldSendCertificate: []interface{}{[]*aggsendertypes.CertificateInfo{}, nil}, @@ -1013,9 +958,6 @@ func TestSendCertificate(t *testing.T) { FromBlock: 31, ToBlock: 41, }, nil}, - getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ - Status: agglayer.Settled, - }, nil}, }, { name: "get bridges error", @@ -1028,11 +970,6 @@ func TestSendCertificate(t *testing.T) { FromBlock: 40, ToBlock: 41, }, nil}, - getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ - Status: agglayer.Settled, - CertificateID: common.HexToHash("0x1110"), - Height: 49, - }, nil}, getBridges: []interface{}{nil, errors.New("error getting bridges")}, expectedError: "error getting bridges", }, @@ -1047,11 +984,6 @@ func TestSendCertificate(t *testing.T) { FromBlock: 50, ToBlock: 51, }, nil}, - getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ - Status: agglayer.Settled, - CertificateID: common.HexToHash("0x1110"), - Height: 59, - }, nil}, getBridges: []interface{}{[]bridgesync.Bridge{}, nil}, }, { @@ -1065,11 +997,6 @@ func TestSendCertificate(t *testing.T) { FromBlock: 60, ToBlock: 61, }, nil}, - getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ - Status: agglayer.Settled, - CertificateID: common.HexToHash("0x1110"), - Height: 69, - }, nil}, getBridges: []interface{}{[]bridgesync.Bridge{ { BlockNum: 61, @@ -1092,11 +1019,6 @@ func TestSendCertificate(t *testing.T) { FromBlock: 70, ToBlock: 71, }, nil}, - getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ - Status: agglayer.Settled, - CertificateID: common.HexToHash("0x1110"), - Height: 79, - }, nil}, getBridges: []interface{}{[]bridgesync.Bridge{ { BlockNum: 71, @@ -1124,11 +1046,6 @@ func TestSendCertificate(t *testing.T) { FromBlock: 80, ToBlock: 81, }, nil}, - getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ - Status: agglayer.Settled, - CertificateID: common.HexToHash("0x1110"), - Height: 89, - }, nil}, getBridges: []interface{}{[]bridgesync.Bridge{ { BlockNum: 81, @@ -1156,11 +1073,6 @@ func TestSendCertificate(t *testing.T) { FromBlock: 90, ToBlock: 91, }, nil}, - getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ - Status: agglayer.Settled, - CertificateID: common.HexToHash("0x11110"), - Height: 99, - }, nil}, getBridges: []interface{}{[]bridgesync.Bridge{ { BlockNum: 91, @@ -1189,11 +1101,6 @@ func TestSendCertificate(t *testing.T) { FromBlock: 100, ToBlock: 101, }, nil}, - getCertificateHeader: []interface{}{&agglayer.CertificateHeader{ - Status: agglayer.Settled, - CertificateID: common.HexToHash("0x11110"), - Height: 109, - }, nil}, getBridges: []interface{}{[]bridgesync.Bridge{ { BlockNum: 101, diff --git a/aggsender/config.go b/aggsender/config.go index 2d88569d..506b4e9a 100644 --- a/aggsender/config.go +++ b/aggsender/config.go @@ -6,8 +6,8 @@ import ( // Config is the configuration for the AggSender type Config struct { - // DBPath is the path of the sqlite db on which the AggSender will store the data - DBPath string `mapstructure:"DBPath"` + // StoragePath is the path of the sqlite db on which the AggSender will store the data + StoragePath string `mapstructure:"StoragePath"` // AggLayerURL is the URL of the AggLayer AggLayerURL string `mapstructure:"AggLayerURL"` // BlockGetInterval is the interval at which the AggSender will get the blocks from L1 @@ -18,4 +18,6 @@ type Config struct { AggsenderPrivateKey types.KeystoreFileConfig `mapstructure:"AggsenderPrivateKey"` // URLRPCL2 is the URL of the L2 RPC node URLRPCL2 string `mapstructure:"URLRPCL2"` + // SaveCertificatesToFiles is a flag which tells the AggSender to save the certificates to a file + SaveCertificatesToFiles bool `mapstructure:"SaveCertificatesToFiles"` } diff --git a/config/default.go b/config/default.go index f07b3f17..6898af50 100644 --- a/config/default.go +++ b/config/default.go @@ -341,10 +341,11 @@ GlobalExitRootManagerAddr = "{{L1Config.polygonZkEVMGlobalExitRootAddress}}" [AggSender] -DBPath = "{{PathRWData}}/aggsender.sqlite" +StoragePath = "{{PathRWData}}/aggsender.sqlite" AggLayerURL = "{{AggLayerURL}}" AggsenderPrivateKey = {Path = "{{SequencerPrivateKeyPath}}", Password = "{{SequencerPrivateKeyPassword}}"} BlockGetInterval = "2s" URLRPCL2="{{L2URL}}" CheckSettledInterval = "2s" +SaveCertificatesToFiles = false ` diff --git a/test/helpers/lxly-bridge-test.bash b/test/helpers/lxly-bridge-test.bash index 7ec356da..7b3cb008 100644 --- a/test/helpers/lxly-bridge-test.bash +++ b/test/helpers/lxly-bridge-test.bash @@ -37,8 +37,6 @@ function claim() { readonly claimable_deposit_file=$(mktemp) echo "Getting full list of deposits" >&3 curl -s "$bridge_api_url/bridges/$destination_addr?limit=100&offset=0" | jq '.' | tee $bridge_deposit_file - cat $bridge_deposit_file >&3 - echo "Looking for claimable deposits" >&3 jq '[.deposits[] | select(.ready_for_claim == true and .claim_tx_hash == "" and .dest_net == '$destination_net')]' $bridge_deposit_file | tee $claimable_deposit_file From c8ee26b199da6bedb690c51441df50841f176a56 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Mon, 28 Oct 2024 21:05:59 +0100 Subject: [PATCH 78/84] fix: ut and lint --- aggsender/aggsender.go | 5 +++-- config/default.go | 1 - test/config/kurtosis-cdk-node-config.toml.template | 2 -- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index b7165946..817a7c89 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -200,7 +200,7 @@ func (a *AggSender) saveCertificateToFile(signedCertificate *agglayer.SignedCert a.log.Errorf("error marshalling certificate: %w", err) } - if err = os.WriteFile(fn, jsonData, 0644); err != nil { + if err = os.WriteFile(fn, jsonData, 0644); err != nil { //nolint:gosec,mnd // we are writing to a tmp file a.log.Errorf("error writing certificate to file: %w", err) } } @@ -322,7 +322,8 @@ 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, + l1Info.L1InfoTreeIndex, l1Info.GlobalExitRoot) 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) diff --git a/config/default.go b/config/default.go index 05e0d393..7f2ae8b6 100644 --- a/config/default.go +++ b/config/default.go @@ -17,7 +17,6 @@ L2Coinbase = "0xfa3b44587990f97ba8b6ba7e230a5f0e95d14b3d" SequencerPrivateKeyPath = "/app/sequencer.keystore" SequencerPrivateKeyPassword = "test" WitnessURL = "http://localhost:8123" -AggLayerURL = "https://agglayer-dev.polygon.technology" AggregatorPrivateKeyPath = "/app/keystore/aggregator.keystore" AggregatorPrivateKeyPassword = "testonly" diff --git a/test/config/kurtosis-cdk-node-config.toml.template b/test/config/kurtosis-cdk-node-config.toml.template index 7dc39910..68f6ec97 100644 --- a/test/config/kurtosis-cdk-node-config.toml.template +++ b/test/config/kurtosis-cdk-node-config.toml.template @@ -25,8 +25,6 @@ polygonBridgeAddr = "{{.zkevm_bridge_address}}" RPCURL = "http://{{.l2_rpc_name}}{{.deployment_suffix}}:{{.zkevm_rpc_http_port}}" WitnessURL = "http://{{.l2_rpc_name}}{{.deployment_suffix}}:{{.zkevm_rpc_http_port}}" -AggLayerURL = "http://agglayer:{{.agglayer_port}}" - # This values can be override directly from genesis.json From 8cd9de3fd6c44552ce7b0d30936e8aa662fea4c8 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Tue, 29 Oct 2024 11:31:01 +0100 Subject: [PATCH 79/84] feat: storing big.Int to db test --- bridgesync/processor_test.go | 48 ++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/bridgesync/processor_test.go b/bridgesync/processor_test.go index 17152651..82432737 100644 --- a/bridgesync/processor_test.go +++ b/bridgesync/processor_test.go @@ -12,6 +12,7 @@ import ( "testing" migrationsBridge "github.com/0xPolygon/cdk/bridgesync/migrations" + "github.com/0xPolygon/cdk/db" "github.com/0xPolygon/cdk/log" "github.com/0xPolygon/cdk/sync" "github.com/0xPolygon/cdk/tree/testvectors" @@ -27,6 +28,53 @@ func TestBigIntString(t *testing.T) { _, ok := new(big.Int).SetString(globalIndex.String(), 10) require.True(t, ok) + + dbPath := path.Join(t.TempDir(), "file::memory:?cache=shared") + + err := migrationsBridge.RunMigrations(dbPath) + require.NoError(t, err) + db, err := db.NewSQLiteDB(dbPath) + require.NoError(t, err) + + ctx := context.Background() + tx, err := db.BeginTx(ctx, nil) + require.NoError(t, err) + + claim := &Claim{ + BlockNum: 1, + BlockPos: 0, + GlobalIndex: GenerateGlobalIndex(true, 0, 1093), + OriginNetwork: 11, + Amount: big.NewInt(11), + OriginAddress: common.HexToAddress("0x11"), + DestinationAddress: common.HexToAddress("0x11"), + ProofLocalExitRoot: types.Proof{}, + ProofRollupExitRoot: types.Proof{}, + MainnetExitRoot: common.Hash{}, + RollupExitRoot: common.Hash{}, + GlobalExitRoot: common.Hash{}, + DestinationNetwork: 12, + } + + _, err = tx.Exec(`INSERT INTO block (num) VALUES ($1)`, claim.BlockNum) + require.NoError(t, err) + require.NoError(t, meddler.Insert(tx, "claim", claim)) + + require.NoError(t, tx.Commit()) + + tx, err = db.BeginTx(ctx, nil) + require.NoError(t, err) + + rows, err := tx.Query(` + SELECT * FROM claim + WHERE block_num >= $1 AND block_num <= $2; + `, claim.BlockNum, claim.BlockNum) + require.NoError(t, err) + + claimsFromDB := []*Claim{} + require.NoError(t, meddler.ScanAll(rows, &claimsFromDB)) + require.Len(t, claimsFromDB, 1) + require.Equal(t, claim, claimsFromDB[0]) } func TestProceessor(t *testing.T) { From dac23e460d13260e3d1cd59f3032bdb99b2966f2 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Tue, 29 Oct 2024 11:42:37 +0100 Subject: [PATCH 80/84] feat: experimental test of different trees and proofs --- l1infotreesync/processor_test.go | 85 ++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/l1infotreesync/processor_test.go b/l1infotreesync/processor_test.go index 1c64d875..13595263 100644 --- a/l1infotreesync/processor_test.go +++ b/l1infotreesync/processor_test.go @@ -1,10 +1,15 @@ package l1infotreesync import ( + "fmt" "testing" "github.com/0xPolygon/cdk/db" + "github.com/0xPolygon/cdk/l1infotree" + "github.com/0xPolygon/cdk/l1infotreesync/migrations" + "github.com/0xPolygon/cdk/log" "github.com/0xPolygon/cdk/sync" + "github.com/0xPolygon/cdk/tree" "github.com/0xPolygon/cdk/tree/types" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" @@ -263,3 +268,83 @@ 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) + + aLeaves := make([][32]byte, len(leaves)) + for i, leaf := range leaves { + aLeaves[i] = l1infotree.HashLeafData( + leaf.GlobalExitRoot, + leaf.PreviousBlockHash, + leaf.Timestamp) + } + + proof, root, 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[:]) + } + + fmt.Println(root) + fmt.Println(hashProof) + fmt.Println("===========================================================================================================") + + dbPath := "file:l1InfoTreeTest?mode=memory&cache=shared" + require.NoError(t, migrations.RunMigrations(dbPath)) + + dbe, err := db.NewSQLiteDB(dbPath) + require.NoError(t, err) + + l1InfoTree := tree.NewAppendOnlyTree(dbe, migrations.L1InfoTreePrefix) + + tx, err := db.NewTx(context.Background(), dbe) + require.NoError(t, err) + + for _, leaf := range leaves { + err = l1InfoTree.AddLeaf(tx, leaf.BlockNumber, leaf.BlockPosition, types.Leaf{ + Index: leaf.L1InfoTreeIndex, + Hash: leaf.Hash, + }) + + require.NoError(t, err) + } + + require.NoError(t, tx.Commit()) + + pro, err := l1InfoTree.GetProof(context.Background(), leaves[0].L1InfoTreeIndex, leaves[0].GlobalExitRoot) + require.NoError(t, err) + + fmt.Println(leaves[0].GlobalExitRoot) + fmt.Println(pro) +} + +func createTestLeaves(numOfLeaves int) []*L1InfoTreeLeaf { + leaves := make([]*L1InfoTreeLeaf, 0, numOfLeaves) + + for i := 0; i < numOfLeaves; i++ { + leaf := &L1InfoTreeLeaf{ + L1InfoTreeIndex: uint32(i), + Timestamp: uint64(i), + BlockNumber: uint64(i), + BlockPosition: uint64(i), + PreviousBlockHash: common.HexToHash(fmt.Sprintf("0x%x", i)), + MainnetExitRoot: common.HexToHash(fmt.Sprintf("0x%x", i)), + RollupExitRoot: common.HexToHash(fmt.Sprintf("0x%x", i)), + } + + leaf.GlobalExitRoot = leaf.globalExitRoot() + leaf.Hash = leaf.hash() + + leaves = append(leaves, leaf) + } + + return leaves +} From da64de87454638754c7eed47bfb493e9af7ebf81 Mon Sep 17 00:00:00 2001 From: joanestebanr <129153821+joanestebanr@users.noreply.github.com> Date: Tue, 29 Oct 2024 12:25:41 +0100 Subject: [PATCH 81/84] fix: partial l1infotree --- l1infotree/tree.go | 17 +++++++------ l1infotree/tree_test.go | 55 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 7 deletions(-) diff --git a/l1infotree/tree.go b/l1infotree/tree.go index f3ad6d36..e0acc409 100644 --- a/l1infotree/tree.go +++ b/l1infotree/tree.go @@ -109,13 +109,16 @@ func (mt *L1InfoTree) ComputeMerkleProof(gerIndex uint32, leaves [][32]byte) ([] if len(leaves)%2 == 1 { leaves = append(leaves, mt.zeroHashes[h]) } - if index >= uint32(len(leaves)) { - siblings = append(siblings, mt.zeroHashes[h]) - } else { - if index%2 == 1 { // If it is odd - siblings = append(siblings, leaves[index-1]) - } else { // It is even - siblings = append(siblings, leaves[index+1]) + if index%2 == 1 { //If it is odd + siblings = append(siblings, leaves[index-1]) + } else { // It is even + if len(leaves) > 1 { + if index >= uint32(len(leaves)) { + // siblings = append(siblings, mt.zeroHashes[h]) + siblings = append(siblings, leaves[index-1]) + } else { + siblings = append(siblings, leaves[index+1]) + } } } var ( diff --git a/l1infotree/tree_test.go b/l1infotree/tree_test.go index 6af4b8b3..69ce6b52 100644 --- a/l1infotree/tree_test.go +++ b/l1infotree/tree_test.go @@ -3,6 +3,7 @@ package l1infotree_test import ( "encoding/hex" "encoding/json" + "fmt" "os" "testing" @@ -129,3 +130,57 @@ func TestAddLeaf2(t *testing.T) { require.Equal(t, testVector.NewRoot, newRoot) } } + +func TestAddLeaf2TestLastLeaf(t *testing.T) { + mt, err := l1infotree.NewL1InfoTree(log.GetDefaultLogger(), uint8(32), [][32]byte{}) + require.NoError(t, err) + leaves := [][32]byte{ + common.HexToHash("0x6a617315ffc0a6831d2de6331f8d3e053889e9385696c13f11853fdcba50e123"), + common.HexToHash("0x1cff355b898cf285bcc3f84a8d6ed51c19fe87ab654f4146f2dc7723a59fc741"), + } + //require.Equal(t, 26, len(leaves)) + siblings, root, err := mt.ComputeMerkleProof(2, leaves) + require.NoError(t, err) + fmt.Printf("Root: %s\n", root.String()) + for i := 0; i < len(siblings); i++ { + hash := common.BytesToHash(siblings[i][:]) + fmt.Printf("Sibling %d: %s\n", i, hash.String()) + } + expectedProof := []string{ + "0x1cff355b898cf285bcc3f84a8d6ed51c19fe87ab654f4146f2dc7723a59fc741", + "0x7ae3eca221dee534b82adffb8003ad3826ddf116132e4ff55c681ff723bc7e42", + "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"} + for i := 0; i < len(siblings); i++ { + require.Equal(t, expectedProof[i], "0x"+hex.EncodeToString(siblings[i][:])) + } + require.Equal(t, "0xb85687d05a6bdccadcc1170a0e2bbba6855c35c984a0bc91697bc066bd38a338", root.String()) +} From 1f2d2b948ccfeb65889720efae7e962cf478e2b6 Mon Sep 17 00:00:00 2001 From: Arnau Bennassar Date: Tue, 29 Oct 2024 22:47:03 +0100 Subject: [PATCH 82/84] fix: use l1info root insetead of ger to generate merkleproof (#148) * use l1info root insetead of ger to generate merkleproof * fix: uts * fix: different l1 info trees test * fix: ut --------- Co-authored-by: Goran Rojovic --- aggsender/aggsender.go | 54 ++- aggsender/aggsender_test.go | 540 +++++++++++++--------- aggsender/mocks/mock_l1infotree_syncer.go | 57 +++ aggsender/types/types.go | 6 +- l1infotreesync/processor_test.go | 34 +- 5 files changed, 446 insertions(+), 245 deletions(-) diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index 817a7c89..a228e1a9 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -304,17 +304,42 @@ 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) { - 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) +func (a *AggSender) getImportedBridgeExits( + ctx context.Context, claims []bridgesync.Claim, +) ([]*agglayer.ImportedBridgeExit, error) { + 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, 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 { return nil, fmt.Errorf("error converting claim to imported bridge exit: %w", err) @@ -322,11 +347,14 @@ 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, l1Info.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", + l1Info.L1InfoTreeIndex, rootToProve.Hash, err, + ) } claim := claims[i] @@ -347,7 +375,7 @@ func (a *AggSender) getImportedBridgeExits(ctx context.Context, Proof: claim.ProofLocalExitRoot, }, ProofGERToL1Root: &agglayer.MerkleProof{ - Root: l1Info.GlobalExitRoot, + Root: rootToProve.Hash, Proof: gerToL1Proof, }, } @@ -372,7 +400,7 @@ func (a *AggSender) getImportedBridgeExits(ctx context.Context, Proof: claim.ProofRollupExitRoot, }, ProofGERToL1Root: &agglayer.MerkleProof{ - Root: l1Info.GlobalExitRoot, + Root: rootToProve.Hash, Proof: gerToL1Proof, }, } diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go index dd4e3901..69dc6ed1 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) @@ -454,7 +456,7 @@ func TestGetImportedBridgeExits(t *testing.T) { name: "No claims", claims: []bridgesync.Claim{}, expectedError: false, - expectedExits: []*agglayer.ImportedBridgeExit{}, + expectedExits: 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 { 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..34c5daef 100644 --- a/l1infotreesync/processor_test.go +++ b/l1infotreesync/processor_test.go @@ -270,12 +270,12 @@ func Test_processor_Reorg(t *testing.T) { } func TestProofsFromDifferentTrees(t *testing.T) { - t.Skip("This is an experiment") + fmt.Println("aggregator L1InfoTree ===============================================") l1Tree, err := l1infotree.NewL1InfoTree(log.WithFields("test"), types.DefaultHeight, [][32]byte{}) require.NoError(t, err) - leaves := createTestLeaves(1) + leaves := createTestLeaves(t, 2) aLeaves := make([][32]byte, len(leaves)) for i, leaf := range leaves { @@ -285,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)) @@ -319,14 +319,24 @@ func TestProofsFromDifferentTrees(t *testing.T) { require.NoError(t, tx.Commit()) - pro, err := l1InfoTree.GetProof(context.Background(), leaves[0].L1InfoTreeIndex, leaves[0].GlobalExitRoot) + l1InfoTreeSyncerRoot, err := l1InfoTree.GetRootByIndex(context.Background(), leaves[1].L1InfoTreeIndex) require.NoError(t, err) + l1InfoTreeSyncerProof, err := l1InfoTree.GetProof(context.Background(), leaves[0].L1InfoTreeIndex, l1InfoTreeSyncerRoot.Hash) + require.NoError(t, err) + 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 5c751049d60a9f4fa168f9e3329e3056354351f5 Mon Sep 17 00:00:00 2001 From: joanestebanr <129153821+joanestebanr@users.noreply.github.com> Date: Wed, 30 Oct 2024 09:31:45 +0100 Subject: [PATCH 83/84] fix: revert PR#140 --- bridgesync/bridge_contract.go | 37 ------------------------------ bridgesync/bridge_contract_test.go | 13 ----------- bridgesync/bridgesync.go | 7 +----- bridgesync/bridgesync_test.go | 3 --- bridgesync/e2e_test.go | 4 +--- bridgesync/processor.go | 26 ++++----------------- bridgesync/processor_test.go | 11 +++------ claimsponsor/e2e_test.go | 5 +--- cmd/run.go | 11 --------- l1infotree/tree.go | 17 +++++++------- l1infotree/tree_test.go | 1 - 11 files changed, 19 insertions(+), 116 deletions(-) delete mode 100644 bridgesync/bridge_contract.go delete mode 100644 bridgesync/bridge_contract_test.go diff --git a/bridgesync/bridge_contract.go b/bridgesync/bridge_contract.go deleted file mode 100644 index 3e19cec2..00000000 --- a/bridgesync/bridge_contract.go +++ /dev/null @@ -1,37 +0,0 @@ -package bridgesync - -import ( - "context" - "fmt" - "math/big" - - "github.com/0xPolygon/cdk-contracts-tooling/contracts/banana/polygonzkevmbridgev2" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" -) - -type BridgeContract struct { - bridgeAddr common.Address - Contract *polygonzkevmbridgev2.Polygonzkevmbridgev2 -} - -func NewBridgeContract(bridgeAddr common.Address, backend bind.ContractBackend) (*BridgeContract, error) { - contract, err := polygonzkevmbridgev2.NewPolygonzkevmbridgev2(bridgeAddr, backend) - if err != nil { - return nil, fmt.Errorf("failed to instantiate bridge contract at addr %s. Err:%w", bridgeAddr.String(), err) - } - - return &BridgeContract{ - bridgeAddr: bridgeAddr, - Contract: contract, - }, nil -} - -// Returns LastUpdatedDepositCount for a specific blockNumber -func (b *BridgeContract) LastUpdatedDepositCount(ctx context.Context, blockNumber uint64) (uint32, error) { - opts := &bind.CallOpts{ - Context: ctx, - BlockNumber: new(big.Int).SetUint64(blockNumber), - } - return b.Contract.LastUpdatedDepositCount(opts) -} diff --git a/bridgesync/bridge_contract_test.go b/bridgesync/bridge_contract_test.go deleted file mode 100644 index 753904ff..00000000 --- a/bridgesync/bridge_contract_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package bridgesync - -import ( - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/require" -) - -func TestLastUpdatedDepositCount(t *testing.T) { - _, err := NewBridgeContract(common.Address{}, nil) - require.NoError(t, err) -} diff --git a/bridgesync/bridgesync.go b/bridgesync/bridgesync.go index 4c4b4e61..b3c3c853 100644 --- a/bridgesync/bridgesync.go +++ b/bridgesync/bridgesync.go @@ -43,7 +43,6 @@ func NewL1( retryAfterErrorPeriod time.Duration, maxRetryAttemptsAfterError int, originNetwork uint32, - bridgeContract BridgeContractor, ) (*BridgeSync, error) { return newBridgeSync( ctx, @@ -60,7 +59,6 @@ func NewL1( maxRetryAttemptsAfterError, originNetwork, false, - bridgeContract, ) } @@ -78,7 +76,6 @@ func NewL2( retryAfterErrorPeriod time.Duration, maxRetryAttemptsAfterError int, originNetwork uint32, - bridgeContract BridgeContractor, ) (*BridgeSync, error) { return newBridgeSync( ctx, @@ -95,7 +92,6 @@ func NewL2( maxRetryAttemptsAfterError, originNetwork, true, - bridgeContract, ) } @@ -114,9 +110,8 @@ func newBridgeSync( maxRetryAttemptsAfterError int, originNetwork uint32, syncFullClaims bool, - bridgeContract BridgeContractor, ) (*BridgeSync, error) { - processor, err := newProcessor(dbPath, l1OrL2ID, bridgeContract) + processor, err := newProcessor(dbPath, l1OrL2ID) if err != nil { return nil, err } diff --git a/bridgesync/bridgesync_test.go b/bridgesync/bridgesync_test.go index 125d58dd..cb328c68 100644 --- a/bridgesync/bridgesync_test.go +++ b/bridgesync/bridgesync_test.go @@ -35,7 +35,6 @@ func TestNewLx(t *testing.T) { originNetwork := uint32(1) mockEthClient := mocksbridgesync.NewEthClienter(t) - mockBridgeContract := mocksbridgesync.NewBridgeContractor(t) mockReorgDetector := mocksbridgesync.NewReorgDetector(t) mockReorgDetector.EXPECT().Subscribe(mock.Anything).Return(nil, nil) @@ -53,7 +52,6 @@ func TestNewLx(t *testing.T) { retryAfterErrorPeriod, maxRetryAttemptsAfterError, originNetwork, - mockBridgeContract, ) assert.NoError(t, err) @@ -74,7 +72,6 @@ func TestNewLx(t *testing.T) { retryAfterErrorPeriod, maxRetryAttemptsAfterError, originNetwork, - mockBridgeContract, ) assert.NoError(t, err) diff --git a/bridgesync/e2e_test.go b/bridgesync/e2e_test.go index f096b7a5..a8868ce1 100644 --- a/bridgesync/e2e_test.go +++ b/bridgesync/e2e_test.go @@ -29,9 +29,7 @@ func TestBridgeEventE2E(t *testing.T) { go rd.Start(ctx) //nolint:errcheck testClient := helpers.TestClient{ClientRenamed: client.Client()} - bridgeContract, err := bridgesync.NewBridgeContract(setup.EBZkevmBridgeAddr, testClient) - require.NoError(t, err) - syncer, err := bridgesync.NewL1(ctx, dbPathSyncer, setup.EBZkevmBridgeAddr, 10, etherman.LatestBlock, rd, testClient, 0, time.Millisecond*10, 0, 0, 1, bridgeContract) + syncer, err := bridgesync.NewL1(ctx, dbPathSyncer, setup.EBZkevmBridgeAddr, 10, etherman.LatestBlock, rd, testClient, 0, time.Millisecond*10, 0, 0, 1) require.NoError(t, err) go syncer.Start(ctx) diff --git a/bridgesync/processor.go b/bridgesync/processor.go index e0a0dae7..e8a79c1f 100644 --- a/bridgesync/processor.go +++ b/bridgesync/processor.go @@ -109,7 +109,7 @@ type processor struct { bridgeContract BridgeContractor } -func newProcessor(dbPath, loggerPrefix string, bridgeContract BridgeContractor) (*processor, error) { +func newProcessor(dbPath, loggerPrefix string) (*processor, error) { err := migrations.RunMigrations(dbPath) if err != nil { return nil, err @@ -121,31 +121,15 @@ func newProcessor(dbPath, loggerPrefix string, bridgeContract BridgeContractor) logger := log.WithFields("bridge-syncer", loggerPrefix) exitTree := tree.NewAppendOnlyTree(db, "") return &processor{ - db: db, - exitTree: exitTree, - log: logger, - bridgeContract: bridgeContract, + db: db, + exitTree: exitTree, + log: logger, }, nil } func (p *processor) GetBridgesPublished( ctx context.Context, fromBlock, toBlock uint64, ) ([]Bridge, error) { - allBridges, err := p.GetBridges(ctx, fromBlock, toBlock) - if err != nil { - return nil, err - } - lastCount, er := p.bridgeContract.LastUpdatedDepositCount(ctx, toBlock) - if er != nil { - return nil, er - } - p.log.Debugf("last updated deposit count: %d in block %d. Num bridges: %d", lastCount, toBlock, len(allBridges)) - var bridges []Bridge - for _, bridge := range allBridges { - if bridge.DepositCount <= lastCount { - bridges = append(bridges, bridge) - } - } - return bridges, nil + return p.GetBridges(ctx, fromBlock, toBlock) } func (p *processor) GetBridges( diff --git a/bridgesync/processor_test.go b/bridgesync/processor_test.go index 82432737..25977ea4 100644 --- a/bridgesync/processor_test.go +++ b/bridgesync/processor_test.go @@ -82,7 +82,7 @@ func TestProceessor(t *testing.T) { log.Debugf("sqlite path: %s", path) err := migrationsBridge.RunMigrations(path) require.NoError(t, err) - p, err := newProcessor(path, "foo", nil) + p, err := newProcessor(path, "foo") require.NoError(t, err) actions := []processAction{ // processed: ~ @@ -735,7 +735,7 @@ func TestInsertAndGetClaim(t *testing.T) { log.Debugf("sqlite path: %s", path) err := migrationsBridge.RunMigrations(path) require.NoError(t, err) - p, err := newProcessor(path, "foo", nil) + p, err := newProcessor(path, "foo") require.NoError(t, err) tx, err := p.db.BeginTx(context.Background(), nil) @@ -853,14 +853,9 @@ func TestGetBridgesPublished(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - mockBridgeContract := &mockBridgeContract{ - lastUpdatedDepositCount: tc.lastUpdatedDepositCount, - err: tc.expectedError, - } - path := path.Join(t.TempDir(), "file::memory:?cache=shared") require.NoError(t, migrationsBridge.RunMigrations(path)) - p, err := newProcessor(path, "foo", mockBridgeContract) + p, err := newProcessor(path, "foo") require.NoError(t, err) tx, err := p.db.BeginTx(context.Background(), nil) diff --git a/claimsponsor/e2e_test.go b/claimsponsor/e2e_test.go index 7c4c3062..426d7b3e 100644 --- a/claimsponsor/e2e_test.go +++ b/claimsponsor/e2e_test.go @@ -26,10 +26,7 @@ func TestE2EL1toEVML2(t *testing.T) { env := aggoraclehelpers.SetupAggoracleWithEVMChain(t) dbPathBridgeSyncL1 := path.Join(t.TempDir(), "file::memory:?cache=shared") testClient := helpers.TestClient{ClientRenamed: env.L1Client.Client()} - bridgeContract, err := bridgesync.NewBridgeContract(env.BridgeL1Addr, testClient) - require.NoError(t, err) - - bridgeSyncL1, err := bridgesync.NewL1(ctx, dbPathBridgeSyncL1, env.BridgeL1Addr, 10, etherman.LatestBlock, env.ReorgDetector, testClient, 0, time.Millisecond*10, 0, 0, 1, bridgeContract) + bridgeSyncL1, err := bridgesync.NewL1(ctx, dbPathBridgeSyncL1, env.BridgeL1Addr, 10, etherman.LatestBlock, env.ReorgDetector, testClient, 0, time.Millisecond*10, 0, 0, 1) require.NoError(t, err) go bridgeSyncL1.Start(ctx) diff --git a/cmd/run.go b/cmd/run.go index 9d9e9625..c30da739 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -692,10 +692,6 @@ func runBridgeSyncL1IfNeeded( if !isNeeded([]string{cdkcommon.RPC}, components) { return nil } - bridgeContract, err := bridgesync.NewBridgeContract(cfg.BridgeAddr, l1Client) - if err != nil { - log.Fatalf("error creating L1 bridge contract: %s", err) - } bridgeSyncL1, err := bridgesync.NewL1( ctx, cfg.DBPath, @@ -709,7 +705,6 @@ func runBridgeSyncL1IfNeeded( cfg.RetryAfterErrorPeriod.Duration, cfg.MaxRetryAttemptsAfterError, cfg.OriginNetwork, - bridgeContract, ) if err != nil { log.Fatalf("error creating bridgeSyncL1: %s", err) @@ -730,11 +725,6 @@ func runBridgeSyncL2IfNeeded( return nil } - bridgeContract, err := bridgesync.NewBridgeContract(cfg.BridgeAddr, l2Client) - if err != nil { - log.Fatalf("error creating L2 bridge contract: %s", err) - } - bridgeSyncL2, err := bridgesync.NewL2( ctx, cfg.DBPath, @@ -748,7 +738,6 @@ func runBridgeSyncL2IfNeeded( cfg.RetryAfterErrorPeriod.Duration, cfg.MaxRetryAttemptsAfterError, cfg.OriginNetwork, - bridgeContract, ) if err != nil { log.Fatalf("error creating bridgeSyncL2: %s", err) diff --git a/l1infotree/tree.go b/l1infotree/tree.go index e0acc409..17258ba0 100644 --- a/l1infotree/tree.go +++ b/l1infotree/tree.go @@ -109,18 +109,17 @@ func (mt *L1InfoTree) ComputeMerkleProof(gerIndex uint32, leaves [][32]byte) ([] if len(leaves)%2 == 1 { leaves = append(leaves, mt.zeroHashes[h]) } - if index%2 == 1 { //If it is odd + if index%2 == 1 { // If it is odd siblings = append(siblings, leaves[index-1]) - } else { // It is even - if len(leaves) > 1 { - if index >= uint32(len(leaves)) { - // siblings = append(siblings, mt.zeroHashes[h]) - siblings = append(siblings, leaves[index-1]) - } else { - siblings = append(siblings, leaves[index+1]) - } + } else if len(leaves) > 1 { // It is even + if index >= uint32(len(leaves)) { + // siblings = append(siblings, mt.zeroHashes[h]) + siblings = append(siblings, leaves[index-1]) + } else { + siblings = append(siblings, leaves[index+1]) } } + var ( nsi [][][]byte hashes [][32]byte diff --git a/l1infotree/tree_test.go b/l1infotree/tree_test.go index 69ce6b52..a0fe9b97 100644 --- a/l1infotree/tree_test.go +++ b/l1infotree/tree_test.go @@ -138,7 +138,6 @@ func TestAddLeaf2TestLastLeaf(t *testing.T) { common.HexToHash("0x6a617315ffc0a6831d2de6331f8d3e053889e9385696c13f11853fdcba50e123"), common.HexToHash("0x1cff355b898cf285bcc3f84a8d6ed51c19fe87ab654f4146f2dc7723a59fc741"), } - //require.Equal(t, 26, len(leaves)) siblings, root, err := mt.ComputeMerkleProof(2, leaves) require.NoError(t, err) fmt.Printf("Root: %s\n", root.String()) From b856c2db501e8c4df968aa2b303304109016b1b2 Mon Sep 17 00:00:00 2001 From: joanestebanr <129153821+joanestebanr@users.noreply.github.com> Date: Thu, 31 Oct 2024 10:36:27 +0100 Subject: [PATCH 84/84] fix: unittest --- bridgesync/processor_test.go | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/bridgesync/processor_test.go b/bridgesync/processor_test.go index 25977ea4..ab31f17d 100644 --- a/bridgesync/processor_test.go +++ b/bridgesync/processor_test.go @@ -800,7 +800,7 @@ func TestGetBridgesPublished(t *testing.T) { toBlock: 10, bridges: []Bridge{}, lastUpdatedDepositCount: 0, - expectedBridges: nil, + expectedBridges: []Bridge{}, expectedError: nil, }, { @@ -818,33 +818,6 @@ func TestGetBridgesPublished(t *testing.T) { }, expectedError: nil, }, - { - name: "bridges exceeding deposit count", - fromBlock: 1, - toBlock: 10, - bridges: []Bridge{ - {DepositCount: 1, BlockNum: 1, Amount: big.NewInt(1)}, - {DepositCount: 2, BlockNum: 2, Amount: big.NewInt(1)}, - {DepositCount: 3, BlockNum: 3, Amount: big.NewInt(1)}, - }, - lastUpdatedDepositCount: 2, - expectedBridges: []Bridge{ - {DepositCount: 1, BlockNum: 1, Amount: big.NewInt(1)}, - {DepositCount: 2, BlockNum: 2, Amount: big.NewInt(1)}, - }, - expectedError: nil, - }, - { - name: "error fetching last updated deposit count", - fromBlock: 1, - toBlock: 10, - bridges: []Bridge{ - {DepositCount: 1, BlockNum: 1, Amount: big.NewInt(1)}, - }, - lastUpdatedDepositCount: 0, - expectedBridges: nil, - expectedError: errors.New("mock error"), - }, } for _, tc := range testCases {