diff --git a/aggregator/agglayer_client.go b/agglayer/client.go similarity index 81% rename from aggregator/agglayer_client.go rename to agglayer/client.go index 4726ccc19..954444cfe 100644 --- a/aggregator/agglayer_client.go +++ b/agglayer/client.go @@ -1,4 +1,4 @@ -package aggregator +package agglayer import ( "context" @@ -17,6 +17,7 @@ import ( 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 @@ -79,3 +80,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_tx.go b/agglayer/tx.go similarity index 98% rename from aggregator/agglayer_tx.go rename to agglayer/tx.go index b0cd09c9e..ca3befbbc 100644 --- a/aggregator/agglayer_tx.go +++ b/agglayer/tx.go @@ -1,4 +1,4 @@ -package aggregator +package agglayer import ( "crypto/ecdsa" diff --git a/agglayer/types.go b/agglayer/types.go new file mode 100644 index 000000000..68ac819b7 --- /dev/null +++ b/agglayer/types.go @@ -0,0 +1,83 @@ +package agglayer + +import ( + "math/big" + + cdkcommon "github.com/0xPolygon/cdk/common" + "github.com/0xPolygon/cdk/tree" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +// 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"` + Signer common.Address `json:"signer"` +} + +// 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"` +} + +// BridgeExit represents a token bridge exit +type BridgeExit struct { + LeafType uint8 `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 { + return crypto.Keccak256Hash( + cdkcommon.Uint8ToBytes(c.LeafType), + 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 [tree.DefaultHeight]common.Hash `json:"inclusion_proof"` + InclusionProofRER [tree.DefaultHeight]common.Hash `json:"inclusion_proof_rer"` + GlobalIndex *big.Int `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 2b90826ee..af784e3f4 100644 --- a/aggregator/aggregator.go +++ b/aggregator/aggregator.go @@ -16,6 +16,7 @@ import ( "github.com/0xPolygon/cdk-rpc/rpc" cdkTypes "github.com/0xPolygon/cdk-rpc/types" + "github.com/0xPolygon/cdk/agglayer" ethmanTypes "github.com/0xPolygon/cdk/aggregator/ethmantypes" "github.com/0xPolygon/cdk/aggregator/prover" cdkcommon "github.com/0xPolygon/cdk/common" @@ -93,7 +94,7 @@ type Aggregator struct { exit context.CancelFunc sequencerPrivateKey *ecdsa.PrivateKey - aggLayerClient AgglayerClientInterface + aggLayerClient agglayer.AgglayerClientInterface } // New creates a new aggregator. @@ -160,14 +161,14 @@ func New( } var ( - aggLayerClient AgglayerClientInterface + aggLayerClient agglayer.AgglayerClientInterface sequencerPrivateKey *ecdsa.PrivateKey ) if !cfg.SyncModeOnlyEnabled && cfg.SettlementBackend == AggLayer { - aggLayerClient = NewAggLayerClient(cfg.AggLayerURL) + aggLayerClient = agglayer.NewAggLayerClient(cfg.AggLayerURL) - sequencerPrivateKey, err = newKeyFromKeystore(cfg.SequencerPrivateKey) + sequencerPrivateKey, err = cdkcommon.NewKeyFromKeystore(cfg.SequencerPrivateKey) if err != nil { return nil, err } @@ -863,10 +864,10 @@ func (a *Aggregator) settleWithAggLayer( inputs ethmanTypes.FinalProofInputs) bool { proofStrNo0x := strings.TrimPrefix(inputs.FinalProof.Proof, "0x") proofBytes := common.Hex2Bytes(proofStrNo0x) - tx := Tx{ + tx := agglayer.Tx{ LastVerifiedBatch: cdkTypes.ArgUint64(proof.BatchNumber - 1), NewVerifiedBatch: cdkTypes.ArgUint64(proof.BatchNumberFinal), - ZKP: ZKP{ + ZKP: agglayer.ZKP{ NewStateRoot: common.BytesToHash(inputs.NewStateRoot), NewLocalExitRoot: common.BytesToHash(inputs.NewLocalExitRoot), Proof: cdkTypes.ArgBytes(proofBytes), diff --git a/aggregator/config.go b/aggregator/config.go index 382801873..1082198f2 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/0xPolygonHermez/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,19 +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/aggsender/aggsender.go b/aggsender/aggsender.go new file mode 100644 index 000000000..615388633 --- /dev/null +++ b/aggsender/aggsender.go @@ -0,0 +1,269 @@ +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/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 + + 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, + 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, + 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, + bridgeEvents []bridgesync.Event, + previousExitRoot common.Hash, lastHeight uint64) (*agglayer.Certificate, error) { + bridgeExits := make([]*agglayer.BridgeExit, 0, len(bridgeEvents)) + importedBridgeExits := make([]*agglayer.ImportedBridgeExit, 0, len(bridgeEvents)) + + for _, bridgeEvent := range bridgeEvents { + bridgeExit := convertBridgeToBridgeExit(bridgeEvent.Bridge) + importedBridgeExit := convertClaimToImportedBridgeExit(bridgeEvent.Claim, bridgeEvent.Bridge, bridgeExit) + + bridgeExits = append(bridgeExits, bridgeExit) + importedBridgeExits = append(importedBridgeExits, importedBridgeExit) + } + + depositCount := bridgeEvents[len(bridgeEvents)-1].Bridge.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, + BridgeExits: bridgeExits, + ImportedBridgeExits: importedBridgeExits, + Height: lastHeight + 1, + }, nil +} + +func convertBridgeToBridgeExit(bridge *bridgesync.Bridge) *agglayer.BridgeExit { + return &agglayer.BridgeExit{ + 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, bridge *bridgesync.Bridge, + bridgeExit *agglayer.BridgeExit) *agglayer.ImportedBridgeExit { + return &agglayer.ImportedBridgeExit{ + BridgeExit: bridgeExit, + ImportedLocalExitRoot: tree.CalculateRoot(bridgeExit.Hash(), claim.ProofLocalExitRoot, uint32(claim.GlobalIndex.Uint64())), + InclusionProof: claim.ProofLocalExitRoot, + InclusionProofRER: claim.ProofRollupExitRoot, + GlobalIndex: claim.GlobalIndex, + } +} + +// 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) + } + + bridgeEvents, err := a.l2Syncer.GetClaimsAndBridges(ctx, lastSentCertificateBlock+1, lastFinalizedBlock.Nonce.Uint64()) + if err != nil { + return fmt.Errorf("error getting claims and bridges: %w", err) + } + + if len(bridgeEvents) == 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, bridgeEvents, 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 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, + Signer: crypto.PubkeyToAddress(a.sequencerKey.PublicKey), + }, nil +} diff --git a/aggsender/config.go b/aggsender/config.go new file mode 100644 index 000000000..ace7943ac --- /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 220c33fd2..33f0c3bba 100644 --- a/bridgesync/bridgesync.go +++ b/bridgesync/bridgesync.go @@ -18,6 +18,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 @@ -33,6 +36,7 @@ func NewL1( waitForNewBlocksPeriod time.Duration, retryAfterErrorPeriod time.Duration, maxRetryAttemptsAfterError int, + originNetwork uint32, ) (*BridgeSync, error) { return newBridgeSync( ctx, @@ -47,6 +51,7 @@ func NewL1( waitForNewBlocksPeriod, retryAfterErrorPeriod, maxRetryAttemptsAfterError, + originNetwork, false, ) } @@ -64,6 +69,7 @@ func NewL2( waitForNewBlocksPeriod time.Duration, retryAfterErrorPeriod time.Duration, maxRetryAttemptsAfterError int, + originNetwork uint32, ) (*BridgeSync, error) { return newBridgeSync( ctx, @@ -78,6 +84,7 @@ func NewL2( waitForNewBlocksPeriod, retryAfterErrorPeriod, maxRetryAttemptsAfterError, + originNetwork, true, ) } @@ -95,6 +102,7 @@ func newBridgeSync( waitForNewBlocksPeriod time.Duration, retryAfterErrorPeriod time.Duration, maxRetryAttemptsAfterError int, + originNetwork uint32, syncFullClaims bool, ) (*BridgeSync, error) { processor, err := newProcessor(ctx, dbPath, l1OrL2ID) @@ -141,8 +149,10 @@ func newBridgeSync( return nil, err } return &BridgeSync{ - processor: processor, - driver: driver, + processor: processor, + driver: driver, + originNetwork: originNetwork, + blockFinality: blockFinalityType, }, nil } @@ -166,3 +176,24 @@ func (s *BridgeSync) GetClaimsAndBridges(ctx context.Context, fromBlock, toBlock func (s *BridgeSync) GetProof(ctx context.Context, depositCount uint32, localExitRoot common.Hash) ([32]common.Hash, error) { return s.processor.exitTree.GetProof(ctx, depositCount, localExitRoot) } + +// 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) (common.Hash, error) { + tx, err := s.processor.db.BeginRo(ctx) + if err != nil { + return common.Hash{}, err + } + defer tx.Rollback() + + return s.processor.exitTree.GetRootByIndex(tx, 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 9aa849e2c..1be261c60 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 6eff5548b..48e7d8a02 100644 --- a/bridgesync/e2e_test.go +++ b/bridgesync/e2e_test.go @@ -57,7 +57,7 @@ func TestBridgeEventE2E(t *testing.T) { go rd.Start(ctx) testClient := helpers.TestClient{ClientRenamed: client.Client()} - syncer, err := bridgesync.NewL1(ctx, dbPathSyncer, bridgeAddr, 10, etherman.LatestBlock, rd, testClient, 0, time.Millisecond*10, 0, 0) + syncer, err := bridgesync.NewL1(ctx, dbPathSyncer, bridgeAddr, 10, etherman.LatestBlock, rd, testClient, 0, time.Millisecond*10, 0, 0, 1) require.NoError(t, err) go syncer.Start(ctx) diff --git a/claimsponsor/e2e_test.go b/claimsponsor/e2e_test.go index 796a09ba2..0b0a9a6a0 100644 --- a/claimsponsor/e2e_test.go +++ b/claimsponsor/e2e_test.go @@ -23,7 +23,7 @@ func TestE2EL1toEVML2(t *testing.T) { env := helpers.SetupAggoracleWithEVMChain(t) dbPathBridgeSyncL1 := t.TempDir() 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 4686902f9..e5c12b51c 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -20,6 +20,8 @@ const ( AGGORACLE = "aggoracle" // RPC name to identify the rpc component RPC = "rpc" + // AGGSENDER name to identify the aggsender component + AGGSENDER = "aggsender" ) const ( @@ -51,7 +53,7 @@ var ( Aliases: []string{"co"}, Usage: "List of components to run", Required: false, - Value: cli.NewStringSlice(SEQUENCE_SENDER, AGGREGATOR, AGGORACLE, RPC), + Value: cli.NewStringSlice(SEQUENCE_SENDER, AGGREGATOR, AGGORACLE, RPC, AGGSENDER), } ) diff --git a/cmd/run.go b/cmd/run.go index b960b3762..14f8a3877 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" "github.com/0xPolygon/cdk/config" @@ -106,6 +108,13 @@ func start(cliCtx *cli.Context) error { log.Fatal(err) } }() + case AGGSENDER: + aggsender, err := createAggSender(cliCtx.Context, c.AggSender, l1BridgeSync, l2BridgeSync, l1Client, l2Client) + if err != nil { + log.Fatal(err) + } + + go aggsender.Start(cliCtx.Context) } } @@ -114,6 +123,19 @@ func start(cliCtx *cli.Context) error { return nil } +func createAggSender( + ctx context.Context, + cfg aggsender.Config, + l1Syncer *bridgesync.BridgeSync, + l2Syncer *bridgesync.BridgeSync, + l1Client bridgesync.EthClienter, + l2Client bridgesync.EthClienter, +) (*aggsender.AggSender, error) { + agglayerClient := agglayer.NewAggLayerClient(cfg.AggLayerUrl) + + return aggsender.New(ctx, cfg, agglayerClient, l2Syncer, l2Client) +} + func createAggregator(ctx context.Context, c config.Config, runMigrations bool) *aggregator.Aggregator { // Migrations if runMigrations { @@ -618,6 +640,7 @@ func runBridgeSyncL1IfNeeded( cfg.WaitForNewBlocksPeriod.Duration, cfg.RetryAfterErrorPeriod.Duration, cfg.MaxRetryAttemptsAfterError, + cfg.OriginNetwork, ) if err != nil { log.Fatalf("error creating bridgeSyncL1: %s", err) @@ -649,6 +672,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 259b2a8dc..82c985640 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" ) @@ -29,6 +34,13 @@ func Uint32ToBytes(num uint32) []byte { return key } +// Uint8ToBytes converts a uint8 to a byte slice +func Uint8ToBytes(num uint8) []byte { + key := make([]byte, 1) + key[0] = num + return key +} + // BytesToUint32 converts a byte slice to a uint32 func BytesToUint32(bytes []byte) uint32 { return binary.BigEndian.Uint32(bytes) @@ -77,3 +89,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/config/config.go b/config/config.go index 76abbf203..6c462d932 100644 --- a/config/config.go +++ b/config/config.go @@ -9,6 +9,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" @@ -76,7 +77,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 @@ -107,6 +107,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 } // Default parses the default configuration values. diff --git a/config/default.go b/config/default.go index 0cd33d798..ec2fda1dd 100644 --- a/config/default.go +++ b/config/default.go @@ -248,4 +248,10 @@ RetryAfterErrorPeriod = "1s" MaxRetryAttemptsAfterError = -1 WaitForNewBlocksPeriod = "1s" DownloadBufferSize = 100 + +[AggSender] +DBPath = "/tmp/aggsender" +AggLayerURL = "http://zkevm-agglayer" +SequencerPrivateKey = {Path = "/pk/sequencer.keystore", Password = "testonly"} +CertificateSendInterval = "1m" ` diff --git a/l1bridge2infoindexsync/e2e_test.go b/l1bridge2infoindexsync/e2e_test.go index 2aa8e38f5..204fe31e0 100644 --- a/l1bridge2infoindexsync/e2e_test.go +++ b/l1bridge2infoindexsync/e2e_test.go @@ -139,7 +139,7 @@ func TestE2E(t *testing.T) { require.NoError(t, rd.Start(ctx)) testClient := helpers.TestClient{ClientRenamed: client.Client()} - bridgeSync, err := bridgesync.NewL1(ctx, dbPathBridgeSync, bridgeAddr, 10, etherman.LatestBlock, rd, testClient, 0, time.Millisecond*10, 0, 0) + bridgeSync, err := bridgesync.NewL1(ctx, dbPathBridgeSync, bridgeAddr, 10, etherman.LatestBlock, rd, testClient, 0, time.Millisecond*10, 0, 0, 1) require.NoError(t, err) go bridgeSync.Start(ctx) diff --git a/tree/tree.go b/tree/tree.go index 77c0e4521..09f0839ab 100644 --- a/tree/tree.go +++ b/tree/tree.go @@ -8,6 +8,7 @@ import ( dbCommon "github.com/0xPolygon/cdk/common" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/ledgerwatch/erigon-lib/kv" "golang.org/x/crypto/sha3" ) @@ -298,3 +299,19 @@ func (t *Tree) GetLeaf(ctx context.Context, index uint32, root common.Hash) (com return currentNodeHash, nil } + +// CalculateRoot calculates the Merkle Root based on the leaf and proof of inclusion +func CalculateRoot(leafHash common.Hash, proof [DefaultHeight]common.Hash, index uint32) common.Hash { + node := leafHash + + // Compute the Merkle root + for height := uint8(0); height < 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 +}