diff --git a/cli/node/README.md b/cli/node/README.md new file mode 100644 index 0000000..fc92b1a --- /dev/null +++ b/cli/node/README.md @@ -0,0 +1,37 @@ +# node cli + +This is the main cli for the node + +## Usage + +``` +NAME: + hermez-node - A new cli application + +USAGE: + node [global options] command [command options] [arguments...] + +VERSION: + 0.1.0-alpha + +COMMANDS: + importkey Import ethereum private key + wipesql Wipe the SQL DB (HistoryDB and L2DB), leaving the DB in a clean state + run Run the hermez-node in the indicated mode + help, h Shows a list of commands or help for one command + +GLOBAL OPTIONS: + --mode MODE Set node MODE (can be "sync" or "coord") + --cfg FILE Node configuration FILE + --help, -h show help (default: false) + --version, -v print the version (default: false) +``` + +## Configuration + +You can find a testing working configuration example at +[cfg.buidler.toml](./cfg.buidler.toml) + +To read the documentation of each configuration parameter, please check the +`type Node` and `type Coordinator` at +[config/config.go](../../config/config.go) diff --git a/cli/node/cfg.buidler.toml b/cli/node/cfg.buidler.toml index b5791ab..4e74d9d 100644 --- a/cli/node/cfg.buidler.toml +++ b/cli/node/cfg.buidler.toml @@ -11,6 +11,7 @@ Type = "bitfinexV2" [Debug] APIAddress = "localhost:12345" +MeddlerLogs = true [StateDB] Path = "/tmp/iden3-test/hermez/statedb" @@ -68,9 +69,6 @@ MaxTx = 512 NLevels = 32 [Coordinator.EthClient] -CallGasLimit = 300000 -DeployGasLimit = 1000000 -GasPriceDiv = 100 ReceiptTimeout = "60s" ReceiptLoopInterval = "500ms" diff --git a/config/config.go b/config/config.go index 236f4a3..222c08b 100644 --- a/config/config.go +++ b/config/config.go @@ -29,6 +29,7 @@ func (d *Duration) UnmarshalText(data []byte) error { // ServerProof is the server proof configuration data. type ServerProof struct { + // URL is the server proof API URL URL string `validate:"required"` } @@ -48,10 +49,17 @@ type Coordinator struct { // SyncRetryInterval is the waiting interval between calls to the main // handler of a synced block after an error SyncRetryInterval Duration `validate:"required"` - L2DB struct { + // L2DB is the DB that holds the pool of L2Txs + L2DB struct { + // SafetyPeriod is the number of batches after which + // non-pending L2Txs are deleted from the pool SafetyPeriod common.BatchNum `validate:"required"` - MaxTxs uint32 `validate:"required"` - TTL Duration `validate:"required"` + // MaxTxs is the number of L2Txs that once reached triggers + // deletion of old L2Txs + MaxTxs uint32 `validate:"required"` + // TTL is the Time To Live for L2Txs in the pool. Once MaxTxs + // L2Txs is reached, L2Txs older than TTL will be deleted. + TTL Duration `validate:"required"` // PurgeBatchDelay is the delay between batches to purge outdated transactions PurgeBatchDelay int64 `validate:"required"` // InvalidateBatchDelay is the delay between batches to mark invalid transactions @@ -62,23 +70,29 @@ type Coordinator struct { InvalidateBlockDelay int64 `validate:"required"` } `validate:"required"` TxSelector struct { + // Path where the TxSelector StateDB is stored Path string `validate:"required"` } `validate:"required"` BatchBuilder struct { + // Path where the BatchBuilder StateDB is stored Path string `validate:"required"` } `validate:"required"` ServerProofs []ServerProof `validate:"required"` Circuit struct { // VerifierIdx uint8 `validate:"required"` - MaxTx int64 `validate:"required"` + // MaxTx is the maximum number of txs supported by the circuit + MaxTx int64 `validate:"required"` + // NLevels is the maximum number of merkle tree levels + // supported by the circuit NLevels int64 `validate:"required"` } `validate:"required"` EthClient struct { - CallGasLimit uint64 `validate:"required"` - DeployGasLimit uint64 `validate:"required"` - GasPriceDiv uint64 `validate:"required"` - ReceiptTimeout Duration `validate:"required"` - ReceiptLoopInterval Duration `validate:"required"` + // CallGasLimit is the default gas limit set for ethereum + // calls, except for methods where a particular gas limit is + // harcoded because it's known to be a big value + CallGasLimit uint64 `validate:"required"` + // GasPriceDiv is the gas price division + GasPriceDiv uint64 `validate:"required"` // CheckLoopInterval is the waiting interval between receipt // checks of ethereum transactions in the TxManager CheckLoopInterval Duration `validate:"required"` @@ -88,12 +102,16 @@ type Coordinator struct { // AttemptsDelay is delay between attempts do do an eth client // RPC call AttemptsDelay Duration `validate:"required"` - Keystore struct { - Path string `validate:"required"` + // Keystore is the ethereum keystore where private keys are kept + Keystore struct { + // Path to the keystore + Path string `validate:"required"` + // Password used to decrypt the keys in the keystore Password string `validate:"required"` } `validate:"required"` } `validate:"required"` API struct { + // Coordinator enables the coordinator API endpoints Coordinator bool } `validate:"required"` Debug struct { @@ -109,43 +127,79 @@ type Coordinator struct { // Node is the hermez node configuration. type Node struct { PriceUpdater struct { + // Interval between price updater calls Interval Duration `valudate:"required"` - URL string `valudate:"required"` - Type string `valudate:"required"` + // URL of the token prices provider + URL string `valudate:"required"` + // Type of the API of the token prices provider + Type string `valudate:"required"` } `validate:"required"` StateDB struct { + // Path where the synchronizer StateDB is stored Path string `validate:"required"` - Keep int `validate:"required"` + // Keep is the number of checkpoints to keep + Keep int `validate:"required"` } `validate:"required"` PostgreSQL struct { - Port int `validate:"required"` - Host string `validate:"required"` - User string `validate:"required"` + // Port of the PostgreSQL server + Port int `validate:"required"` + // Host of the PostgreSQL server + Host string `validate:"required"` + // User of the PostgreSQL server + User string `validate:"required"` + // Password of the PostgreSQL server Password string `validate:"required"` - Name string `validate:"required"` + // Name of the PostgreSQL server database + Name string `validate:"required"` } `validate:"required"` Web3 struct { + // URL is the URL of the web3 ethereum-node RPC server URL string `validate:"required"` } `validate:"required"` Synchronizer struct { - SyncLoopInterval Duration `validate:"required"` + // SyncLoopInterval is the interval between attempts to + // synchronize a new block from an ethereum node + SyncLoopInterval Duration `validate:"required"` + // StatsRefreshPeriod is the interval between updates of the + // synchronizer state Eth parameters (`Eth.LastBlock` and + // `Eth.LastBatch`) StatsRefreshPeriod Duration `validate:"required"` } `validate:"required"` SmartContracts struct { - Rollup ethCommon.Address `validate:"required"` - Auction ethCommon.Address `validate:"required"` - WDelayer ethCommon.Address `validate:"required"` - TokenHEZ ethCommon.Address `validate:"required"` - TokenHEZName string `validate:"required"` + // Rollup is the address of the Hermez.sol smart contract + Rollup ethCommon.Address `validate:"required"` + // Rollup is the address of the HermezAuctionProtocol.sol smart + // contract + Auction ethCommon.Address `validate:"required"` + // WDelayer is the address of the WithdrawalDelayer.sol smart + // contract + WDelayer ethCommon.Address `validate:"required"` + // TokenHEZ is the address of the HEZTokenFull.sol smart + // contract + TokenHEZ ethCommon.Address `validate:"required"` + // TokenHEZName is the name of the HEZ token deployed at + // TokenHEZ address + TokenHEZName string `validate:"required"` } `validate:"required"` API struct { - Address string - Explorer bool - UpdateMetricsInterval Duration + // Address where the API will listen if set + Address string + // Explorer enables the Explorer API endpoints + Explorer bool + // UpdateMetricsInterval is the interval between updates of the + // API metrics + UpdateMetricsInterval Duration + // UpdateMetricsInterval is the interval between updates of the + // recommended fees UpdateRecommendedFeeInterval Duration } `validate:"required"` Debug struct { + // APIAddress is the address where the debugAPI will listen if + // set APIAddress string + // MeddlerLogs enables meddler debug mode, where unused columns and struct + // fields will be logged + MeddlerLogs bool } Coordinator Coordinator `validate:"-"` } diff --git a/coordinator/coordinator_test.go b/coordinator/coordinator_test.go index 274fc96..fec2625 100644 --- a/coordinator/coordinator_test.go +++ b/coordinator/coordinator_test.go @@ -735,11 +735,8 @@ func TestRollupForgeBatch(t *testing.T) { ethClient, err := ethclient.Dial(web3URL) require.NoError(t, err) ethCfg := eth.EthereumConfig{ - CallGasLimit: 300000, - DeployGasLimit: 1000000, - GasPriceDiv: 100, - ReceiptTimeout: 60 * time.Second, - IntervalReceiptLoop: 500 * time.Millisecond, + CallGasLimit: 300000, + GasPriceDiv: 100, } scryptN := ethKeystore.LightScryptN scryptP := ethKeystore.LightScryptP diff --git a/eth/ethereum.go b/eth/ethereum.go index d2b2426..09de3ca 100644 --- a/eth/ethereum.go +++ b/eth/ethereum.go @@ -40,45 +40,31 @@ type EthereumInterface interface { var ( // ErrAccountNil is used when the calls can not be made because the account is nil ErrAccountNil = fmt.Errorf("Authorized calls can't be made when the account is nil") - // ErrReceiptStatusFailed is used when receiving a failed transaction - ErrReceiptStatusFailed = fmt.Errorf("receipt status is failed") - // ErrReceiptNotReceived is used when unable to retrieve a transaction - ErrReceiptNotReceived = fmt.Errorf("receipt not available") // ErrBlockHashMismatchEvent is used when there's a block hash mismatch // beetween different events of the same block ErrBlockHashMismatchEvent = fmt.Errorf("block hash mismatch in event log") ) const ( - errStrDeploy = "deployment of %s failed: %w" - errStrWaitReceipt = "wait receipt of %s deploy failed: %w" - // default values - defaultCallGasLimit = 300000 - defaultDeployGasLimit = 1000000 - defaultGasPriceDiv = 100 - defaultReceiptTimeout = 60 - defaultIntervalReceiptLoop = 200 + defaultCallGasLimit = 300000 + defaultGasPriceDiv = 100 ) // EthereumConfig defines the configuration parameters of the EthereumClient type EthereumConfig struct { - CallGasLimit uint64 - DeployGasLimit uint64 - GasPriceDiv uint64 - ReceiptTimeout time.Duration - IntervalReceiptLoop time.Duration + CallGasLimit uint64 + GasPriceDiv uint64 } // EthereumClient is an ethereum client to call Smart Contract methods and check blockchain information. type EthereumClient struct { - client *ethclient.Client - chainID *big.Int - account *accounts.Account - ks *ethKeystore.KeyStore - ReceiptTimeout time.Duration - config *EthereumConfig - opts *bind.CallOpts + client *ethclient.Client + chainID *big.Int + account *accounts.Account + ks *ethKeystore.KeyStore + config *EthereumConfig + opts *bind.CallOpts } // NewEthereumClient creates a EthereumClient instance. The account is not mandatory (it can @@ -86,20 +72,16 @@ type EthereumClient struct { func NewEthereumClient(client *ethclient.Client, account *accounts.Account, ks *ethKeystore.KeyStore, config *EthereumConfig) (*EthereumClient, error) { if config == nil { config = &EthereumConfig{ - CallGasLimit: defaultCallGasLimit, - DeployGasLimit: defaultDeployGasLimit, - GasPriceDiv: defaultGasPriceDiv, - ReceiptTimeout: defaultReceiptTimeout, - IntervalReceiptLoop: defaultIntervalReceiptLoop, + CallGasLimit: defaultCallGasLimit, + GasPriceDiv: defaultGasPriceDiv, } } c := &EthereumClient{ - client: client, - account: account, - ks: ks, - ReceiptTimeout: config.ReceiptTimeout * time.Second, - config: config, - opts: newCallOpts(), + client: client, + account: account, + ks: ks, + config: config, + opts: newCallOpts(), } chainID, err := c.EthChainID() if err != nil { @@ -180,93 +162,16 @@ type ContractData struct { Receipt *types.Receipt } -// Deploy a smart contract. `name` is used to log deployment information. fn -// is a wrapper to the deploy function generated by abigen. In case of error, -// the returned `ContractData` may have some parameters filled depending on the -// kind of error that occurred. -func (c *EthereumClient) Deploy(name string, - fn func(c *ethclient.Client, auth *bind.TransactOpts) (ethCommon.Address, *types.Transaction, interface{}, error)) (ContractData, error) { - var contractData ContractData - log.Infow("Deploying", "contract", name) - tx, err := c.CallAuth( - c.config.DeployGasLimit, - func(client *ethclient.Client, auth *bind.TransactOpts) (*types.Transaction, error) { - addr, tx, _, err := fn(client, auth) - if err != nil { - return nil, tracerr.Wrap(err) - } - contractData.Address = addr - return tx, nil - }, - ) - if err != nil { - return contractData, tracerr.Wrap(fmt.Errorf(errStrDeploy, name, err)) - } - log.Infow("Waiting receipt", "tx", tx.Hash().Hex(), "contract", name) - contractData.Tx = tx - receipt, err := c.WaitReceipt(tx) - if err != nil { - return contractData, tracerr.Wrap(fmt.Errorf(errStrWaitReceipt, name, err)) - } - contractData.Receipt = receipt - return contractData, nil -} - // Call performs a read only Smart Contract method call. func (c *EthereumClient) Call(fn func(*ethclient.Client) error) error { return fn(c.client) } -// WaitReceipt will block until a transaction is confirmed. Internally it -// polls the state every 200 milliseconds. -func (c *EthereumClient) WaitReceipt(tx *types.Transaction) (*types.Receipt, error) { - return c.waitReceipt(context.TODO(), tx, c.ReceiptTimeout) -} - -// GetReceipt will check if a transaction is confirmed and return -// immediately, waiting at most 1 second and returning error if the transaction -// is still pending. -func (c *EthereumClient) GetReceipt(tx *types.Transaction) (*types.Receipt, error) { - ctx, cancel := context.WithTimeout(context.TODO(), 1*time.Second) - defer cancel() - return c.waitReceipt(ctx, tx, 0) -} - // EthTransactionReceipt returns the transaction receipt of the given txHash func (c *EthereumClient) EthTransactionReceipt(ctx context.Context, txHash ethCommon.Hash) (*types.Receipt, error) { return c.client.TransactionReceipt(ctx, txHash) } -func (c *EthereumClient) waitReceipt(ctx context.Context, tx *types.Transaction, timeout time.Duration) (*types.Receipt, error) { - var err error - var receipt *types.Receipt - - txHash := tx.Hash() - log.Debugw("Waiting for receipt", "tx", txHash.Hex()) - - start := time.Now() - for { - receipt, err = c.client.TransactionReceipt(ctx, txHash) - if receipt != nil || time.Since(start) >= timeout { - break - } - time.Sleep(c.config.IntervalReceiptLoop * time.Millisecond) - } - - if receipt != nil && receipt.Status == types.ReceiptStatusFailed { - log.Errorw("Failed transaction", "tx", txHash.Hex()) - return receipt, tracerr.Wrap(ErrReceiptStatusFailed) - } - - if receipt == nil { - log.Debugw("Pendingtransaction / Wait receipt timeout", "tx", txHash.Hex(), "lasterr", err) - return receipt, tracerr.Wrap(ErrReceiptNotReceived) - } - log.Debugw("Successful transaction", "tx", txHash.Hex()) - - return receipt, tracerr.Wrap(err) -} - // EthLastBlock returns the last block number in the blockchain func (c *EthereumClient) EthLastBlock() (int64, error) { ctx, cancel := context.WithTimeout(context.TODO(), 1*time.Second) diff --git a/node/node.go b/node/node.go index cda0997..0c1f58c 100644 --- a/node/node.go +++ b/node/node.go @@ -32,6 +32,7 @@ import ( "github.com/hermeznetwork/hermez-node/txselector" "github.com/hermeznetwork/tracerr" "github.com/jmoiron/sqlx" + "github.com/russross/meddler" ) // Mode sets the working mode of the node (synchronizer or coordinator) @@ -71,6 +72,7 @@ type Node struct { // NewNode creates a Node func NewNode(mode Mode, cfg *config.Node) (*Node, error) { + meddler.Debug = cfg.Debug.MeddlerLogs // Stablish DB connection db, err := dbUtils.InitSQLDB( cfg.PostgreSQL.Port, @@ -94,11 +96,8 @@ func NewNode(mode Mode, cfg *config.Node) (*Node, error) { var keyStore *ethKeystore.KeyStore if mode == ModeCoordinator { ethCfg = eth.EthereumConfig{ - CallGasLimit: cfg.Coordinator.EthClient.CallGasLimit, - DeployGasLimit: cfg.Coordinator.EthClient.DeployGasLimit, - GasPriceDiv: cfg.Coordinator.EthClient.GasPriceDiv, - ReceiptTimeout: cfg.Coordinator.EthClient.ReceiptTimeout.Duration, - IntervalReceiptLoop: cfg.Coordinator.EthClient.ReceiptLoopInterval.Duration, + CallGasLimit: cfg.Coordinator.EthClient.CallGasLimit, + GasPriceDiv: cfg.Coordinator.EthClient.GasPriceDiv, } scryptN := ethKeystore.StandardScryptN