From a563026aa28f3a389130cdfbbe34255ad4307269 Mon Sep 17 00:00:00 2001 From: Vui-Chee Date: Wed, 20 Nov 2024 09:36:37 +0800 Subject: [PATCH 1/9] Initial port of signer code from xlayer-node --- etherman/etherman_xlayer.go | 526 ++++++++++++++++++++++++++++++++++++ etherman/mock_etherman.go | 87 ++++++ signer/Dockerfile | 19 ++ signer/Makefile | 52 ++++ signer/config/config.go | 101 +++++++ signer/config/default.go | 21 ++ signer/main.go | 84 ++++++ signer/service/service.go | 317 ++++++++++++++++++++++ signer/service/type.go | 83 ++++++ signer/signer.config.toml | 17 ++ 10 files changed, 1307 insertions(+) create mode 100644 etherman/etherman_xlayer.go create mode 100644 etherman/mock_etherman.go create mode 100644 signer/Dockerfile create mode 100644 signer/Makefile create mode 100644 signer/config/config.go create mode 100644 signer/config/default.go create mode 100644 signer/main.go create mode 100644 signer/service/service.go create mode 100644 signer/service/type.go create mode 100644 signer/signer.config.toml diff --git a/etherman/etherman_xlayer.go b/etherman/etherman_xlayer.go new file mode 100644 index 00000000..535afa98 --- /dev/null +++ b/etherman/etherman_xlayer.go @@ -0,0 +1,526 @@ +package etherman + +import ( + "context" + "crypto/ecdsa" + "errors" + "fmt" + "math/big" + "os" + "path/filepath" + "time" + + "github.com/0xPolygon/cdk-contracts-tooling/contracts/banana/idataavailabilityprotocol" + polygonzkevm "github.com/0xPolygon/cdk-contracts-tooling/contracts/banana/polygonvalidiumetrog" + cdkcommon "github.com/0xPolygon/cdk/common" + "github.com/0xPolygon/cdk/etherman/config" + "github.com/0xPolygon/cdk/etherman/contracts" + "github.com/0xPolygon/cdk/log" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/keystore" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/rpc" +) + +// EventOrder is the type used to identify the events order +type EventOrder string + +const ( + // GlobalExitRootsOrder identifies a GlobalExitRoot event + GlobalExitRootsOrder EventOrder = "GlobalExitRoots" + // L1InfoTreeOrder identifies a L1InTree event + L1InfoTreeOrder EventOrder = "L1InfoTreeOrder" + // SequenceBatchesOrder identifies a VerifyBatch event + SequenceBatchesOrder EventOrder = "SequenceBatches" + // ForcedBatchesOrder identifies a ForcedBatches event + ForcedBatchesOrder EventOrder = "ForcedBatches" + // TrustedVerifyBatchOrder identifies a TrustedVerifyBatch event + TrustedVerifyBatchOrder EventOrder = "TrustedVerifyBatch" + // VerifyBatchOrder identifies a VerifyBatch event + VerifyBatchOrder EventOrder = "VerifyBatch" + // SequenceForceBatchesOrder identifies a SequenceForceBatches event + SequenceForceBatchesOrder EventOrder = "SequenceForceBatches" + // ForkIDsOrder identifies an updateZkevmVersion event + ForkIDsOrder EventOrder = "forkIDs" +) + +type ethereumClient interface { + ethereum.ChainReader + ethereum.ChainStateReader + ethereum.ContractCaller + ethereum.GasEstimator + ethereum.GasPricer + ethereum.GasPricer1559 + ethereum.LogFilterer + ethereum.TransactionReader + ethereum.TransactionSender + + bind.DeployBackend +} + +// L1Config represents the configuration of the network used in L1 +type L1Config struct { + // Chain ID of the L1 network + L1ChainID uint64 `json:"chainId" mapstructure:"ChainID"` + // ZkEVMAddr Address of the L1 contract polygonZkEVMAddress + ZkEVMAddr common.Address `json:"polygonZkEVMAddress" mapstructure:"ZkEVMAddr"` + // RollupManagerAddr Address of the L1 contract + RollupManagerAddr common.Address `json:"polygonRollupManagerAddress" mapstructure:"RollupManagerAddr"` + // PolAddr Address of the L1 Pol token Contract + PolAddr common.Address `json:"polTokenAddress" mapstructure:"PolAddr"` + // GlobalExitRootManagerAddr Address of the L1 GlobalExitRootManager contract + GlobalExitRootManagerAddr common.Address `json:"polygonZkEVMGlobalExitRootAddress" mapstructure:"GlobalExitRootManagerAddr"` //nolint:lll +} + +// Client is a simple implementation of EtherMan. +type Client struct { + EthClient ethereumClient + DAProtocol *idataavailabilityprotocol.Idataavailabilityprotocol + + Contracts *contracts.Contracts + RollupID uint32 + + l1Cfg config.L1Config + cfg config.Config + auth map[common.Address]bind.TransactOpts // empty in case of read-only client + + // X Layer fields + ZkEVM *polygonzkevm.Polygonvalidiumetrog +} + +// NewClient creates a new etherman. +func NewClient(cfg config.Config, l1Config config.L1Config, commonConfig cdkcommon.Config) (*Client, error) { + log.Warnf("ETHMAN CONFIG = %s", cfg.EthermanConfig.URL) + // Connect to ethereum node + ethClient, err := ethclient.Dial(cfg.EthermanConfig.URL) + if err != nil { + log.Errorf("error connecting to %s: %+v", cfg.EthermanConfig.URL, err) + + return nil, err + } + zkevm, err := polygonzkevm.NewPolygonvalidiumetrog(l1Config.ZkEVMAddr, ethClient) + if err != nil { + log.Errorf("error creating Polygonzkevm client (%s). Error: %w", l1Config.ZkEVMAddr.String(), err) + return nil, err + } + L1chainID, err := ethClient.ChainID(context.Background()) + if err != nil { + log.Errorf("error getting L1chainID from %s: %+v", cfg.EthermanConfig.URL, err) + + return nil, err + } + log.Infof("L1ChainID: %d", L1chainID.Uint64()) + contracts, err := contracts.NewContracts(l1Config, ethClient) + if err != nil { + return nil, err + } + log.Info(contracts.String()) + // Get RollupID + rollupID, err := contracts.Banana.RollupManager.RollupAddressToID(&bind.CallOpts{Pending: false}, l1Config.ZkEVMAddr) + if err != nil { + log.Errorf("error getting rollupID from %s : %+v", contracts.Banana.RollupManager.String(), err) + + return nil, err + } + if rollupID == 0 { + return nil, errors.New( + "rollupID is 0, is not a valid value. Check that rollup Address is correct " + + l1Config.ZkEVMAddr.String(), + ) + } + log.Infof("rollupID: %d (obtenied from SMC: %s )", rollupID, contracts.Banana.RollupManager.String()) + + client := &Client{ + EthClient: ethClient, + Contracts: contracts, + + RollupID: rollupID, + l1Cfg: l1Config, + cfg: cfg, + auth: map[common.Address]bind.TransactOpts{}, + + ZkEVM: zkevm, + } + + if commonConfig.IsValidiumMode { + dapAddr, err := contracts.Banana.Rollup.DataAvailabilityProtocol(&bind.CallOpts{Pending: false}) + if err != nil { + return nil, err + } + + client.DAProtocol, err = idataavailabilityprotocol.NewIdataavailabilityprotocol(dapAddr, ethClient) + if err != nil { + return nil, err + } + } + + return client, nil +} + +// Order contains the event order to let the synchronizer store the information following this order. +type Order struct { + Name EventOrder + Pos int +} + +// WaitTxToBeMined waits for an L1 tx to be mined. It will return error if the tx is reverted or timeout is exceeded +func (etherMan *Client) WaitTxToBeMined( + ctx context.Context, tx *types.Transaction, timeout time.Duration, +) (bool, error) { + // err := operations.WaitTxToBeMined(ctx, etherMan.EthClient, tx, timeout) + // if errors.Is(err, context.DeadlineExceeded) { + // return false, nil + // } + // if err != nil { + // return false, err + // } + return true, nil +} + +// GetSendSequenceFee get super/trusted sequencer fee +func (etherMan *Client) GetSendSequenceFee(numBatches uint64) (*big.Int, error) { + f, err := etherMan.Contracts.Banana.RollupManager.GetBatchFee(&bind.CallOpts{Pending: false}) + if err != nil { + return nil, err + } + fee := new(big.Int).Mul(f, new(big.Int).SetUint64(numBatches)) + + return fee, nil +} + +// TrustedSequencer gets trusted sequencer address +func (etherMan *Client) TrustedSequencer() (common.Address, error) { + return etherMan.Contracts.Banana.Rollup.TrustedSequencer(&bind.CallOpts{Pending: false}) +} + +// HeaderByNumber returns a block header from the current canonical chain. If number is +// nil, the latest known header is returned. +func (etherMan *Client) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { + return etherMan.EthClient.HeaderByNumber(ctx, number) +} + +// EthBlockByNumber function retrieves the ethereum block information by ethereum block number. +func (etherMan *Client) EthBlockByNumber(ctx context.Context, blockNumber uint64) (*types.Block, error) { + block, err := etherMan.EthClient.BlockByNumber(ctx, new(big.Int).SetUint64(blockNumber)) + if err != nil { + if errors.Is(err, ethereum.NotFound) || err.Error() == "block does not exist in blockchain" { + return nil, ErrNotFound + } + + return nil, err + } + + return block, nil +} + +// GetLatestBatchNumber function allows to retrieve the latest proposed batch in the smc +func (etherMan *Client) GetLatestBatchNumber() (uint64, error) { + rollupData, err := etherMan.Contracts.Banana.RollupManager.RollupIDToRollupData( + &bind.CallOpts{Pending: false}, + etherMan.RollupID, + ) + if err != nil { + return 0, err + } + + return rollupData.LastBatchSequenced, nil +} + +// GetLatestBlockNumber gets the latest block number from the ethereum +func (etherMan *Client) GetLatestBlockNumber(ctx context.Context) (uint64, error) { + return etherMan.getBlockNumber(ctx, rpc.LatestBlockNumber) +} + +// GetSafeBlockNumber gets the safe block number from the ethereum +func (etherMan *Client) GetSafeBlockNumber(ctx context.Context) (uint64, error) { + return etherMan.getBlockNumber(ctx, rpc.SafeBlockNumber) +} + +// GetFinalizedBlockNumber gets the Finalized block number from the ethereum +func (etherMan *Client) GetFinalizedBlockNumber(ctx context.Context) (uint64, error) { + return etherMan.getBlockNumber(ctx, rpc.FinalizedBlockNumber) +} + +// getBlockNumber gets the block header by the provided block number from the ethereum +func (etherMan *Client) getBlockNumber(ctx context.Context, blockNumber rpc.BlockNumber) (uint64, error) { + header, err := etherMan.EthClient.HeaderByNumber(ctx, big.NewInt(int64(blockNumber))) + if err != nil || header == nil { + return 0, err + } + + return header.Number.Uint64(), nil +} + +// GetLatestBlockTimestamp gets the latest block timestamp from the ethereum +func (etherMan *Client) GetLatestBlockTimestamp(ctx context.Context) (uint64, error) { + header, err := etherMan.EthClient.HeaderByNumber(ctx, nil) + if err != nil || header == nil { + return 0, err + } + + return header.Time, nil +} + +// GetLatestVerifiedBatchNum gets latest verified batch from ethereum +func (etherMan *Client) GetLatestVerifiedBatchNum() (uint64, error) { + rollupData, err := etherMan.Contracts.Banana.RollupManager.RollupIDToRollupData( + &bind.CallOpts{Pending: false}, + etherMan.RollupID, + ) + if err != nil { + return 0, err + } + + return rollupData.LastVerifiedBatch, nil +} + +// GetTx function get ethereum tx +func (etherMan *Client) GetTx(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) { + return etherMan.EthClient.TransactionByHash(ctx, txHash) +} + +// GetTxReceipt function gets ethereum tx receipt +func (etherMan *Client) GetTxReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { + return etherMan.EthClient.TransactionReceipt(ctx, txHash) +} + +// GetTrustedSequencerURL Gets the trusted sequencer url from rollup smc +func (etherMan *Client) GetTrustedSequencerURL() (string, error) { + return etherMan.Contracts.Banana.Rollup.TrustedSequencerURL(&bind.CallOpts{Pending: false}) +} + +// GetL2ChainID returns L2 Chain ID +func (etherMan *Client) GetL2ChainID() (uint64, error) { + rollupData, err := etherMan.Contracts.Banana.RollupManager.RollupIDToRollupData( + &bind.CallOpts{Pending: false}, + etherMan.RollupID, + ) + log.Debug("chainID read from rollupManager: ", rollupData.ChainID) + if err != nil { + log.Debug("error from rollupManager: ", err) + + return 0, err + } else if rollupData.ChainID == 0 { + return rollupData.ChainID, fmt.Errorf("error: chainID received is 0") + } + + return rollupData.ChainID, nil +} + +// SendTx sends a tx to L1 +func (etherMan *Client) SendTx(ctx context.Context, tx *types.Transaction) error { + return etherMan.EthClient.SendTransaction(ctx, tx) +} + +// CurrentNonce returns the current nonce for the provided account +func (etherMan *Client) CurrentNonce(ctx context.Context, account common.Address) (uint64, error) { + return etherMan.EthClient.NonceAt(ctx, account, nil) +} + +// EstimateGas returns the estimated gas for the tx +func (etherMan *Client) EstimateGas( + ctx context.Context, from common.Address, to *common.Address, value *big.Int, data []byte, +) (uint64, error) { + return etherMan.EthClient.EstimateGas(ctx, ethereum.CallMsg{ + From: from, + To: to, + Value: value, + Data: data, + }) +} + +// CheckTxWasMined check if a tx was already mined +func (etherMan *Client) CheckTxWasMined(ctx context.Context, txHash common.Hash) (bool, *types.Receipt, error) { + receipt, err := etherMan.EthClient.TransactionReceipt(ctx, txHash) + if errors.Is(err, ethereum.NotFound) { + return false, nil, nil + } else if err != nil { + return false, nil, err + } + + return true, receipt, nil +} + +// SignTx tries to sign a transaction accordingly to the provided sender +func (etherMan *Client) SignTx( + ctx context.Context, sender common.Address, tx *types.Transaction, +) (*types.Transaction, error) { + auth, err := etherMan.getAuthByAddress(sender) + if errors.Is(err, ErrNotFound) { + return nil, ErrPrivateKeyNotFound + } + signedTx, err := auth.Signer(auth.From, tx) + if err != nil { + return nil, err + } + + return signedTx, nil +} + +// GetRevertMessage tries to get a revert message of a transaction +func (etherMan *Client) GetRevertMessage(ctx context.Context, tx *types.Transaction) (string, error) { + // if tx == nil { + // return "", nil + // } + + // receipt, err := etherMan.GetTxReceipt(ctx, tx.Hash()) + // if err != nil { + // return "", err + // } + + // if receipt.Status == types.ReceiptStatusFailed { + // revertMessage, err := operations.RevertReason(ctx, etherMan.EthClient, tx, receipt.BlockNumber) + // if err != nil { + // return "", err + // } + // return revertMessage, nil + // } + return "", nil +} + +// AddOrReplaceAuth adds an authorization or replace an existent one to the same account +func (etherMan *Client) AddOrReplaceAuth(auth bind.TransactOpts) error { + log.Infof("added or replaced authorization for address: %v", auth.From.String()) + etherMan.auth[auth.From] = auth + + return nil +} + +// LoadAuthFromKeyStore loads an authorization from a key store file +func (etherMan *Client) LoadAuthFromKeyStore(path, password string) (*bind.TransactOpts, *ecdsa.PrivateKey, error) { + auth, pk, err := newAuthFromKeystore(path, password, etherMan.l1Cfg.L1ChainID) + if err != nil { + return nil, nil, err + } + + log.Infof("loaded authorization for address: %v", auth.From.String()) + etherMan.auth[auth.From] = auth + + return &auth, pk, nil +} + +// newKeyFromKeystore creates an instance of a keystore key from a keystore file +func newKeyFromKeystore(path, password string) (*keystore.Key, error) { + if path == "" && password == "" { + return nil, nil + } + keystoreEncrypted, err := os.ReadFile(filepath.Clean(path)) + if err != nil { + return nil, err + } + log.Infof("decrypting key from: %v", path) + key, err := keystore.DecryptKey(keystoreEncrypted, password) + if err != nil { + return nil, err + } + + return key, nil +} + +// newAuthFromKeystore an authorization instance from a keystore file +func newAuthFromKeystore(path, password string, chainID uint64) (bind.TransactOpts, *ecdsa.PrivateKey, error) { + log.Infof("reading key from: %v", path) + key, err := newKeyFromKeystore(path, password) + if err != nil { + return bind.TransactOpts{}, nil, err + } + if key == nil { + return bind.TransactOpts{}, nil, nil + } + auth, err := bind.NewKeyedTransactorWithChainID(key.PrivateKey, new(big.Int).SetUint64(chainID)) + if err != nil { + return bind.TransactOpts{}, nil, err + } + + return *auth, key.PrivateKey, nil +} + +// getAuthByAddress tries to get an authorization from the authorizations map +func (etherMan *Client) getAuthByAddress(addr common.Address) (bind.TransactOpts, error) { + auth, found := etherMan.auth[addr] + if !found { + return bind.TransactOpts{}, ErrNotFound + } + + return auth, nil +} + +// GetLatestBlockHeader gets the latest block header from the ethereum +func (etherMan *Client) GetLatestBlockHeader(ctx context.Context) (*types.Header, error) { + header, err := etherMan.EthClient.HeaderByNumber(ctx, big.NewInt(int64(rpc.LatestBlockNumber))) + if err != nil || header == nil { + return nil, err + } + + return header, nil +} + +// GetDAProtocolAddr returns the address of the data availability protocol +func (etherMan *Client) GetDAProtocolAddr() (common.Address, error) { + return etherMan.Contracts.Banana.Rollup.DataAvailabilityProtocol(&bind.CallOpts{Pending: false}) +} + +// GetDAProtocolName returns the name of the data availability protocol +func (etherMan *Client) GetDAProtocolName() (string, error) { + return etherMan.DAProtocol.GetProcotolName(&bind.CallOpts{Pending: false}) +} + +// LastAccInputHash gets the last acc input hash from the SC +func (etherMan *Client) LastAccInputHash() (common.Hash, error) { + return etherMan.Contracts.Banana.Rollup.LastAccInputHash(&bind.CallOpts{Pending: false}) +} + +// GetL1InfoRoot gets the L1 info root from the SC +func (etherMan *Client) GetL1InfoRoot(indexL1InfoRoot uint32) (common.Hash, error) { + // Get lastL1InfoTreeRoot (if index==0 then root=0, no call is needed) + var ( + lastL1InfoTreeRoot common.Hash + err error + ) + + if indexL1InfoRoot > 0 { + lastL1InfoTreeRoot, err = etherMan.Contracts.Banana.GlobalExitRoot.L1InfoRootMap( + &bind.CallOpts{Pending: false}, + indexL1InfoRoot, + ) + if err != nil { + log.Errorf("error calling SC globalexitroot L1InfoLeafMap: %v", err) + } + } + + return lastL1InfoTreeRoot, err +} + +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ X Layer ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /// + +// LoadAuthFromKeyStoreXLayer loads an authorization from a key store file +func (etherMan *Client) LoadAuthFromKeyStoreXLayer(path, password string) (*bind.TransactOpts, *ecdsa.PrivateKey, error) { + auth, pk, err := newAuthFromKeystoreXLayer(path, password, etherMan.l1Cfg.L1ChainID) + if err != nil { + return nil, nil, err + } + + log.Infof("loaded authorization for address: %v", auth.From.String()) + etherMan.auth[auth.From] = auth + return &auth, pk, nil +} + +// newAuthFromKeystoreXLayer an authorization instance from a keystore file +func newAuthFromKeystoreXLayer(path, password string, chainID uint64) (bind.TransactOpts, *ecdsa.PrivateKey, error) { + log.Infof("reading key from: %v", path) + key, err := newKeyFromKeystore(path, password) + if err != nil { + return bind.TransactOpts{}, nil, err + } + if key == nil { + return bind.TransactOpts{}, nil, nil + } + auth, err := bind.NewKeyedTransactorWithChainID(key.PrivateKey, new(big.Int).SetUint64(chainID)) + if err != nil { + return bind.TransactOpts{}, nil, err + } + return *auth, key.PrivateKey, nil +} diff --git a/etherman/mock_etherman.go b/etherman/mock_etherman.go new file mode 100644 index 00000000..75908cb3 --- /dev/null +++ b/etherman/mock_etherman.go @@ -0,0 +1,87 @@ +package etherman + +import ( + "errors" + "fmt" + "math/big" + + polygonzkevm "github.com/0xPolygon/cdk-contracts-tooling/contracts/banana/polygonvalidiumetrog" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +// BuildMockSequenceBatchesTxData builds a []bytes to be sent to the PoE SC method SequenceBatches. +func (etherMan *Client) BuildMockSequenceBatchesTxData(sender common.Address, + validiumBatchData []polygonzkevm.PolygonValidiumEtrogValidiumBatchData, + maxSequenceTimestamp uint64, + l2Coinbase common.Address, + dataAvailabilityMessage []byte, + l1InfoTreeLeafCount uint32, + expectedInputHash string, +) (to *common.Address, data []byte, err error) { + opts, err := etherMan.getAuthByAddress(sender) + if errors.Is(err, ErrNotFound) { + return nil, nil, fmt.Errorf("failed to build sequence batches, err: %w", ErrPrivateKeyNotFound) + } + opts.NoSend = true + // force nonce, gas limit and gas price to avoid querying it from the chain + opts.Nonce = big.NewInt(1) + opts.GasLimit = uint64(1) + opts.GasPrice = big.NewInt(1) + + tx, err := etherMan.sequenceMockBatches( + opts, + validiumBatchData, + maxSequenceTimestamp, + l2Coinbase, + dataAvailabilityMessage, + l1InfoTreeLeafCount, + expectedInputHash, + ) + if err != nil { + return nil, nil, err + } + + return tx.To(), tx.Data(), nil +} + +func (etherMan *Client) sequenceMockBatches(opts bind.TransactOpts, + validiumBatchData []polygonzkevm.PolygonValidiumEtrogValidiumBatchData, + maxSequenceTimestamp uint64, + l2Coinbase common.Address, + dataAvailabilityMessage []byte, + l1InfoTreeLeafCount uint32, + expectedInputHash string, +) (*types.Transaction, error) { + var tx *types.Transaction + var err error + + var finalInputHash = [32]byte{} + // Request will a hex string beginning with "0x...", so strip first 2 chars. + for i, bb := range common.Hex2Bytes(expectedInputHash[2:]) { + finalInputHash[i] = bb + } + + tx, err = etherMan.ZkEVM.SequenceBatchesValidium( + &opts, + validiumBatchData, + l1InfoTreeLeafCount, + maxSequenceTimestamp, + finalInputHash, + l2Coinbase, + dataAvailabilityMessage, + ) + + if err != nil { + if parsedErr, ok := TryParseError(err); ok { + err = parsedErr + } + err = fmt.Errorf( + "error sequencing batches: %w, dataAvailabilityMessage: %s", + err, common.Bytes2Hex(dataAvailabilityMessage), + ) + } + + return tx, err +} diff --git a/signer/Dockerfile b/signer/Dockerfile new file mode 100644 index 00000000..277bd7db --- /dev/null +++ b/signer/Dockerfile @@ -0,0 +1,19 @@ +# CONTAINER FOR BUILDING BINARY +FROM golang:1.22 AS build + +# INSTALL DEPENDENCIES +RUN go install github.com/gobuffalo/packr/v2/packr2@v2.8.3 +COPY go.mod go.sum /src/ +RUN cd /src && go mod download + +# BUILD BINARY +COPY . /src +RUN cd /src/db && packr2 +RUN cd /src/signer && make build + +# CONTAINER FOR RUNNING BINARY +FROM alpine:3.18.0 +COPY --from=build /src/signer/dist/xlayer-signer /app/xlayer-signer + +EXPOSE 7001 +CMD ["/bin/sh", "-c", "/app/xlayer-signer"] diff --git a/signer/Makefile b/signer/Makefile new file mode 100644 index 00000000..3b0efec7 --- /dev/null +++ b/signer/Makefile @@ -0,0 +1,52 @@ +ARCH := $(shell arch) + +ifeq ($(ARCH),x86_64) + ARCH = amd64 +else + ifeq ($(ARCH),aarch64) + ARCH = arm64 + endif +endif +GOBASE := $(shell pwd) +GOBIN := $(GOBASE)/dist +GOENVVARS := GOBIN=$(GOBIN) CGO_ENABLED=0 GOOS=linux GOARCH=$(ARCH) +GOBINARY := xlayer-signer +GOCMD := $(GOBASE) + +LDFLAGS += -X 'github.com/0xPolygonHermez/zkevm-node.Version=$(VERSION)' +LDFLAGS += -X 'github.com/0xPolygonHermez/zkevm-node.GitRev=$(GITREV)' +LDFLAGS += -X 'github.com/0xPolygonHermez/zkevm-node.GitBranch=$(GITBRANCH)' +LDFLAGS += -X 'github.com/0xPolygonHermez/zkevm-node.BuildDate=$(DATE)' + +# Check dependencies +# Check for Go +.PHONY: check-go +check-go: + @which go > /dev/null || (echo "Error: Go is not installed" && exit 1) + +# Targets that require the checks +http: check-go + +arguments := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS)) + +.PHONY: http +http: ## Runs the signer server + go run main.go http -cfg config/signer.config.toml + +.PHONY: build +build: ## Builds the binary locally into ./dist + $(GOENVVARS) go build -ldflags "all=$(LDFLAGS)" -o $(GOBIN)/$(GOBINARY) $(GOCMD) + +## Help display. +## Pulls comments from beside commands and prints a nicely formatted +## display with the commands and their usage information. +.DEFAULT_GOAL := help + +.PHONY: help +help: ## Prints this help + @grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) \ + | sort \ + | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +.DEFAULT: + @echo "" diff --git a/signer/config/config.go b/signer/config/config.go new file mode 100644 index 00000000..ed1911da --- /dev/null +++ b/signer/config/config.go @@ -0,0 +1,101 @@ +package config + +import ( + "bytes" + "path/filepath" + "strings" + + "github.com/0xPolygon/cdk/config/types" + "github.com/0xPolygon/cdk/log" + "github.com/ethereum/go-ethereum/common" + "github.com/mitchellh/mapstructure" + "github.com/spf13/viper" + "github.com/urfave/cli/v2" +) + +const ( + // FlagCfg is the flag for cfg + FlagCfg = "cfg" +) + +// L1 is the default configuration values +type L1 struct { + ChainId uint64 `mapstructure:"ChainId"` + RPC string `mapstructure:"RPC"` + SeqPrivateKey types.KeystoreFileConfig `mapstructure:"SeqPrivateKey"` + AggPrivateKey types.KeystoreFileConfig `mapstructure:"AggPrivateKey"` + + PolygonMaticAddress common.Address `mapstructure:"PolygonMaticAddress"` + GlobalExitRootManagerAddr common.Address `mapstructure:"GlobalExitRootManagerAddress"` + DataCommitteeAddr common.Address `mapstructure:"DataCommitteeAddress"` + PolygonZkEVMAddress common.Address `mapstructure:"PolygonZkEVMAddress"` + PolygonRollupManagerAddress common.Address `mapstructure:"PolygonRollupManagerAddress"` +} + +// Config is the configuration for the tool +type Config struct { + Port int `mapstructure:"Port"` + L1 L1 `mapstructure:"L1"` + Log log.Config `mapstructure:"Log"` +} + +// Default parses the default configuration values. +func Default() (*Config, error) { + var cfg Config + viper.SetConfigType("toml") + + err := viper.ReadConfig(bytes.NewBuffer([]byte(DefaultValues))) + if err != nil { + return nil, err + } + err = viper.Unmarshal(&cfg, viper.DecodeHook(mapstructure.TextUnmarshallerHookFunc())) + if err != nil { + return nil, err + } + return &cfg, nil +} + +// Load parses the configuration values from the config file and environment variables +func Load(ctx *cli.Context) (*Config, error) { + cfg, err := Default() + if err != nil { + return nil, err + } + configFilePath := ctx.String(FlagCfg) + if configFilePath != "" { + dirName, fileName := filepath.Split(configFilePath) + + fileExtension := strings.TrimPrefix(filepath.Ext(fileName), ".") + fileNameWithoutExtension := strings.TrimSuffix(fileName, "."+fileExtension) + + viper.AddConfigPath(dirName) + viper.SetConfigName(fileNameWithoutExtension) + viper.SetConfigType(fileExtension) + } + viper.AutomaticEnv() + replacer := strings.NewReplacer(".", "_") + viper.SetEnvKeyReplacer(replacer) + viper.SetEnvPrefix("ZKEVM_DATA_STREAMER") + err = viper.ReadInConfig() + if err != nil { + _, ok := err.(viper.ConfigFileNotFoundError) + if ok { + log.Infof("config file not found") + } else { + log.Infof("error reading config file: ", err) + return nil, err + } + } + + decodeHooks := []viper.DecoderConfigOption{ + // this allows arrays to be decoded from env var separated by ",", example: MY_VAR="value1,value2,value3" + viper.DecodeHook(mapstructure.ComposeDecodeHookFunc(mapstructure.TextUnmarshallerHookFunc(), mapstructure.StringToSliceHookFunc(","))), + } + + err = viper.Unmarshal(&cfg, decodeHooks...) + if err != nil { + return nil, err + } + + return cfg, nil +} diff --git a/signer/config/default.go b/signer/config/default.go new file mode 100644 index 00000000..04ef01f4 --- /dev/null +++ b/signer/config/default.go @@ -0,0 +1,21 @@ +package config + +// DefaultValues is the default configuration +const DefaultValues = ` +Port = 8080 + +[L1] +ChainId = 11155111 +RPC = "https://rpc.ankr.com/eth_sepolia/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +PolygonZkEVMAddress = "0x812cB73e48841a6736bB94c65c56341817cE6304" +GlobalExitRootManagerAddr = "0x0e9Bb928351a50227ebFEC9782Db005Ba9b6C052" +DataCommitteeAddr = "0x246EcFCae4423631c9eE3A86DE37F77BCF27FAaE" +PolygonMaticAddress = "0xe223519d64C0A49e7C08303c2220251be6b70e1d" +SeqPrivateKey = {Path = "../../test/sequencer.keystore", Password = "testonly"} +AggPrivateKey = {Path = "../../test/aggregator.keystore", Password = "testonly"} + +[Log] +Environment = "development" +Level = "debug" +Outputs = ["stdout"] +` diff --git a/signer/main.go b/signer/main.go new file mode 100644 index 00000000..20313a5e --- /dev/null +++ b/signer/main.go @@ -0,0 +1,84 @@ +package main + +import ( + "fmt" + "net/http" + "os" + "time" + + "github.com/0xPolygon/cdk/log" + "github.com/0xPolygon/cdk/signer/config" + "github.com/0xPolygon/cdk/signer/service" + "github.com/urfave/cli/v2" +) + +const ( + appName = "xlayer-signer" //nolint:gosec + appUsage = "xlayer signer tool" + timeout = 5 * time.Second + httpGetPath = "/priapi/v1/assetonchain/ecology/querySignDataByOrderNo" + httpPostPath = "/priapi/v1/assetonchain/ecology/ecologyOperate" +) + +var ( + configFileFlag = cli.StringFlag{ + Name: config.FlagCfg, + Aliases: []string{"c"}, + Usage: "Configuration `FILE`", + DefaultText: "./config/signer.config.toml", + Required: true, + } +) + +// main is the entry point for the tool +func main() { + app := cli.NewApp() + app.Name = appName + app.Usage = appUsage + + app.Commands = []*cli.Command{ + { + Name: "http", + Aliases: []string{}, + Usage: "Generate stream file from scratch", + Action: HttpService, + Flags: []cli.Flag{ + &configFileFlag, + }, + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Errorf("Error: %v", err) + os.Exit(1) + } +} + +// HttpService is the entry point for the http service +func HttpService(cliCtx *cli.Context) error { + c, err := config.Load(cliCtx) + if err != nil { + log.Errorf("Error: %v", err) + os.Exit(1) + } + + log.Init(c.Log) + srv := service.NewServer(c, cliCtx.Context) + http.HandleFunc(httpGetPath, srv.GetSignDataByOrderNo) + http.HandleFunc(httpPostPath, srv.PostSignDataByOrderNo) + + log.Infof("Listen port:%v", c.Port) + + server := &http.Server{ + Addr: fmt.Sprintf(":%d", c.Port), + ReadHeaderTimeout: timeout, + } + + err = server.ListenAndServe() + if err != nil { + log.Errorf("Error:%v", err) + } + + return nil +} diff --git a/signer/service/service.go b/signer/service/service.go new file mode 100644 index 00000000..86fe13b3 --- /dev/null +++ b/signer/service/service.go @@ -0,0 +1,317 @@ +package service + +import ( + "context" + "crypto/ecdsa" + "encoding/json" + "fmt" + "io" + "math/big" + "net/http" + + polygonzkevm "github.com/0xPolygon/cdk-contracts-tooling/contracts/banana/polygonvalidiumetrog" + aggTypes "github.com/0xPolygon/cdk/aggregator/ethmantypes" + "github.com/0xPolygon/cdk/aggregator/prover" + cdkcommon "github.com/0xPolygon/cdk/common" + "github.com/0xPolygon/cdk/etherman" + ethConfig "github.com/0xPolygon/cdk/etherman/config" + "github.com/0xPolygon/cdk/hex" + "github.com/0xPolygon/cdk/log" + "github.com/0xPolygon/cdk/signer/config" + zkEthman "github.com/0xPolygon/zkevm-ethtx-manager/etherman" + "github.com/ethereum/go-ethereum/common" + ethTypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" +) + +const ( + maxGas = 5000000 + proofLen = 24 + forkIDChunkSize = 20000 + statusSuccess = 200 + operateTypeSeq = 1 + operateTypeAgg = 2 + codeSuccess = 0 + codeFail = 1 +) + +// Server is an API backend to handle RPC requests +type Server struct { + ethCfg ethConfig.Config + l1Cfg ethConfig.L1Config + ctx context.Context + + seqPrivateKey *ecdsa.PrivateKey + aggPrivateKey *ecdsa.PrivateKey + ethClient *etherman.Client + + seqAddress common.Address + aggAddress common.Address + + result map[string]string +} + +// NewServer creates a new server +func NewServer(cfg *config.Config, ctx context.Context) *Server { + srv := &Server{ + ctx: ctx, + } + + srv.ethCfg = ethConfig.Config{ + URL: cfg.L1.RPC, + EthermanConfig: zkEthman.Config{ + URL: cfg.L1.RPC, // New ethman client "dials" this addr + }, + ForkIDChunkSize: forkIDChunkSize, + } + + srv.l1Cfg = ethConfig.L1Config{ + L1ChainID: cfg.L1.ChainId, + ZkEVMAddr: cfg.L1.PolygonZkEVMAddress, + PolAddr: cfg.L1.PolygonMaticAddress, + GlobalExitRootManagerAddr: cfg.L1.GlobalExitRootManagerAddr, + RollupManagerAddr: cfg.L1.PolygonRollupManagerAddress, + } + + var err error + srv.ethClient, err = etherman.NewClient(srv.ethCfg, srv.l1Cfg, cdkcommon.Config{}) + log.Infof("url: %v, l1 chain id: %v, zkevm addr: %v, rollup manager addr: %v", srv.ethCfg.URL, srv.l1Cfg.L1ChainID, srv.l1Cfg.ZkEVMAddr, srv.l1Cfg.RollupManagerAddr) + if err != nil { + log.Fatal("error creating etherman client. Error: %v", err) + } + + _, srv.seqPrivateKey, err = srv.ethClient.LoadAuthFromKeyStoreXLayer(cfg.L1.SeqPrivateKey.Path, cfg.L1.SeqPrivateKey.Password) + if err != nil { + log.Fatal("error loading sequencer private key. Error: %v", err) + } + + srv.seqAddress = crypto.PubkeyToAddress(srv.seqPrivateKey.PublicKey) + log.Infof("Sequencer address: %s", srv.seqAddress.String()) + + _, srv.aggPrivateKey, err = srv.ethClient.LoadAuthFromKeyStoreXLayer(cfg.L1.AggPrivateKey.Path, cfg.L1.AggPrivateKey.Password) + if err != nil { + log.Fatal("error loading aggregator private key. Error: %v", err) + } + + srv.aggAddress = crypto.PubkeyToAddress(srv.aggPrivateKey.PublicKey) + log.Infof("Agg address: %s", srv.aggAddress.String()) + + srv.result = make(map[string]string) + + return srv +} + +// Response is the response struct +func sendJSONResponse(w http.ResponseWriter, data interface{}) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(data) //nolint:errcheck +} + +// PostSignDataByOrderNo is the handler for the /priapi/v1/assetonchain/ecology/ecologyOperate endpoint +func (s *Server) PostSignDataByOrderNo(w http.ResponseWriter, r *http.Request) { + log.Infof("PostSignDataByOrderNo start") + response := Response{Code: codeFail, Data: "", DetailMsg: "", Msg: "", Status: statusSuccess, Success: false} + body, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, "Error reading request body", http.StatusBadRequest) + response.DetailMsg = err.Error() + sendJSONResponse(w, response) + return + } + + var requestData Request + err = json.Unmarshal(body, &requestData) + if err != nil { + http.Error(w, "Error decoding JSON", http.StatusBadRequest) + response.DetailMsg = err.Error() + sendJSONResponse(w, response) + return + } + + log.Infof("Request:%v", requestData.String()) + + if value, ok := s.result[requestData.RefOrderId]; ok { + response.DetailMsg = "already exist" + log.Infof("already exist, key:%v, value:%v", requestData.RefOrderId, value) + sendJSONResponse(w, response) + return + } + + if requestData.OperateType == operateTypeSeq { + err, data := s.signSeq(requestData) + if err != nil { + response.DetailMsg = err.Error() + log.Errorf("error signSeq: %v", err) + } else { + response.Code = codeSuccess + response.Success = true + s.result[requestData.RefOrderId] = data + } + } else if requestData.OperateType == operateTypeAgg { + err, data := s.signAgg(requestData) + if err != nil { + response.DetailMsg = err.Error() + log.Errorf("error signAgg: %v", err) + } else { + response.Code = codeSuccess + response.Success = true + s.result[requestData.RefOrderId] = data + } + } else { + log.Error("error operateType") + response.DetailMsg = "error operateType" + } + sendJSONResponse(w, response) +} + +// signSeq is the handler for the /priapi/v1/assetonchain/ecology/ecologyOperate endpoint +func (s *Server) signSeq(requestData Request) (error, string) { + var seqData SeqData + err := json.Unmarshal([]byte(requestData.OtherInfo), &seqData) + if err != nil { + log.Errorf("error Unmarshal: %v", err) + return err, "" + } + + var validiumBatchData []polygonzkevm.PolygonValidiumEtrogValidiumBatchData + for _, batch := range seqData.Batches { + validiumBatchData = append(validiumBatchData, polygonzkevm.PolygonValidiumEtrogValidiumBatchData{ + TransactionsHash: common.HexToHash(batch.TransactionsHash), + ForcedGlobalExitRoot: common.HexToHash(batch.ForcedGlobalExitRoot), + ForcedTimestamp: batch.ForcedTimestamp, + ForcedBlockHashL1: common.HexToHash(batch.ForcedBlockHashL1), + }) + } + maxSequenceTimestamp := seqData.MaxSequenceTimestamp + l2Coinbase := seqData.L2Coinbase + l1InfoTreeLeafCount := seqData.L1InfoTreeLeafCount + expectedInputHash := seqData.ExpectedFinalAccInputHash + + var dataAvailabilityMessage []byte + dataAvailabilityMessage, err = hex.DecodeHex(seqData.DataAvailabilityMessage) + if err != nil { + dataAvailabilityMessage = nil + } + + _, data, err := s.ethClient.BuildMockSequenceBatchesTxData( + s.seqAddress, + validiumBatchData, + maxSequenceTimestamp, + l2Coinbase, + dataAvailabilityMessage, + l1InfoTreeLeafCount, + expectedInputHash, + ) + if err != nil { + log.Errorf("error BuildSequenceBatchesTxData: %v", err) + return err, "" + } + to := &seqData.ContractAddress + + // return s.getTxData(s.seqAddress, to, data) + return s.getLegacyTxData(s.seqAddress, to, data, seqData.Nonce, seqData.GasLimit, seqData.GasPrice) +} + +// signAgg is the handler for the /priapi/v1/assetonchain/ecology/ecologyOperate endpoint +func (s *Server) signAgg(requestData Request) (error, string) { + var aggData AggData + err := json.Unmarshal([]byte(requestData.OtherInfo), &aggData) + if err != nil { + log.Errorf("error Unmarshal: %v", err) + return err, "" + } + + newLocal, err := hex.DecodeHex(aggData.NewLocalExitRoot) + if err != nil { + log.Errorf("error DecodeHex: %v", err) + return err, "" + } + + newStateRoot, err := hex.DecodeHex(aggData.NewStateRoot) + if err != nil { + log.Errorf("error DecodeHex: %v", err) + return err, "" + } + + if len(aggData.Proof) != proofLen { + log.Errorf("agg data len is not 24") + return fmt.Errorf("agg proof len is not 24"), "" + } + proofStr := "0x" + for _, v := range aggData.Proof { + proofStr += v + } + + log.Infof("proofStr: %v", proofStr) + + proof := &prover.FinalProof{ + Proof: proofStr, + } + + var inputs = &aggTypes.FinalProofInputs{ + NewLocalExitRoot: newLocal, + NewStateRoot: newStateRoot, + FinalProof: proof, + } + + _, data, err := s.ethClient.BuildTrustedVerifyBatchesTxData(aggData.InitNumBatch, aggData.FinalNewBatch, inputs, aggData.Beneficiary) + if err != nil { + log.Errorf("error BuildTrustedVerifyBatchesTxData: %v", err) + return err, "" + } + + to := &aggData.ContractAddress + // return s.getTxData(s.aggAddress, to, data) + return s.getLegacyTxData(s.aggAddress, to, data, aggData.Nonce, aggData.GasLimit, aggData.GasPrice) +} + +func (s *Server) getLegacyTxData(from common.Address, to *common.Address, data []byte, nonce, gasLimit uint64, gasPrice string) (error, string) { + bigFloatGasPrice := new(big.Float) + bigFloatGasPrice, _ = bigFloatGasPrice.SetString(gasPrice) + result := new(big.Float).Mul(bigFloatGasPrice, new(big.Float).SetInt(big.NewInt(params.Ether))) + gp := new(big.Int) + result.Int(gp) + + tx := ethTypes.NewTx(ðTypes.LegacyTx{ + Nonce: nonce, + GasPrice: gp, + Gas: gasLimit, + To: to, + Data: data, + }) + + signedTx, err := s.ethClient.SignTx(s.ctx, from, tx) + if err != nil { + log.Errorf("error SignTx: %v", err) + return err, "" + } + + txBin, err := signedTx.MarshalBinary() + if err != nil { + log.Errorf("error MarshalBinary: %v", err) + return err, "" + } + + log.Infof("TxHash: %v", signedTx.Hash().String()) + return nil, hex.EncodeToString(txBin) +} + +// GetSignDataByOrderNo is the handler for the /priapi/v1/assetonchain/ecology/ecologyOperate endpoint +func (s *Server) GetSignDataByOrderNo(w http.ResponseWriter, r *http.Request) { + response := Response{Code: codeFail, Data: "", DetailMsg: "", Msg: "", Status: statusSuccess, Success: false} + + orderID := r.URL.Query().Get("orderId") + projectSymbol := r.URL.Query().Get("projectSymbol") + log.Infof("GetSignDataByOrderNo: %v,%v", orderID, projectSymbol) + if value, ok := s.result[orderID]; ok { + response.Code = codeSuccess + response.Success = true + response.Data = value + } else { + response.DetailMsg = "not exist" + } + + sendJSONResponse(w, response) +} diff --git a/signer/service/type.go b/signer/service/type.go new file mode 100644 index 00000000..4cf62eaa --- /dev/null +++ b/signer/service/type.go @@ -0,0 +1,83 @@ +package service + +import ( + "bytes" + "encoding/json" + "fmt" + + "github.com/ethereum/go-ethereum/common" +) + +// Request is the request body +type Request struct { + OperateType int `json:"operateType"` + OperateAddress string `json:"operateAddress"` + Symbol int `json:"symbol"` + ProjectSymbol int `json:"projectSymbol"` + RefOrderId string `json:"refOrderId"` + OperateSymbol int `json:"operateSymbol"` + OperateAmount int `json:"operateAmount"` + SysFrom int `json:"sysFrom"` + OtherInfo string `json:"otherInfo"` +} + +func (args *Request) String() string { + b, err := json.Marshal(*args) + if err != nil { + return fmt.Sprintf("%+v", *args) + } + var out bytes.Buffer + err = json.Indent(&out, b, "", " ") + if err != nil { + return fmt.Sprintf("%+v", *args) + } + return out.String() +} + +// SeqData is the data for sequence operation +type SeqData struct { + Batches []Batch `json:"batches"` + MaxSequenceTimestamp uint64 `json:"maxSequenceTimestamp"` + L2Coinbase common.Address `json:"l2Coinbase"` + DataAvailabilityMessage string `json:"dataAvailabilityMessage"` + ContractAddress common.Address `json:"contractAddress"` + GasLimit uint64 `json:"gasLimit"` + GasPrice string `json:"gasPrice"` + Nonce uint64 `json:"nonce"` + L1InfoTreeLeafCount uint32 `json:"l1InfoTreeLeafCount"` + ExpectedFinalAccInputHash string `json:"expectedFinalAccInputHash"` +} + +// Batch is the data for batch operation +type Batch struct { + TransactionsHash string `json:"transactionsHash"` + ForcedGlobalExitRoot string `json:"forcedGlobalExitRoot"` + ForcedTimestamp uint64 `json:"forcedTimestamp"` + ForcedBlockHashL1 string `json:"forcedBlockHashL1"` +} + +// AggData is the data for aggregate operation +type AggData struct { + RollupId uint64 `json:"rollupID"` + NewLocalExitRoot string `json:"newLocalExitRoot"` + NewStateRoot string `json:"newStateRoot"` + FinalNewBatch uint64 `json:"finalNewBatch"` + Proof []string `json:"proof"` + InitNumBatch uint64 `json:"initNumBatch"` + PendingStateNum int `json:"pendingStateNum"` + ContractAddress common.Address `json:"contractAddress"` + GasLimit uint64 `json:"gasLimit"` + GasPrice string `json:"gasPrice"` + Nonce uint64 `json:"nonce"` + Beneficiary common.Address `json:"beneficiary"` +} + +// Response is the response body +type Response struct { + Code int `json:"code"` + Data string `json:"data"` + DetailMsg string `json:"detailMsg"` + Msg string `json:"msg"` + Status int `json:"status"` + Success bool `json:"success"` +} diff --git a/signer/signer.config.toml b/signer/signer.config.toml new file mode 100644 index 00000000..2f32d195 --- /dev/null +++ b/signer/signer.config.toml @@ -0,0 +1,17 @@ +Port = 7001 + +[L1] +ChainId = 1337 +RPC = "http://xlayer-mock-l1-network:8545" +PolygonZkEVMAddress = "0xeb173087729c88a47568AF87b17C653039377BA6" +PolygonRollupManagerAddress = "0x2d42E2899662EFf08b13eeb65b154b904C7a1c8a" +GlobalExitRootManagerAddr = "0xB8cedD4B9eF683f0887C44a6E4312dC7A6e2fcdB" +DataCommitteeAddr = "0x3bFa19E4588962D1834B2e4007F150f4447Aa9fe" +PolygonMaticAddress = "0x5FbDB2315678afecb367f032d93F642f64180aa3" +SeqPrivateKey = {Path = "/pk/sequencer.keystore", Password = "testonly"} +AggPrivateKey = {Path = "/pk/aggregator.keystore", Password = "testonly"} + +[Log] +Environment = "development" +Level = "debug" +Outputs = ["stdout"] From 2d3547a8196cdca22471efae77ff5030921b18c5 Mon Sep 17 00:00:00 2001 From: Vui-Chee Date: Wed, 20 Nov 2024 09:48:12 +0800 Subject: [PATCH 2/9] Ignore existing etherman --- etherman/etherman.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/etherman/etherman.go b/etherman/etherman.go index 4f9e1c81..abb7f8fe 100644 --- a/etherman/etherman.go +++ b/etherman/etherman.go @@ -1,3 +1,5 @@ +//go:build ignore +// +build ignore package etherman import ( From 7bc218c4d46fa42df484af0e46616d8aa7c707d6 Mon Sep 17 00:00:00 2001 From: Vui-Chee Date: Wed, 20 Nov 2024 09:48:50 +0800 Subject: [PATCH 3/9] Add build and docker compose setup --- Makefile | 4 ++++ test/docker-compose.yml | 45 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/Makefile b/Makefile index 2adb0c40..1a9b84ff 100644 --- a/Makefile +++ b/Makefile @@ -70,6 +70,10 @@ build-go: build-docker: ## Builds a docker image with the cdk binary docker build -t cdk -f ./Dockerfile . +.PHONY: build-mock-signer +build-mock-signer-docker: + docker build --progress=plain -t xlayer-signer -f ./signer/Dockerfile . + .PHONY: build-docker-nc build-docker-nc: ## Builds a docker image with the cdk binary - but without build cache docker build --no-cache=true -t cdk -f ./Dockerfile . diff --git a/test/docker-compose.yml b/test/docker-compose.yml index a280d675..c2985b68 100644 --- a/test/docker-compose.yml +++ b/test/docker-compose.yml @@ -89,3 +89,48 @@ services: - "postgres" - "-N" - "500" + + xlayer-mock-l1-network: + container_name: xlayer-mock-l1-network + image: zjg555543/geth:fork13-v0.0.3 + ports: + - 8545:8545 + - 8546:8546 + command: + - "--http" + - "--http.api" + - "admin,eth,debug,miner,net,txpool,personal,web3" + - "--http.addr" + - "0.0.0.0" + - "--http.corsdomain" + - "*" + - "--http.vhosts" + - "*" + - "--ws" + - "--ws.origins" + - "*" + - "--ws.addr" + - "0.0.0.0" + - "--dev" + - "--dev.period" + - "1" + - "--datadir" + - "/geth_data" + - "--syncmode" + - "full" + - "--rpc.allow-unprotected-txs" + + xlayer-signer: + container_name: xlayer-signer + restart: unless-stopped + image: xlayer-signer:latest # assuming you build docker image using `make build-mock-signer-docker` + ports: + - 7001:7001 + volumes: + - ../signer/signer.config.toml:/app/config.toml + - ./sequencer.keystore:/pk/sequencer.keystore + - ./aggregator.keystore:/pk/aggregator.keystore + command: + - "/bin/sh" + - "-c" + - "/app/xlayer-signer http -cfg /app/config.toml" From 52fb26181c8047120e7b3f11de60c80e446d30e8 Mon Sep 17 00:00:00 2001 From: Vui-Chee Date: Wed, 20 Nov 2024 10:25:38 +0800 Subject: [PATCH 4/9] Fix lint attempt #1 --- etherman/etherman_xlayer.go | 7 ++-- signer/config/config.go | 10 +++--- signer/main.go | 8 ++--- signer/service/service.go | 68 +++++++++++++++++++++++-------------- signer/service/type.go | 4 +-- 5 files changed, 59 insertions(+), 38 deletions(-) diff --git a/etherman/etherman_xlayer.go b/etherman/etherman_xlayer.go index 535afa98..f48ccfec 100644 --- a/etherman/etherman_xlayer.go +++ b/etherman/etherman_xlayer.go @@ -93,7 +93,6 @@ type Client struct { // NewClient creates a new etherman. func NewClient(cfg config.Config, l1Config config.L1Config, commonConfig cdkcommon.Config) (*Client, error) { - log.Warnf("ETHMAN CONFIG = %s", cfg.EthermanConfig.URL) // Connect to ethereum node ethClient, err := ethclient.Dial(cfg.EthermanConfig.URL) if err != nil { @@ -497,7 +496,11 @@ func (etherMan *Client) GetL1InfoRoot(indexL1InfoRoot uint32) (common.Hash, erro /// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ X Layer ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /// // LoadAuthFromKeyStoreXLayer loads an authorization from a key store file -func (etherMan *Client) LoadAuthFromKeyStoreXLayer(path, password string) (*bind.TransactOpts, *ecdsa.PrivateKey, error) { +func (etherMan *Client) LoadAuthFromKeyStoreXLayer(path, password string) ( + *bind.TransactOpts, + *ecdsa.PrivateKey, + error, +) { auth, pk, err := newAuthFromKeystoreXLayer(path, password, etherMan.l1Cfg.L1ChainID) if err != nil { return nil, nil, err diff --git a/signer/config/config.go b/signer/config/config.go index ed1911da..3dc1d71e 100644 --- a/signer/config/config.go +++ b/signer/config/config.go @@ -2,6 +2,7 @@ package config import ( "bytes" + "errors" "path/filepath" "strings" @@ -20,7 +21,7 @@ const ( // L1 is the default configuration values type L1 struct { - ChainId uint64 `mapstructure:"ChainId"` + ChainID uint64 `mapstructure:"ChainId"` RPC string `mapstructure:"RPC"` SeqPrivateKey types.KeystoreFileConfig `mapstructure:"SeqPrivateKey"` AggPrivateKey types.KeystoreFileConfig `mapstructure:"AggPrivateKey"` @@ -78,8 +79,8 @@ func Load(ctx *cli.Context) (*Config, error) { viper.SetEnvPrefix("ZKEVM_DATA_STREAMER") err = viper.ReadInConfig() if err != nil { - _, ok := err.(viper.ConfigFileNotFoundError) - if ok { + var fileNotFoundErr *viper.ConfigFileNotFoundError + if errors.As(err, fileNotFoundErr) { log.Infof("config file not found") } else { log.Infof("error reading config file: ", err) @@ -89,7 +90,8 @@ func Load(ctx *cli.Context) (*Config, error) { decodeHooks := []viper.DecoderConfigOption{ // this allows arrays to be decoded from env var separated by ",", example: MY_VAR="value1,value2,value3" - viper.DecodeHook(mapstructure.ComposeDecodeHookFunc(mapstructure.TextUnmarshallerHookFunc(), mapstructure.StringToSliceHookFunc(","))), + viper.DecodeHook(mapstructure.ComposeDecodeHookFunc( + mapstructure.TextUnmarshallerHookFunc(), mapstructure.StringToSliceHookFunc(","))), } err = viper.Unmarshal(&cfg, decodeHooks...) diff --git a/signer/main.go b/signer/main.go index 20313a5e..cc0ee778 100644 --- a/signer/main.go +++ b/signer/main.go @@ -13,7 +13,7 @@ import ( ) const ( - appName = "xlayer-signer" //nolint:gosec + appName = "xlayer-signer" appUsage = "xlayer signer tool" timeout = 5 * time.Second httpGetPath = "/priapi/v1/assetonchain/ecology/querySignDataByOrderNo" @@ -41,7 +41,7 @@ func main() { Name: "http", Aliases: []string{}, Usage: "Generate stream file from scratch", - Action: HttpService, + Action: HTTPService, Flags: []cli.Flag{ &configFileFlag, }, @@ -55,8 +55,8 @@ func main() { } } -// HttpService is the entry point for the http service -func HttpService(cliCtx *cli.Context) error { +// HTTPService is the entry point for the http service +func HTTPService(cliCtx *cli.Context) error { c, err := config.Load(cliCtx) if err != nil { log.Errorf("Error: %v", err) diff --git a/signer/service/service.go b/signer/service/service.go index 86fe13b3..2f37f36a 100644 --- a/signer/service/service.go +++ b/signer/service/service.go @@ -67,7 +67,7 @@ func NewServer(cfg *config.Config, ctx context.Context) *Server { } srv.l1Cfg = ethConfig.L1Config{ - L1ChainID: cfg.L1.ChainId, + L1ChainID: cfg.L1.ChainID, ZkEVMAddr: cfg.L1.PolygonZkEVMAddress, PolAddr: cfg.L1.PolygonMaticAddress, GlobalExitRootManagerAddr: cfg.L1.GlobalExitRootManagerAddr, @@ -76,12 +76,19 @@ func NewServer(cfg *config.Config, ctx context.Context) *Server { var err error srv.ethClient, err = etherman.NewClient(srv.ethCfg, srv.l1Cfg, cdkcommon.Config{}) - log.Infof("url: %v, l1 chain id: %v, zkevm addr: %v, rollup manager addr: %v", srv.ethCfg.URL, srv.l1Cfg.L1ChainID, srv.l1Cfg.ZkEVMAddr, srv.l1Cfg.RollupManagerAddr) + log.Infof("url: %v, l1 chain id: %v, zkevm addr: %v, rollup manager addr: %v", + srv.ethCfg.URL, + srv.l1Cfg.L1ChainID, + srv.l1Cfg.ZkEVMAddr, + srv.l1Cfg.RollupManagerAddr, + ) if err != nil { log.Fatal("error creating etherman client. Error: %v", err) } - _, srv.seqPrivateKey, err = srv.ethClient.LoadAuthFromKeyStoreXLayer(cfg.L1.SeqPrivateKey.Path, cfg.L1.SeqPrivateKey.Password) + _, srv.seqPrivateKey, err = srv.ethClient.LoadAuthFromKeyStoreXLayer( + cfg.L1.SeqPrivateKey.Path, cfg.L1.SeqPrivateKey.Password, + ) if err != nil { log.Fatal("error loading sequencer private key. Error: %v", err) } @@ -89,7 +96,9 @@ func NewServer(cfg *config.Config, ctx context.Context) *Server { srv.seqAddress = crypto.PubkeyToAddress(srv.seqPrivateKey.PublicKey) log.Infof("Sequencer address: %s", srv.seqAddress.String()) - _, srv.aggPrivateKey, err = srv.ethClient.LoadAuthFromKeyStoreXLayer(cfg.L1.AggPrivateKey.Path, cfg.L1.AggPrivateKey.Password) + _, srv.aggPrivateKey, err = srv.ethClient.LoadAuthFromKeyStoreXLayer( + cfg.L1.AggPrivateKey.Path, cfg.L1.AggPrivateKey.Password, + ) if err != nil { log.Fatal("error loading aggregator private key. Error: %v", err) } @@ -132,32 +141,32 @@ func (s *Server) PostSignDataByOrderNo(w http.ResponseWriter, r *http.Request) { log.Infof("Request:%v", requestData.String()) - if value, ok := s.result[requestData.RefOrderId]; ok { + if value, ok := s.result[requestData.RefOrderID]; ok { response.DetailMsg = "already exist" - log.Infof("already exist, key:%v, value:%v", requestData.RefOrderId, value) + log.Infof("already exist, key:%v, value:%v", requestData.RefOrderID, value) sendJSONResponse(w, response) return } if requestData.OperateType == operateTypeSeq { - err, data := s.signSeq(requestData) + data, err := s.signSeq(requestData) if err != nil { response.DetailMsg = err.Error() log.Errorf("error signSeq: %v", err) } else { response.Code = codeSuccess response.Success = true - s.result[requestData.RefOrderId] = data + s.result[requestData.RefOrderID] = data } } else if requestData.OperateType == operateTypeAgg { - err, data := s.signAgg(requestData) + data, err := s.signAgg(requestData) if err != nil { response.DetailMsg = err.Error() log.Errorf("error signAgg: %v", err) } else { response.Code = codeSuccess response.Success = true - s.result[requestData.RefOrderId] = data + s.result[requestData.RefOrderID] = data } } else { log.Error("error operateType") @@ -167,15 +176,15 @@ func (s *Server) PostSignDataByOrderNo(w http.ResponseWriter, r *http.Request) { } // signSeq is the handler for the /priapi/v1/assetonchain/ecology/ecologyOperate endpoint -func (s *Server) signSeq(requestData Request) (error, string) { +func (s *Server) signSeq(requestData Request) (string, error) { var seqData SeqData err := json.Unmarshal([]byte(requestData.OtherInfo), &seqData) if err != nil { log.Errorf("error Unmarshal: %v", err) - return err, "" + return "", err } - var validiumBatchData []polygonzkevm.PolygonValidiumEtrogValidiumBatchData + validiumBatchData := []polygonzkevm.PolygonValidiumEtrogValidiumBatchData{} for _, batch := range seqData.Batches { validiumBatchData = append(validiumBatchData, polygonzkevm.PolygonValidiumEtrogValidiumBatchData{ TransactionsHash: common.HexToHash(batch.TransactionsHash), @@ -206,38 +215,37 @@ func (s *Server) signSeq(requestData Request) (error, string) { ) if err != nil { log.Errorf("error BuildSequenceBatchesTxData: %v", err) - return err, "" + return "", err } to := &seqData.ContractAddress - // return s.getTxData(s.seqAddress, to, data) return s.getLegacyTxData(s.seqAddress, to, data, seqData.Nonce, seqData.GasLimit, seqData.GasPrice) } // signAgg is the handler for the /priapi/v1/assetonchain/ecology/ecologyOperate endpoint -func (s *Server) signAgg(requestData Request) (error, string) { +func (s *Server) signAgg(requestData Request) (string, error) { var aggData AggData err := json.Unmarshal([]byte(requestData.OtherInfo), &aggData) if err != nil { log.Errorf("error Unmarshal: %v", err) - return err, "" + return "", err } newLocal, err := hex.DecodeHex(aggData.NewLocalExitRoot) if err != nil { log.Errorf("error DecodeHex: %v", err) - return err, "" + return "", err } newStateRoot, err := hex.DecodeHex(aggData.NewStateRoot) if err != nil { log.Errorf("error DecodeHex: %v", err) - return err, "" + return "", err } if len(aggData.Proof) != proofLen { log.Errorf("agg data len is not 24") - return fmt.Errorf("agg proof len is not 24"), "" + return "", fmt.Errorf("agg proof len is not 24") } proofStr := "0x" for _, v := range aggData.Proof { @@ -256,10 +264,12 @@ func (s *Server) signAgg(requestData Request) (error, string) { FinalProof: proof, } - _, data, err := s.ethClient.BuildTrustedVerifyBatchesTxData(aggData.InitNumBatch, aggData.FinalNewBatch, inputs, aggData.Beneficiary) + _, data, err := s.ethClient.BuildTrustedVerifyBatchesTxData( + aggData.InitNumBatch, aggData.FinalNewBatch, inputs, aggData.Beneficiary, + ) if err != nil { log.Errorf("error BuildTrustedVerifyBatchesTxData: %v", err) - return err, "" + return "", err } to := &aggData.ContractAddress @@ -267,7 +277,13 @@ func (s *Server) signAgg(requestData Request) (error, string) { return s.getLegacyTxData(s.aggAddress, to, data, aggData.Nonce, aggData.GasLimit, aggData.GasPrice) } -func (s *Server) getLegacyTxData(from common.Address, to *common.Address, data []byte, nonce, gasLimit uint64, gasPrice string) (error, string) { +func (s *Server) getLegacyTxData( + from common.Address, + to *common.Address, + data []byte, + nonce, gasLimit uint64, + gasPrice string, +) (string, error) { bigFloatGasPrice := new(big.Float) bigFloatGasPrice, _ = bigFloatGasPrice.SetString(gasPrice) result := new(big.Float).Mul(bigFloatGasPrice, new(big.Float).SetInt(big.NewInt(params.Ether))) @@ -285,17 +301,17 @@ func (s *Server) getLegacyTxData(from common.Address, to *common.Address, data [ signedTx, err := s.ethClient.SignTx(s.ctx, from, tx) if err != nil { log.Errorf("error SignTx: %v", err) - return err, "" + return "", err } txBin, err := signedTx.MarshalBinary() if err != nil { log.Errorf("error MarshalBinary: %v", err) - return err, "" + return "", err } log.Infof("TxHash: %v", signedTx.Hash().String()) - return nil, hex.EncodeToString(txBin) + return hex.EncodeToString(txBin), nil } // GetSignDataByOrderNo is the handler for the /priapi/v1/assetonchain/ecology/ecologyOperate endpoint diff --git a/signer/service/type.go b/signer/service/type.go index 4cf62eaa..2bb652d9 100644 --- a/signer/service/type.go +++ b/signer/service/type.go @@ -14,7 +14,7 @@ type Request struct { OperateAddress string `json:"operateAddress"` Symbol int `json:"symbol"` ProjectSymbol int `json:"projectSymbol"` - RefOrderId string `json:"refOrderId"` + RefOrderID string `json:"refOrderId"` OperateSymbol int `json:"operateSymbol"` OperateAmount int `json:"operateAmount"` SysFrom int `json:"sysFrom"` @@ -58,7 +58,7 @@ type Batch struct { // AggData is the data for aggregate operation type AggData struct { - RollupId uint64 `json:"rollupID"` + RollupID uint64 `json:"rollupID"` NewLocalExitRoot string `json:"newLocalExitRoot"` NewStateRoot string `json:"newStateRoot"` FinalNewBatch uint64 `json:"finalNewBatch"` From 3241a136eef20e5b075fac9c5cc86eddb8c7d3bc Mon Sep 17 00:00:00 2001 From: Vui-Chee Date: Wed, 20 Nov 2024 10:34:19 +0800 Subject: [PATCH 5/9] Move mock signer svc under test --- Makefile | 2 +- test/docker-compose.yml | 2 +- {signer => test/signer}/Dockerfile | 4 ++-- {signer => test/signer}/Makefile | 0 {signer => test/signer}/config/config.go | 0 {signer => test/signer}/config/default.go | 0 {signer => test/signer}/main.go | 4 ++-- {signer => test/signer}/service/service.go | 2 +- {signer => test/signer}/service/type.go | 0 {signer => test/signer}/signer.config.toml | 0 10 files changed, 7 insertions(+), 7 deletions(-) rename {signer => test/signer}/Dockerfile (76%) rename {signer => test/signer}/Makefile (100%) rename {signer => test/signer}/config/config.go (100%) rename {signer => test/signer}/config/default.go (100%) rename {signer => test/signer}/main.go (94%) rename {signer => test/signer}/service/service.go (99%) rename {signer => test/signer}/service/type.go (100%) rename {signer => test/signer}/signer.config.toml (100%) diff --git a/Makefile b/Makefile index 1a9b84ff..4068f378 100644 --- a/Makefile +++ b/Makefile @@ -72,7 +72,7 @@ build-docker: ## Builds a docker image with the cdk binary .PHONY: build-mock-signer build-mock-signer-docker: - docker build --progress=plain -t xlayer-signer -f ./signer/Dockerfile . + docker build --progress=plain -t xlayer-signer -f ./test/signer/Dockerfile . .PHONY: build-docker-nc build-docker-nc: ## Builds a docker image with the cdk binary - but without build cache diff --git a/test/docker-compose.yml b/test/docker-compose.yml index c2985b68..2ce4030c 100644 --- a/test/docker-compose.yml +++ b/test/docker-compose.yml @@ -127,7 +127,7 @@ services: ports: - 7001:7001 volumes: - - ../signer/signer.config.toml:/app/config.toml + - ../test/signer/signer.config.toml:/app/config.toml - ./sequencer.keystore:/pk/sequencer.keystore - ./aggregator.keystore:/pk/aggregator.keystore command: diff --git a/signer/Dockerfile b/test/signer/Dockerfile similarity index 76% rename from signer/Dockerfile rename to test/signer/Dockerfile index 277bd7db..b13051cb 100644 --- a/signer/Dockerfile +++ b/test/signer/Dockerfile @@ -9,11 +9,11 @@ RUN cd /src && go mod download # BUILD BINARY COPY . /src RUN cd /src/db && packr2 -RUN cd /src/signer && make build +RUN cd /src/test/signer && make build # CONTAINER FOR RUNNING BINARY FROM alpine:3.18.0 -COPY --from=build /src/signer/dist/xlayer-signer /app/xlayer-signer +COPY --from=build /src/test/signer/dist/xlayer-signer /app/xlayer-signer EXPOSE 7001 CMD ["/bin/sh", "-c", "/app/xlayer-signer"] diff --git a/signer/Makefile b/test/signer/Makefile similarity index 100% rename from signer/Makefile rename to test/signer/Makefile diff --git a/signer/config/config.go b/test/signer/config/config.go similarity index 100% rename from signer/config/config.go rename to test/signer/config/config.go diff --git a/signer/config/default.go b/test/signer/config/default.go similarity index 100% rename from signer/config/default.go rename to test/signer/config/default.go diff --git a/signer/main.go b/test/signer/main.go similarity index 94% rename from signer/main.go rename to test/signer/main.go index cc0ee778..d2b0d031 100644 --- a/signer/main.go +++ b/test/signer/main.go @@ -7,8 +7,8 @@ import ( "time" "github.com/0xPolygon/cdk/log" - "github.com/0xPolygon/cdk/signer/config" - "github.com/0xPolygon/cdk/signer/service" + "github.com/0xPolygon/cdk/test/signer/config" + "github.com/0xPolygon/cdk/test/signer/service" "github.com/urfave/cli/v2" ) diff --git a/signer/service/service.go b/test/signer/service/service.go similarity index 99% rename from signer/service/service.go rename to test/signer/service/service.go index 2f37f36a..d67aa115 100644 --- a/signer/service/service.go +++ b/test/signer/service/service.go @@ -17,7 +17,7 @@ import ( ethConfig "github.com/0xPolygon/cdk/etherman/config" "github.com/0xPolygon/cdk/hex" "github.com/0xPolygon/cdk/log" - "github.com/0xPolygon/cdk/signer/config" + "github.com/0xPolygon/cdk/test/signer/config" zkEthman "github.com/0xPolygon/zkevm-ethtx-manager/etherman" "github.com/ethereum/go-ethereum/common" ethTypes "github.com/ethereum/go-ethereum/core/types" diff --git a/signer/service/type.go b/test/signer/service/type.go similarity index 100% rename from signer/service/type.go rename to test/signer/service/type.go diff --git a/signer/signer.config.toml b/test/signer/signer.config.toml similarity index 100% rename from signer/signer.config.toml rename to test/signer/signer.config.toml From aef49a62ac3740b0300269e6fc6bbf9cf525bc50 Mon Sep 17 00:00:00 2001 From: Vui-Chee Date: Wed, 20 Nov 2024 10:48:25 +0800 Subject: [PATCH 6/9] Unwire zkevm, reuse contract code abstraction --- etherman/etherman_xlayer.go | 17 +++-------------- etherman/mock_etherman.go | 2 +- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/etherman/etherman_xlayer.go b/etherman/etherman_xlayer.go index f48ccfec..87683b13 100644 --- a/etherman/etherman_xlayer.go +++ b/etherman/etherman_xlayer.go @@ -11,7 +11,6 @@ import ( "time" "github.com/0xPolygon/cdk-contracts-tooling/contracts/banana/idataavailabilityprotocol" - polygonzkevm "github.com/0xPolygon/cdk-contracts-tooling/contracts/banana/polygonvalidiumetrog" cdkcommon "github.com/0xPolygon/cdk/common" "github.com/0xPolygon/cdk/etherman/config" "github.com/0xPolygon/cdk/etherman/contracts" @@ -86,9 +85,6 @@ type Client struct { l1Cfg config.L1Config cfg config.Config auth map[common.Address]bind.TransactOpts // empty in case of read-only client - - // X Layer fields - ZkEVM *polygonzkevm.Polygonvalidiumetrog } // NewClient creates a new etherman. @@ -100,11 +96,6 @@ func NewClient(cfg config.Config, l1Config config.L1Config, commonConfig cdkcomm return nil, err } - zkevm, err := polygonzkevm.NewPolygonvalidiumetrog(l1Config.ZkEVMAddr, ethClient) - if err != nil { - log.Errorf("error creating Polygonzkevm client (%s). Error: %w", l1Config.ZkEVMAddr.String(), err) - return nil, err - } L1chainID, err := ethClient.ChainID(context.Background()) if err != nil { log.Errorf("error getting L1chainID from %s: %+v", cfg.EthermanConfig.URL, err) @@ -140,8 +131,6 @@ func NewClient(cfg config.Config, l1Config config.L1Config, commonConfig cdkcomm l1Cfg: l1Config, cfg: cfg, auth: map[common.Address]bind.TransactOpts{}, - - ZkEVM: zkevm, } if commonConfig.IsValidiumMode { @@ -497,9 +486,9 @@ func (etherMan *Client) GetL1InfoRoot(indexL1InfoRoot uint32) (common.Hash, erro // LoadAuthFromKeyStoreXLayer loads an authorization from a key store file func (etherMan *Client) LoadAuthFromKeyStoreXLayer(path, password string) ( - *bind.TransactOpts, - *ecdsa.PrivateKey, - error, + *bind.TransactOpts, + *ecdsa.PrivateKey, + error, ) { auth, pk, err := newAuthFromKeystoreXLayer(path, password, etherMan.l1Cfg.L1ChainID) if err != nil { diff --git a/etherman/mock_etherman.go b/etherman/mock_etherman.go index 75908cb3..aab45788 100644 --- a/etherman/mock_etherman.go +++ b/etherman/mock_etherman.go @@ -63,7 +63,7 @@ func (etherMan *Client) sequenceMockBatches(opts bind.TransactOpts, finalInputHash[i] = bb } - tx, err = etherMan.ZkEVM.SequenceBatchesValidium( + tx, err = etherMan.Contracts.Banana.Rollup.SequenceBatchesValidium( &opts, validiumBatchData, l1InfoTreeLeafCount, From 1b47e8e81002716bd0f8df95cf3eeed52b233f2d Mon Sep 17 00:00:00 2001 From: Vui-Chee Date: Wed, 20 Nov 2024 11:06:05 +0800 Subject: [PATCH 7/9] Fix misplaced build comment --- etherman/etherman.go | 1 + 1 file changed, 1 insertion(+) diff --git a/etherman/etherman.go b/etherman/etherman.go index abb7f8fe..5a195b15 100644 --- a/etherman/etherman.go +++ b/etherman/etherman.go @@ -1,5 +1,6 @@ //go:build ignore // +build ignore + package etherman import ( From 2bcb3778dfd5a7fccdc5e52a6685ab1c344ce1e7 Mon Sep 17 00:00:00 2001 From: Vui-Chee Date: Wed, 20 Nov 2024 11:12:08 +0800 Subject: [PATCH 8/9] Build mock signer in same cmd --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 4068f378..123a8c7b 100644 --- a/Makefile +++ b/Makefile @@ -66,11 +66,11 @@ build-rust: build-go: $(GOENVVARS) go build -ldflags "all=$(LDFLAGS)" -o $(GOBIN)/$(GOBINARY) $(GOCMD) -.PHONY: build-docker -build-docker: ## Builds a docker image with the cdk binary +.PHONY: build-docker +build-docker: build-mock-signer-docker ## Builds a docker image with the cdk binary docker build -t cdk -f ./Dockerfile . -.PHONY: build-mock-signer +.PHONY: build-mock-signer-docker build-mock-signer-docker: docker build --progress=plain -t xlayer-signer -f ./test/signer/Dockerfile . From 3aa6ca907d34cf06526bbc0b2e92c082bffdde87 Mon Sep 17 00:00:00 2001 From: Vui-Chee Date: Wed, 20 Nov 2024 15:37:01 +0800 Subject: [PATCH 9/9] Keep xlayer related funcs and discard rest --- etherman/etherman.go | 3 - etherman/etherman_xlayer.go | 476 ------------------------------------ 2 files changed, 479 deletions(-) diff --git a/etherman/etherman.go b/etherman/etherman.go index 5a195b15..4f9e1c81 100644 --- a/etherman/etherman.go +++ b/etherman/etherman.go @@ -1,6 +1,3 @@ -//go:build ignore -// +build ignore - package etherman import ( diff --git a/etherman/etherman_xlayer.go b/etherman/etherman_xlayer.go index 87683b13..3f97aafa 100644 --- a/etherman/etherman_xlayer.go +++ b/etherman/etherman_xlayer.go @@ -1,489 +1,13 @@ package etherman import ( - "context" "crypto/ecdsa" - "errors" - "fmt" "math/big" - "os" - "path/filepath" - "time" - "github.com/0xPolygon/cdk-contracts-tooling/contracts/banana/idataavailabilityprotocol" - cdkcommon "github.com/0xPolygon/cdk/common" - "github.com/0xPolygon/cdk/etherman/config" - "github.com/0xPolygon/cdk/etherman/contracts" "github.com/0xPolygon/cdk/log" - "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/accounts/keystore" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/ethereum/go-ethereum/rpc" ) -// EventOrder is the type used to identify the events order -type EventOrder string - -const ( - // GlobalExitRootsOrder identifies a GlobalExitRoot event - GlobalExitRootsOrder EventOrder = "GlobalExitRoots" - // L1InfoTreeOrder identifies a L1InTree event - L1InfoTreeOrder EventOrder = "L1InfoTreeOrder" - // SequenceBatchesOrder identifies a VerifyBatch event - SequenceBatchesOrder EventOrder = "SequenceBatches" - // ForcedBatchesOrder identifies a ForcedBatches event - ForcedBatchesOrder EventOrder = "ForcedBatches" - // TrustedVerifyBatchOrder identifies a TrustedVerifyBatch event - TrustedVerifyBatchOrder EventOrder = "TrustedVerifyBatch" - // VerifyBatchOrder identifies a VerifyBatch event - VerifyBatchOrder EventOrder = "VerifyBatch" - // SequenceForceBatchesOrder identifies a SequenceForceBatches event - SequenceForceBatchesOrder EventOrder = "SequenceForceBatches" - // ForkIDsOrder identifies an updateZkevmVersion event - ForkIDsOrder EventOrder = "forkIDs" -) - -type ethereumClient interface { - ethereum.ChainReader - ethereum.ChainStateReader - ethereum.ContractCaller - ethereum.GasEstimator - ethereum.GasPricer - ethereum.GasPricer1559 - ethereum.LogFilterer - ethereum.TransactionReader - ethereum.TransactionSender - - bind.DeployBackend -} - -// L1Config represents the configuration of the network used in L1 -type L1Config struct { - // Chain ID of the L1 network - L1ChainID uint64 `json:"chainId" mapstructure:"ChainID"` - // ZkEVMAddr Address of the L1 contract polygonZkEVMAddress - ZkEVMAddr common.Address `json:"polygonZkEVMAddress" mapstructure:"ZkEVMAddr"` - // RollupManagerAddr Address of the L1 contract - RollupManagerAddr common.Address `json:"polygonRollupManagerAddress" mapstructure:"RollupManagerAddr"` - // PolAddr Address of the L1 Pol token Contract - PolAddr common.Address `json:"polTokenAddress" mapstructure:"PolAddr"` - // GlobalExitRootManagerAddr Address of the L1 GlobalExitRootManager contract - GlobalExitRootManagerAddr common.Address `json:"polygonZkEVMGlobalExitRootAddress" mapstructure:"GlobalExitRootManagerAddr"` //nolint:lll -} - -// Client is a simple implementation of EtherMan. -type Client struct { - EthClient ethereumClient - DAProtocol *idataavailabilityprotocol.Idataavailabilityprotocol - - Contracts *contracts.Contracts - RollupID uint32 - - l1Cfg config.L1Config - cfg config.Config - auth map[common.Address]bind.TransactOpts // empty in case of read-only client -} - -// NewClient creates a new etherman. -func NewClient(cfg config.Config, l1Config config.L1Config, commonConfig cdkcommon.Config) (*Client, error) { - // Connect to ethereum node - ethClient, err := ethclient.Dial(cfg.EthermanConfig.URL) - if err != nil { - log.Errorf("error connecting to %s: %+v", cfg.EthermanConfig.URL, err) - - return nil, err - } - L1chainID, err := ethClient.ChainID(context.Background()) - if err != nil { - log.Errorf("error getting L1chainID from %s: %+v", cfg.EthermanConfig.URL, err) - - return nil, err - } - log.Infof("L1ChainID: %d", L1chainID.Uint64()) - contracts, err := contracts.NewContracts(l1Config, ethClient) - if err != nil { - return nil, err - } - log.Info(contracts.String()) - // Get RollupID - rollupID, err := contracts.Banana.RollupManager.RollupAddressToID(&bind.CallOpts{Pending: false}, l1Config.ZkEVMAddr) - if err != nil { - log.Errorf("error getting rollupID from %s : %+v", contracts.Banana.RollupManager.String(), err) - - return nil, err - } - if rollupID == 0 { - return nil, errors.New( - "rollupID is 0, is not a valid value. Check that rollup Address is correct " + - l1Config.ZkEVMAddr.String(), - ) - } - log.Infof("rollupID: %d (obtenied from SMC: %s )", rollupID, contracts.Banana.RollupManager.String()) - - client := &Client{ - EthClient: ethClient, - Contracts: contracts, - - RollupID: rollupID, - l1Cfg: l1Config, - cfg: cfg, - auth: map[common.Address]bind.TransactOpts{}, - } - - if commonConfig.IsValidiumMode { - dapAddr, err := contracts.Banana.Rollup.DataAvailabilityProtocol(&bind.CallOpts{Pending: false}) - if err != nil { - return nil, err - } - - client.DAProtocol, err = idataavailabilityprotocol.NewIdataavailabilityprotocol(dapAddr, ethClient) - if err != nil { - return nil, err - } - } - - return client, nil -} - -// Order contains the event order to let the synchronizer store the information following this order. -type Order struct { - Name EventOrder - Pos int -} - -// WaitTxToBeMined waits for an L1 tx to be mined. It will return error if the tx is reverted or timeout is exceeded -func (etherMan *Client) WaitTxToBeMined( - ctx context.Context, tx *types.Transaction, timeout time.Duration, -) (bool, error) { - // err := operations.WaitTxToBeMined(ctx, etherMan.EthClient, tx, timeout) - // if errors.Is(err, context.DeadlineExceeded) { - // return false, nil - // } - // if err != nil { - // return false, err - // } - return true, nil -} - -// GetSendSequenceFee get super/trusted sequencer fee -func (etherMan *Client) GetSendSequenceFee(numBatches uint64) (*big.Int, error) { - f, err := etherMan.Contracts.Banana.RollupManager.GetBatchFee(&bind.CallOpts{Pending: false}) - if err != nil { - return nil, err - } - fee := new(big.Int).Mul(f, new(big.Int).SetUint64(numBatches)) - - return fee, nil -} - -// TrustedSequencer gets trusted sequencer address -func (etherMan *Client) TrustedSequencer() (common.Address, error) { - return etherMan.Contracts.Banana.Rollup.TrustedSequencer(&bind.CallOpts{Pending: false}) -} - -// HeaderByNumber returns a block header from the current canonical chain. If number is -// nil, the latest known header is returned. -func (etherMan *Client) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { - return etherMan.EthClient.HeaderByNumber(ctx, number) -} - -// EthBlockByNumber function retrieves the ethereum block information by ethereum block number. -func (etherMan *Client) EthBlockByNumber(ctx context.Context, blockNumber uint64) (*types.Block, error) { - block, err := etherMan.EthClient.BlockByNumber(ctx, new(big.Int).SetUint64(blockNumber)) - if err != nil { - if errors.Is(err, ethereum.NotFound) || err.Error() == "block does not exist in blockchain" { - return nil, ErrNotFound - } - - return nil, err - } - - return block, nil -} - -// GetLatestBatchNumber function allows to retrieve the latest proposed batch in the smc -func (etherMan *Client) GetLatestBatchNumber() (uint64, error) { - rollupData, err := etherMan.Contracts.Banana.RollupManager.RollupIDToRollupData( - &bind.CallOpts{Pending: false}, - etherMan.RollupID, - ) - if err != nil { - return 0, err - } - - return rollupData.LastBatchSequenced, nil -} - -// GetLatestBlockNumber gets the latest block number from the ethereum -func (etherMan *Client) GetLatestBlockNumber(ctx context.Context) (uint64, error) { - return etherMan.getBlockNumber(ctx, rpc.LatestBlockNumber) -} - -// GetSafeBlockNumber gets the safe block number from the ethereum -func (etherMan *Client) GetSafeBlockNumber(ctx context.Context) (uint64, error) { - return etherMan.getBlockNumber(ctx, rpc.SafeBlockNumber) -} - -// GetFinalizedBlockNumber gets the Finalized block number from the ethereum -func (etherMan *Client) GetFinalizedBlockNumber(ctx context.Context) (uint64, error) { - return etherMan.getBlockNumber(ctx, rpc.FinalizedBlockNumber) -} - -// getBlockNumber gets the block header by the provided block number from the ethereum -func (etherMan *Client) getBlockNumber(ctx context.Context, blockNumber rpc.BlockNumber) (uint64, error) { - header, err := etherMan.EthClient.HeaderByNumber(ctx, big.NewInt(int64(blockNumber))) - if err != nil || header == nil { - return 0, err - } - - return header.Number.Uint64(), nil -} - -// GetLatestBlockTimestamp gets the latest block timestamp from the ethereum -func (etherMan *Client) GetLatestBlockTimestamp(ctx context.Context) (uint64, error) { - header, err := etherMan.EthClient.HeaderByNumber(ctx, nil) - if err != nil || header == nil { - return 0, err - } - - return header.Time, nil -} - -// GetLatestVerifiedBatchNum gets latest verified batch from ethereum -func (etherMan *Client) GetLatestVerifiedBatchNum() (uint64, error) { - rollupData, err := etherMan.Contracts.Banana.RollupManager.RollupIDToRollupData( - &bind.CallOpts{Pending: false}, - etherMan.RollupID, - ) - if err != nil { - return 0, err - } - - return rollupData.LastVerifiedBatch, nil -} - -// GetTx function get ethereum tx -func (etherMan *Client) GetTx(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) { - return etherMan.EthClient.TransactionByHash(ctx, txHash) -} - -// GetTxReceipt function gets ethereum tx receipt -func (etherMan *Client) GetTxReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { - return etherMan.EthClient.TransactionReceipt(ctx, txHash) -} - -// GetTrustedSequencerURL Gets the trusted sequencer url from rollup smc -func (etherMan *Client) GetTrustedSequencerURL() (string, error) { - return etherMan.Contracts.Banana.Rollup.TrustedSequencerURL(&bind.CallOpts{Pending: false}) -} - -// GetL2ChainID returns L2 Chain ID -func (etherMan *Client) GetL2ChainID() (uint64, error) { - rollupData, err := etherMan.Contracts.Banana.RollupManager.RollupIDToRollupData( - &bind.CallOpts{Pending: false}, - etherMan.RollupID, - ) - log.Debug("chainID read from rollupManager: ", rollupData.ChainID) - if err != nil { - log.Debug("error from rollupManager: ", err) - - return 0, err - } else if rollupData.ChainID == 0 { - return rollupData.ChainID, fmt.Errorf("error: chainID received is 0") - } - - return rollupData.ChainID, nil -} - -// SendTx sends a tx to L1 -func (etherMan *Client) SendTx(ctx context.Context, tx *types.Transaction) error { - return etherMan.EthClient.SendTransaction(ctx, tx) -} - -// CurrentNonce returns the current nonce for the provided account -func (etherMan *Client) CurrentNonce(ctx context.Context, account common.Address) (uint64, error) { - return etherMan.EthClient.NonceAt(ctx, account, nil) -} - -// EstimateGas returns the estimated gas for the tx -func (etherMan *Client) EstimateGas( - ctx context.Context, from common.Address, to *common.Address, value *big.Int, data []byte, -) (uint64, error) { - return etherMan.EthClient.EstimateGas(ctx, ethereum.CallMsg{ - From: from, - To: to, - Value: value, - Data: data, - }) -} - -// CheckTxWasMined check if a tx was already mined -func (etherMan *Client) CheckTxWasMined(ctx context.Context, txHash common.Hash) (bool, *types.Receipt, error) { - receipt, err := etherMan.EthClient.TransactionReceipt(ctx, txHash) - if errors.Is(err, ethereum.NotFound) { - return false, nil, nil - } else if err != nil { - return false, nil, err - } - - return true, receipt, nil -} - -// SignTx tries to sign a transaction accordingly to the provided sender -func (etherMan *Client) SignTx( - ctx context.Context, sender common.Address, tx *types.Transaction, -) (*types.Transaction, error) { - auth, err := etherMan.getAuthByAddress(sender) - if errors.Is(err, ErrNotFound) { - return nil, ErrPrivateKeyNotFound - } - signedTx, err := auth.Signer(auth.From, tx) - if err != nil { - return nil, err - } - - return signedTx, nil -} - -// GetRevertMessage tries to get a revert message of a transaction -func (etherMan *Client) GetRevertMessage(ctx context.Context, tx *types.Transaction) (string, error) { - // if tx == nil { - // return "", nil - // } - - // receipt, err := etherMan.GetTxReceipt(ctx, tx.Hash()) - // if err != nil { - // return "", err - // } - - // if receipt.Status == types.ReceiptStatusFailed { - // revertMessage, err := operations.RevertReason(ctx, etherMan.EthClient, tx, receipt.BlockNumber) - // if err != nil { - // return "", err - // } - // return revertMessage, nil - // } - return "", nil -} - -// AddOrReplaceAuth adds an authorization or replace an existent one to the same account -func (etherMan *Client) AddOrReplaceAuth(auth bind.TransactOpts) error { - log.Infof("added or replaced authorization for address: %v", auth.From.String()) - etherMan.auth[auth.From] = auth - - return nil -} - -// LoadAuthFromKeyStore loads an authorization from a key store file -func (etherMan *Client) LoadAuthFromKeyStore(path, password string) (*bind.TransactOpts, *ecdsa.PrivateKey, error) { - auth, pk, err := newAuthFromKeystore(path, password, etherMan.l1Cfg.L1ChainID) - if err != nil { - return nil, nil, err - } - - log.Infof("loaded authorization for address: %v", auth.From.String()) - etherMan.auth[auth.From] = auth - - return &auth, pk, nil -} - -// newKeyFromKeystore creates an instance of a keystore key from a keystore file -func newKeyFromKeystore(path, password string) (*keystore.Key, error) { - if path == "" && password == "" { - return nil, nil - } - keystoreEncrypted, err := os.ReadFile(filepath.Clean(path)) - if err != nil { - return nil, err - } - log.Infof("decrypting key from: %v", path) - key, err := keystore.DecryptKey(keystoreEncrypted, password) - if err != nil { - return nil, err - } - - return key, nil -} - -// newAuthFromKeystore an authorization instance from a keystore file -func newAuthFromKeystore(path, password string, chainID uint64) (bind.TransactOpts, *ecdsa.PrivateKey, error) { - log.Infof("reading key from: %v", path) - key, err := newKeyFromKeystore(path, password) - if err != nil { - return bind.TransactOpts{}, nil, err - } - if key == nil { - return bind.TransactOpts{}, nil, nil - } - auth, err := bind.NewKeyedTransactorWithChainID(key.PrivateKey, new(big.Int).SetUint64(chainID)) - if err != nil { - return bind.TransactOpts{}, nil, err - } - - return *auth, key.PrivateKey, nil -} - -// getAuthByAddress tries to get an authorization from the authorizations map -func (etherMan *Client) getAuthByAddress(addr common.Address) (bind.TransactOpts, error) { - auth, found := etherMan.auth[addr] - if !found { - return bind.TransactOpts{}, ErrNotFound - } - - return auth, nil -} - -// GetLatestBlockHeader gets the latest block header from the ethereum -func (etherMan *Client) GetLatestBlockHeader(ctx context.Context) (*types.Header, error) { - header, err := etherMan.EthClient.HeaderByNumber(ctx, big.NewInt(int64(rpc.LatestBlockNumber))) - if err != nil || header == nil { - return nil, err - } - - return header, nil -} - -// GetDAProtocolAddr returns the address of the data availability protocol -func (etherMan *Client) GetDAProtocolAddr() (common.Address, error) { - return etherMan.Contracts.Banana.Rollup.DataAvailabilityProtocol(&bind.CallOpts{Pending: false}) -} - -// GetDAProtocolName returns the name of the data availability protocol -func (etherMan *Client) GetDAProtocolName() (string, error) { - return etherMan.DAProtocol.GetProcotolName(&bind.CallOpts{Pending: false}) -} - -// LastAccInputHash gets the last acc input hash from the SC -func (etherMan *Client) LastAccInputHash() (common.Hash, error) { - return etherMan.Contracts.Banana.Rollup.LastAccInputHash(&bind.CallOpts{Pending: false}) -} - -// GetL1InfoRoot gets the L1 info root from the SC -func (etherMan *Client) GetL1InfoRoot(indexL1InfoRoot uint32) (common.Hash, error) { - // Get lastL1InfoTreeRoot (if index==0 then root=0, no call is needed) - var ( - lastL1InfoTreeRoot common.Hash - err error - ) - - if indexL1InfoRoot > 0 { - lastL1InfoTreeRoot, err = etherMan.Contracts.Banana.GlobalExitRoot.L1InfoRootMap( - &bind.CallOpts{Pending: false}, - indexL1InfoRoot, - ) - if err != nil { - log.Errorf("error calling SC globalexitroot L1InfoLeafMap: %v", err) - } - } - - return lastL1InfoTreeRoot, err -} - -/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ X Layer ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /// - // LoadAuthFromKeyStoreXLayer loads an authorization from a key store file func (etherMan *Client) LoadAuthFromKeyStoreXLayer(path, password string) ( *bind.TransactOpts,