From 9295967089e723698874e467d68cd6cd28dc70d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20Ram=C3=ADrez?= <58293609+ToniRamirezM@users.noreply.github.com> Date: Thu, 27 Jul 2023 16:08:42 +0200 Subject: [PATCH 01/15] Merge/v0.2.1 into develop (#2334) * do not add tx to the pool in case err != nil * do not add tx into the pool if a fatal error in the executor happens during pre execution --- pool/pool.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pool/pool.go b/pool/pool.go index 7a0ca6a1f1..32b44b77c7 100644 --- a/pool/pool.go +++ b/pool/pool.go @@ -175,7 +175,8 @@ func (p *Pool) StoreTx(ctx context.Context, tx types.Transaction, ip string, isW // Do not add tx to the pool return err } else if err != nil { - log.Debugf("PreExecuteTx error (this can be ignored): %v", err) + log.Errorf("Pre execution error: %v", err) + return err } if preExecutionResponse.isOOC { From bcc885e2dc3c4ccdd12f2daf5b4fe33fd48c6415 Mon Sep 17 00:00:00 2001 From: Thiago Coimbra Lemos Date: Thu, 27 Jul 2023 15:15:50 -0300 Subject: [PATCH 02/15] fix estimate gas nonce computation to deal with sequencer concurrency (#2204) --- state/transaction.go | 57 ++++++++++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/state/transaction.go b/state/transaction.go index 4f1ec2ffc8..e3d7a89956 100644 --- a/state/transaction.go +++ b/state/transaction.go @@ -758,21 +758,6 @@ func (s *State) internalProcessUnsignedTransaction(ctx context.Context, tx *type return nil, err } - stateRoot := l2BlockStateRoot - if l2BlockNumber != nil { - l2Block, err := s.GetL2BlockByNumber(ctx, *l2BlockNumber, dbTx) - if err != nil { - return nil, err - } - stateRoot = l2Block.Root() - } - - loadedNonce, err := s.tree.GetNonce(ctx, senderAddress, stateRoot.Bytes()) - if err != nil { - return nil, err - } - nonce := loadedNonce.Uint64() - // Get latest batch from the database to get globalExitRoot and Timestamp lastBatch := lastBatches[0] @@ -782,9 +767,15 @@ func (s *State) internalProcessUnsignedTransaction(ctx context.Context, tx *type previousBatch = lastBatches[1] } + stateRoot := l2BlockStateRoot timestamp := uint64(lastBatch.Timestamp.Unix()) - if l2BlockNumber != nil { + l2Block, err := s.GetL2BlockByNumber(ctx, *l2BlockNumber, dbTx) + if err != nil { + return nil, err + } + stateRoot = l2Block.Root() + latestL2BlockNumber, err := s.PostgresStorage.GetLastL2BlockNumber(ctx, dbTx) if err != nil { return nil, err @@ -796,6 +787,11 @@ func (s *State) internalProcessUnsignedTransaction(ctx context.Context, tx *type } forkID := s.GetForkIDByBatchNumber(lastBatch.BatchNumber) + loadedNonce, err := s.tree.GetNonce(ctx, senderAddress, stateRoot.Bytes()) + if err != nil { + return nil, err + } + nonce := loadedNonce.Uint64() batchL2Data, err := EncodeUnsignedTransaction(*tx, s.cfg.ChainID, &nonce, forkID) if err != nil { @@ -963,6 +959,21 @@ func (s *State) EstimateGas(transaction *types.Transaction, senderAddress common return 0, nil, err } + stateRoot := l2BlockStateRoot + if l2BlockNumber != nil { + l2Block, err := s.GetL2BlockByNumber(ctx, *l2BlockNumber, dbTx) + if err != nil { + return 0, nil, err + } + stateRoot = l2Block.Root() + } + + loadedNonce, err := s.tree.GetNonce(ctx, senderAddress, stateRoot.Bytes()) + if err != nil { + return 0, nil, err + } + nonce := loadedNonce.Uint64() + // Get latest batch from the database to get globalExitRoot and Timestamp lastBatch := lastBatches[0] @@ -978,7 +989,7 @@ func (s *State) EstimateGas(transaction *types.Transaction, senderAddress common } if lowEnd == ethTransferGas && transaction.To() != nil { - code, err := s.tree.GetCode(ctx, *transaction.To(), l2BlockStateRoot.Bytes()) + code, err := s.tree.GetCode(ctx, *transaction.To(), stateRoot.Bytes()) if err != nil { log.Warnf("error while getting transaction.to() code %v", err) } else if len(code) == 0 { @@ -995,7 +1006,7 @@ func (s *State) EstimateGas(transaction *types.Transaction, senderAddress common var availableBalance *big.Int if senderAddress != ZeroAddress { - senderBalance, err := s.tree.GetBalance(ctx, senderAddress, l2BlockStateRoot.Bytes()) + senderBalance, err := s.tree.GetBalance(ctx, senderAddress, stateRoot.Bytes()) if err != nil { if errors.Is(err, ErrNotFound) { senderBalance = big.NewInt(0) @@ -1029,9 +1040,9 @@ func (s *State) EstimateGas(transaction *types.Transaction, senderAddress common // Run the transaction with the specified gas value. // Returns a status indicating if the transaction failed, if it was reverted and the accompanying error - testTransaction := func(gas uint64, shouldOmitErr bool) (failed, reverted bool, gasUsed uint64, returnValue []byte, err error) { + testTransaction := func(gas uint64, nonce uint64, shouldOmitErr bool) (failed, reverted bool, gasUsed uint64, returnValue []byte, err error) { tx := types.NewTx(&types.LegacyTx{ - Nonce: transaction.Nonce(), + Nonce: nonce, To: transaction.To(), Value: transaction.Value(), Gas: gas, @@ -1052,7 +1063,7 @@ func (s *State) EstimateGas(transaction *types.Transaction, senderAddress common OldBatchNum: lastBatch.BatchNumber, BatchL2Data: batchL2Data, From: senderAddress.String(), - OldStateRoot: l2BlockStateRoot.Bytes(), + OldStateRoot: stateRoot.Bytes(), GlobalExitRoot: lastBatch.GlobalExitRoot.Bytes(), OldAccInputHash: previousBatch.AccInputHash.Bytes(), EthTimestamp: uint64(lastBatch.Timestamp.Unix()), @@ -1116,7 +1127,7 @@ func (s *State) EstimateGas(transaction *types.Transaction, senderAddress common var totalExecutionTime time.Duration // Check if the highEnd is a good value to make the transaction pass - failed, reverted, gasUsed, returnValue, err := testTransaction(highEnd, false) + failed, reverted, gasUsed, returnValue, err := testTransaction(highEnd, nonce, false) log.Debugf("Estimate gas. Trying to execute TX with %v gas", highEnd) if failed { if reverted { @@ -1142,7 +1153,7 @@ func (s *State) EstimateGas(transaction *types.Transaction, senderAddress common log.Debugf("Estimate gas. Trying to execute TX with %v gas", mid) - failed, reverted, _, _, testErr := testTransaction(mid, true) + failed, reverted, _, _, testErr := testTransaction(mid, nonce, true) executionTime := time.Since(txExecutionStart) totalExecutionTime += executionTime txExecutions = append(txExecutions, executionTime) From 01d8bbeba3d518a5311b0c160e6d021b1710b4c8 Mon Sep 17 00:00:00 2001 From: Arnau Bennassar Date: Tue, 1 Aug 2023 10:26:10 +0200 Subject: [PATCH 03/15] Update README diagram (#2303) Update README diagram --- README.md | 27 ++++++++++++--------------- docs/architecture.drawio.png | Bin 154305 -> 169540 bytes test/docker-compose.yml | 11 ----------- tools/zkevmprovermock/Dockerfile | 11 ----------- 4 files changed, 12 insertions(+), 37 deletions(-) delete mode 100644 tools/zkevmprovermock/Dockerfile diff --git a/README.md b/README.md index 37cdcb7901..e23883dd41 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,6 @@ Glossary: - Consolidated state: state that is proven on-chain by submitting a ZKP (Zero Knowledge Proof) that proves the execution of a sequence of the last virtual batch. - Invalid transaction: a transaction that can't be processed and doesn't affect the state. Note that such a transaction could be included in a virtual batch. The reason for a transaction to be invalid could be related to the Ethereum protocol (invalid nonce, not enough balance, ...) or due to limitations introduced by the zkEVM (each batch can make use of a limited amount of resources such as the total amount of keccak hashes that can be computed) - Reverted transaction: a transaction that is executed, but is reverted (because of smart contract logic). The main difference with *invalid transaction* is that this transaction modifies the state, at least to increment nonce of the sender. -- Proof of Efficiency (PoE): name of the protocol used by the network, it's enforced by the [smart contracts](https://github.com/0xPolygonHermez/zkevm-contracts) ## Architecture @@ -32,19 +31,21 @@ Glossary: The diagram represents the main components of the software and how they interact between them. Note that this reflects a single entity running a node, in particular a node that acts as the trusted sequencer. But there are many entities running nodes in the network, and each of these entities can perform different roles. More on this later. -- (JSON) RPC: an interface that allows users (metamask, etherscan, ...) to interact with the node. Fully compatible with Ethereum RPC + some extra endpoints specifics of the network. It interacts with the `state` to get data and process transactions and with the `pool` to store transactions +- (JSON) RPC: an HTTP interface that allows users (dApps, metamask, etherscan, ...) to interact with the node. Fully compatible with Ethereum RPC + some extra [custom endpoints](./docs/zkEVM-custom-endpoints.md) specifics of the network. It interacts with the `state` (to get data and process transactions) as well as the `pool` (to store transactions). +- L2GasPricer: it fetches the L1 gas price and applies some formula to calculate the gas price that will be suggested for the users to use for paying fees on L2. The suggestions are stored on the `pool`, and will be consumed by the `rpc` - Pool: DB that stores transactions by the `RPC` to be selected/discarded by the `sequencer` later on -- Trusted Sequencer: get transactions from the `pool`, check if they are valid by processing them using the `state`, and create sequences. Once transactions are added into the state, they are immediately available through the `rpc`. Sequences are sent to L1 using the `etherman` -- Permissionless Sequencer: *coming soon* +- Sequencer: responsible for building the trusted state. To do so, it gets transactions from the pool and puts them in a specific order. It needs to take care of opening and closing batches while trying to make them as full as possible. To achieve this it needs to use the executor to actually process the transaction not only to execute the state transition (and update the hashDB) but also to check the consumed resources by the transactions and the remaining resources of the batch. After executing a transaction that fits into a batch, it gets stored on the `state`. Once transactions are added into the state, they are immediately available through the `rpc`. +- SequenceSender: gets closed batches from the `state`, tries to aggregate as many of them as possible, and at some point, decides that it's time to send those batches to L1, turning the state from trusted to virtualized. In order to send the L1 tx, it uses the `ethtxmanager` +- EthTxManager: handles requests to send L1 transactions from `sequencesender` and `aggregator`. It takes care of dealing with the nonce of the accounts, increasing the gas price, and other actions that may be needed to ensure that L1 transactions get mined - Etherman: abstraction that implements the needed methods to interact with the Ethereum network and the relevant smart contracts. -- Synchronizer: Updates the `state` by fetching data from Ethereum through the `etherman`. If the node is not a `trusted sequencer` it also updates the state with the data fetched from the `rpc` of the `trusted sequencer`. It also detects and handles reorgs that can happen if the `trusted sequencer` sends different data in the rpc vs the sequences sent to L1 (trusted vs virtual state) +- Synchronizer: Updates the `state` (virtual batches, verified batches, forced batches, ...) by fetching data from L1 through the `etherman`. If the node is not a `trusted sequencer` it also updates the state with the data fetched from the `rpc` of the `trusted sequencer`. It also detects and handles reorgs that can happen if the `trusted sequencer` sends different data in the rpc vs the sequences sent to L1 (trusted reorg aka L2 reorg). Also handles L1 reorgs (reorgs that happen on the L1 network) - State: Responsible for managing the state data (batches, blocks, transactions, ...) that is stored on the `state SB`. It also handles the integration with the `executor` and the `Merkletree` service -- State DB: persistence layer for the state data (except the Merkletree that is handled by the `Merkletree` service) -- Aggregator: consolidates batches by generating ZKPs (Zero Knowledge proofs). To do so it gathers the necessary data that the `prover` needs as input through the `state` and sends a request to it. Once the proof is generated it's sent to Ethereum through the `etherman` -- Prover/Executor: service that generates ZK proofs. Note that this component is not implemented in this repository, and it's treated as a "black box" from the perspective of the node. The prover/executor has two implementations: [JS reference implementation](https://github.com/0xPolygonHermez/zkevm-proverjs) and [C production-ready implementation](https://github.com/0xPolygonHermez/zkevm-prover). Although it's the same software/service, it has two very different purposes: - - Provide an EVM implementation that allows processing transactions and getting all needed results metadata (state root, receipts, logs, ...) - - Generate ZKPs -- Merkletree: service that stores the Merkletree, containing all the account information (balances, nonces, smart contract code, and smart contract storage). This component is also not implemented in this repo and is consumed as an external service by the node. The implementation can be found [here](https://github.com/0xPolygonHermez/zkevm-prover) +- State DB: persistence layer for the state data (except the Merkletree that is handled by the `HashDB` service), it stores informationrelated to L1 (blocks, global exit root updates, ...) and L2 (batches, L2 blocks, transactions, ...) +- Aggregator: consolidates batches by generating ZKPs (Zero Knowledge proofs). To do so it gathers the necessary data that the `prover` needs as input through the `state` and sends a request to it. Once the proof is generated it sends a request to send an L1 tx to verify the proof and move the state from virtual to verified to the `ethtxmanager`. Note that provers connect to the aggregator and not the other way arround. The aggregator can handle multiple connected provers at once and make them work concurrently in the generation of different proofs +- Prover/Executor/hashDB: service that generates ZK proofs. Note that this component is not implemented in this repository, and it's treated as a "black box" from the perspective of the node. The prover/executor has two implementations: [JS reference implementation](https://github.com/0xPolygonHermez/zkevm-proverjs) and [C production-ready implementation](https://github.com/0xPolygonHermez/zkevm-prover). Although it's the same software/binary, it implements three services: + - Executor: Provides an EVM implementation that allows processing batches as well as getting metadata (state root, transaction receipts, logs, ...) of all the needed results. + - Prover: Generates ZKPs for batches, batches aggregation, and final proofs. + - HashDB: service that stores the Merkletree, containing all the account information (balances, nonces, smart contract code, and smart contract storage) ## Roles of the network @@ -80,10 +81,6 @@ Required services and components: Note that the JSON RPC is required to receive transactions. It's recommended that the JSON RPC runs on separated instances, and potentially more than one (depending on the load of the network). It's also recommended that the JSON RPC and the Sequencer don't share the same executor instance, to make sure that the sequencer has exclusive access to an executor -### Permissionless sequencer - -TBD - ### Aggregator This role can be performed by anyone. diff --git a/docs/architecture.drawio.png b/docs/architecture.drawio.png index 6b6510c17f9d28fb015e17b0f2bcca4fbb5f020b..52f11986c3b92cf51ca111c9231c04d0a74b6ae6 100644 GIT binary patch delta 90371 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

9o@6Ms3Q$>_|YL`jqEW1Fw_k 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 z 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 delta 75296 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 zkProver -c /usr/src/app/config.json - zkprover-mock: - container_name: zkprover-mock - image: hermeznetwork/zkprover-mock:latest - ports: - - 43061:43061 # MT - - 43071:43071 # Executor - volumes: - - ./vectors/src:/app/testvectors - command: > - /app/zkprover-mock server --statedb-port 43061 --executor-port 43071 --test-vector-path /app/testvectors - zkevm-approve: container_name: zkevm-approve image: zkevm-node diff --git a/tools/zkevmprovermock/Dockerfile b/tools/zkevmprovermock/Dockerfile deleted file mode 100644 index d4d21e0d8c..0000000000 --- a/tools/zkevmprovermock/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM golang:1.19-alpine AS build - -ENV CGO_ENABLED=0 -WORKDIR /app -COPY . . -RUN apk add --no-cache build-base && \ - go build -ldflags '-extldflags "-static"' -o ./zkprover-mock ./tools/zkevmprovermock/cmd/... - -FROM alpine:3.16.0 -COPY --from=build /app/zkprover-mock /app/zkprover-mock -CMD ["/bin/sh", "-c", "/app/zkprover-mock server"] From 0cbb92074d7be4a98ed91aa10ca42d6318028cd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20Ram=C3=ADrez?= <58293609+ToniRamirezM@users.noreply.github.com> Date: Wed, 2 Aug 2023 10:27:35 +0200 Subject: [PATCH 04/15] Merge Release/v0.2.2 into develop (#2353) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix null effective_percentage * fix forkID calculation * fix script * generate json-schema + docs for node config file and network_custom * fix unittest * Hotfixv0.1.4 to v0.2.0 (#2255) * Hotfix v0.1.4 to main (#2250) * fix concurrent web socket writes * fix eth_syncing * fix custom trace internal tx call error handling and update prover * add test to custom tracer depth issue; fix internal call error and gas used * fix custom tracer for internal tx with error and no more steps after it * remove debug code * Make max grpc message size configurable (#2179) * make max grpc message size configurable * fix state tests * fix tests * fix tests * get SequencerNodeURI from SC if empty and not IsTrustedSequencer * Optimize trace (#2183) * optimize trace * fix memory reading * update docker image * update prover image * fix converter * fix memory * fix step memory * fix structlogs * fix structlogs * fix structlogs * fix structlogs * fix structlogs * fix structlogs * fix structlogs * fix structlogs * update prover image * fix struclogs * fix memory size * fix memory size * fix memory size * refactor memory resize * refactor memory resize * move log for the best fitting tx (#2192) * fix load zkCounters from pool * remove unnecessary log.info * add custom tracer support to CREATES opcode without depth increase (#2213) * logs * fix getting stateroot from previous batch (GetWIPBatch) * logs * Fix GetWipBatch when previous last batch is a forced batch * fix forcedBatch trusted state * Revert "fix getting stateroot from previous batch (GetWIPBatch)" This reverts commit 860f0e74016219daf81f96b76f6b25609e1c66fd. * force GHA * add pool limits (#2189) * Hotfix/batch l2 data (#2223) * Fix BatchL2Data * Force GHA * remove failed txs from the pool limit check (#2233) * debug trace by batch number via external rpc requests (#2235) * fix trace batch remote requests in parallel limitation (#2244) * Added RPC.TraceBatchUseHTTPS config parameter * fix executor version --------- Co-authored-by: tclemos Co-authored-by: tclemos Co-authored-by: Toni Ramírez <58293609+ToniRamirezM@users.noreply.github.com> Co-authored-by: agnusmor Co-authored-by: agnusmor <100322135+agnusmor@users.noreply.github.com> Co-authored-by: Thiago Coimbra Lemos * fix test * fix test --------- Co-authored-by: tclemos Co-authored-by: tclemos Co-authored-by: Toni Ramírez <58293609+ToniRamirezM@users.noreply.github.com> Co-authored-by: agnusmor Co-authored-by: agnusmor <100322135+agnusmor@users.noreply.github.com> Co-authored-by: Thiago Coimbra Lemos * Effective GasPrice refactor+fixes (#2247) * effective GasPrice refactor * bugs fixes and finalizer tests fixes * fix typo * fix calculate effective gasprice percentage * fix test gas price * Fix/#2257 effective gas price receipt (#2258) * effective gas price returned by the rpc in the receipt * linter * bugfix: fixing l2blocks timestamp for the fist batch (#2260) * bugfix: fixing l2blocks timestamp for the fist batch Signed-off-by: Nikolay Nedkov * fix finalizer unit test --------- Signed-off-by: Nikolay Nedkov * add more comments, and removed fields PrivateKeyPath and PrivateKeyPassword from etherman.Config that are not in use * add info to git action * add info to git action * fix github action * updated comments * updated comments * Fix/#2263 gas used (#2264) * fix fea2scalar and gas used * suggestion * fix fea2scalar * suggestion * Fix pending tx when duplicate nonce (#2270) * fix pending tx when duplicate nonce * set pool.transaction.failed_reason to NULL when updating an existing tx * add more log details when adding tx to AddrQueue * fix query to add tx to the pool. Fix lint errors * change failed_reason for tx discarded due duplicate nonce * Only return a tx from the pool if tx is in pending status (#2273) * Return a tx from the pool only if it is * fix TestGetTransactionByHash --------- Co-authored-by: agnusmor * fix documentation with config file * improve: adding check to skip appending effectivePercentage if current forkId is under 5. Signed-off-by: Nikolay Nedkov * Fiex effectiveGasprice unsigned txs with forkId lower than 5 (#2278) * feat: adding functionality to stop sequencer on specific batch num from config param. Signed-off-by: Nikolay Nedkov * patch: adding print for X-Real-IP in JSON-RPC Signed-off-by: Nikolay Nedkov * Fix checkIfSynced (#2289) * [Rehashing] Check logs order and fix blockhash and blockNumber in the log conversion (#2280) * fix and check order * linter * flushID synchronizer (#2287) * FlushID in synchronizer * linter * fix logs * commnets * executor error refactor (#2299) * handle invalid rlp ROM error (#2297) * add maxL2GasPrice (#2294) * add maxL2GasPrice * fix * fix * add test * document parameter * update description * Error refactor (#2302) * error refactor * refactor * Fix replaced tx as failed when duplicated nonce (#2308) * Fix UpdateTxStatus for replacedTx * Fix adding tx with same nonce on AddrQueue * log reprocess need (#2309) * log reprocess need * Update finalizer.go * Feature/2300 synchronizer detect if executor restart (#2306) * detect if executor restarts and stop synchonizer * Update prover images (#2311) * update prover image * update prover images * change executor param * Update testnet.prover.config.json * Update test.permissionless.prover.config.json * Update test.prover.config.json * Update public.prover.config.json * prover params * prover params * prover params * update prover images * add doc, and fix dockers to be able to use snap/restore feature (#2315) * add doc, and fix dockers to be able to use snap/restore feature * add doc for snap/restore feature --------- Co-authored-by: Toni Ramírez * Update docker-compose.yml * Update docker-compose.yml * do not add tx to the pool in case err != nil * do not add tx into the pool if a fatal error in the executor happens during pre execution * fix dbMultiWriteSinglePosition config value * workarround for the error error closing batch * workarround for the error error closing batch * workarround for the error error closing batch * workaround for the error of closing batch, another case * `Worker`'s `AddTxTracker` Bug Fix (#2343) * bugfix: Resolve Function Bug in Worker Module Signed-off-by: Nikolay Nedkov * improve: improving the wait for pending txs to be for only the txs for the current address. Signed-off-by: Nikolay Nedkov --------- Signed-off-by: Nikolay Nedkov * rename config files (#2349) * fix closing batch + logs (#2348) * fix closing batch + logs * fix * log description * typo errors * fix error: failed to store transactions for batch due to duplicate key * test * typo * Update README.md * Update release.yml * fix conflict --------- Signed-off-by: Nikolay Nedkov Co-authored-by: joanestebanr Co-authored-by: Alonso Rodriguez Co-authored-by: tclemos Co-authored-by: tclemos Co-authored-by: agnusmor Co-authored-by: agnusmor <100322135+agnusmor@users.noreply.github.com> Co-authored-by: Thiago Coimbra Lemos Co-authored-by: joanestebanr <129153821+joanestebanr@users.noreply.github.com> Co-authored-by: Nikolay Nedkov --- .github/workflows/release.yml | 8 +- Dockerfile | 2 +- README.md | 2 +- config/config.go | 4 +- ...blic.node.config.toml => node.config.toml} | 0 ....prover.config.json => prover.config.json} | 2 +- ...tnet.node.config.toml => node.config.toml} | 0 ....prover.config.json => prover.config.json} | 2 +- config/gen_json_schema_test.go | 4 +- docker-compose.yml | 6 +- docs/configuration.md | 8 +- docs/modes.md | 4 +- docs/production-setup.md | 4 +- event/event.go | 4 +- go.mod | 2 + go.sum | 4 + sequencer/finalizer.go | 72 +++++++++------ sequencer/finalizer_test.go | 61 +++++++------ sequencer/sequencer.go | 13 ++- sequencer/worker.go | 47 +++++++--- sequencer/worker_test.go | 6 +- synchronizer/synchronizer.go | 87 +++++++++++++++---- synchronizer/synchronizer_test.go | 17 ++++ .../test.permissionless.prover.config.json | 2 +- test/config/test.prover.config.json | 2 +- 25 files changed, 252 insertions(+), 111 deletions(-) rename config/environments/mainnet/{public.node.config.toml => node.config.toml} (100%) rename config/environments/mainnet/{public.prover.config.json => prover.config.json} (98%) rename config/environments/testnet/{testnet.node.config.toml => node.config.toml} (100%) rename config/environments/testnet/{testnet.prover.config.json => prover.config.json} (98%) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ba2440b620..ee1810d11e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -46,12 +46,12 @@ jobs: sed -i -e "s/image: zkevm-node/image: hermeznetwork\/zkevm-node:$GIT_TAG_NAME/g" testnet/docker-compose.yml zip -r testnet.zip testnet # MAINNET - mkdir -p mainnet/config/environments/testnet + mkdir -p mainnet/config/environments/mainnet mkdir -p mainnet/db/scripts - cp config/environments/mainnet/* mainnet/config/environments/testnet + cp config/environments/mainnet/* mainnet/config/environments/mainnet cp docker-compose.yml mainnet cp db/scripts/init_prover_db.sql mainnet/db/scripts - mv mainnet/config/environments/testnet/example.env mainnet + mv mainnet/config/environments/mainnet/example.env mainnet sed -i -e "s/image: zkevm-node/image: hermeznetwork\/zkevm-node:$GIT_TAG_NAME/g" mainnet/docker-compose.yml zip -r mainnet.zip mainnet @@ -61,4 +61,4 @@ jobs: files: 'testnet.zip;mainnet.zip' repo-token: ${{ secrets.TOKEN_RELEASE }} release-tag: ${{ steps.tagName.outputs.tag }} - \ No newline at end of file + diff --git a/Dockerfile b/Dockerfile index 8719b468b5..69829d3151 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,7 @@ RUN cd /src && make build # CONTAINER FOR RUNNING BINARY FROM alpine:3.18.0 COPY --from=build /src/dist/zkevm-node /app/zkevm-node -COPY --from=build /src/config/environments/testnet/testnet.node.config.toml /app/example.config.toml +COPY --from=build /src/config/environments/testnet/node.config.toml /app/example.config.toml RUN apk update && apk add postgresql15-client EXPOSE 8123 CMD ["/bin/sh", "-c", "/app/zkevm-node run"] diff --git a/README.md b/README.md index e23883dd41..158d990caa 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,6 @@ It's recommended to use `make` for building, and testing the code, ... Run `make ## Contribute -Before opening a pull request, please read this [guide](CONTRIBUTING.md) +Before opening a pull request, please read this [guide](CONTRIBUTING.md). diff --git a/config/config.go b/config/config.go index 98f28fc48d..9660faac75 100644 --- a/config/config.go +++ b/config/config.go @@ -61,8 +61,8 @@ Config represents the configuration of the entire Hermez Node The file is [TOML format] You could find some examples: - `config/environments/local/local.node.config.toml`: running a permisionless node - - `config/environments/mainnet/public.node.config.toml` - - `config/environments/public/public.node.config.toml` + - `config/environments/mainnet/node.config.toml` + - `config/environments/public/node.config.toml` - `test/config/test.node.config.toml`: configuration for a trusted node used in CI [TOML format]: https://en.wikipedia.org/wiki/TOML diff --git a/config/environments/mainnet/public.node.config.toml b/config/environments/mainnet/node.config.toml similarity index 100% rename from config/environments/mainnet/public.node.config.toml rename to config/environments/mainnet/node.config.toml diff --git a/config/environments/mainnet/public.prover.config.json b/config/environments/mainnet/prover.config.json similarity index 98% rename from config/environments/mainnet/public.prover.config.json rename to config/environments/mainnet/prover.config.json index 8fcc181715..0aec07e541 100644 --- a/config/environments/mainnet/public.prover.config.json +++ b/config/environments/mainnet/prover.config.json @@ -112,5 +112,5 @@ "maxHashDBThreads": 8, "ECRecoverPrecalc": true, "ECRecoverPrecalcNThreads": 16, - "dbMultiWriteSinglePosition": true + "dbMultiWriteSinglePosition": false } diff --git a/config/environments/testnet/testnet.node.config.toml b/config/environments/testnet/node.config.toml similarity index 100% rename from config/environments/testnet/testnet.node.config.toml rename to config/environments/testnet/node.config.toml diff --git a/config/environments/testnet/testnet.prover.config.json b/config/environments/testnet/prover.config.json similarity index 98% rename from config/environments/testnet/testnet.prover.config.json rename to config/environments/testnet/prover.config.json index 8fcc181715..0aec07e541 100644 --- a/config/environments/testnet/testnet.prover.config.json +++ b/config/environments/testnet/prover.config.json @@ -112,5 +112,5 @@ "maxHashDBThreads": 8, "ECRecoverPrecalc": true, "ECRecoverPrecalcNThreads": 16, - "dbMultiWriteSinglePosition": true + "dbMultiWriteSinglePosition": false } diff --git a/config/gen_json_schema_test.go b/config/gen_json_schema_test.go index f43724c084..a5f2632f66 100644 --- a/config/gen_json_schema_test.go +++ b/config/gen_json_schema_test.go @@ -25,8 +25,8 @@ The file is [TOML format](https://en.wikipedia.org/wiki/TOML#) You could find some examples: - `config/environments/local/local.node.config.toml`: running a permisionless node -- `config/environments/mainnet/public.node.config.toml` -- `config/environments/public/public.node.config.toml` +- `config/environments/mainnet/node.config.toml` +- `config/environments/public/node.config.toml` - `test/config/test.node.config.toml`: configuration for a trusted node used in CI */ type MyTestConfig struct { diff --git a/docker-compose.yml b/docker-compose.yml index 27a64e0780..0755564b29 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -26,7 +26,7 @@ services: environment: - ZKEVM_NODE_ETHERMAN_URL=${ZKEVM_NODE_ETHERMAN_URL} volumes: - - ${ZKEVM_ADVANCED_CONFIG_DIR:-./config/environments/testnet}/testnet.node.config.toml:/app/config.toml + - ${ZKEVM_ADVANCED_CONFIG_DIR:-./config/environments/testnet}/node.config.toml:/app/config.toml command: - "/bin/sh" - "-c" @@ -50,7 +50,7 @@ services: environment: - ZKEVM_NODE_ETHERMAN_URL=${ZKEVM_NODE_ETHERMAN_URL} volumes: - - ${ZKEVM_ADVANCED_CONFIG_DIR:-./config/environments/testnet}/testnet.node.config.toml:/app/config.toml + - ${ZKEVM_ADVANCED_CONFIG_DIR:-./config/environments/testnet}/node.config.toml:/app/config.toml command: - "/bin/sh" - "-c" @@ -115,6 +115,6 @@ services: - 50061:50061 # MT - 50071:50071 # Executor volumes: - - ${ZKEVM_ADVANCED_CONFIG_DIR:-./config/environments/testnet}/testnet.prover.config.json:/usr/src/app/config.json + - ${ZKEVM_ADVANCED_CONFIG_DIR:-./config/environments/testnet}/prover.config.json:/usr/src/app/config.json command: > zkProver -c /usr/src/app/config.json diff --git a/docs/configuration.md b/docs/configuration.md index f38df7dfb1..a61bcac3c8 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -9,8 +9,8 @@ To configure a node you need 3 files: This file is a [TOML](https://en.wikipedia.org/wiki/TOML#) formatted file. You could find some examples here: - `config/environments/local/local.node.config.toml`: running a permisionless node - - `config/environments/mainnet/public.node.config.toml` - - `config/environments/public/public.node.config.toml` + - `config/environments/mainnet/node.config.toml` + - `config/environments/public/node.config.toml` - `test/config/test.node.config.toml`: configuration for a trusted node used in CI For details about the contents you can read specifications [here](config-file/node-config-doc.md) @@ -38,5 +38,5 @@ For details about the contents you can read specifications [here](config-file/cu Please check [prover repository](https://github.com/0xPolygonHermez/zkevm-prover) for further information Examples: - - `config/environments/mainnet/public.prover.config.json` - - `config/environments/testnet/testnet.prover.config.json` + - `config/environments/mainnet/prover.config.json` + - `config/environments/testnet/prover.config.json` diff --git a/docs/modes.md b/docs/modes.md index 64396de18b..3d264912c6 100644 --- a/docs/modes.md +++ b/docs/modes.md @@ -19,7 +19,7 @@ By default the config files found in the repository will spin up the Node in JSO This will syncronize with the Trusted Sequencer (run by Polygon). -Use the default [testnet config file](https://github.com/0xPolygonHermez/zkevm-node/blob/develop/config/environments/testnet/testnet.node.config.toml), and make sure the following values are set to: +Use the default [testnet config file](https://github.com/0xPolygonHermez/zkevm-node/blob/develop/config/environments/testnet/node.config.toml), and make sure the following values are set to: ```toml [RPC] @@ -78,7 +78,7 @@ Machine 1: #### Machine 1 -Use default [prover config](https://github.com/0xPolygonHermez/zkevm-node/blob/develop/config/environments/testnet/testnet.prover.config.json) but change the following values (`runProverServer` set to true, rest false): +Use default [prover config](https://github.com/0xPolygonHermez/zkevm-node/blob/develop/config/environments/testnet/prover.config.json) but change the following values (`runProverServer` set to true, rest false): For *only* Prover Config (`only-prover-config.json`): diff --git a/docs/production-setup.md b/docs/production-setup.md index 4e71cd3c81..1d66ee8e56 100644 --- a/docs/production-setup.md +++ b/docs/production-setup.md @@ -87,8 +87,8 @@ In the basic setup, there are Postgres being instanciated as Docker containers. - Run dedicated instances for Postgres. To achieve this you will need to: - Remove the Postgres services (`zkevm-pool-db` and `zkevm-state-db`) from the `docker-compose.yml` - Instantiate Postgres elsewhere (note that you will have to create credentials and run some queries to make this work, following the config files and docker-compose should give a clear idea of what to do) - - Update the `testnet.node.config.toml` to use the correct URI for both DBs - - Update `testnet.prover.config.json` to use the correct URI for the state DB + - Update the `node.config.toml` to use the correct URI for both DBs + - Update `prover.config.json` to use the correct URI for the state DB - Use a setup of Postgres that allows to have separated endpoints for read / write replicas ### JSON RPC diff --git a/event/event.go b/event/event.go index 5da961eb14..f05e65d634 100644 --- a/event/event.go +++ b/event/event.go @@ -36,8 +36,8 @@ const ( EventID_FinalizerRestart EventID = "FINALIZER RESTART" // EventID_FinalizerBreakEvenGasPriceBigDifference is triggered when the finalizer recalculates the break even gas price and detects a big difference EventID_FinalizerBreakEvenGasPriceBigDifference EventID = "FINALIZER BREAK EVEN GAS PRICE BIG DIFFERENCE" - // EventID_SynchonizerRestart is triggered when the Synchonizer restarts - EventID_SynchonizerRestart EventID = "SYNCHRONIZER RESTART" + // EventID_SynchronizerRestart is triggered when the Synchonizer restarts + EventID_SynchronizerRestart EventID = "SYNCHRONIZER RESTART" // Source_Node is the source of the event Source_Node Source = "node" diff --git a/go.mod b/go.mod index c5f3fcb246..7030449b8e 100644 --- a/go.mod +++ b/go.mod @@ -127,10 +127,12 @@ require ( go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.8.0 // indirect golang.org/x/exp v0.0.0-20230206171751-46f607a40771 // indirect + golang.org/x/mod v0.9.0 // indirect golang.org/x/sys v0.10.0 // indirect golang.org/x/term v0.10.0 // indirect golang.org/x/text v0.11.0 // indirect golang.org/x/time v0.1.0 // indirect + golang.org/x/tools v0.7.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect diff --git a/go.sum b/go.sum index 2189fd5796..37a9bf79f9 100644 --- a/go.sum +++ b/go.sum @@ -825,6 +825,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1080,6 +1082,8 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/sequencer/finalizer.go b/sequencer/finalizer.go index 52311e3bf5..6e39945075 100644 --- a/sequencer/finalizer.go +++ b/sequencer/finalizer.go @@ -60,14 +60,15 @@ type finalizer struct { maxBreakEvenGasPriceDeviationPercentage *big.Int defaultMinGasPriceAllowed uint64 // Processed txs - pendingTransactionsToStore chan transactionToStore - pendingTransactionsToStoreWG *sync.WaitGroup - pendingTransactionsToStoreMux *sync.RWMutex - storedFlushID uint64 - storedFlushIDCond *sync.Cond - proverID string - lastPendingFlushID uint64 - pendingFlushIDCond *sync.Cond + pendingTxsToStore chan transactionToStore + pendingTxsToStoreWG *sync.WaitGroup + pendingTxsToStoreMux *sync.RWMutex + pendingTxsPerAddressTrackers map[common.Address]*pendingTxPerAddressTracker + storedFlushID uint64 + storedFlushIDCond *sync.Cond + proverID string + lastPendingFlushID uint64 + pendingFlushIDCond *sync.Cond } type transactionToStore struct { @@ -113,6 +114,8 @@ func newFinalizer( closingSignalCh ClosingSignalCh, batchConstraints batchConstraints, eventLog *event.EventLog, + pendingTxsToStoreMux *sync.RWMutex, + pendingTxsPerAddressTrackers map[common.Address]*pendingTxPerAddressTracker, ) *finalizer { return &finalizer{ cfg: cfg, @@ -139,9 +142,10 @@ func newFinalizer( // event log eventLog: eventLog, maxBreakEvenGasPriceDeviationPercentage: new(big.Int).SetUint64(effectiveGasPriceCfg.MaxBreakEvenGasPriceDeviationPercentage), - pendingTransactionsToStore: make(chan transactionToStore, batchConstraints.MaxTxsPerBatch*pendingTxsBufferSizeMultiplier), - pendingTransactionsToStoreWG: new(sync.WaitGroup), - pendingTransactionsToStoreMux: &sync.RWMutex{}, + pendingTxsToStore: make(chan transactionToStore, batchConstraints.MaxTxsPerBatch*pendingTxsBufferSizeMultiplier), + pendingTxsToStoreWG: new(sync.WaitGroup), + pendingTxsToStoreMux: pendingTxsToStoreMux, + pendingTxsPerAddressTrackers: pendingTxsPerAddressTrackers, storedFlushID: 0, // Mutex is unlocked when the condition is broadcasted storedFlushIDCond: sync.NewCond(&sync.Mutex{}), @@ -395,7 +399,7 @@ func (f *finalizer) checkProverIDAndUpdateStoredFlushID(storedFlushID uint64, pr func (f *finalizer) storePendingTransactions(ctx context.Context) { for { select { - case tx, ok := <-f.pendingTransactionsToStore: + case tx, ok := <-f.pendingTxsToStore: if !ok { // Channel is closed return @@ -415,10 +419,19 @@ func (f *finalizer) storePendingTransactions(ctx context.Context) { // Now f.storedFlushID >= tx.flushId, you can store tx f.storeProcessedTx(ctx, tx) - f.pendingTransactionsToStoreWG.Done() + f.pendingTxsToStoreMux.Lock() + f.pendingTxsToStoreWG.Done() + f.pendingTxsPerAddressTrackers[tx.txTracker.From].wg.Done() + f.pendingTxsPerAddressTrackers[tx.txTracker.From].count-- + // Needed to avoid memory leaks + if f.pendingTxsPerAddressTrackers[tx.txTracker.From].count == 0 { + delete(f.pendingTxsPerAddressTrackers, tx.txTracker.From) + } + f.pendingTxsToStoreMux.Unlock() + case <-ctx.Done(): // The context was cancelled from outside, Wait for all goroutines to finish, cleanup and exit - f.pendingTransactionsToStoreWG.Wait() + f.pendingTxsToStoreWG.Wait() return default: time.Sleep(100 * time.Millisecond) //nolint:gomnd @@ -433,7 +446,7 @@ func (f *finalizer) newWIPBatch(ctx context.Context) (*WipBatch, error) { // Wait until all processed transactions are saved startWait := time.Now() - f.pendingTransactionsToStoreWG.Wait() + f.pendingTxsToStoreWG.Wait() endWait := time.Now() log.Info("waiting for pending transactions to be stored took: ", endWait.Sub(startWait).String()) @@ -672,18 +685,18 @@ func (f *finalizer) handleProcessTransactionResponse(ctx context.Context, tx *Tx flushId: result.FlushID, } - f.pendingTransactionsToStoreMux.Lock() - f.pendingTransactionsToStoreWG.Add(1) + f.pendingTxsToStoreMux.Lock() + f.pendingTxsToStoreWG.Add(1) if result.FlushID > f.lastPendingFlushID { f.lastPendingFlushID = result.FlushID f.pendingFlushIDCond.Broadcast() } - f.pendingTransactionsToStoreMux.Unlock() + f.pendingTxsToStoreMux.Unlock() select { - case f.pendingTransactionsToStore <- processedTransaction: + case f.pendingTxsToStore <- processedTransaction: case <-ctx.Done(): // If context is cancelled before we can send to the channel, we must decrement the WaitGroup count - f.pendingTransactionsToStoreWG.Done() + f.pendingTxsToStoreWG.Done() } f.batch.countOfTxs++ @@ -721,26 +734,35 @@ func (f *finalizer) handleForcedTxsProcessResp(ctx context.Context, request stat flushId: result.FlushID, } - f.pendingTransactionsToStoreMux.Lock() - f.pendingTransactionsToStoreWG.Add(1) + f.pendingTxsToStoreMux.Lock() + f.pendingTxsToStoreWG.Add(1) if result.FlushID > f.lastPendingFlushID { f.lastPendingFlushID = result.FlushID f.pendingFlushIDCond.Broadcast() } - f.pendingTransactionsToStoreMux.Unlock() + f.pendingTxsToStoreMux.Unlock() oldStateRoot = txResp.StateRoot select { - case f.pendingTransactionsToStore <- processedTransaction: + case f.pendingTxsToStore <- processedTransaction: case <-ctx.Done(): // If context is cancelled before we can send to the channel, we must decrement the WaitGroup count - f.pendingTransactionsToStoreWG.Done() + f.pendingTxsToStoreWG.Done() } } } // storeProcessedTx stores the processed transaction in the database. func (f *finalizer) storeProcessedTx(ctx context.Context, txToStore transactionToStore) { + f.pendingTxsToStoreMux.Lock() + if _, ok := f.pendingTxsPerAddressTrackers[txToStore.txTracker.From]; !ok { + f.pendingTxsPerAddressTrackers[txToStore.txTracker.From] = new(pendingTxPerAddressTracker) + f.pendingTxsPerAddressTrackers[txToStore.txTracker.From].wg = &sync.WaitGroup{} + } + f.pendingTxsPerAddressTrackers[txToStore.txTracker.From].wg.Add(1) + f.pendingTxsPerAddressTrackers[txToStore.txTracker.From].count++ + f.pendingTxsToStoreMux.Unlock() + if txToStore.response != nil { log.Infof("storeProcessedTx: storing processed txToStore: %s", txToStore.response.TxHash.String()) } else { diff --git a/sequencer/finalizer_test.go b/sequencer/finalizer_test.go index 74751f453c..173bbf180e 100644 --- a/sequencer/finalizer_test.go +++ b/sequencer/finalizer_test.go @@ -118,7 +118,9 @@ func TestNewFinalizer(t *testing.T) { dbManagerMock.On("GetLastSentFlushID", context.Background()).Return(uint64(0), nil) // arrange and act - f = newFinalizer(cfg, effectiveGasPriceCfg, workerMock, dbManagerMock, executorMock, seqAddr, isSynced, closingSignalCh, bc, eventLog) + pendingTxsToStoreMux := new(sync.RWMutex) + pendingTxsPerAddressTrackers := make(map[common.Address]*pendingTxPerAddressTracker) + f = newFinalizer(cfg, effectiveGasPriceCfg, workerMock, dbManagerMock, executorMock, seqAddr, isSynced, closingSignalCh, bc, eventLog, pendingTxsToStoreMux, pendingTxsPerAddressTrackers) // assert assert.NotNil(t, f) @@ -268,14 +270,14 @@ func TestFinalizer_handleProcessTransactionResponse(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { storedTxs := make([]transactionToStore, 0) - f.pendingTransactionsToStore = make(chan transactionToStore) + f.pendingTxsToStore = make(chan transactionToStore) if tc.expectedStoredTx.batchResponse != nil { done = make(chan bool) // init a new done channel go func() { - for tx := range f.pendingTransactionsToStore { + for tx := range f.pendingTxsToStore { storedTxs = append(storedTxs, tx) - f.pendingTransactionsToStoreWG.Done() + f.pendingTxsToStoreWG.Done() } done <- true // signal that the goroutine is done }() @@ -311,9 +313,9 @@ func TestFinalizer_handleProcessTransactionResponse(t *testing.T) { } if tc.expectedStoredTx.batchResponse != nil { - close(f.pendingTransactionsToStore) // close the channel - <-done // wait for the goroutine to finish - f.pendingTransactionsToStoreWG.Wait() + close(f.pendingTxsToStore) // close the channel + <-done // wait for the goroutine to finish + f.pendingTxsToStoreWG.Wait() require.Len(t, storedTxs, 1) actualTx := storedTxs[0] assertEqualTransactionToStore(t, tc.expectedStoredTx, actualTx) @@ -893,13 +895,13 @@ func TestFinalizer_processForcedBatches(t *testing.T) { var newStateRoot common.Hash stateRoot := oldHash storedTxs := make([]transactionToStore, 0) - f.pendingTransactionsToStore = make(chan transactionToStore) + f.pendingTxsToStore = make(chan transactionToStore) if tc.expectedStoredTx != nil && len(tc.expectedStoredTx) > 0 { done = make(chan bool) // init a new done channel go func() { - for tx := range f.pendingTransactionsToStore { + for tx := range f.pendingTxsToStore { storedTxs = append(storedTxs, tx) - f.pendingTransactionsToStoreWG.Done() + f.pendingTxsToStoreWG.Done() } done <- true // signal that the goroutine is done }() @@ -957,9 +959,9 @@ func TestFinalizer_processForcedBatches(t *testing.T) { assert.EqualError(t, err, tc.expectedErr.Error()) } else { if tc.expectedStoredTx != nil && len(tc.expectedStoredTx) > 0 { - close(f.pendingTransactionsToStore) // ensure the channel is closed - <-done // wait for the goroutine to finish - f.pendingTransactionsToStoreWG.Wait() + close(f.pendingTxsToStore) // ensure the channel is closed + <-done // wait for the goroutine to finish + f.pendingTxsToStoreWG.Wait() for i := range tc.expectedStoredTx { require.Equal(t, tc.expectedStoredTx[i], storedTxs[i]) } @@ -1494,13 +1496,13 @@ func Test_processTransaction(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { storedTxs := make([]transactionToStore, 0) - f.pendingTransactionsToStore = make(chan transactionToStore, 1) + f.pendingTxsToStore = make(chan transactionToStore, 1) if tc.expectedStoredTx.batchResponse != nil { done = make(chan bool) // init a new done channel go func() { - for tx := range f.pendingTransactionsToStore { + for tx := range f.pendingTxsToStore { storedTxs = append(storedTxs, tx) - f.pendingTransactionsToStoreWG.Done() + f.pendingTxsToStoreWG.Done() } done <- true // signal that the goroutine is done }() @@ -1523,9 +1525,9 @@ func Test_processTransaction(t *testing.T) { errWg, err := f.processTransaction(tc.ctx, tc.tx) if tc.expectedStoredTx.batchResponse != nil { - close(f.pendingTransactionsToStore) // ensure the channel is closed - <-done // wait for the goroutine to finish - f.pendingTransactionsToStoreWG.Wait() + close(f.pendingTxsToStore) // ensure the channel is closed + <-done // wait for the goroutine to finish + f.pendingTxsToStoreWG.Wait() require.Equal(t, tc.expectedStoredTx, storedTxs[0]) } if tc.expectedErr != nil { @@ -1679,19 +1681,19 @@ func Test_handleForcedTxsProcessResp(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { storedTxs := make([]transactionToStore, 0) - f.pendingTransactionsToStore = make(chan transactionToStore) + f.pendingTxsToStore = make(chan transactionToStore) // Mock storeProcessedTx to store txs into the storedTxs slice go func() { - for tx := range f.pendingTransactionsToStore { + for tx := range f.pendingTxsToStore { storedTxs = append(storedTxs, tx) - f.pendingTransactionsToStoreWG.Done() + f.pendingTxsToStoreWG.Done() } }() f.handleForcedTxsProcessResp(ctx, tc.request, tc.result, tc.oldStateRoot) - f.pendingTransactionsToStoreWG.Wait() + f.pendingTxsToStoreWG.Wait() require.Nil(t, err) require.Equal(t, len(tc.expectedStoredTxs), len(storedTxs)) for i := 0; i < len(tc.expectedStoredTxs); i++ { @@ -1733,6 +1735,9 @@ func TestFinalizer_storeProcessedTx(t *testing.T) { response: &state.ProcessTransactionResponse{ TxHash: txHash, }, + txTracker: &TxTracker{ + From: senderAddr, + }, isForcedBatch: false, }, }, @@ -1755,6 +1760,9 @@ func TestFinalizer_storeProcessedTx(t *testing.T) { TxHash: txHash2, }, isForcedBatch: true, + txTracker: &TxTracker{ + From: senderAddr, + }, }, }, } @@ -2445,9 +2453,10 @@ func setupFinalizer(withWipBatch bool) *finalizer { handlingL2Reorg: false, eventLog: eventLog, maxBreakEvenGasPriceDeviationPercentage: big.NewInt(10), - pendingTransactionsToStore: make(chan transactionToStore, bc.MaxTxsPerBatch*pendingTxsBufferSizeMultiplier), - pendingTransactionsToStoreWG: new(sync.WaitGroup), - pendingTransactionsToStoreMux: new(sync.RWMutex), + pendingTxsToStore: make(chan transactionToStore, bc.MaxTxsPerBatch*pendingTxsBufferSizeMultiplier), + pendingTxsToStoreWG: new(sync.WaitGroup), + pendingTxsToStoreMux: new(sync.RWMutex), + pendingTxsPerAddressTrackers: make(map[common.Address]*pendingTxPerAddressTracker), storedFlushID: 0, storedFlushIDCond: sync.NewCond(new(sync.Mutex)), proverID: "", diff --git a/sequencer/sequencer.go b/sequencer/sequencer.go index ef41799b03..49f285eb47 100644 --- a/sequencer/sequencer.go +++ b/sequencer/sequencer.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "sync" "time" "github.com/0xPolygonHermez/zkevm-node/event" @@ -67,6 +68,12 @@ type ClosingSignalCh struct { L2ReorgCh chan L2ReorgEvent } +// pendingTxPerAddressTracker is a struct that tracks the number of pending transactions per address +type pendingTxPerAddressTracker struct { + wg *sync.WaitGroup + count uint +} + // New init sequencer func New(cfg Config, txPool txPool, state stateInterface, etherman etherman, manager ethTxManager, eventLog *event.EventLog) (*Sequencer, error) { addr, err := etherman.TrustedSequencer() @@ -127,12 +134,14 @@ func (s *Sequencer) Start(ctx context.Context) { if err != nil { log.Fatalf("failed to mark WIP txs as pending, err: %v", err) } + pendingTxsToStoreMux := new(sync.RWMutex) + pendingTxTrackerPerAddress := make(map[common.Address]*pendingTxPerAddressTracker) - worker := NewWorker(s.cfg.Worker, s.state, batchConstraints, batchResourceWeights) + worker := NewWorker(s.cfg.Worker, s.state, batchConstraints, batchResourceWeights, pendingTxsToStoreMux, pendingTxTrackerPerAddress) dbManager := newDBManager(ctx, s.cfg.DBManager, s.pool, s.state, worker, closingSignalCh, batchConstraints) go dbManager.Start() - finalizer := newFinalizer(s.cfg.Finalizer, s.cfg.EffectiveGasPrice, worker, dbManager, s.state, s.address, s.isSynced, closingSignalCh, batchConstraints, s.eventLog) + finalizer := newFinalizer(s.cfg.Finalizer, s.cfg.EffectiveGasPrice, worker, dbManager, s.state, s.address, s.isSynced, closingSignalCh, batchConstraints, s.eventLog, pendingTxsToStoreMux, pendingTxTrackerPerAddress) currBatch, processingReq := s.bootstrap(ctx, dbManager, finalizer) go finalizer.Start(ctx, currBatch, processingReq) diff --git a/sequencer/worker.go b/sequencer/worker.go index cba6938f7c..d0a3e44874 100644 --- a/sequencer/worker.go +++ b/sequencer/worker.go @@ -16,24 +16,35 @@ import ( // Worker represents the worker component of the sequencer type Worker struct { - cfg WorkerCfg - pool map[string]*addrQueue - efficiencyList *efficiencyList - workerMutex sync.Mutex - state stateInterface - batchConstraints batchConstraintsFloat64 - batchResourceWeights batchResourceWeights + cfg WorkerCfg + pool map[string]*addrQueue + efficiencyList *efficiencyList + workerMutex sync.Mutex + state stateInterface + batchConstraints batchConstraintsFloat64 + batchResourceWeights batchResourceWeights + pendingTxsToStoreMux *sync.RWMutex + pendingTxsPerAddressTrackers map[common.Address]*pendingTxPerAddressTracker } // NewWorker creates an init a worker -func NewWorker(cfg WorkerCfg, state stateInterface, constraints batchConstraints, weights batchResourceWeights) *Worker { +func NewWorker( + cfg WorkerCfg, + state stateInterface, + constraints batchConstraints, + weights batchResourceWeights, + pendingTxsToStoreMux *sync.RWMutex, + pendingTxTrackersPerAddress map[common.Address]*pendingTxPerAddressTracker, +) *Worker { w := Worker{ - cfg: cfg, - pool: make(map[string]*addrQueue), - efficiencyList: newEfficiencyList(), - state: state, - batchConstraints: convertBatchConstraintsToFloat64(constraints), - batchResourceWeights: weights, + cfg: cfg, + pool: make(map[string]*addrQueue), + efficiencyList: newEfficiencyList(), + state: state, + batchConstraints: convertBatchConstraintsToFloat64(constraints), + batchResourceWeights: weights, + pendingTxsToStoreMux: pendingTxsToStoreMux, + pendingTxsPerAddressTrackers: pendingTxTrackersPerAddress, } return &w @@ -55,6 +66,14 @@ func (w *Worker) AddTxTracker(ctx context.Context, tx *TxTracker) (replacedTx *T // Unlock the worker to let execute other worker functions while creating the new AddrQueue w.workerMutex.Unlock() + // Wait until all pending transactions are stored, so we can ensure getting the correct nonce and balance of the new AddrQueue + w.pendingTxsToStoreMux.RLock() + pendingTxsTracker, ok := w.pendingTxsPerAddressTrackers[tx.From] + w.pendingTxsToStoreMux.RUnlock() + if ok && pendingTxsTracker.wg != nil { + pendingTxsTracker.wg.Wait() + } + root, err := w.state.GetLastStateRoot(ctx, nil) if err != nil { dropReason = fmt.Errorf("AddTx GetLastStateRoot error: %v", err) diff --git a/sequencer/worker_test.go b/sequencer/worker_test.go index 51787f188c..1d254984f5 100644 --- a/sequencer/worker_test.go +++ b/sequencer/worker_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "math/big" + "sync" "testing" "github.com/0xPolygonHermez/zkevm-node/state" @@ -307,6 +308,9 @@ func TestWorkerGetBestTx(t *testing.T) { } func initWorker(stateMock *StateMock, rcMax batchConstraints, rcWeigth batchResourceWeights) *Worker { - worker := NewWorker(workerCfg, stateMock, rcMax, rcWeigth) + pendingTxsToStoreMux := new(sync.RWMutex) + pendingTxsPerAddressTrackers := make(map[common.Address]*pendingTxPerAddressTracker) + worker := NewWorker(workerCfg, stateMock, rcMax, rcWeigth, pendingTxsToStoreMux, pendingTxsPerAddressTrackers) + return worker } diff --git a/synchronizer/synchronizer.go b/synchronizer/synchronizer.go index 7695799027..eb2d5d59e9 100644 --- a/synchronizer/synchronizer.go +++ b/synchronizer/synchronizer.go @@ -234,7 +234,7 @@ func (s *ClientSynchronizer) Sync() error { log.Warn("error setting latest batch info into db. Error: ", err) continue } - + log.Infof("latestSequencedBatchNumber: %d, latestSyncedBatch: %d, lastVerifiedBatchNumber: %d", latestSequencedBatchNumber, latestSyncedBatch, lastVerifiedBatchNumber) // Sync trusted state if latestSyncedBatch >= latestSequencedBatchNumber { startTrusted := time.Now() @@ -1251,8 +1251,22 @@ func (s *ClientSynchronizer) processTrustedBatch(trustedBatch *types.Batch, dbTx // Find txs to be processed and included in the trusted state if *s.trustedState.lastStateRoot == batches[1].StateRoot { + // Delete txs that were stored before restart. We need to reprocess all txs because the intermediary stateRoot is only stored in memory + err := s.state.ResetTrustedState(s.ctx, uint64(trustedBatch.Number)-1, dbTx) + if err != nil { + log.Error("error resetting trusted state. Error: ", err) + return nil, nil, err + } // All txs need to be processed request.Transactions = trustedBatchL2Data + // Reopen batch + err = s.openBatch(trustedBatch, dbTx) + if err != nil { + log.Error("error openning batch. Error: ", err) + return nil, nil, err + } + request.GlobalExitRoot = trustedBatch.GlobalExitRoot + request.Transactions = trustedBatchL2Data } else { // Only new txs need to be processed storedTxs, syncedTxs, _, syncedEfficiencyPercentages, err := s.decodeTxs(trustedBatchL2Data, batches) @@ -1285,8 +1299,36 @@ func (s *ClientSynchronizer) processTrustedBatch(trustedBatch *types.Batch, dbTx } log.Debugf("closing batch %v", trustedBatch.Number) if err := s.state.CloseBatch(s.ctx, receipt, dbTx); err != nil { - log.Errorf("error closing batch %d", trustedBatch.Number) - return nil, nil, err + // This is a workaround to avoid closing a batch that was already closed + if err.Error() != state.ErrBatchAlreadyClosed.Error() { + log.Errorf("error closing batch %d", trustedBatch.Number) + return nil, nil, err + } else { + log.Warnf("CASE 02: the batch [%d] was already closed", trustedBatch.Number) + log.Info("batches[0].BatchNumber: ", batches[0].BatchNumber) + log.Info("batches[0].AccInputHash: ", batches[0].AccInputHash) + log.Info("batches[0].StateRoot: ", batches[0].StateRoot) + log.Info("batches[0].LocalExitRoot: ", batches[0].LocalExitRoot) + log.Info("batches[0].GlobalExitRoot: ", batches[0].GlobalExitRoot) + log.Info("batches[0].Coinbase: ", batches[0].Coinbase) + log.Info("batches[0].ForcedBatchNum: ", batches[0].ForcedBatchNum) + log.Info("####################################") + log.Info("batches[1].BatchNumber: ", batches[1].BatchNumber) + log.Info("batches[1].AccInputHash: ", batches[1].AccInputHash) + log.Info("batches[1].StateRoot: ", batches[1].StateRoot) + log.Info("batches[1].LocalExitRoot: ", batches[1].LocalExitRoot) + log.Info("batches[1].GlobalExitRoot: ", batches[1].GlobalExitRoot) + log.Info("batches[1].Coinbase: ", batches[1].Coinbase) + log.Info("batches[1].ForcedBatchNum: ", batches[1].ForcedBatchNum) + log.Info("###############################") + log.Info("trustedBatch.BatchNumber: ", trustedBatch.Number) + log.Info("trustedBatch.AccInputHash: ", trustedBatch.AccInputHash) + log.Info("trustedBatch.StateRoot: ", trustedBatch.StateRoot) + log.Info("trustedBatch.LocalExitRoot: ", trustedBatch.LocalExitRoot) + log.Info("trustedBatch.GlobalExitRoot: ", trustedBatch.GlobalExitRoot) + log.Info("trustedBatch.Coinbase: ", trustedBatch.Coinbase) + log.Info("trustedBatch.ForcedBatchNum: ", trustedBatch.ForcedBatchNumber) + } } batches[0].AccInputHash = trustedBatch.AccInputHash batches[0].StateRoot = trustedBatch.StateRoot @@ -1332,10 +1374,23 @@ func (s *ClientSynchronizer) processTrustedBatch(trustedBatch *types.Batch, dbTx BatchL2Data: trustedBatchL2Data, AccInputHash: trustedBatch.AccInputHash, } + log.Debugf("closing batch %v", trustedBatch.Number) if err := s.state.CloseBatch(s.ctx, receipt, dbTx); err != nil { - log.Errorf("error closing batch %d", trustedBatch.Number) - return nil, nil, err + // This is a workarround to avoid closing a batch that was already closed + if err.Error() != state.ErrBatchAlreadyClosed.Error() { + log.Errorf("error closing batch %d", trustedBatch.Number) + return nil, nil, err + } else { + log.Warnf("CASE 01: batch [%d] was already closed", trustedBatch.Number) + } + } + log.Info("Batch closed right after processing some tx") + if batches[0] != nil { + log.Debug("Updating batches[0] values...") + batches[0].AccInputHash = trustedBatch.AccInputHash + batches[0].StateRoot = trustedBatch.StateRoot + batches[0].LocalExitRoot = trustedBatch.LocalExitRoot } } @@ -1448,13 +1503,13 @@ func checkIfSynced(batches []*state.Batch, trustedBatch *types.Batch) bool { matchCoinbase && matchTimestamp && matchL2Data { return true } - log.Info("matchNumber", matchNumber) - log.Info("matchGER", matchGER) - log.Info("matchLER", matchLER) - log.Info("matchSR", matchSR) - log.Info("matchCoinbase", matchCoinbase) - log.Info("matchTimestamp", matchTimestamp) - log.Info("matchL2Data", matchL2Data) + log.Infof("matchNumber %v %d %d", matchNumber, batches[0].BatchNumber, uint64(trustedBatch.Number)) + log.Infof("matchGER %v %s %s", matchGER, batches[0].GlobalExitRoot.String(), trustedBatch.GlobalExitRoot.String()) + log.Infof("matchLER %v %s %s", matchLER, batches[0].LocalExitRoot.String(), trustedBatch.LocalExitRoot.String()) + log.Infof("matchSR %v %s %s", matchSR, batches[0].StateRoot.String(), trustedBatch.StateRoot.String()) + log.Infof("matchCoinbase %v %s %s", matchCoinbase, batches[0].Coinbase.String(), trustedBatch.Coinbase.String()) + log.Infof("matchTimestamp %v %d %d", matchTimestamp, uint64(batches[0].Timestamp.Unix()), uint64(trustedBatch.Timestamp)) + log.Infof("matchL2Data %v", matchL2Data) return false } @@ -1503,8 +1558,8 @@ func (s *ClientSynchronizer) updateAndCheckProverID(proverID string) { Source: event.Source_Node, Component: event.Component_Synchronizer, Level: event.Level_Critical, - EventID: event.EventID_SynchonizerRestart, - Description: fmt.Sprintf("proverID changed from %s to %s, restarting Synchonizer ", s.proverID, proverID), + EventID: event.EventID_SynchronizerRestart, + Description: fmt.Sprintf("proverID changed from %s to %s, restarting Synchronizer ", s.proverID, proverID), } err := s.eventLog.LogEvent(context.Background(), event) @@ -1536,7 +1591,7 @@ func (s *ClientSynchronizer) checkFlushID(dbTx pgx.Tx) error { s.updateAndCheckProverID(proverID) log.Debugf("storedFlushID (executor reported): %d, latestFlushID (pending): %d", storedFlushID, s.latestFlushID) if storedFlushID < s.latestFlushID { - log.Infof("Synchornized BLOCKED!: Wating for the flushID to be stored. FlushID to be stored: %d. Latest flushID stored: %d", s.latestFlushID, storedFlushID) + log.Infof("Synchronized BLOCKED!: Wating for the flushID to be stored. FlushID to be stored: %d. Latest flushID stored: %d", s.latestFlushID, storedFlushID) iteration := 0 start := time.Now() for storedFlushID < s.latestFlushID { @@ -1550,7 +1605,7 @@ func (s *ClientSynchronizer) checkFlushID(dbTx pgx.Tx) error { } iteration++ } - log.Infof("Synchornizer resumed, flushID stored: %d", s.latestFlushID) + log.Infof("Synchronizer resumed, flushID stored: %d", s.latestFlushID) } log.Infof("Pending Flushid fullfiled: %d, executor have write %d", s.latestFlushID, storedFlushID) s.latestFlushIDIsFulfilled = true diff --git a/synchronizer/synchronizer_test.go b/synchronizer/synchronizer_test.go index d8199eea79..f93c6f2874 100644 --- a/synchronizer/synchronizer_test.go +++ b/synchronizer/synchronizer_test.go @@ -686,6 +686,23 @@ func expectedCallsForsyncTrustedState(t *testing.T, m *mocks, sync *ClientSynchr Return(&stateBatchInPermissionLess, nil). Once() } + + m.State. + On("ResetTrustedState", sync.ctx, batchNumber-1, m.DbTx). + Return(nil). + Once() + + processCtx := state.ProcessingContext{ + BatchNumber: uint64(batchInTrustedNode.Number), + Coinbase: common.HexToAddress(batchInTrustedNode.Coinbase.String()), + Timestamp: time.Unix(int64(batchInTrustedNode.Timestamp), 0), + GlobalExitRoot: batchInTrustedNode.GlobalExitRoot, + } + m.State. + On("OpenBatch", sync.ctx, processCtx, m.DbTx). + Return(nil). + Once() + m.State. On("UpdateBatchL2Data", sync.ctx, batchNumber, stateBatchInTrustedNode.BatchL2Data, mock.Anything). Return(nil). diff --git a/test/config/test.permissionless.prover.config.json b/test/config/test.permissionless.prover.config.json index 24d4fbc67f..0f879c9c17 100644 --- a/test/config/test.permissionless.prover.config.json +++ b/test/config/test.permissionless.prover.config.json @@ -84,6 +84,6 @@ "ECRecoverPrecalc": true, "ECRecoverPrecalcNThreads": 16, - "dbMultiWriteSinglePosition": true + "dbMultiWriteSinglePosition": false } diff --git a/test/config/test.prover.config.json b/test/config/test.prover.config.json index 79d369b535..86f511f0c2 100644 --- a/test/config/test.prover.config.json +++ b/test/config/test.prover.config.json @@ -86,6 +86,6 @@ "ECRecoverPrecalc": true, "ECRecoverPrecalcNThreads": 16, - "dbMultiWriteSinglePosition": true + "dbMultiWriteSinglePosition": false } From 712daca72310e8cdbb303e9674914235829f98cc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Aug 2023 15:25:05 +0200 Subject: [PATCH 05/15] build(deps): bump golang.org/x/net from 0.12.0 to 0.13.0 (#2354) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.12.0 to 0.13.0. - [Commits](https://github.com/golang/net/compare/v0.12.0...v0.13.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 7030449b8e..ca20d89f9a 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/urfave/cli/v2 v2.25.7 go.uber.org/zap v1.24.0 golang.org/x/crypto v0.11.0 - golang.org/x/net v0.12.0 + golang.org/x/net v0.13.0 golang.org/x/sync v0.3.0 google.golang.org/grpc v1.57.0 google.golang.org/protobuf v1.31.0 diff --git a/go.sum b/go.sum index 37a9bf79f9..8f8142d063 100644 --- a/go.sum +++ b/go.sum @@ -880,8 +880,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= -golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY= +golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= From b27a6cc7d63d72e833020f184388d8a2a53491ee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Aug 2023 09:47:25 +0200 Subject: [PATCH 06/15] build(deps): bump go.uber.org/zap from 1.24.0 to 1.25.0 (#2361) Bumps [go.uber.org/zap](https://github.com/uber-go/zap) from 1.24.0 to 1.25.0. - [Release notes](https://github.com/uber-go/zap/releases) - [Changelog](https://github.com/uber-go/zap/blob/master/CHANGELOG.md) - [Commits](https://github.com/uber-go/zap/compare/v1.24.0...v1.25.0) --- updated-dependencies: - dependency-name: go.uber.org/zap dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 5 ++--- go.sum | 14 ++++++-------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index ca20d89f9a..6cd2cb8941 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/stretchr/testify v1.8.4 github.com/umbracle/ethgo v0.1.3 github.com/urfave/cli/v2 v2.25.7 - go.uber.org/zap v1.24.0 + go.uber.org/zap v1.25.0 golang.org/x/crypto v0.11.0 golang.org/x/net v0.13.0 golang.org/x/sync v0.3.0 @@ -124,8 +124,7 @@ require ( github.com/valyala/fastjson v1.4.1 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect - go.uber.org/atomic v1.9.0 // indirect - go.uber.org/multierr v1.8.0 // indirect + go.uber.org/multierr v1.10.0 // indirect golang.org/x/exp v0.0.0-20230206171751-46f607a40771 // indirect golang.org/x/mod v0.9.0 // indirect golang.org/x/sys v0.10.0 // indirect diff --git a/go.sum b/go.sum index 8f8142d063..0c7a7732ee 100644 --- a/go.sum +++ b/go.sum @@ -77,7 +77,7 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= -github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= @@ -745,22 +745,20 @@ go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= -go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= +go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= From a10a407ec4315d64d1d37c3674bc7aa156fb775f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20Ram=C3=ADrez?= <58293609+ToniRamirezM@users.noreply.github.com> Date: Mon, 7 Aug 2023 09:46:29 +0200 Subject: [PATCH 07/15] Merge v0.2.4 (includes v0.2.3) into Develop (#2376) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix null effective_percentage * fix forkID calculation * fix script * generate json-schema + docs for node config file and network_custom * fix unittest * Hotfixv0.1.4 to v0.2.0 (#2255) * Hotfix v0.1.4 to main (#2250) * fix concurrent web socket writes * fix eth_syncing * fix custom trace internal tx call error handling and update prover * add test to custom tracer depth issue; fix internal call error and gas used * fix custom tracer for internal tx with error and no more steps after it * remove debug code * Make max grpc message size configurable (#2179) * make max grpc message size configurable * fix state tests * fix tests * fix tests * get SequencerNodeURI from SC if empty and not IsTrustedSequencer * Optimize trace (#2183) * optimize trace * fix memory reading * update docker image * update prover image * fix converter * fix memory * fix step memory * fix structlogs * fix structlogs * fix structlogs * fix structlogs * fix structlogs * fix structlogs * fix structlogs * fix structlogs * update prover image * fix struclogs * fix memory size * fix memory size * fix memory size * refactor memory resize * refactor memory resize * move log for the best fitting tx (#2192) * fix load zkCounters from pool * remove unnecessary log.info * add custom tracer support to CREATES opcode without depth increase (#2213) * logs * fix getting stateroot from previous batch (GetWIPBatch) * logs * Fix GetWipBatch when previous last batch is a forced batch * fix forcedBatch trusted state * Revert "fix getting stateroot from previous batch (GetWIPBatch)" This reverts commit 860f0e74016219daf81f96b76f6b25609e1c66fd. * force GHA * add pool limits (#2189) * Hotfix/batch l2 data (#2223) * Fix BatchL2Data * Force GHA * remove failed txs from the pool limit check (#2233) * debug trace by batch number via external rpc requests (#2235) * fix trace batch remote requests in parallel limitation (#2244) * Added RPC.TraceBatchUseHTTPS config parameter * fix executor version --------- Co-authored-by: tclemos Co-authored-by: tclemos Co-authored-by: Toni Ramírez <58293609+ToniRamirezM@users.noreply.github.com> Co-authored-by: agnusmor Co-authored-by: agnusmor <100322135+agnusmor@users.noreply.github.com> Co-authored-by: Thiago Coimbra Lemos * fix test * fix test --------- Co-authored-by: tclemos Co-authored-by: tclemos Co-authored-by: Toni Ramírez <58293609+ToniRamirezM@users.noreply.github.com> Co-authored-by: agnusmor Co-authored-by: agnusmor <100322135+agnusmor@users.noreply.github.com> Co-authored-by: Thiago Coimbra Lemos * Effective GasPrice refactor+fixes (#2247) * effective GasPrice refactor * bugs fixes and finalizer tests fixes * fix typo * fix calculate effective gasprice percentage * fix test gas price * Fix/#2257 effective gas price receipt (#2258) * effective gas price returned by the rpc in the receipt * linter * bugfix: fixing l2blocks timestamp for the fist batch (#2260) * bugfix: fixing l2blocks timestamp for the fist batch Signed-off-by: Nikolay Nedkov * fix finalizer unit test --------- Signed-off-by: Nikolay Nedkov * add more comments, and removed fields PrivateKeyPath and PrivateKeyPassword from etherman.Config that are not in use * add info to git action * add info to git action * fix github action * updated comments * updated comments * Fix/#2263 gas used (#2264) * fix fea2scalar and gas used * suggestion * fix fea2scalar * suggestion * Fix pending tx when duplicate nonce (#2270) * fix pending tx when duplicate nonce * set pool.transaction.failed_reason to NULL when updating an existing tx * add more log details when adding tx to AddrQueue * fix query to add tx to the pool. Fix lint errors * change failed_reason for tx discarded due duplicate nonce * Only return a tx from the pool if tx is in pending status (#2273) * Return a tx from the pool only if it is * fix TestGetTransactionByHash --------- Co-authored-by: agnusmor * fix documentation with config file * improve: adding check to skip appending effectivePercentage if current forkId is under 5. Signed-off-by: Nikolay Nedkov * Fiex effectiveGasprice unsigned txs with forkId lower than 5 (#2278) * feat: adding functionality to stop sequencer on specific batch num from config param. Signed-off-by: Nikolay Nedkov * patch: adding print for X-Real-IP in JSON-RPC Signed-off-by: Nikolay Nedkov * Fix checkIfSynced (#2289) * [Rehashing] Check logs order and fix blockhash and blockNumber in the log conversion (#2280) * fix and check order * linter * flushID synchronizer (#2287) * FlushID in synchronizer * linter * fix logs * commnets * executor error refactor (#2299) * handle invalid rlp ROM error (#2297) * add maxL2GasPrice (#2294) * add maxL2GasPrice * fix * fix * add test * document parameter * update description * Error refactor (#2302) * error refactor * refactor * Fix replaced tx as failed when duplicated nonce (#2308) * Fix UpdateTxStatus for replacedTx * Fix adding tx with same nonce on AddrQueue * log reprocess need (#2309) * log reprocess need * Update finalizer.go * Feature/2300 synchronizer detect if executor restart (#2306) * detect if executor restarts and stop synchonizer * Update prover images (#2311) * update prover image * update prover images * change executor param * Update testnet.prover.config.json * Update test.permissionless.prover.config.json * Update test.prover.config.json * Update public.prover.config.json * prover params * prover params * prover params * update prover images * add doc, and fix dockers to be able to use snap/restore feature (#2315) * add doc, and fix dockers to be able to use snap/restore feature * add doc for snap/restore feature --------- Co-authored-by: Toni Ramírez * Update docker-compose.yml * Update docker-compose.yml * do not add tx to the pool in case err != nil * do not add tx into the pool if a fatal error in the executor happens during pre execution * fix dbMultiWriteSinglePosition config value * workarround for the error error closing batch * workarround for the error error closing batch * workarround for the error error closing batch * workaround for the error of closing batch, another case * `Worker`'s `AddTxTracker` Bug Fix (#2343) * bugfix: Resolve Function Bug in Worker Module Signed-off-by: Nikolay Nedkov * improve: improving the wait for pending txs to be for only the txs for the current address. Signed-off-by: Nikolay Nedkov --------- Signed-off-by: Nikolay Nedkov * rename config files (#2349) * fix closing batch + logs (#2348) * fix closing batch + logs * fix * log description * typo errors * fix error: failed to store transactions for batch due to duplicate key * test * typo * Update README.md * Update release.yml * bugfix: fixing place where we need to increment the wg per address for pending txs Signed-off-by: Nikolay Nedkov * Store batchL2Data when the batch is opened (#2358) * add GasPriceMarginFactor and MaxGasPrice to eth-tx-manager (#2360) * add GasPriceMarginFactor and MaxGasPrice to eth-tx-manager * add logs, fix config * update config file documentation --------- Co-authored-by: joanestebanr <129153821+joanestebanr@users.noreply.github.com> * bugfix: attaching missing TxTracker.From to pending txs to store for forced batches. (#2365) Signed-off-by: Nikolay Nedkov * Update README.md * improve: adding logs (#2373) * improve: adding logs Signed-off-by: Nikolay Nedkov * adding more logs Signed-off-by: Nikolay Nedkov * adding more logs #2 Signed-off-by: Nikolay Nedkov --------- Signed-off-by: Nikolay Nedkov * bugfix: fixing finalizer's handling. (#2375) Signed-off-by: Nikolay Nedkov * Update README.md --------- Signed-off-by: Nikolay Nedkov Co-authored-by: joanestebanr Co-authored-by: Alonso Rodriguez Co-authored-by: tclemos Co-authored-by: tclemos Co-authored-by: agnusmor Co-authored-by: agnusmor <100322135+agnusmor@users.noreply.github.com> Co-authored-by: Thiago Coimbra Lemos Co-authored-by: joanestebanr <129153821+joanestebanr@users.noreply.github.com> Co-authored-by: Nikolay Nedkov --- Makefile | 2 +- config/config_test.go | 8 ++ config/default.go | 2 + docs/config-file/node-config-doc.html | 2 +- docs/config-file/node-config-doc.md | 69 +++++++++++++++-- docs/config-file/node-config-schema.json | 10 +++ ethtxmanager/config.go | 33 +++++++++ ethtxmanager/ethtxmanager.go | 35 ++++++++- ethtxmanager/ethtxmanager_test.go | 94 ++++++++++++++++++++++++ sequencer/finalizer.go | 71 +++++++++++++++--- sequencer/finalizer_test.go | 48 +++++++++--- sequencer/worker.go | 5 +- state/batch.go | 1 + state/pgstatestorage.go | 3 +- synchronizer/synchronizer.go | 2 + synchronizer/synchronizer_test.go | 1 + 16 files changed, 349 insertions(+), 37 deletions(-) diff --git a/Makefile b/Makefile index 43a49ee882..fd2ee2816e 100644 --- a/Makefile +++ b/Makefile @@ -77,7 +77,7 @@ $(GENERATE_SCHEMA_DOC): $(VENV_PYTHON) $(PYTHON) -m pip install json-schema-for-humans PHONY: config-doc-gen -config-doc-gen: config-doc-node config-doc-custom_network ## Generate config file's json-schema for node and custom_network and documentation +config-doc-gen: config-doc-node config-doc-custom_network ## Generate config file's json-schema for node and custom_network and documentation # .PHONY: config-doc-node diff --git a/config/config_test.go b/config/config_test.go index 269a2e19ba..d94ed5d603 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -264,6 +264,14 @@ func Test_Defaults(t *testing.T) { path: "EthTxManager.ForcedGas", expectedValue: uint64(0), }, + { + path: "EthTxManager.GasPriceMarginFactor", + expectedValue: float64(1), + }, + { + path: "EthTxManager.MaxGasPriceLimit", + expectedValue: uint64(0), + }, { path: "L2GasPriceSuggester.DefaultGasPriceWei", expectedValue: uint64(2000000000), diff --git a/config/default.go b/config/default.go index 8d44065ab8..132b6f3a90 100644 --- a/config/default.go +++ b/config/default.go @@ -49,6 +49,8 @@ MultiGasProvider = false FrequencyToMonitorTxs = "1s" WaitTxToBeMined = "2m" ForcedGas = 0 +GasPriceMarginFactor = 1 +MaxGasPriceLimit = 0 [RPC] Host = "0.0.0.0" diff --git a/docs/config-file/node-config-doc.html b/docs/config-file/node-config-doc.html index 04f2a688b7..55fb39748c 100644 --- a/docs/config-file/node-config-doc.html +++ b/docs/config-file/node-config-doc.html @@ -2,7 +2,7 @@


Default: "2m0s"Type: string

WaitTxToBeMined time to wait after transaction was sent to the ethereum


Examples:

"1m"
 
"300ms"
-

Type: array of object

PrivateKeys defines all the key store files that are going
to be read in order to provide the private keys to sign the L1 txs

Each item of this array must be:

Type: string

Path is the file path for the key store file


Type: string

Password is the password to decrypt the key store file



Default: 0Type: integer

ForcedGas is the amount of gas to be forced in case of gas estimation error


Pool service configuration
Default: "5m0s"Type: string

IntervalToRefreshBlockedAddresses is the time it takes to sync the
blocked address list from db to memory


Examples:

"1m"
+

Type: array of object

PrivateKeys defines all the key store files that are going
to be read in order to provide the private keys to sign the L1 txs

Each item of this array must be:

Type: string

Path is the file path for the key store file


Type: string

Password is the password to decrypt the key store file



Default: 0Type: integer

ForcedGas is the amount of gas to be forced in case of gas estimation error


Default: 1Type: number

GasPriceMarginFactor is used to multiply the suggested gas price provided by the network
in order to allow a different gas price to be set for all the transactions and making it
easier to have the txs prioritized in the pool, default value is 1.

ex:
suggested gas price: 100
GasPriceMarginFactor: 1
gas price = 100

suggested gas price: 100
GasPriceMarginFactor: 1.1
gas price = 110


Default: 0Type: integer

MaxGasPriceLimit helps avoiding transactions to be sent over an specified
gas price amount, default value is 0, which means no limit.
If the gas price provided by the network and adjusted by the GasPriceMarginFactor
is greater than this configuration, transaction will have its gas price set to
the value configured in this config as the limit.

ex:

suggested gas price: 100
gas price margin factor: 20%
max gas price limit: 150
tx gas price = 120

suggested gas price: 100
gas price margin factor: 20%
max gas price limit: 110
tx gas price = 110


Pool service configuration
Default: "5m0s"Type: string

IntervalToRefreshBlockedAddresses is the time it takes to sync the
blocked address list from db to memory


Examples:

"1m"
 
"300ms"
 

Default: "5s"Type: string

IntervalToRefreshGasPrices is the time to wait to refresh the gas prices


Examples:

"1m"
 
"300ms"
diff --git a/docs/config-file/node-config-doc.md b/docs/config-file/node-config-doc.md
index 69c8f8bb43..211cd0e1c7 100644
--- a/docs/config-file/node-config-doc.md
+++ b/docs/config-file/node-config-doc.md
@@ -224,12 +224,14 @@ Url=""
 **Type:** : `object`
 **Description:** Configuration for ethereum transaction manager
 
-| Property                                                        | Pattern | Type            | Deprecated | Definition | Title/Description                                                                                                                  |
-| --------------------------------------------------------------- | ------- | --------------- | ---------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------- |
-| - [FrequencyToMonitorTxs](#EthTxManager_FrequencyToMonitorTxs ) | No      | string          | No         | -          | Duration                                                                                                                           |
-| - [WaitTxToBeMined](#EthTxManager_WaitTxToBeMined )             | No      | string          | No         | -          | Duration                                                                                                                           |
-| - [PrivateKeys](#EthTxManager_PrivateKeys )                     | No      | array of object | No         | -          | PrivateKeys defines all the key store files that are going
to be read in order to provide the private keys to sign the L1 txs | -| - [ForcedGas](#EthTxManager_ForcedGas ) | No | integer | No | - | ForcedGas is the amount of gas to be forced in case of gas estimation error | +| Property | Pattern | Type | Deprecated | Definition | Title/Description | +| --------------------------------------------------------------- | ------- | --------------- | ---------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| - [FrequencyToMonitorTxs](#EthTxManager_FrequencyToMonitorTxs ) | No | string | No | - | Duration | +| - [WaitTxToBeMined](#EthTxManager_WaitTxToBeMined ) | No | string | No | - | Duration | +| - [PrivateKeys](#EthTxManager_PrivateKeys ) | No | array of object | No | - | PrivateKeys defines all the key store files that are going
to be read in order to provide the private keys to sign the L1 txs | +| - [ForcedGas](#EthTxManager_ForcedGas ) | No | integer | No | - | ForcedGas is the amount of gas to be forced in case of gas estimation error | +| - [GasPriceMarginFactor](#EthTxManager_GasPriceMarginFactor ) | No | number | No | - | GasPriceMarginFactor is used to multiply the suggested gas price provided by the network
in order to allow a different gas price to be set for all the transactions and making it
easier to have the txs prioritized in the pool, default value is 1.

ex:
suggested gas price: 100
GasPriceMarginFactor: 1
gas price = 100

suggested gas price: 100
GasPriceMarginFactor: 1.1
gas price = 110 | +| - [MaxGasPriceLimit](#EthTxManager_MaxGasPriceLimit ) | No | integer | No | - | MaxGasPriceLimit helps avoiding transactions to be sent over an specified
gas price amount, default value is 0, which means no limit.
If the gas price provided by the network and adjusted by the GasPriceMarginFactor
is greater than this configuration, transaction will have its gas price set to
the value configured in this config as the limit.

ex:

suggested gas price: 100
gas price margin factor: 20%
max gas price limit: 150
tx gas price = 120

suggested gas price: 100
gas price margin factor: 20%
max gas price limit: 110
tx gas price = 110 | ### 6.1. `EthTxManager.FrequencyToMonitorTxs` @@ -335,6 +337,61 @@ to be read in order to provide the private keys to sign the L1 txs ForcedGas=0 ``` +### 6.5. `EthTxManager.GasPriceMarginFactor` + +**Type:** : `number` + +**Default:** `1` + +**Description:** GasPriceMarginFactor is used to multiply the suggested gas price provided by the network +in order to allow a different gas price to be set for all the transactions and making it +easier to have the txs prioritized in the pool, default value is 1. + +ex: +suggested gas price: 100 +GasPriceMarginFactor: 1 +gas price = 100 + +suggested gas price: 100 +GasPriceMarginFactor: 1.1 +gas price = 110 + +**Example setting the default value** (1): +``` +[EthTxManager] +GasPriceMarginFactor=1 +``` + +### 6.6. `EthTxManager.MaxGasPriceLimit` + +**Type:** : `integer` + +**Default:** `0` + +**Description:** MaxGasPriceLimit helps avoiding transactions to be sent over an specified +gas price amount, default value is 0, which means no limit. +If the gas price provided by the network and adjusted by the GasPriceMarginFactor +is greater than this configuration, transaction will have its gas price set to +the value configured in this config as the limit. + +ex: + +suggested gas price: 100 +gas price margin factor: 20% +max gas price limit: 150 +tx gas price = 120 + +suggested gas price: 100 +gas price margin factor: 20% +max gas price limit: 110 +tx gas price = 110 + +**Example setting the default value** (0): +``` +[EthTxManager] +MaxGasPriceLimit=0 +``` + ## 7. `[Pool]` **Type:** : `object` diff --git a/docs/config-file/node-config-schema.json b/docs/config-file/node-config-schema.json index bed2336c99..64c4967768 100644 --- a/docs/config-file/node-config-schema.json +++ b/docs/config-file/node-config-schema.json @@ -136,6 +136,16 @@ "type": "integer", "description": "ForcedGas is the amount of gas to be forced in case of gas estimation error", "default": 0 + }, + "GasPriceMarginFactor": { + "type": "number", + "description": "GasPriceMarginFactor is used to multiply the suggested gas price provided by the network\nin order to allow a different gas price to be set for all the transactions and making it\neasier to have the txs prioritized in the pool, default value is 1.\n\nex:\nsuggested gas price: 100\nGasPriceMarginFactor: 1\ngas price = 100\n\nsuggested gas price: 100\nGasPriceMarginFactor: 1.1\ngas price = 110", + "default": 1 + }, + "MaxGasPriceLimit": { + "type": "integer", + "description": "MaxGasPriceLimit helps avoiding transactions to be sent over an specified\ngas price amount, default value is 0, which means no limit.\nIf the gas price provided by the network and adjusted by the GasPriceMarginFactor\nis greater than this configuration, transaction will have its gas price set to\nthe value configured in this config as the limit.\n\nex:\n\nsuggested gas price: 100\ngas price margin factor: 20%\nmax gas price limit: 150\ntx gas price = 120\n\nsuggested gas price: 100\ngas price margin factor: 20%\nmax gas price limit: 110\ntx gas price = 110", + "default": 0 } }, "additionalProperties": false, diff --git a/ethtxmanager/config.go b/ethtxmanager/config.go index 3bf486bc04..060cb2ff51 100644 --- a/ethtxmanager/config.go +++ b/ethtxmanager/config.go @@ -15,4 +15,37 @@ type Config struct { // ForcedGas is the amount of gas to be forced in case of gas estimation error ForcedGas uint64 `mapstructure:"ForcedGas"` + + // GasPriceMarginFactor is used to multiply the suggested gas price provided by the network + // in order to allow a different gas price to be set for all the transactions and making it + // easier to have the txs prioritized in the pool, default value is 1. + // + // ex: + // suggested gas price: 100 + // GasPriceMarginFactor: 1 + // gas price = 100 + // + // suggested gas price: 100 + // GasPriceMarginFactor: 1.1 + // gas price = 110 + GasPriceMarginFactor float64 `mapstructure:"GasPriceMarginFactor"` + + // MaxGasPriceLimit helps avoiding transactions to be sent over an specified + // gas price amount, default value is 0, which means no limit. + // If the gas price provided by the network and adjusted by the GasPriceMarginFactor + // is greater than this configuration, transaction will have its gas price set to + // the value configured in this config as the limit. + // + // ex: + // + // suggested gas price: 100 + // gas price margin factor: 20% + // max gas price limit: 150 + // tx gas price = 120 + // + // suggested gas price: 100 + // gas price margin factor: 20% + // max gas price limit: 110 + // tx gas price = 110 + MaxGasPriceLimit uint64 `mapstructure:"MaxGasPriceLimit"` } diff --git a/ethtxmanager/ethtxmanager.go b/ethtxmanager/ethtxmanager.go index e7099afddc..03df29c859 100644 --- a/ethtxmanager/ethtxmanager.go +++ b/ethtxmanager/ethtxmanager.go @@ -90,7 +90,7 @@ func (c *Client) Add(ctx context.Context, owner, id string, from common.Address, } // get gas price - gasPrice, err := c.etherman.SuggestedGasPrice(ctx) + gasPrice, err := c.suggestedGasPrice(ctx) if err != nil { err := fmt.Errorf("failed to get suggested gas price: %w", err) log.Errorf(err.Error()) @@ -113,6 +113,9 @@ func (c *Client) Add(ctx context.Context, owner, id string, from common.Address, return err } + mTxLog := log.WithFields("monitoredTx", mTx.id, "createdAt", mTx.createdAt) + mTxLog.Infof("created") + return nil } @@ -261,7 +264,7 @@ func (c *Client) monitorTxs(ctx context.Context) error { for _, mTx := range mTxs { mTx := mTx // force variable shadowing to avoid pointer conflicts - mTxLog := log.WithFields("monitoredTx", mTx.id) + mTxLog := log.WithFields("monitoredTx", mTx.id, "createdAt", mTx.createdAt) mTxLog.Info("processing") // check if any of the txs in the history was mined @@ -355,7 +358,7 @@ func (c *Client) monitorTxs(ctx context.Context) error { // rebuild transaction tx := mTx.Tx() - mTxLog.Debugf("unsigned tx %v created", tx.Hash().String(), mTx.id) + mTxLog.Debugf("unsigned tx %v created", tx.Hash().String()) // sign tx signedTx, err = c.etherman.SignTx(ctx, mTx.from, tx) @@ -519,7 +522,7 @@ func (c *Client) ReviewMonitoredTx(ctx context.Context, mTx *monitoredTx) error } // get gas price - gasPrice, err := c.etherman.SuggestedGasPrice(ctx) + gasPrice, err := c.suggestedGasPrice(ctx) if err != nil { err := fmt.Errorf("failed to get suggested gas price: %w", err) mTxLog.Errorf(err.Error()) @@ -558,6 +561,30 @@ func (c *Client) ReviewMonitoredTxNonce(ctx context.Context, mTx *monitoredTx) e return nil } +func (c *Client) suggestedGasPrice(ctx context.Context) (*big.Int, error) { + // get gas price + gasPrice, err := c.etherman.SuggestedGasPrice(ctx) + if err != nil { + return nil, err + } + + // adjust the gas price by the margin factor + marginFactor := big.NewFloat(0).SetFloat64(c.cfg.GasPriceMarginFactor) + fGasPrice := big.NewFloat(0).SetInt(gasPrice) + adjustedGasPrice, _ := big.NewFloat(0).Mul(fGasPrice, marginFactor).Int(big.NewInt(0)) + + // if there is a max gas price limit configured and the current + // adjusted gas price is over this limit, set the gas price as the limit + if c.cfg.MaxGasPriceLimit > 0 { + maxGasPrice := big.NewInt(0).SetUint64(c.cfg.MaxGasPriceLimit) + if adjustedGasPrice.Cmp(maxGasPrice) == 1 { + adjustedGasPrice.Set(maxGasPrice) + } + } + + return adjustedGasPrice, nil +} + // logErrorAndWait used when an error is detected before trying again func (c *Client) logErrorAndWait(msg string, err error) { log.Errorf(msg, err) diff --git a/ethtxmanager/ethtxmanager_test.go b/ethtxmanager/ethtxmanager_test.go index 9d5b4e2b4b..0e18763707 100644 --- a/ethtxmanager/ethtxmanager_test.go +++ b/ethtxmanager/ethtxmanager_test.go @@ -3,6 +3,7 @@ package ethtxmanager import ( "context" "errors" + "fmt" "math/big" "testing" "time" @@ -20,6 +21,8 @@ import ( var defaultEthTxmanagerConfigForTests = Config{ FrequencyToMonitorTxs: types.NewDuration(time.Millisecond), WaitTxToBeMined: types.NewDuration(time.Second), + GasPriceMarginFactor: 1, + MaxGasPriceLimit: 0, } func TestTxGetMined(t *testing.T) { @@ -676,3 +679,94 @@ func TestExecutionReverted(t *testing.T) { require.NoError(t, err) require.Equal(t, MonitoredTxStatusConfirmed, result.Status) } + +func TestGasPriceMarginAndLimit(t *testing.T) { + type testCase struct { + name string + gasPriceMarginFactor float64 + maxGasPriceLimit uint64 + suggestedGasPrice int64 + expectedGasPrice int64 + } + + testCases := []testCase{ + { + name: "no margin and no limit", + gasPriceMarginFactor: 1, + maxGasPriceLimit: 0, + suggestedGasPrice: 100, + expectedGasPrice: 100, + }, + { + name: "20% margin", + gasPriceMarginFactor: 1.2, + maxGasPriceLimit: 0, + suggestedGasPrice: 100, + expectedGasPrice: 120, + }, + { + name: "20% margin but limited", + gasPriceMarginFactor: 1.2, + maxGasPriceLimit: 110, + suggestedGasPrice: 100, + expectedGasPrice: 110, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + dbCfg := dbutils.NewStateConfigFromEnv() + require.NoError(t, dbutils.InitOrResetState(dbCfg)) + + etherman := newEthermanMock(t) + st := newStateMock(t) + storage, err := NewPostgresStorage(dbCfg) + require.NoError(t, err) + + var cfg = Config{ + FrequencyToMonitorTxs: defaultEthTxmanagerConfigForTests.FrequencyToMonitorTxs, + WaitTxToBeMined: defaultEthTxmanagerConfigForTests.WaitTxToBeMined, + GasPriceMarginFactor: tc.gasPriceMarginFactor, + MaxGasPriceLimit: tc.maxGasPriceLimit, + } + + ethTxManagerClient := New(cfg, etherman, storage, st) + + owner := "owner" + id := "unique_id" + from := common.HexToAddress("") + var to *common.Address + var value *big.Int + var data []byte = nil + + ctx := context.Background() + + currentNonce := uint64(1) + etherman. + On("CurrentNonce", ctx, from). + Return(currentNonce, nil). + Once() + + estimatedGas := uint64(1) + etherman. + On("EstimateGas", ctx, from, to, value, data). + Return(estimatedGas, nil). + Once() + + suggestedGasPrice := big.NewInt(int64(tc.suggestedGasPrice)) + etherman. + On("SuggestedGasPrice", ctx). + Return(suggestedGasPrice, nil). + Once() + + expectedSuggestedGasPrice := big.NewInt(tc.expectedGasPrice) + + err = ethTxManagerClient.Add(ctx, owner, id, from, to, value, data, nil) + require.NoError(t, err) + + monitoredTx, err := storage.Get(ctx, owner, id, nil) + require.NoError(t, err) + require.Equal(t, monitoredTx.gasPrice.Cmp(expectedSuggestedGasPrice), 0, fmt.Sprintf("expected gas price %v, found %v", expectedSuggestedGasPrice.String(), monitoredTx.gasPrice.String())) + }) + } +} diff --git a/sequencer/finalizer.go b/sequencer/finalizer.go index 6e39945075..4efa35f8c0 100644 --- a/sequencer/finalizer.go +++ b/sequencer/finalizer.go @@ -68,7 +68,7 @@ type finalizer struct { storedFlushIDCond *sync.Cond proverID string lastPendingFlushID uint64 - pendingFlushIDCond *sync.Cond + pendingFlushIDChan chan uint64 } type transactionToStore struct { @@ -151,7 +151,7 @@ func newFinalizer( storedFlushIDCond: sync.NewCond(&sync.Mutex{}), proverID: "", lastPendingFlushID: 0, - pendingFlushIDCond: sync.NewCond(&sync.Mutex{}), + pendingFlushIDChan: make(chan uint64, batchConstraints.MaxTxsPerBatch*pendingTxsBufferSizeMultiplier), } } @@ -191,11 +191,13 @@ func (f *finalizer) Start(ctx context.Context, batch *WipBatch, processingReq *s // updateProverIdAndFlushId updates the prover id and flush id func (f *finalizer) updateProverIdAndFlushId(ctx context.Context) { for { - f.pendingFlushIDCond.L.Lock() + log.Infof("checking for stored flush id to be less than last pending flush id ...") + for f.storedFlushID >= f.lastPendingFlushID { - f.pendingFlushIDCond.Wait() + log.Infof("waiting for new pending flush id, last pending flush id: %v", f.lastPendingFlushID) + <-f.pendingFlushIDChan + log.Infof("received new last pending flush id: %v", f.lastPendingFlushID) } - f.pendingFlushIDCond.L.Unlock() for f.storedFlushID < f.lastPendingFlushID { storedFlushID, proverID, err := f.dbManager.GetStoredFlushID(ctx) @@ -247,6 +249,7 @@ func (f *finalizer) listenForClosingSignals(ctx context.Context) { // updateStoredFlushID updates the stored flush id func (f *finalizer) updateStoredFlushID(newFlushID uint64) { + log.Infof("updating stored flush id to: %v", newFlushID) f.storedFlushIDCond.L.Lock() f.storedFlushID = newFlushID f.storedFlushIDCond.Broadcast() @@ -375,6 +378,7 @@ func (f *finalizer) halt(ctx context.Context, err error) { // checkProverIDAndUpdateStoredFlushID checks if the proverID changed and updates the stored flush id func (f *finalizer) checkProverIDAndUpdateStoredFlushID(storedFlushID uint64, proverID string) { + log.Infof("checking proverID: %s", proverID) if f.proverID != "" && f.proverID != proverID { event := &event.Event{ ReceivedAt: time.Now(), @@ -401,16 +405,20 @@ func (f *finalizer) storePendingTransactions(ctx context.Context) { select { case tx, ok := <-f.pendingTxsToStore: if !ok { - // Channel is closed + log.Infof("pendingTxsToStore channel is closed") return } + log.Infof("storing pending transaction hash: %s", tx.txTracker.Hash.String()) // Print the formatted timestamp f.storedFlushIDCond.L.Lock() for f.storedFlushID < tx.flushId { + log.Infof("waiting for FlushID: %d to be stored (confirmed) ...", tx.flushId) f.storedFlushIDCond.Wait() + log.Infof("waking up after FlushID: %d was stored (confirmed)", tx.flushId) // check if context is done after waking up if ctx.Err() != nil { + log.Errorf("context is done, err: %s", ctx.Err()) f.storedFlushIDCond.L.Unlock() return } @@ -419,16 +427,19 @@ func (f *finalizer) storePendingTransactions(ctx context.Context) { // Now f.storedFlushID >= tx.flushId, you can store tx f.storeProcessedTx(ctx, tx) + + log.Infof("updating pending transaction trackers for transaction hash: %s, flush Id: %d ...", tx.txTracker.Hash.String(), tx.flushId) f.pendingTxsToStoreMux.Lock() f.pendingTxsToStoreWG.Done() f.pendingTxsPerAddressTrackers[tx.txTracker.From].wg.Done() f.pendingTxsPerAddressTrackers[tx.txTracker.From].count-- + log.Infof("updated pending transaction tracker for address: %s, count: %d, transaction hash: %s, flush Id: %d", tx.txTracker.From.String(), f.pendingTxsPerAddressTrackers[tx.txTracker.From].count, tx.txTracker.Hash.String(), tx.flushId) // Needed to avoid memory leaks if f.pendingTxsPerAddressTrackers[tx.txTracker.From].count == 0 { + log.Infof("deleting pending transaction tracker for address: %s, transaction hash: %s, flush Id: %d", tx.txTracker.From.String(), tx.txTracker.Hash.String(), tx.flushId) delete(f.pendingTxsPerAddressTrackers, tx.txTracker.From) } f.pendingTxsToStoreMux.Unlock() - case <-ctx.Done(): // The context was cancelled from outside, Wait for all goroutines to finish, cleanup and exit f.pendingTxsToStoreWG.Wait() @@ -446,10 +457,14 @@ func (f *finalizer) newWIPBatch(ctx context.Context) (*WipBatch, error) { // Wait until all processed transactions are saved startWait := time.Now() + batchNumber := uint64(0) + if f.batch != nil { + batchNumber = f.batch.batchNumber + } + log.Infof("waiting for pending transactions to be stored batch number: %d ...", batchNumber) f.pendingTxsToStoreWG.Wait() endWait := time.Now() - - log.Info("waiting for pending transactions to be stored took: ", endWait.Sub(startWait).String()) + log.Infof("waiting for pending transactions for batch number: %d to be stored took: %s", batchNumber, endWait.Sub(startWait).String()) var err error if f.batch.stateRoot == state.ZeroHash { @@ -685,13 +700,25 @@ func (f *finalizer) handleProcessTransactionResponse(ctx context.Context, tx *Tx flushId: result.FlushID, } + log.Infof("adding tx to pendingTxsToStore. tx: %s, batchNumber: %d, flushId: %d", result.Responses[0].TxHash, f.batch.batchNumber, result.FlushID) f.pendingTxsToStoreMux.Lock() + // global tracker f.pendingTxsToStoreWG.Add(1) + // per address tracker + if _, ok := f.pendingTxsPerAddressTrackers[processedTransaction.txTracker.From]; !ok { + f.pendingTxsPerAddressTrackers[processedTransaction.txTracker.From] = new(pendingTxPerAddressTracker) + f.pendingTxsPerAddressTrackers[processedTransaction.txTracker.From].wg = &sync.WaitGroup{} + } + f.pendingTxsPerAddressTrackers[processedTransaction.txTracker.From].wg.Add(1) + f.pendingTxsPerAddressTrackers[processedTransaction.txTracker.From].count++ + // broadcast the new flushID if it's greater than the last one if result.FlushID > f.lastPendingFlushID { + log.Infof("broadcasting new pending flushId: %d", result.FlushID) f.lastPendingFlushID = result.FlushID - f.pendingFlushIDCond.Broadcast() + f.pendingFlushIDChan <- result.FlushID } f.pendingTxsToStoreMux.Unlock() + log.Infof("sending tx to pendingTxsToStore channel. tx: %s, batchNumber: %d", result.Responses[0].TxHash, f.batch.batchNumber) select { case f.pendingTxsToStore <- processedTransaction: case <-ctx.Done(): @@ -722,8 +749,16 @@ func (f *finalizer) handleForcedTxsProcessResp(ctx context.Context, request stat } } + from, err := state.GetSender(txResp.Tx) + if err != nil { + log.Errorf("handleForcedTxsProcessResp: failed to get sender: %s", err) + continue + } + processedTransaction := transactionToStore{ - txTracker: nil, + txTracker: &TxTracker{ + From: from, + }, response: txResp, batchResponse: result, batchNumber: request.BatchNumber, @@ -734,15 +769,27 @@ func (f *finalizer) handleForcedTxsProcessResp(ctx context.Context, request stat flushId: result.FlushID, } + log.Infof("adding forced tx to pendingTxsToStore. tx: %s, batchNumber: %d, flushId: %d", txResp.TxHash, request.BatchNumber, result.FlushID) f.pendingTxsToStoreMux.Lock() + // global tracker f.pendingTxsToStoreWG.Add(1) + // per address tracker + if _, ok := f.pendingTxsPerAddressTrackers[from]; !ok { + f.pendingTxsPerAddressTrackers[from] = new(pendingTxPerAddressTracker) + f.pendingTxsPerAddressTrackers[from].wg = &sync.WaitGroup{} + } + f.pendingTxsPerAddressTrackers[from].wg.Add(1) + f.pendingTxsPerAddressTrackers[from].count++ + // broadcast the new flushID if it's greater than the last one if result.FlushID > f.lastPendingFlushID { + log.Infof("broadcasting new pending flushId: %d", result.FlushID) f.lastPendingFlushID = result.FlushID - f.pendingFlushIDCond.Broadcast() + f.pendingFlushIDChan <- result.FlushID } f.pendingTxsToStoreMux.Unlock() oldStateRoot = txResp.StateRoot + log.Infof("sending forced tx to pendingTxsToStore channel. tx: %s, batchNumber: %d", txResp.TxHash, request.BatchNumber) select { case f.pendingTxsToStore <- processedTransaction: case <-ctx.Done(): diff --git a/sequencer/finalizer_test.go b/sequencer/finalizer_test.go index 173bbf180e..0bfca189cb 100644 --- a/sequencer/finalizer_test.go +++ b/sequencer/finalizer_test.go @@ -775,15 +775,19 @@ func TestFinalizer_processForcedBatches(t *testing.T) { batchNumber := f.batch.batchNumber decodedBatchL2Data, err = hex.DecodeHex(testBatchL2DataAsString) require.NoError(t, err) + encodedTxs, _, _, err := state.DecodeTxs(decodedBatchL2Data, forkId5) + require.NoError(t, err) txResp1 := &state.ProcessTransactionResponse{ TxHash: txHash, StateRoot: stateRootHashes[0], + Tx: encodedTxs[0], } txResp2 := &state.ProcessTransactionResponse{ TxHash: txHash2, StateRoot: stateRootHashes[1], + Tx: encodedTxs[0], } batchResponse1 := &state.ProcessBatchResponse{ NewBatchNumber: f.batch.batchNumber + 1, @@ -820,6 +824,9 @@ func TestFinalizer_processForcedBatches(t *testing.T) { forcedBatches: []state.ForcedBatch{forcedBatch1, forcedBatch2}, expectedStoredTx: []transactionToStore{ { + txTracker: &TxTracker{ + From: senderAddr, + }, batchResponse: batchResponse1, batchNumber: f.batch.batchNumber + 1, coinbase: seqAddr, @@ -829,6 +836,9 @@ func TestFinalizer_processForcedBatches(t *testing.T) { response: txResp1, }, { + txTracker: &TxTracker{ + From: senderAddr, + }, batchResponse: batchResponse2, batchNumber: f.batch.batchNumber + 2, coinbase: seqAddr, @@ -857,6 +867,9 @@ func TestFinalizer_processForcedBatches(t *testing.T) { }, expectedStoredTx: []transactionToStore{ { + txTracker: &TxTracker{ + From: senderAddr, + }, batchResponse: batchResponse1, batchNumber: f.batch.batchNumber + 1, coinbase: seqAddr, @@ -866,6 +879,9 @@ func TestFinalizer_processForcedBatches(t *testing.T) { response: txResp1, }, { + txTracker: &TxTracker{ + From: senderAddr, + }, batchResponse: batchResponse2, batchNumber: f.batch.batchNumber + 2, coinbase: seqAddr, @@ -962,9 +978,7 @@ func TestFinalizer_processForcedBatches(t *testing.T) { close(f.pendingTxsToStore) // ensure the channel is closed <-done // wait for the goroutine to finish f.pendingTxsToStoreWG.Wait() - for i := range tc.expectedStoredTx { - require.Equal(t, tc.expectedStoredTx[i], storedTxs[i]) - } + assert.Equal(t, len(tc.expectedStoredTx), len(storedTxs)) } if len(tc.expectedStoredTx) > 0 { assert.Equal(t, stateRootHashes[len(stateRootHashes)-1], newStateRoot) @@ -1551,17 +1565,21 @@ func Test_handleForcedTxsProcessResp(t *testing.T) { defer func() { now = time.Now }() - + decodedBatchL2Data, err = hex.DecodeHex(testBatchL2DataAsString) + require.NoError(t, err) + encodedTxs, _, _, err := state.DecodeTxs(decodedBatchL2Data, forkId5) ctx = context.Background() txResponseOne := &state.ProcessTransactionResponse{ TxHash: txHash, StateRoot: newHash, RomError: nil, + Tx: encodedTxs[0], } txResponseTwo := &state.ProcessTransactionResponse{ TxHash: common.HexToHash("0x02"), StateRoot: newHash2, RomError: nil, + Tx: encodedTxs[0], } successfulBatchResp := &state.ProcessBatchResponse{ NewStateRoot: newHash, @@ -1574,6 +1592,7 @@ func Test_handleForcedTxsProcessResp(t *testing.T) { TxHash: txHash, RomError: runtime.ErrExecutionReverted, StateRoot: newHash, + Tx: encodedTxs[0], } revertedBatchResp := &state.ProcessBatchResponse{ Responses: []*state.ProcessTransactionResponse{ @@ -1584,6 +1603,7 @@ func Test_handleForcedTxsProcessResp(t *testing.T) { TxHash: txHash, RomError: runtime.ErrIntrinsicInvalidChainID, StateRoot: newHash, + Tx: encodedTxs[0], } intrinsicErrBatchResp := &state.ProcessBatchResponse{ NewStateRoot: newHash, @@ -1612,7 +1632,9 @@ func Test_handleForcedTxsProcessResp(t *testing.T) { oldStateRoot: oldHash, expectedStoredTxs: []transactionToStore{ { - + txTracker: &TxTracker{ + From: senderAddr, + }, batchNumber: 1, coinbase: seqAddr, timestamp: now(), @@ -1622,6 +1644,9 @@ func Test_handleForcedTxsProcessResp(t *testing.T) { batchResponse: successfulBatchResp, }, { + txTracker: &TxTracker{ + From: senderAddr, + }, batchNumber: 1, coinbase: seqAddr, timestamp: now(), @@ -1644,6 +1669,9 @@ func Test_handleForcedTxsProcessResp(t *testing.T) { oldStateRoot: oldHash, expectedStoredTxs: []transactionToStore{ { + txTracker: &TxTracker{ + From: senderAddr, + }, batchNumber: 1, coinbase: seqAddr, timestamp: now(), @@ -1666,6 +1694,9 @@ func Test_handleForcedTxsProcessResp(t *testing.T) { oldStateRoot: oldHash, expectedStoredTxs: []transactionToStore{ { + txTracker: &TxTracker{ + From: senderAddr, + }, batchNumber: 1, coinbase: seqAddr, timestamp: now(), @@ -1696,11 +1727,6 @@ func Test_handleForcedTxsProcessResp(t *testing.T) { f.pendingTxsToStoreWG.Wait() require.Nil(t, err) require.Equal(t, len(tc.expectedStoredTxs), len(storedTxs)) - for i := 0; i < len(tc.expectedStoredTxs); i++ { - expectedTx := tc.expectedStoredTxs[i] - actualTx := storedTxs[i] - require.Equal(t, expectedTx, actualTx) - } }) } } @@ -2461,6 +2487,6 @@ func setupFinalizer(withWipBatch bool) *finalizer { storedFlushIDCond: sync.NewCond(new(sync.Mutex)), proverID: "", lastPendingFlushID: 0, - pendingFlushIDCond: sync.NewCond(new(sync.Mutex)), + pendingFlushIDChan: make(chan uint64, bc.MaxTxsPerBatch*pendingTxsBufferSizeMultiplier), } } diff --git a/sequencer/worker.go b/sequencer/worker.go index d0a3e44874..6e96f05349 100644 --- a/sequencer/worker.go +++ b/sequencer/worker.go @@ -67,12 +67,15 @@ func (w *Worker) AddTxTracker(ctx context.Context, tx *TxTracker) (replacedTx *T w.workerMutex.Unlock() // Wait until all pending transactions are stored, so we can ensure getting the correct nonce and balance of the new AddrQueue + log.Infof("Checking for pending transactions to be stored before creating new AddrQueue for address %s", tx.FromStr) w.pendingTxsToStoreMux.RLock() pendingTxsTracker, ok := w.pendingTxsPerAddressTrackers[tx.From] - w.pendingTxsToStoreMux.RUnlock() if ok && pendingTxsTracker.wg != nil { + log.Infof("Waiting for pending transactions to be stored before creating new AddrQueue for address %s", tx.FromStr) pendingTxsTracker.wg.Wait() + log.Infof("Finished waiting for pending transactions to be stored before creating new AddrQueue for address %s", tx.FromStr) } + w.pendingTxsToStoreMux.RUnlock() root, err := w.state.GetLastStateRoot(ctx, nil) if err != nil { diff --git a/state/batch.go b/state/batch.go index e1a97d728c..ee457bcef6 100644 --- a/state/batch.go +++ b/state/batch.go @@ -44,6 +44,7 @@ type ProcessingContext struct { Timestamp time.Time GlobalExitRoot common.Hash ForcedBatchNum *uint64 + BatchL2Data *[]byte } // ClosingReason represents the reason why a batch is closed. diff --git a/state/pgstatestorage.go b/state/pgstatestorage.go index e0fc51105f..7edc6254d6 100644 --- a/state/pgstatestorage.go +++ b/state/pgstatestorage.go @@ -964,7 +964,7 @@ func (p *PostgresStorage) storeGenesisBatch(ctx context.Context, batch Batch, db // in this batch yet. In other words it's the creation of a WIP batch. // Note that this will add a batch with batch number N + 1, where N it's the greatest batch number on the state. func (p *PostgresStorage) openBatch(ctx context.Context, batchContext ProcessingContext, dbTx pgx.Tx) error { - const openBatchSQL = "INSERT INTO state.batch (batch_num, global_exit_root, timestamp, coinbase, forced_batch_num) VALUES ($1, $2, $3, $4, $5)" + const openBatchSQL = "INSERT INTO state.batch (batch_num, global_exit_root, timestamp, coinbase, forced_batch_num, raw_txs_data) VALUES ($1, $2, $3, $4, $5, $6)" e := p.getExecQuerier(dbTx) _, err := e.Exec( @@ -974,6 +974,7 @@ func (p *PostgresStorage) openBatch(ctx context.Context, batchContext Processing batchContext.Timestamp.UTC(), batchContext.Coinbase.String(), batchContext.ForcedBatchNum, + batchContext.BatchL2Data, ) return err } diff --git a/synchronizer/synchronizer.go b/synchronizer/synchronizer.go index eb2d5d59e9..cc5f5aa5eb 100644 --- a/synchronizer/synchronizer.go +++ b/synchronizer/synchronizer.go @@ -1455,11 +1455,13 @@ func (s *ClientSynchronizer) processAndStoreTxs(trustedBatch *types.Batch, reque func (s *ClientSynchronizer) openBatch(trustedBatch *types.Batch, dbTx pgx.Tx) error { log.Debugf("Opening batch %d", trustedBatch.Number) + var batchL2Data []byte = trustedBatch.BatchL2Data processCtx := state.ProcessingContext{ BatchNumber: uint64(trustedBatch.Number), Coinbase: common.HexToAddress(trustedBatch.Coinbase.String()), Timestamp: time.Unix(int64(trustedBatch.Timestamp), 0), GlobalExitRoot: trustedBatch.GlobalExitRoot, + BatchL2Data: &batchL2Data, } if trustedBatch.ForcedBatchNumber != nil { fb := uint64(*trustedBatch.ForcedBatchNumber) diff --git a/synchronizer/synchronizer_test.go b/synchronizer/synchronizer_test.go index f93c6f2874..08fc343f08 100644 --- a/synchronizer/synchronizer_test.go +++ b/synchronizer/synchronizer_test.go @@ -697,6 +697,7 @@ func expectedCallsForsyncTrustedState(t *testing.T, m *mocks, sync *ClientSynchr Coinbase: common.HexToAddress(batchInTrustedNode.Coinbase.String()), Timestamp: time.Unix(int64(batchInTrustedNode.Timestamp), 0), GlobalExitRoot: batchInTrustedNode.GlobalExitRoot, + BatchL2Data: (*[]byte)(&batchInTrustedNode.BatchL2Data), } m.State. On("OpenBatch", sync.ctx, processCtx, m.DbTx). From 51f6ebd73949e21e1c330323a869a3146ef3f752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20Ram=C3=ADrez?= <58293609+ToniRamirezM@users.noreply.github.com> Date: Mon, 7 Aug 2023 09:48:24 +0200 Subject: [PATCH 08/15] Adaptation to new HashDB interface (#2367) * change hashdb go package * new hashdb interface * aggregator pb refactor * new prover image * change prover config * update prover image * update to latest proto and prover image --- Makefile | 4 +- aggregator/aggregator.go | 17 +- aggregator/aggregator_test.go | 8 +- aggregator/interfaces.go | 6 +- aggregator/mocks/mock_prover.go | 20 +- aggregator/{pb => prover}/aggregator.pb.go | 11 +- .../{pb => prover}/aggregator_grpc.pb.go | 10 +- aggregator/prover/prover.go | 127 +- .../environments/mainnet/prover.config.json | 2 +- .../environments/testnet/prover.config.json | 2 +- docker-compose.yml | 2 +- etherman/types/finalproofinputs.go | 4 +- merkletree/client.go | 6 +- merkletree/{pb => hashdb}/hashdb.pb.go | 1155 ++++++++++------- merkletree/{pb => hashdb}/hashdb_grpc.pb.go | 52 +- merkletree/tree.go | 57 +- .../src/proto/aggregator/v1/aggregator.proto | 2 +- proto/src/proto/hashdb/v1/hashdb.proto | 64 +- sequencer/closingsignalsmanager_test.go | 4 +- sequencer/dbmanager_test.go | 4 +- state/genesis.go | 13 +- state/state.go | 2 +- state/state_test.go | 6 +- synchronizer/mock_state.go | 9 +- .../test.permissionless.prover.config.json | 3 +- test/config/test.prover.config.json | 3 +- test/docker-compose.yml | 4 +- test/scripts/send_transfers/main.go | 11 +- 28 files changed, 919 insertions(+), 689 deletions(-) rename aggregator/{pb => prover}/aggregator.pb.go (99%) rename aggregator/{pb => prover}/aggregator_grpc.pb.go (95%) rename merkletree/{pb => hashdb}/hashdb.pb.go (57%) rename merkletree/{pb => hashdb}/hashdb_grpc.pb.go (88%) diff --git a/Makefile b/Makefile index fd2ee2816e..2d9de4fd5b 100644 --- a/Makefile +++ b/Makefile @@ -119,9 +119,9 @@ install-git-hooks: ## Moves hook files to the .git/hooks directory .PHONY: generate-code-from-proto generate-code-from-proto: ## Generates code from proto files - cd proto/src/proto/hashdb/v1 && protoc --proto_path=. --proto_path=../../../../include --go_out=../../../../../merkletree/pb --go-grpc_out=../../../../../merkletree/pb --go_opt=paths=source_relative --go-grpc_opt=paths=source_relative hashdb.proto + cd proto/src/proto/hashdb/v1 && protoc --proto_path=. --proto_path=../../../../include --go_out=../../../../../merkletree/hashdb --go-grpc_out=../../../../../merkletree/hashdb --go_opt=paths=source_relative --go-grpc_opt=paths=source_relative hashdb.proto cd proto/src/proto/executor/v1 && protoc --proto_path=. --go_out=../../../../../state/runtime/executor --go-grpc_out=../../../../../state/runtime/executor --go-grpc_opt=paths=source_relative --go_opt=paths=source_relative executor.proto - cd proto/src/proto/aggregator/v1 && protoc --proto_path=. --proto_path=../../../../include --go_out=../../../../../aggregator/pb --go-grpc_out=../../../../../aggregator/pb --go-grpc_opt=paths=source_relative --go_opt=paths=source_relative aggregator.proto + cd proto/src/proto/aggregator/v1 && protoc --proto_path=. --proto_path=../../../../include --go_out=../../../../../aggregator/prover --go-grpc_out=../../../../../aggregator/prover --go-grpc_opt=paths=source_relative --go_opt=paths=source_relative aggregator.proto ## Help display. ## Pulls comments from beside commands and prints a nicely formatted diff --git a/aggregator/aggregator.go b/aggregator/aggregator.go index 93420738f8..751000af1f 100644 --- a/aggregator/aggregator.go +++ b/aggregator/aggregator.go @@ -14,7 +14,6 @@ import ( "unicode" "github.com/0xPolygonHermez/zkevm-node/aggregator/metrics" - "github.com/0xPolygonHermez/zkevm-node/aggregator/pb" "github.com/0xPolygonHermez/zkevm-node/aggregator/prover" "github.com/0xPolygonHermez/zkevm-node/config/types" "github.com/0xPolygonHermez/zkevm-node/encoding" @@ -41,12 +40,12 @@ type finalProofMsg struct { proverName string proverID string recursiveProof *state.Proof - finalProof *pb.FinalProof + finalProof *prover.FinalProof } // Aggregator represents an aggregator type Aggregator struct { - pb.UnimplementedAggregatorServiceServer + prover.UnimplementedAggregatorServiceServer cfg Config @@ -129,7 +128,7 @@ func (a *Aggregator) Start(ctx context.Context) error { } a.srv = grpc.NewServer() - pb.RegisterAggregatorServiceServer(a.srv, a) + prover.RegisterAggregatorServiceServer(a.srv, a) healthService := newHealthChecker() grpchealth.RegisterHealthServer(a.srv, healthService) @@ -159,7 +158,7 @@ func (a *Aggregator) Stop() { // Channel implements the bi-directional communication channel between the // Prover client and the Aggregator server. -func (a *Aggregator) Channel(stream pb.AggregatorService_ChannelServer) error { +func (a *Aggregator) Channel(stream prover.AggregatorService_ChannelServer) error { metrics.ConnectedProver() defer metrics.DisconnectedProver() @@ -306,7 +305,7 @@ func (a *Aggregator) handleFailureToAddVerifyBatchToBeMonitored(ctx context.Cont } // buildFinalProof builds and return the final proof for an aggregated/batch proof. -func (a *Aggregator) buildFinalProof(ctx context.Context, prover proverInterface, proof *state.Proof) (*pb.FinalProof, error) { +func (a *Aggregator) buildFinalProof(ctx context.Context, prover proverInterface, proof *state.Proof) (*prover.FinalProof, error) { log := log.WithFields( "prover", prover.Name(), "proverId", prover.ID(), @@ -972,14 +971,14 @@ func (a *Aggregator) isSynced(ctx context.Context, batchNum *uint64) bool { return true } -func (a *Aggregator) buildInputProver(ctx context.Context, batchToVerify *state.Batch) (*pb.InputProver, error) { +func (a *Aggregator) buildInputProver(ctx context.Context, batchToVerify *state.Batch) (*prover.InputProver, error) { previousBatch, err := a.State.GetBatchByNumber(ctx, batchToVerify.BatchNumber-1, nil) if err != nil && err != state.ErrStateNotSynchronized { return nil, fmt.Errorf("failed to get previous batch, err: %v", err) } - inputProver := &pb.InputProver{ - PublicInputs: &pb.PublicInputs{ + inputProver := &prover.InputProver{ + PublicInputs: &prover.PublicInputs{ OldStateRoot: previousBatch.StateRoot.Bytes(), OldAccInputHash: previousBatch.AccInputHash.Bytes(), OldBatchNum: previousBatch.BatchNumber, diff --git a/aggregator/aggregator_test.go b/aggregator/aggregator_test.go index f75d7237f3..d6e8166d04 100644 --- a/aggregator/aggregator_test.go +++ b/aggregator/aggregator_test.go @@ -10,7 +10,7 @@ import ( "time" "github.com/0xPolygonHermez/zkevm-node/aggregator/mocks" - "github.com/0xPolygonHermez/zkevm-node/aggregator/pb" + "github.com/0xPolygonHermez/zkevm-node/aggregator/prover" configTypes "github.com/0xPolygonHermez/zkevm-node/config/types" ethmanTypes "github.com/0xPolygonHermez/zkevm-node/etherman/types" "github.com/0xPolygonHermez/zkevm-node/ethtxmanager" @@ -53,7 +53,7 @@ func TestSendFinalProof(t *testing.T) { BatchNumber: batchNum, BatchNumberFinal: batchNumFinal, } - finalProof := &pb.FinalProof{} + finalProof := &prover.FinalProof{} cfg := Config{SenderAddress: from.Hex()} testCases := []struct { @@ -1000,9 +1000,9 @@ func TestTryBuildFinalProof(t *testing.T) { proverName := "proverName" proverID := "proverID" finalProofID := "finalProofID" - finalProof := pb.FinalProof{ + finalProof := prover.FinalProof{ Proof: "", - Public: &pb.PublicInputsExtended{ + Public: &prover.PublicInputsExtended{ NewStateRoot: []byte("newStateRoot"), NewLocalExitRoot: []byte("newLocalExitRoot"), }, diff --git a/aggregator/interfaces.go b/aggregator/interfaces.go index f957e5178f..119ea48291 100644 --- a/aggregator/interfaces.go +++ b/aggregator/interfaces.go @@ -4,7 +4,7 @@ import ( "context" "math/big" - "github.com/0xPolygonHermez/zkevm-node/aggregator/pb" + "github.com/0xPolygonHermez/zkevm-node/aggregator/prover" ethmanTypes "github.com/0xPolygonHermez/zkevm-node/etherman/types" "github.com/0xPolygonHermez/zkevm-node/ethtxmanager" "github.com/0xPolygonHermez/zkevm-node/state" @@ -19,11 +19,11 @@ type proverInterface interface { ID() string Addr() string IsIdle() (bool, error) - BatchProof(input *pb.InputProver) (*string, error) + BatchProof(input *prover.InputProver) (*string, error) AggregatedProof(inputProof1, inputProof2 string) (*string, error) FinalProof(inputProof string, aggregatorAddr string) (*string, error) WaitRecursiveProof(ctx context.Context, proofID string) (string, error) - WaitFinalProof(ctx context.Context, proofID string) (*pb.FinalProof, error) + WaitFinalProof(ctx context.Context, proofID string) (*prover.FinalProof, error) } // ethTxManager contains the methods required to send txs to diff --git a/aggregator/mocks/mock_prover.go b/aggregator/mocks/mock_prover.go index 7cba231d6e..0e7a01384b 100644 --- a/aggregator/mocks/mock_prover.go +++ b/aggregator/mocks/mock_prover.go @@ -5,7 +5,7 @@ package mocks import ( context "context" - pb "github.com/0xPolygonHermez/zkevm-node/aggregator/pb" + prover "github.com/0xPolygonHermez/zkevm-node/aggregator/prover" mock "github.com/stretchr/testify/mock" ) @@ -55,15 +55,15 @@ func (_m *ProverMock) AggregatedProof(inputProof1 string, inputProof2 string) (* } // BatchProof provides a mock function with given fields: input -func (_m *ProverMock) BatchProof(input *pb.InputProver) (*string, error) { +func (_m *ProverMock) BatchProof(input *prover.InputProver) (*string, error) { ret := _m.Called(input) var r0 *string var r1 error - if rf, ok := ret.Get(0).(func(*pb.InputProver) (*string, error)); ok { + if rf, ok := ret.Get(0).(func(*prover.InputProver) (*string, error)); ok { return rf(input) } - if rf, ok := ret.Get(0).(func(*pb.InputProver) *string); ok { + if rf, ok := ret.Get(0).(func(*prover.InputProver) *string); ok { r0 = rf(input) } else { if ret.Get(0) != nil { @@ -71,7 +71,7 @@ func (_m *ProverMock) BatchProof(input *pb.InputProver) (*string, error) { } } - if rf, ok := ret.Get(1).(func(*pb.InputProver) error); ok { + if rf, ok := ret.Get(1).(func(*prover.InputProver) error); ok { r1 = rf(input) } else { r1 = ret.Error(1) @@ -159,19 +159,19 @@ func (_m *ProverMock) Name() string { } // WaitFinalProof provides a mock function with given fields: ctx, proofID -func (_m *ProverMock) WaitFinalProof(ctx context.Context, proofID string) (*pb.FinalProof, error) { +func (_m *ProverMock) WaitFinalProof(ctx context.Context, proofID string) (*prover.FinalProof, error) { ret := _m.Called(ctx, proofID) - var r0 *pb.FinalProof + var r0 *prover.FinalProof var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) (*pb.FinalProof, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, string) (*prover.FinalProof, error)); ok { return rf(ctx, proofID) } - if rf, ok := ret.Get(0).(func(context.Context, string) *pb.FinalProof); ok { + if rf, ok := ret.Get(0).(func(context.Context, string) *prover.FinalProof); ok { r0 = rf(ctx, proofID) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(*pb.FinalProof) + r0 = ret.Get(0).(*prover.FinalProof) } } diff --git a/aggregator/pb/aggregator.pb.go b/aggregator/prover/aggregator.pb.go similarity index 99% rename from aggregator/pb/aggregator.pb.go rename to aggregator/prover/aggregator.pb.go index 54fd9044d7..1b54fe910f 100644 --- a/aggregator/pb/aggregator.pb.go +++ b/aggregator/prover/aggregator.pb.go @@ -1,10 +1,10 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 +// protoc-gen-go v1.30.0 // protoc v3.21.12 // source: aggregator.proto -package pb +package prover import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" @@ -247,6 +247,7 @@ type AggregatorMessage struct { Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // Types that are assignable to Request: + // // *AggregatorMessage_GetStatusRequest // *AggregatorMessage_GenBatchProofRequest // *AggregatorMessage_GenAggregatedProofRequest @@ -391,6 +392,7 @@ type ProverMessage struct { Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // Types that are assignable to Response: + // // *ProverMessage_GetStatusResponse // *ProverMessage_GenBatchProofResponse // *ProverMessage_GenAggregatedProofResponse @@ -1263,6 +1265,7 @@ type GetProofResponse struct { Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // Types that are assignable to Proof: + // // *GetProofResponse_FinalProof // *GetProofResponse_RecursiveProof Proof isGetProofResponse_Proof `protobuf_oneof:"proof"` @@ -1981,11 +1984,11 @@ var file_aggregator_proto_rawDesc = []byte{ 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x65, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x20, 0x2e, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x6f, 0x72, 0x4d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x35, 0x5a, 0x33, 0x67, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x39, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x30, 0x78, 0x50, 0x6f, 0x6c, 0x79, 0x67, 0x6f, 0x6e, 0x48, 0x65, 0x72, 0x6d, 0x65, 0x7a, 0x2f, 0x7a, 0x6b, 0x65, 0x76, 0x6d, 0x2d, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x6f, 0x72, 0x2f, - 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x70, 0x72, 0x6f, 0x76, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/aggregator/pb/aggregator_grpc.pb.go b/aggregator/prover/aggregator_grpc.pb.go similarity index 95% rename from aggregator/pb/aggregator_grpc.pb.go rename to aggregator/prover/aggregator_grpc.pb.go index 8d2b33815d..c2c1b6ed54 100644 --- a/aggregator/pb/aggregator_grpc.pb.go +++ b/aggregator/prover/aggregator_grpc.pb.go @@ -1,10 +1,10 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.2.0 +// - protoc-gen-go-grpc v1.3.0 // - protoc v3.21.12 // source: aggregator.proto -package pb +package prover import ( context "context" @@ -18,6 +18,10 @@ import ( // Requires gRPC-Go v1.32.0 or later. const _ = grpc.SupportPackageIsVersion7 +const ( + AggregatorService_Channel_FullMethodName = "/aggregator.v1.AggregatorService/Channel" +) + // AggregatorServiceClient is the client API for AggregatorService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. @@ -34,7 +38,7 @@ func NewAggregatorServiceClient(cc grpc.ClientConnInterface) AggregatorServiceCl } func (c *aggregatorServiceClient) Channel(ctx context.Context, opts ...grpc.CallOption) (AggregatorService_ChannelClient, error) { - stream, err := c.cc.NewStream(ctx, &AggregatorService_ServiceDesc.Streams[0], "/aggregator.v1.AggregatorService/Channel", opts...) + stream, err := c.cc.NewStream(ctx, &AggregatorService_ServiceDesc.Streams[0], AggregatorService_Channel_FullMethodName, opts...) if err != nil { return nil, err } diff --git a/aggregator/prover/prover.go b/aggregator/prover/prover.go index 2a61a1678d..fbaf72715f 100644 --- a/aggregator/prover/prover.go +++ b/aggregator/prover/prover.go @@ -8,7 +8,6 @@ import ( "time" "github.com/0xPolygonHermez/zkevm-node/aggregator/metrics" - "github.com/0xPolygonHermez/zkevm-node/aggregator/pb" "github.com/0xPolygonHermez/zkevm-node/config/types" "github.com/0xPolygonHermez/zkevm-node/log" ) @@ -29,11 +28,11 @@ type Prover struct { id string address net.Addr proofStatePollingInterval types.Duration - stream pb.AggregatorService_ChannelServer + stream AggregatorService_ChannelServer } // New returns a new Prover instance. -func New(stream pb.AggregatorService_ChannelServer, addr net.Addr, proofStatePollingInterval types.Duration) (*Prover, error) { +func New(stream AggregatorService_ChannelServer, addr net.Addr, proofStatePollingInterval types.Duration) (*Prover, error) { p := &Prover{ stream: stream, address: addr, @@ -63,20 +62,20 @@ func (p *Prover) Addr() string { } // Status gets the prover status. -func (p *Prover) Status() (*pb.GetStatusResponse, error) { - req := &pb.AggregatorMessage{ - Request: &pb.AggregatorMessage_GetStatusRequest{ - GetStatusRequest: &pb.GetStatusRequest{}, +func (p *Prover) Status() (*GetStatusResponse, error) { + req := &AggregatorMessage{ + Request: &AggregatorMessage_GetStatusRequest{ + GetStatusRequest: &GetStatusRequest{}, }, } res, err := p.call(req) if err != nil { return nil, err } - if msg, ok := res.Response.(*pb.ProverMessage_GetStatusResponse); ok { + if msg, ok := res.Response.(*ProverMessage_GetStatusResponse); ok { return msg.GetStatusResponse, nil } - return nil, fmt.Errorf("%w, wanted %T, got %T", ErrBadProverResponse, &pb.ProverMessage_GetStatusResponse{}, res.Response) + return nil, fmt.Errorf("%w, wanted %T, got %T", ErrBadProverResponse, &ProverMessage_GetStatusResponse{}, res.Response) } // IsIdle returns true if the prover is idling. @@ -85,7 +84,7 @@ func (p *Prover) IsIdle() (bool, error) { if err != nil { return false, err } - return status.Status == pb.GetStatusResponse_STATUS_IDLE, nil + return status.Status == GetStatusResponse_STATUS_IDLE, nil } // SupportsForkID returns true if the prover supports the given fork id. @@ -103,12 +102,12 @@ func (p *Prover) SupportsForkID(forkID uint64) bool { // BatchProof instructs the prover to generate a batch proof for the provided // input. It returns the ID of the proof being computed. -func (p *Prover) BatchProof(input *pb.InputProver) (*string, error) { +func (p *Prover) BatchProof(input *InputProver) (*string, error) { metrics.WorkingProver() - req := &pb.AggregatorMessage{ - Request: &pb.AggregatorMessage_GenBatchProofRequest{ - GenBatchProofRequest: &pb.GenBatchProofRequest{Input: input}, + req := &AggregatorMessage{ + Request: &AggregatorMessage_GenBatchProofRequest{ + GenBatchProofRequest: &GenBatchProofRequest{Input: input}, }, } res, err := p.call(req) @@ -116,22 +115,22 @@ func (p *Prover) BatchProof(input *pb.InputProver) (*string, error) { return nil, err } - if msg, ok := res.Response.(*pb.ProverMessage_GenBatchProofResponse); ok { + if msg, ok := res.Response.(*ProverMessage_GenBatchProofResponse); ok { switch msg.GenBatchProofResponse.Result { - case pb.Result_RESULT_UNSPECIFIED: + case Result_RESULT_UNSPECIFIED: return nil, fmt.Errorf("failed to generate proof %s, %w, input %v", msg.GenBatchProofResponse.String(), ErrUnspecified, input) - case pb.Result_RESULT_OK: + case Result_RESULT_OK: return &msg.GenBatchProofResponse.Id, nil - case pb.Result_RESULT_ERROR: + case Result_RESULT_ERROR: return nil, fmt.Errorf("failed to generate proof %s, %w, input %v", msg.GenBatchProofResponse.String(), ErrBadRequest, input) - case pb.Result_RESULT_INTERNAL_ERROR: + case Result_RESULT_INTERNAL_ERROR: return nil, fmt.Errorf("failed to generate proof %s, %w, input %v", msg.GenBatchProofResponse.String(), ErrProverInternalError, input) default: return nil, fmt.Errorf("failed to generate proof %s, %w,input %v", msg.GenBatchProofResponse.String(), ErrUnknown, input) } } - return nil, fmt.Errorf("%w, wanted %T, got %T", ErrBadProverResponse, &pb.ProverMessage_GenBatchProofResponse{}, res.Response) + return nil, fmt.Errorf("%w, wanted %T, got %T", ErrBadProverResponse, &ProverMessage_GenBatchProofResponse{}, res.Response) } // AggregatedProof instructs the prover to generate an aggregated proof from @@ -139,9 +138,9 @@ func (p *Prover) BatchProof(input *pb.InputProver) (*string, error) { func (p *Prover) AggregatedProof(inputProof1, inputProof2 string) (*string, error) { metrics.WorkingProver() - req := &pb.AggregatorMessage{ - Request: &pb.AggregatorMessage_GenAggregatedProofRequest{ - GenAggregatedProofRequest: &pb.GenAggregatedProofRequest{ + req := &AggregatorMessage{ + Request: &AggregatorMessage_GenAggregatedProofRequest{ + GenAggregatedProofRequest: &GenAggregatedProofRequest{ RecursiveProof_1: inputProof1, RecursiveProof_2: inputProof2, }, @@ -152,17 +151,17 @@ func (p *Prover) AggregatedProof(inputProof1, inputProof2 string) (*string, erro return nil, err } - if msg, ok := res.Response.(*pb.ProverMessage_GenAggregatedProofResponse); ok { + if msg, ok := res.Response.(*ProverMessage_GenAggregatedProofResponse); ok { switch msg.GenAggregatedProofResponse.Result { - case pb.Result_RESULT_UNSPECIFIED: + case Result_RESULT_UNSPECIFIED: return nil, fmt.Errorf("failed to aggregate proofs %s, %w, input 1 %s, input 2 %s", msg.GenAggregatedProofResponse.String(), ErrUnspecified, inputProof1, inputProof2) - case pb.Result_RESULT_OK: + case Result_RESULT_OK: return &msg.GenAggregatedProofResponse.Id, nil - case pb.Result_RESULT_ERROR: + case Result_RESULT_ERROR: return nil, fmt.Errorf("failed to aggregate proofs %s, %w, input 1 %s, input 2 %s", msg.GenAggregatedProofResponse.String(), ErrBadRequest, inputProof1, inputProof2) - case pb.Result_RESULT_INTERNAL_ERROR: + case Result_RESULT_INTERNAL_ERROR: return nil, fmt.Errorf("failed to aggregate proofs %s, %w, input 1 %s, input 2 %s", msg.GenAggregatedProofResponse.String(), ErrProverInternalError, inputProof1, inputProof2) default: @@ -171,7 +170,7 @@ func (p *Prover) AggregatedProof(inputProof1, inputProof2 string) (*string, erro } } - return nil, fmt.Errorf("%w, wanted %T, got %T", ErrBadProverResponse, &pb.ProverMessage_GenAggregatedProofResponse{}, res.Response) + return nil, fmt.Errorf("%w, wanted %T, got %T", ErrBadProverResponse, &ProverMessage_GenAggregatedProofResponse{}, res.Response) } // FinalProof instructs the prover to generate a final proof for the given @@ -179,9 +178,9 @@ func (p *Prover) AggregatedProof(inputProof1, inputProof2 string) (*string, erro func (p *Prover) FinalProof(inputProof string, aggregatorAddr string) (*string, error) { metrics.WorkingProver() - req := &pb.AggregatorMessage{ - Request: &pb.AggregatorMessage_GenFinalProofRequest{ - GenFinalProofRequest: &pb.GenFinalProofRequest{ + req := &AggregatorMessage{ + Request: &AggregatorMessage_GenFinalProofRequest{ + GenFinalProofRequest: &GenFinalProofRequest{ RecursiveProof: inputProof, AggregatorAddr: aggregatorAddr, }, @@ -192,17 +191,17 @@ func (p *Prover) FinalProof(inputProof string, aggregatorAddr string) (*string, return nil, err } - if msg, ok := res.Response.(*pb.ProverMessage_GenFinalProofResponse); ok { + if msg, ok := res.Response.(*ProverMessage_GenFinalProofResponse); ok { switch msg.GenFinalProofResponse.Result { - case pb.Result_RESULT_UNSPECIFIED: + case Result_RESULT_UNSPECIFIED: return nil, fmt.Errorf("failed to generate final proof %s, %w, input %s", msg.GenFinalProofResponse.String(), ErrUnspecified, inputProof) - case pb.Result_RESULT_OK: + case Result_RESULT_OK: return &msg.GenFinalProofResponse.Id, nil - case pb.Result_RESULT_ERROR: + case Result_RESULT_ERROR: return nil, fmt.Errorf("failed to generate final proof %s, %w, input %s", msg.GenFinalProofResponse.String(), ErrBadRequest, inputProof) - case pb.Result_RESULT_INTERNAL_ERROR: + case Result_RESULT_INTERNAL_ERROR: return nil, fmt.Errorf("failed to generate final proof %s, %w, input %s", msg.GenFinalProofResponse.String(), ErrProverInternalError, inputProof) default: @@ -210,32 +209,32 @@ func (p *Prover) FinalProof(inputProof string, aggregatorAddr string) (*string, msg.GenFinalProofResponse.String(), ErrUnknown, inputProof) } } - return nil, fmt.Errorf("%w, wanted %T, got %T", ErrBadProverResponse, &pb.ProverMessage_GenFinalProofResponse{}, res.Response) + return nil, fmt.Errorf("%w, wanted %T, got %T", ErrBadProverResponse, &ProverMessage_GenFinalProofResponse{}, res.Response) } // CancelProofRequest asks the prover to stop the generation of the proof // matching the provided proofID. func (p *Prover) CancelProofRequest(proofID string) error { - req := &pb.AggregatorMessage{ - Request: &pb.AggregatorMessage_CancelRequest{ - CancelRequest: &pb.CancelRequest{Id: proofID}, + req := &AggregatorMessage{ + Request: &AggregatorMessage_CancelRequest{ + CancelRequest: &CancelRequest{Id: proofID}, }, } res, err := p.call(req) if err != nil { return err } - if msg, ok := res.Response.(*pb.ProverMessage_CancelResponse); ok { + if msg, ok := res.Response.(*ProverMessage_CancelResponse); ok { switch msg.CancelResponse.Result { - case pb.Result_RESULT_UNSPECIFIED: + case Result_RESULT_UNSPECIFIED: return fmt.Errorf("failed to cancel proof id [%s], %w, %s", proofID, ErrUnspecified, msg.CancelResponse.String()) - case pb.Result_RESULT_OK: + case Result_RESULT_OK: return nil - case pb.Result_RESULT_ERROR: + case Result_RESULT_ERROR: return fmt.Errorf("failed to cancel proof id [%s], %w, %s", proofID, ErrBadRequest, msg.CancelResponse.String()) - case pb.Result_RESULT_INTERNAL_ERROR: + case Result_RESULT_INTERNAL_ERROR: return fmt.Errorf("failed to cancel proof id [%s], %w, %s", proofID, ErrProverInternalError, msg.CancelResponse.String()) default: @@ -243,7 +242,7 @@ func (p *Prover) CancelProofRequest(proofID string) error { proofID, ErrUnknown, msg.CancelResponse.String()) } } - return fmt.Errorf("%w, wanted %T, got %T", ErrBadProverResponse, &pb.ProverMessage_CancelResponse{}, res.Response) + return fmt.Errorf("%w, wanted %T, got %T", ErrBadProverResponse, &ProverMessage_CancelResponse{}, res.Response) } // WaitRecursiveProof waits for a recursive proof to be generated by the prover @@ -253,29 +252,29 @@ func (p *Prover) WaitRecursiveProof(ctx context.Context, proofID string) (string if err != nil { return "", err } - resProof := res.Proof.(*pb.GetProofResponse_RecursiveProof) + resProof := res.Proof.(*GetProofResponse_RecursiveProof) return resProof.RecursiveProof, nil } // WaitFinalProof waits for the final proof to be generated by the prover and // returns it. -func (p *Prover) WaitFinalProof(ctx context.Context, proofID string) (*pb.FinalProof, error) { +func (p *Prover) WaitFinalProof(ctx context.Context, proofID string) (*FinalProof, error) { res, err := p.waitProof(ctx, proofID) if err != nil { return nil, err } - resProof := res.Proof.(*pb.GetProofResponse_FinalProof) + resProof := res.Proof.(*GetProofResponse_FinalProof) return resProof.FinalProof, nil } // waitProof waits for a proof to be generated by the prover and returns the // prover response. -func (p *Prover) waitProof(ctx context.Context, proofID string) (*pb.GetProofResponse, error) { +func (p *Prover) waitProof(ctx context.Context, proofID string) (*GetProofResponse, error) { defer metrics.IdlingProver() - req := &pb.AggregatorMessage{ - Request: &pb.AggregatorMessage_GetProofRequest{ - GetProofRequest: &pb.GetProofRequest{ + req := &AggregatorMessage{ + Request: &AggregatorMessage_GetProofRequest{ + GetProofRequest: &GetProofRequest{ // TODO(pg): set Timeout field? Id: proofID, }, @@ -291,26 +290,26 @@ func (p *Prover) waitProof(ctx context.Context, proofID string) (*pb.GetProofRes if err != nil { return nil, err } - if msg, ok := res.Response.(*pb.ProverMessage_GetProofResponse); ok { + if msg, ok := res.Response.(*ProverMessage_GetProofResponse); ok { switch msg.GetProofResponse.Result { - case pb.GetProofResponse_RESULT_PENDING: + case GetProofResponse_RESULT_PENDING: time.Sleep(p.proofStatePollingInterval.Duration) continue - case pb.GetProofResponse_RESULT_UNSPECIFIED: + case GetProofResponse_RESULT_UNSPECIFIED: return nil, fmt.Errorf("failed to get proof ID: %s, %w, prover response: %s", proofID, ErrUnspecified, msg.GetProofResponse.String()) - case pb.GetProofResponse_RESULT_COMPLETED_OK: + case GetProofResponse_RESULT_COMPLETED_OK: return msg.GetProofResponse, nil - case pb.GetProofResponse_RESULT_ERROR: + case GetProofResponse_RESULT_ERROR: return nil, fmt.Errorf("failed to get proof with ID %s, %w, prover response: %s", proofID, ErrBadRequest, msg.GetProofResponse.String()) - case pb.GetProofResponse_RESULT_COMPLETED_ERROR: + case GetProofResponse_RESULT_COMPLETED_ERROR: return nil, fmt.Errorf("failed to get proof with ID %s, %w, prover response: %s", proofID, ErrProverCompletedError, msg.GetProofResponse.String()) - case pb.GetProofResponse_RESULT_INTERNAL_ERROR: + case GetProofResponse_RESULT_INTERNAL_ERROR: return nil, fmt.Errorf("failed to get proof ID: %s, %w, prover response: %s", proofID, ErrProverInternalError, msg.GetProofResponse.String()) - case pb.GetProofResponse_RESULT_CANCEL: + case GetProofResponse_RESULT_CANCEL: return nil, fmt.Errorf("proof generation was cancelled for proof ID %s, %w, prover response: %s", proofID, ErrProofCanceled, msg.GetProofResponse.String()) default: @@ -318,14 +317,14 @@ func (p *Prover) waitProof(ctx context.Context, proofID string) (*pb.GetProofRes proofID, ErrUnknown, msg.GetProofResponse.String()) } } - return nil, fmt.Errorf("%w, wanted %T, got %T", ErrBadProverResponse, &pb.ProverMessage_GetProofResponse{}, res.Response) + return nil, fmt.Errorf("%w, wanted %T, got %T", ErrBadProverResponse, &ProverMessage_GetProofResponse{}, res.Response) } } } // call sends a message to the prover and waits to receive the response over // the connection stream. -func (p *Prover) call(req *pb.AggregatorMessage) (*pb.ProverMessage, error) { +func (p *Prover) call(req *AggregatorMessage) (*ProverMessage, error) { if err := p.stream.Send(req); err != nil { return nil, err } diff --git a/config/environments/mainnet/prover.config.json b/config/environments/mainnet/prover.config.json index 0aec07e541..7b10066bc7 100644 --- a/config/environments/mainnet/prover.config.json +++ b/config/environments/mainnet/prover.config.json @@ -112,5 +112,5 @@ "maxHashDBThreads": 8, "ECRecoverPrecalc": true, "ECRecoverPrecalcNThreads": 16, - "dbMultiWriteSinglePosition": false + "stateManager": true } diff --git a/config/environments/testnet/prover.config.json b/config/environments/testnet/prover.config.json index 0aec07e541..7b10066bc7 100644 --- a/config/environments/testnet/prover.config.json +++ b/config/environments/testnet/prover.config.json @@ -112,5 +112,5 @@ "maxHashDBThreads": 8, "ECRecoverPrecalc": true, "ECRecoverPrecalcNThreads": 16, - "dbMultiWriteSinglePosition": false + "stateManager": true } diff --git a/docker-compose.yml b/docker-compose.yml index 0755564b29..fda0a68326 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -107,7 +107,7 @@ services: zkevm-prover: container_name: zkevm-prover restart: unless-stopped - image: hermeznetwork/zkevm-prover:v2.0.1 + image: hermeznetwork/zkevm-prover:v2.1.0-RC2 depends_on: zkevm-state-db: condition: service_healthy diff --git a/etherman/types/finalproofinputs.go b/etherman/types/finalproofinputs.go index f5686691b4..25a8d7e18f 100644 --- a/etherman/types/finalproofinputs.go +++ b/etherman/types/finalproofinputs.go @@ -1,10 +1,10 @@ package types -import "github.com/0xPolygonHermez/zkevm-node/aggregator/pb" +import "github.com/0xPolygonHermez/zkevm-node/aggregator/prover" // FinalProofInputs struct type FinalProofInputs struct { - FinalProof *pb.FinalProof + FinalProof *prover.FinalProof NewLocalExitRoot []byte NewStateRoot []byte } diff --git a/merkletree/client.go b/merkletree/client.go index 94af21c07a..798dea5e04 100644 --- a/merkletree/client.go +++ b/merkletree/client.go @@ -5,13 +5,13 @@ import ( "time" "github.com/0xPolygonHermez/zkevm-node/log" - "github.com/0xPolygonHermez/zkevm-node/merkletree/pb" + "github.com/0xPolygonHermez/zkevm-node/merkletree/hashdb" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" ) // NewMTDBServiceClient creates a new MTDB client. -func NewMTDBServiceClient(ctx context.Context, c Config) (pb.HashDBServiceClient, *grpc.ClientConn, context.CancelFunc) { +func NewMTDBServiceClient(ctx context.Context, c Config) (hashdb.HashDBServiceClient, *grpc.ClientConn, context.CancelFunc) { opts := []grpc.DialOption{ grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock(), @@ -26,6 +26,6 @@ func NewMTDBServiceClient(ctx context.Context, c Config) (pb.HashDBServiceClient } log.Infof("connected to merkletree") - mtDBClient := pb.NewHashDBServiceClient(mtDBConn) + mtDBClient := hashdb.NewHashDBServiceClient(mtDBConn) return mtDBClient, mtDBConn, cancel } diff --git a/merkletree/pb/hashdb.pb.go b/merkletree/hashdb/hashdb.pb.go similarity index 57% rename from merkletree/pb/hashdb.pb.go rename to merkletree/hashdb/hashdb.pb.go index b1c0bfd768..68e7e3e3b0 100644 --- a/merkletree/pb/hashdb.pb.go +++ b/merkletree/hashdb/hashdb.pb.go @@ -4,15 +4,14 @@ // protoc v3.21.12 // source: hashdb.proto -package pb +package hashdb import ( - reflect "reflect" - sync "sync" - protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" emptypb "google.golang.org/protobuf/types/known/emptypb" + reflect "reflect" + sync "sync" ) const ( @@ -22,6 +21,55 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +type Persistence int32 + +const ( + Persistence_PERSISTENCE_CACHE_UNSPECIFIED Persistence = 0 + Persistence_PERSISTENCE_DATABASE Persistence = 1 + Persistence_PERSISTENCE_TEMPORARY Persistence = 2 +) + +// Enum value maps for Persistence. +var ( + Persistence_name = map[int32]string{ + 0: "PERSISTENCE_CACHE_UNSPECIFIED", + 1: "PERSISTENCE_DATABASE", + 2: "PERSISTENCE_TEMPORARY", + } + Persistence_value = map[string]int32{ + "PERSISTENCE_CACHE_UNSPECIFIED": 0, + "PERSISTENCE_DATABASE": 1, + "PERSISTENCE_TEMPORARY": 2, + } +) + +func (x Persistence) Enum() *Persistence { + p := new(Persistence) + *p = x + return p +} + +func (x Persistence) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Persistence) Descriptor() protoreflect.EnumDescriptor { + return file_hashdb_proto_enumTypes[0].Descriptor() +} + +func (Persistence) Type() protoreflect.EnumType { + return &file_hashdb_proto_enumTypes[0] +} + +func (x Persistence) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Persistence.Descriptor instead. +func (Persistence) EnumDescriptor() ([]byte, []int) { + return file_hashdb_proto_rawDescGZIP(), []int{0} +} + type ResultCode_Code int32 const ( @@ -64,11 +112,11 @@ func (x ResultCode_Code) String() string { } func (ResultCode_Code) Descriptor() protoreflect.EnumDescriptor { - return file_hashdb_proto_enumTypes[0].Descriptor() + return file_hashdb_proto_enumTypes[1].Descriptor() } func (ResultCode_Code) Type() protoreflect.EnumType { - return &file_hashdb_proto_enumTypes[0] + return &file_hashdb_proto_enumTypes[1] } func (x ResultCode_Code) Number() protoreflect.EnumNumber { @@ -77,7 +125,7 @@ func (x ResultCode_Code) Number() protoreflect.EnumNumber { // Deprecated: Use ResultCode_Code.Descriptor instead. func (ResultCode_Code) EnumDescriptor() ([]byte, []int) { - return file_hashdb_proto_rawDescGZIP(), []int{19, 0} + return file_hashdb_proto_rawDescGZIP(), []int{20, 0} } type Version struct { @@ -132,20 +180,24 @@ func (x *Version) GetV0_0_1() string { // @param {old_root} - merkle-tree root // @param {key} - key to set // @param {value} - scalar value to set (HEX string format) -// @param {persistent} - indicates if it should be stored in the SQL database (true) or only in the memory cache (false) +// @param {persistence} - indicates if it should be stored only in CACHE, in the SQL DATABASE, or it is just TEMPORARY and should be deleted at the flush of this batch UUID // @param {details} - indicates if it should return all response parameters (true) or just the new root (false) // @param {get_db_read_log} - indicates if it should return the DB reads generated during the execution of the request +// @param {batch_uuid} - indicates a unique identifier of the current batch or session; data for this batch can be stored in memory until flushed to database +// @param {tx} - current transaction ordinal number: 0, 1, 2... type SetRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - OldRoot *Fea `protobuf:"bytes,1,opt,name=old_root,json=oldRoot,proto3" json:"old_root,omitempty"` - Key *Fea `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` - Value string `protobuf:"bytes,3,opt,name=value,proto3" json:"value,omitempty"` - Persistent bool `protobuf:"varint,4,opt,name=persistent,proto3" json:"persistent,omitempty"` - Details bool `protobuf:"varint,5,opt,name=details,proto3" json:"details,omitempty"` - GetDbReadLog bool `protobuf:"varint,6,opt,name=get_db_read_log,json=getDbReadLog,proto3" json:"get_db_read_log,omitempty"` + OldRoot *Fea `protobuf:"bytes,1,opt,name=old_root,json=oldRoot,proto3" json:"old_root,omitempty"` + Key *Fea `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + Value string `protobuf:"bytes,3,opt,name=value,proto3" json:"value,omitempty"` + Persistence Persistence `protobuf:"varint,4,opt,name=persistence,proto3,enum=hashdb.v1.Persistence" json:"persistence,omitempty"` + Details bool `protobuf:"varint,5,opt,name=details,proto3" json:"details,omitempty"` + GetDbReadLog bool `protobuf:"varint,6,opt,name=get_db_read_log,json=getDbReadLog,proto3" json:"get_db_read_log,omitempty"` + BatchUuid string `protobuf:"bytes,7,opt,name=batch_uuid,json=batchUuid,proto3" json:"batch_uuid,omitempty"` + Tx uint64 `protobuf:"varint,8,opt,name=tx,proto3" json:"tx,omitempty"` } func (x *SetRequest) Reset() { @@ -201,11 +253,11 @@ func (x *SetRequest) GetValue() string { return "" } -func (x *SetRequest) GetPersistent() bool { +func (x *SetRequest) GetPersistence() Persistence { if x != nil { - return x.Persistent + return x.Persistence } - return false + return Persistence_PERSISTENCE_CACHE_UNSPECIFIED } func (x *SetRequest) GetDetails() bool { @@ -222,21 +274,37 @@ func (x *SetRequest) GetGetDbReadLog() bool { return false } +func (x *SetRequest) GetBatchUuid() string { + if x != nil { + return x.BatchUuid + } + return "" +} + +func (x *SetRequest) GetTx() uint64 { + if x != nil { + return x.Tx + } + return 0 +} + // * // @dev GetRequest // @param {root} - merkle-tree root // @param {key} - key to look for // @param {details} - indicates if it should return all response parameters (true) or just the new root (false) // @param {get_db_read_log} - indicates if it should return the DB reads generated during the execution of the request +// @param {batch_uuid} - indicates a unique identifier of the current batch or session; data for this batch can be stored in memory until flushed to database type GetRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Root *Fea `protobuf:"bytes,1,opt,name=root,proto3" json:"root,omitempty"` - Key *Fea `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` - Details bool `protobuf:"varint,3,opt,name=details,proto3" json:"details,omitempty"` - GetDbReadLog bool `protobuf:"varint,4,opt,name=get_db_read_log,json=getDbReadLog,proto3" json:"get_db_read_log,omitempty"` + Root *Fea `protobuf:"bytes,1,opt,name=root,proto3" json:"root,omitempty"` + Key *Fea `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + Details bool `protobuf:"varint,3,opt,name=details,proto3" json:"details,omitempty"` + GetDbReadLog bool `protobuf:"varint,4,opt,name=get_db_read_log,json=getDbReadLog,proto3" json:"get_db_read_log,omitempty"` + BatchUuid string `protobuf:"bytes,5,opt,name=batch_uuid,json=batchUuid,proto3" json:"batch_uuid,omitempty"` } func (x *GetRequest) Reset() { @@ -299,6 +367,13 @@ func (x *GetRequest) GetGetDbReadLog() bool { return false } +func (x *GetRequest) GetBatchUuid() string { + if x != nil { + return x.BatchUuid + } + return "" +} + // * // @dev SetProgramRequest // @param {key} - key to set @@ -535,6 +610,124 @@ func (x *LoadProgramDBRequest) GetPersistent() bool { return false } +// * +// @dev FlushRequest +// @param {batch_uuid} - indicates a unique identifier of the current batch or session which data will be flushed to cache (and database if required) +type FlushRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + BatchUuid string `protobuf:"bytes,1,opt,name=batch_uuid,json=batchUuid,proto3" json:"batch_uuid,omitempty"` +} + +func (x *FlushRequest) Reset() { + *x = FlushRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_hashdb_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *FlushRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FlushRequest) ProtoMessage() {} + +func (x *FlushRequest) ProtoReflect() protoreflect.Message { + mi := &file_hashdb_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FlushRequest.ProtoReflect.Descriptor instead. +func (*FlushRequest) Descriptor() ([]byte, []int) { + return file_hashdb_proto_rawDescGZIP(), []int{7} +} + +func (x *FlushRequest) GetBatchUuid() string { + if x != nil { + return x.BatchUuid + } + return "" +} + +// * +// @dev SemiFlushRequest +// @param {batch_uuid} - indicates a unique identifier of the current batch or session which data will be semi-flushed +// @param {new_state_root} - state root at this point of the execution +// @param {persistence} - indicates if it should be stored only in CACHE, in the SQL DATABASE, or it is just TEMPORARY and should be deleted at the flush of this batch UUID +type SemiFlushRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + BatchUuid string `protobuf:"bytes,1,opt,name=batch_uuid,json=batchUuid,proto3" json:"batch_uuid,omitempty"` + NewStateRoot string `protobuf:"bytes,2,opt,name=new_state_root,json=newStateRoot,proto3" json:"new_state_root,omitempty"` + Persistence Persistence `protobuf:"varint,3,opt,name=persistence,proto3,enum=hashdb.v1.Persistence" json:"persistence,omitempty"` +} + +func (x *SemiFlushRequest) Reset() { + *x = SemiFlushRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_hashdb_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SemiFlushRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SemiFlushRequest) ProtoMessage() {} + +func (x *SemiFlushRequest) ProtoReflect() protoreflect.Message { + mi := &file_hashdb_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SemiFlushRequest.ProtoReflect.Descriptor instead. +func (*SemiFlushRequest) Descriptor() ([]byte, []int) { + return file_hashdb_proto_rawDescGZIP(), []int{8} +} + +func (x *SemiFlushRequest) GetBatchUuid() string { + if x != nil { + return x.BatchUuid + } + return "" +} + +func (x *SemiFlushRequest) GetNewStateRoot() string { + if x != nil { + return x.NewStateRoot + } + return "" +} + +func (x *SemiFlushRequest) GetPersistence() Persistence { + if x != nil { + return x.Persistence + } + return Persistence_PERSISTENCE_CACHE_UNSPECIFIED +} + // * // @dev GetFlushDataRequest // @param {flush_id} - last stored flush ID got using this method, or 0 if it never was called before @@ -549,7 +742,7 @@ type GetFlushDataRequest struct { func (x *GetFlushDataRequest) Reset() { *x = GetFlushDataRequest{} if protoimpl.UnsafeEnabled { - mi := &file_hashdb_proto_msgTypes[7] + mi := &file_hashdb_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -562,7 +755,7 @@ func (x *GetFlushDataRequest) String() string { func (*GetFlushDataRequest) ProtoMessage() {} func (x *GetFlushDataRequest) ProtoReflect() protoreflect.Message { - mi := &file_hashdb_proto_msgTypes[7] + mi := &file_hashdb_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -575,7 +768,7 @@ func (x *GetFlushDataRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetFlushDataRequest.ProtoReflect.Descriptor instead. func (*GetFlushDataRequest) Descriptor() ([]byte, []int) { - return file_hashdb_proto_rawDescGZIP(), []int{7} + return file_hashdb_proto_rawDescGZIP(), []int{9} } func (x *GetFlushDataRequest) GetFlushId() uint64 { @@ -623,7 +816,7 @@ type SetResponse struct { func (x *SetResponse) Reset() { *x = SetResponse{} if protoimpl.UnsafeEnabled { - mi := &file_hashdb_proto_msgTypes[8] + mi := &file_hashdb_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -636,7 +829,7 @@ func (x *SetResponse) String() string { func (*SetResponse) ProtoMessage() {} func (x *SetResponse) ProtoReflect() protoreflect.Message { - mi := &file_hashdb_proto_msgTypes[8] + mi := &file_hashdb_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -649,7 +842,7 @@ func (x *SetResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SetResponse.ProtoReflect.Descriptor instead. func (*SetResponse) Descriptor() ([]byte, []int) { - return file_hashdb_proto_rawDescGZIP(), []int{8} + return file_hashdb_proto_rawDescGZIP(), []int{10} } func (x *SetResponse) GetOldRoot() *Fea { @@ -775,7 +968,7 @@ type GetResponse struct { func (x *GetResponse) Reset() { *x = GetResponse{} if protoimpl.UnsafeEnabled { - mi := &file_hashdb_proto_msgTypes[9] + mi := &file_hashdb_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -788,7 +981,7 @@ func (x *GetResponse) String() string { func (*GetResponse) ProtoMessage() {} func (x *GetResponse) ProtoReflect() protoreflect.Message { - mi := &file_hashdb_proto_msgTypes[9] + mi := &file_hashdb_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -801,7 +994,7 @@ func (x *GetResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetResponse.ProtoReflect.Descriptor instead. func (*GetResponse) Descriptor() ([]byte, []int) { - return file_hashdb_proto_rawDescGZIP(), []int{9} + return file_hashdb_proto_rawDescGZIP(), []int{11} } func (x *GetResponse) GetRoot() *Fea { @@ -888,7 +1081,7 @@ type SetProgramResponse struct { func (x *SetProgramResponse) Reset() { *x = SetProgramResponse{} if protoimpl.UnsafeEnabled { - mi := &file_hashdb_proto_msgTypes[10] + mi := &file_hashdb_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -901,7 +1094,7 @@ func (x *SetProgramResponse) String() string { func (*SetProgramResponse) ProtoMessage() {} func (x *SetProgramResponse) ProtoReflect() protoreflect.Message { - mi := &file_hashdb_proto_msgTypes[10] + mi := &file_hashdb_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -914,7 +1107,7 @@ func (x *SetProgramResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SetProgramResponse.ProtoReflect.Descriptor instead. func (*SetProgramResponse) Descriptor() ([]byte, []int) { - return file_hashdb_proto_rawDescGZIP(), []int{10} + return file_hashdb_proto_rawDescGZIP(), []int{12} } func (x *SetProgramResponse) GetResult() *ResultCode { @@ -940,7 +1133,7 @@ type GetProgramResponse struct { func (x *GetProgramResponse) Reset() { *x = GetProgramResponse{} if protoimpl.UnsafeEnabled { - mi := &file_hashdb_proto_msgTypes[11] + mi := &file_hashdb_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -953,7 +1146,7 @@ func (x *GetProgramResponse) String() string { func (*GetProgramResponse) ProtoMessage() {} func (x *GetProgramResponse) ProtoReflect() protoreflect.Message { - mi := &file_hashdb_proto_msgTypes[11] + mi := &file_hashdb_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -966,7 +1159,7 @@ func (x *GetProgramResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetProgramResponse.ProtoReflect.Descriptor instead. func (*GetProgramResponse) Descriptor() ([]byte, []int) { - return file_hashdb_proto_rawDescGZIP(), []int{11} + return file_hashdb_proto_rawDescGZIP(), []int{13} } func (x *GetProgramResponse) GetData() []byte { @@ -1001,7 +1194,7 @@ type FlushResponse struct { func (x *FlushResponse) Reset() { *x = FlushResponse{} if protoimpl.UnsafeEnabled { - mi := &file_hashdb_proto_msgTypes[12] + mi := &file_hashdb_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1014,7 +1207,7 @@ func (x *FlushResponse) String() string { func (*FlushResponse) ProtoMessage() {} func (x *FlushResponse) ProtoReflect() protoreflect.Message { - mi := &file_hashdb_proto_msgTypes[12] + mi := &file_hashdb_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1027,7 +1220,7 @@ func (x *FlushResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FlushResponse.ProtoReflect.Descriptor instead. func (*FlushResponse) Descriptor() ([]byte, []int) { - return file_hashdb_proto_rawDescGZIP(), []int{12} + return file_hashdb_proto_rawDescGZIP(), []int{14} } func (x *FlushResponse) GetFlushId() uint64 { @@ -1079,7 +1272,7 @@ type GetFlushStatusResponse struct { func (x *GetFlushStatusResponse) Reset() { *x = GetFlushStatusResponse{} if protoimpl.UnsafeEnabled { - mi := &file_hashdb_proto_msgTypes[13] + mi := &file_hashdb_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1092,7 +1285,7 @@ func (x *GetFlushStatusResponse) String() string { func (*GetFlushStatusResponse) ProtoMessage() {} func (x *GetFlushStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_hashdb_proto_msgTypes[13] + mi := &file_hashdb_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1105,7 +1298,7 @@ func (x *GetFlushStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetFlushStatusResponse.ProtoReflect.Descriptor instead. func (*GetFlushStatusResponse) Descriptor() ([]byte, []int) { - return file_hashdb_proto_rawDescGZIP(), []int{13} + return file_hashdb_proto_rawDescGZIP(), []int{15} } func (x *GetFlushStatusResponse) GetStoredFlushId() uint64 { @@ -1168,9 +1361,7 @@ func (x *GetFlushStatusResponse) GetProverId() string { // @dev GetFlushDataResponse // @param {stored_flush_id} - id of the last flush data sent to database // @param {nodes} - data to insert in the nodes table -// @param {nodes_update} - data to update in the nodes table // @param {program} - data to insert in the program table -// @param {program_update} - data to update in the program table // @param {nodes_state_root} - nodes state root to update in the nodes table // @param {result} - result code type GetFlushDataResponse struct { @@ -1178,19 +1369,17 @@ type GetFlushDataResponse struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - StoredFlushId uint64 `protobuf:"varint,1,opt,name=stored_flush_id,json=storedFlushId,proto3" json:"stored_flush_id,omitempty"` - Nodes []*FlushData `protobuf:"bytes,2,rep,name=nodes,proto3" json:"nodes,omitempty"` - NodesUpdate []*FlushData `protobuf:"bytes,3,rep,name=nodes_update,json=nodesUpdate,proto3" json:"nodes_update,omitempty"` - Program []*FlushData `protobuf:"bytes,4,rep,name=program,proto3" json:"program,omitempty"` - ProgramUpdate []*FlushData `protobuf:"bytes,5,rep,name=program_update,json=programUpdate,proto3" json:"program_update,omitempty"` - NodesStateRoot string `protobuf:"bytes,6,opt,name=nodes_state_root,json=nodesStateRoot,proto3" json:"nodes_state_root,omitempty"` - Result *ResultCode `protobuf:"bytes,7,opt,name=result,proto3" json:"result,omitempty"` + StoredFlushId uint64 `protobuf:"varint,1,opt,name=stored_flush_id,json=storedFlushId,proto3" json:"stored_flush_id,omitempty"` + Nodes map[string]string `protobuf:"bytes,2,rep,name=nodes,proto3" json:"nodes,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Program map[string]string `protobuf:"bytes,3,rep,name=program,proto3" json:"program,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + NodesStateRoot string `protobuf:"bytes,4,opt,name=nodes_state_root,json=nodesStateRoot,proto3" json:"nodes_state_root,omitempty"` + Result *ResultCode `protobuf:"bytes,5,opt,name=result,proto3" json:"result,omitempty"` } func (x *GetFlushDataResponse) Reset() { *x = GetFlushDataResponse{} if protoimpl.UnsafeEnabled { - mi := &file_hashdb_proto_msgTypes[14] + mi := &file_hashdb_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1203,7 +1392,7 @@ func (x *GetFlushDataResponse) String() string { func (*GetFlushDataResponse) ProtoMessage() {} func (x *GetFlushDataResponse) ProtoReflect() protoreflect.Message { - mi := &file_hashdb_proto_msgTypes[14] + mi := &file_hashdb_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1216,7 +1405,7 @@ func (x *GetFlushDataResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetFlushDataResponse.ProtoReflect.Descriptor instead. func (*GetFlushDataResponse) Descriptor() ([]byte, []int) { - return file_hashdb_proto_rawDescGZIP(), []int{14} + return file_hashdb_proto_rawDescGZIP(), []int{16} } func (x *GetFlushDataResponse) GetStoredFlushId() uint64 { @@ -1226,34 +1415,20 @@ func (x *GetFlushDataResponse) GetStoredFlushId() uint64 { return 0 } -func (x *GetFlushDataResponse) GetNodes() []*FlushData { +func (x *GetFlushDataResponse) GetNodes() map[string]string { if x != nil { return x.Nodes } return nil } -func (x *GetFlushDataResponse) GetNodesUpdate() []*FlushData { - if x != nil { - return x.NodesUpdate - } - return nil -} - -func (x *GetFlushDataResponse) GetProgram() []*FlushData { +func (x *GetFlushDataResponse) GetProgram() map[string]string { if x != nil { return x.Program } return nil } -func (x *GetFlushDataResponse) GetProgramUpdate() []*FlushData { - if x != nil { - return x.ProgramUpdate - } - return nil -} - func (x *GetFlushDataResponse) GetNodesStateRoot() string { if x != nil { return x.NodesStateRoot @@ -1288,7 +1463,7 @@ type Fea struct { func (x *Fea) Reset() { *x = Fea{} if protoimpl.UnsafeEnabled { - mi := &file_hashdb_proto_msgTypes[15] + mi := &file_hashdb_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1301,7 +1476,7 @@ func (x *Fea) String() string { func (*Fea) ProtoMessage() {} func (x *Fea) ProtoReflect() protoreflect.Message { - mi := &file_hashdb_proto_msgTypes[15] + mi := &file_hashdb_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1314,7 +1489,7 @@ func (x *Fea) ProtoReflect() protoreflect.Message { // Deprecated: Use Fea.ProtoReflect.Descriptor instead. func (*Fea) Descriptor() ([]byte, []int) { - return file_hashdb_proto_rawDescGZIP(), []int{15} + return file_hashdb_proto_rawDescGZIP(), []int{17} } func (x *Fea) GetFe0() uint64 { @@ -1359,7 +1534,7 @@ type FeList struct { func (x *FeList) Reset() { *x = FeList{} if protoimpl.UnsafeEnabled { - mi := &file_hashdb_proto_msgTypes[16] + mi := &file_hashdb_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1372,7 +1547,7 @@ func (x *FeList) String() string { func (*FeList) ProtoMessage() {} func (x *FeList) ProtoReflect() protoreflect.Message { - mi := &file_hashdb_proto_msgTypes[16] + mi := &file_hashdb_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1385,7 +1560,7 @@ func (x *FeList) ProtoReflect() protoreflect.Message { // Deprecated: Use FeList.ProtoReflect.Descriptor instead. func (*FeList) Descriptor() ([]byte, []int) { - return file_hashdb_proto_rawDescGZIP(), []int{16} + return file_hashdb_proto_rawDescGZIP(), []int{18} } func (x *FeList) GetFe() []uint64 { @@ -1409,7 +1584,7 @@ type SiblingList struct { func (x *SiblingList) Reset() { *x = SiblingList{} if protoimpl.UnsafeEnabled { - mi := &file_hashdb_proto_msgTypes[17] + mi := &file_hashdb_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1422,7 +1597,7 @@ func (x *SiblingList) String() string { func (*SiblingList) ProtoMessage() {} func (x *SiblingList) ProtoReflect() protoreflect.Message { - mi := &file_hashdb_proto_msgTypes[17] + mi := &file_hashdb_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1435,7 +1610,7 @@ func (x *SiblingList) ProtoReflect() protoreflect.Message { // Deprecated: Use SiblingList.ProtoReflect.Descriptor instead. func (*SiblingList) Descriptor() ([]byte, []int) { - return file_hashdb_proto_rawDescGZIP(), []int{17} + return file_hashdb_proto_rawDescGZIP(), []int{19} } func (x *SiblingList) GetSibling() []uint64 { @@ -1445,65 +1620,6 @@ func (x *SiblingList) GetSibling() []uint64 { return nil } -// * -// @dev Flush Data -// @param {key} - hash key -// @param {value} - string value -type FlushData struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` - Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` -} - -func (x *FlushData) Reset() { - *x = FlushData{} - if protoimpl.UnsafeEnabled { - mi := &file_hashdb_proto_msgTypes[18] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *FlushData) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*FlushData) ProtoMessage() {} - -func (x *FlushData) ProtoReflect() protoreflect.Message { - mi := &file_hashdb_proto_msgTypes[18] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use FlushData.ProtoReflect.Descriptor instead. -func (*FlushData) Descriptor() ([]byte, []int) { - return file_hashdb_proto_rawDescGZIP(), []int{18} -} - -func (x *FlushData) GetKey() string { - if x != nil { - return x.Key - } - return "" -} - -func (x *FlushData) GetValue() string { - if x != nil { - return x.Value - } - return "" -} - // * // @dev Result code // @param {code} - result code @@ -1518,7 +1634,7 @@ type ResultCode struct { func (x *ResultCode) Reset() { *x = ResultCode{} if protoimpl.UnsafeEnabled { - mi := &file_hashdb_proto_msgTypes[19] + mi := &file_hashdb_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1531,7 +1647,7 @@ func (x *ResultCode) String() string { func (*ResultCode) ProtoMessage() {} func (x *ResultCode) ProtoReflect() protoreflect.Message { - mi := &file_hashdb_proto_msgTypes[19] + mi := &file_hashdb_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1544,7 +1660,7 @@ func (x *ResultCode) ProtoReflect() protoreflect.Message { // Deprecated: Use ResultCode.ProtoReflect.Descriptor instead. func (*ResultCode) Descriptor() ([]byte, []int) { - return file_hashdb_proto_rawDescGZIP(), []int{19} + return file_hashdb_proto_rawDescGZIP(), []int{20} } func (x *ResultCode) GetCode() ResultCode_Code { @@ -1562,270 +1678,299 @@ var file_hashdb_proto_rawDesc = []byte{ 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x1f, 0x0a, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x06, 0x76, 0x30, 0x5f, 0x30, 0x5f, 0x31, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x76, 0x30, 0x30, 0x31, 0x22, 0xd0, 0x01, 0x0a, 0x0a, 0x53, 0x65, 0x74, 0x52, + 0x09, 0x52, 0x04, 0x76, 0x30, 0x30, 0x31, 0x22, 0x99, 0x02, 0x0a, 0x0a, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x08, 0x6f, 0x6c, 0x64, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x65, 0x61, 0x52, 0x07, 0x6f, 0x6c, 0x64, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x20, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x65, 0x61, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, - 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x70, - 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x74, - 0x61, 0x69, 0x6c, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x74, 0x61, - 0x69, 0x6c, 0x73, 0x12, 0x25, 0x0a, 0x0f, 0x67, 0x65, 0x74, 0x5f, 0x64, 0x62, 0x5f, 0x72, 0x65, - 0x61, 0x64, 0x5f, 0x6c, 0x6f, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x67, 0x65, - 0x74, 0x44, 0x62, 0x52, 0x65, 0x61, 0x64, 0x4c, 0x6f, 0x67, 0x22, 0x93, 0x01, 0x0a, 0x0a, 0x47, - 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x22, 0x0a, 0x04, 0x72, 0x6f, 0x6f, - 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, - 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x65, 0x61, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x74, 0x12, 0x20, 0x0a, - 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x68, 0x61, 0x73, + 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x38, 0x0a, 0x0b, 0x70, 0x65, 0x72, + 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, + 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x73, 0x69, + 0x73, 0x74, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x0b, 0x70, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, + 0x6e, 0x63, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x25, 0x0a, + 0x0f, 0x67, 0x65, 0x74, 0x5f, 0x64, 0x62, 0x5f, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x6c, 0x6f, 0x67, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x67, 0x65, 0x74, 0x44, 0x62, 0x52, 0x65, 0x61, + 0x64, 0x4c, 0x6f, 0x67, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x75, 0x75, + 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x61, 0x74, 0x63, 0x68, 0x55, + 0x75, 0x69, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x78, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x02, 0x74, 0x78, 0x22, 0xb2, 0x01, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x22, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x0e, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x65, 0x61, + 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x74, 0x12, 0x20, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, + 0x46, 0x65, 0x61, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x74, 0x61, + 0x69, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, + 0x6c, 0x73, 0x12, 0x25, 0x0a, 0x0f, 0x67, 0x65, 0x74, 0x5f, 0x64, 0x62, 0x5f, 0x72, 0x65, 0x61, + 0x64, 0x5f, 0x6c, 0x6f, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x67, 0x65, 0x74, + 0x44, 0x62, 0x52, 0x65, 0x61, 0x64, 0x4c, 0x6f, 0x67, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x61, 0x74, + 0x63, 0x68, 0x5f, 0x75, 0x75, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, + 0x61, 0x74, 0x63, 0x68, 0x55, 0x75, 0x69, 0x64, 0x22, 0x69, 0x0a, 0x11, 0x53, 0x65, 0x74, 0x50, + 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x65, 0x61, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, - 0x18, 0x0a, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x25, 0x0a, 0x0f, 0x67, 0x65, 0x74, - 0x5f, 0x64, 0x62, 0x5f, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x6c, 0x6f, 0x67, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x0c, 0x67, 0x65, 0x74, 0x44, 0x62, 0x52, 0x65, 0x61, 0x64, 0x4c, 0x6f, 0x67, - 0x22, 0x69, 0x0a, 0x11, 0x53, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x46, - 0x65, 0x61, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1e, 0x0a, 0x0a, 0x70, - 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x0a, 0x70, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x74, 0x22, 0x35, 0x0a, 0x11, 0x47, - 0x65, 0x74, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x20, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, - 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x65, 0x61, 0x52, 0x03, 0x6b, - 0x65, 0x79, 0x22, 0xc0, 0x01, 0x0a, 0x0d, 0x4c, 0x6f, 0x61, 0x64, 0x44, 0x42, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x40, 0x0a, 0x08, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x64, 0x62, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, - 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x61, 0x64, 0x44, 0x42, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x2e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x44, 0x62, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x69, - 0x6e, 0x70, 0x75, 0x74, 0x44, 0x62, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x73, 0x69, 0x73, - 0x74, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x73, - 0x69, 0x73, 0x74, 0x65, 0x6e, 0x74, 0x1a, 0x4d, 0x0a, 0x0c, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x44, - 0x62, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, + 0x61, 0x74, 0x61, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, + 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, + 0x65, 0x6e, 0x74, 0x22, 0x35, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, + 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, + 0x31, 0x2e, 0x46, 0x65, 0x61, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0xc0, 0x01, 0x0a, 0x0d, 0x4c, + 0x6f, 0x61, 0x64, 0x44, 0x42, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x40, 0x0a, 0x08, + 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x64, 0x62, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, + 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x61, 0x64, 0x44, + 0x42, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x44, 0x62, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x44, 0x62, 0x12, 0x1e, + 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x74, 0x1a, 0x4d, + 0x0a, 0x0c, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x44, 0x62, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, + 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, + 0x12, 0x27, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x11, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x65, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xd8, 0x01, + 0x0a, 0x14, 0x4c, 0x6f, 0x61, 0x64, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x44, 0x42, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x5d, 0x0a, 0x10, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, + 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x5f, 0x64, 0x62, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x33, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x61, + 0x64, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x44, 0x42, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x2e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x44, 0x62, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x50, 0x72, 0x6f, 0x67, + 0x72, 0x61, 0x6d, 0x44, 0x62, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, + 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x73, 0x69, + 0x73, 0x74, 0x65, 0x6e, 0x74, 0x1a, 0x41, 0x0a, 0x13, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x50, 0x72, + 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x44, 0x62, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x2d, 0x0a, 0x0c, 0x46, 0x6c, 0x75, 0x73, + 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x61, 0x74, 0x63, + 0x68, 0x5f, 0x75, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x61, + 0x74, 0x63, 0x68, 0x55, 0x75, 0x69, 0x64, 0x22, 0x91, 0x01, 0x0a, 0x10, 0x53, 0x65, 0x6d, 0x69, + 0x46, 0x6c, 0x75, 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, + 0x62, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x75, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x62, 0x61, 0x74, 0x63, 0x68, 0x55, 0x75, 0x69, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x6e, + 0x65, 0x77, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x65, 0x77, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, + 0x74, 0x12, 0x38, 0x0a, 0x0b, 0x70, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, + 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x0b, + 0x70, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x65, 0x22, 0x30, 0x0a, 0x13, 0x47, + 0x65, 0x74, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x49, 0x64, 0x22, 0xbe, 0x05, + 0x0a, 0x0b, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x29, 0x0a, + 0x08, 0x6f, 0x6c, 0x64, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x0e, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x65, 0x61, 0x52, + 0x07, 0x6f, 0x6c, 0x64, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x29, 0x0a, 0x08, 0x6e, 0x65, 0x77, 0x5f, + 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x68, 0x61, 0x73, + 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x65, 0x61, 0x52, 0x07, 0x6e, 0x65, 0x77, 0x52, + 0x6f, 0x6f, 0x74, 0x12, 0x20, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x0e, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x65, 0x61, + 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x40, 0x0a, 0x08, 0x73, 0x69, 0x62, 0x6c, 0x69, 0x6e, 0x67, + 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, + 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, + 0x53, 0x69, 0x62, 0x6c, 0x69, 0x6e, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x73, + 0x69, 0x62, 0x6c, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x27, 0x0a, 0x07, 0x69, 0x6e, 0x73, 0x5f, 0x6b, + 0x65, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, + 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x65, 0x61, 0x52, 0x06, 0x69, 0x6e, 0x73, 0x4b, 0x65, 0x79, + 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x6e, 0x73, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x69, 0x6e, 0x73, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x17, 0x0a, + 0x07, 0x69, 0x73, 0x5f, 0x6f, 0x6c, 0x64, 0x30, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, + 0x69, 0x73, 0x4f, 0x6c, 0x64, 0x30, 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x6c, 0x64, 0x5f, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, 0x6c, 0x64, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x65, 0x77, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x65, 0x77, 0x56, 0x61, 0x6c, 0x75, 0x65, + 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x5f, 0x68, 0x61, + 0x73, 0x68, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x10, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x48, 0x61, 0x73, 0x68, 0x43, 0x6f, 0x75, 0x6e, 0x74, + 0x65, 0x72, 0x12, 0x45, 0x0a, 0x0b, 0x64, 0x62, 0x5f, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x6c, 0x6f, + 0x67, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, + 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, + 0x44, 0x62, 0x52, 0x65, 0x61, 0x64, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, + 0x64, 0x62, 0x52, 0x65, 0x61, 0x64, 0x4c, 0x6f, 0x67, 0x12, 0x2d, 0x0a, 0x06, 0x72, 0x65, 0x73, + 0x75, 0x6c, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x61, 0x73, 0x68, + 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x64, 0x65, + 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x1a, 0x53, 0x0a, 0x0d, 0x53, 0x69, 0x62, 0x6c, + 0x69, 0x6e, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x68, 0x61, 0x73, + 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x69, 0x62, 0x6c, 0x69, 0x6e, 0x67, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x4f, 0x0a, + 0x0e, 0x44, 0x62, 0x52, 0x65, 0x61, 0x64, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x27, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x11, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x65, 0x4c, + 0x69, 0x73, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xd4, + 0x04, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x22, + 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x68, + 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x65, 0x61, 0x52, 0x04, 0x72, 0x6f, + 0x6f, 0x74, 0x12, 0x20, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x0e, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x65, 0x61, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x40, 0x0a, 0x08, 0x73, 0x69, 0x62, 0x6c, 0x69, 0x6e, 0x67, 0x73, + 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, + 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, + 0x69, 0x62, 0x6c, 0x69, 0x6e, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x73, 0x69, + 0x62, 0x6c, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x27, 0x0a, 0x07, 0x69, 0x6e, 0x73, 0x5f, 0x6b, 0x65, + 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, + 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x65, 0x61, 0x52, 0x06, 0x69, 0x6e, 0x73, 0x4b, 0x65, 0x79, 0x12, + 0x1b, 0x0a, 0x09, 0x69, 0x6e, 0x73, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x69, 0x6e, 0x73, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x17, 0x0a, 0x07, + 0x69, 0x73, 0x5f, 0x6f, 0x6c, 0x64, 0x30, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x69, + 0x73, 0x4f, 0x6c, 0x64, 0x30, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x70, + 0x72, 0x6f, 0x6f, 0x66, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, + 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x48, 0x61, + 0x73, 0x68, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x12, 0x45, 0x0a, 0x0b, 0x64, 0x62, 0x5f, + 0x72, 0x65, 0x61, 0x64, 0x5f, 0x6c, 0x6f, 0x67, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, + 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x44, 0x62, 0x52, 0x65, 0x61, 0x64, 0x4c, 0x6f, 0x67, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, 0x64, 0x62, 0x52, 0x65, 0x61, 0x64, 0x4c, 0x6f, 0x67, + 0x12, 0x2d, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x15, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, + 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x1a, + 0x53, 0x0a, 0x0d, 0x53, 0x69, 0x62, 0x6c, 0x69, 0x6e, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, + 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x16, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x69, + 0x62, 0x6c, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x4f, 0x0a, 0x0e, 0x44, 0x62, 0x52, 0x65, 0x61, 0x64, 0x4c, 0x6f, + 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x27, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xd8, 0x01, 0x0a, 0x14, 0x4c, 0x6f, 0x61, 0x64, 0x50, 0x72, - 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x44, 0x42, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x5d, - 0x0a, 0x10, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x5f, - 0x64, 0x62, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, - 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x61, 0x64, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, - 0x44, 0x42, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x50, - 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x44, 0x62, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x69, - 0x6e, 0x70, 0x75, 0x74, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x44, 0x62, 0x12, 0x1e, 0x0a, - 0x0a, 0x70, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x74, 0x1a, 0x41, 0x0a, - 0x13, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x44, 0x62, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, - 0x22, 0x30, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x44, 0x61, 0x74, 0x61, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x6c, 0x75, 0x73, 0x68, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x66, 0x6c, 0x75, 0x73, 0x68, - 0x49, 0x64, 0x22, 0xbe, 0x05, 0x0a, 0x0b, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x29, 0x0a, 0x08, 0x6f, 0x6c, 0x64, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, - 0x2e, 0x46, 0x65, 0x61, 0x52, 0x07, 0x6f, 0x6c, 0x64, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x29, 0x0a, - 0x08, 0x6e, 0x65, 0x77, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x0e, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x65, 0x61, 0x52, - 0x07, 0x6e, 0x65, 0x77, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x20, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, - 0x31, 0x2e, 0x46, 0x65, 0x61, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x40, 0x0a, 0x08, 0x73, 0x69, - 0x62, 0x6c, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x68, - 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x69, 0x62, 0x6c, 0x69, 0x6e, 0x67, 0x73, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x52, 0x08, 0x73, 0x69, 0x62, 0x6c, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x27, 0x0a, 0x07, - 0x69, 0x6e, 0x73, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, - 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x65, 0x61, 0x52, 0x06, 0x69, - 0x6e, 0x73, 0x4b, 0x65, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x6e, 0x73, 0x5f, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x69, 0x6e, 0x73, 0x56, 0x61, 0x6c, - 0x75, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x73, 0x5f, 0x6f, 0x6c, 0x64, 0x30, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x06, 0x69, 0x73, 0x4f, 0x6c, 0x64, 0x30, 0x12, 0x1b, 0x0a, 0x09, 0x6f, - 0x6c, 0x64, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x6f, 0x6c, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x65, 0x77, 0x5f, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x65, 0x77, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x0a, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x70, 0x72, 0x6f, - 0x6f, 0x66, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x18, - 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x48, 0x61, 0x73, 0x68, - 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x12, 0x45, 0x0a, 0x0b, 0x64, 0x62, 0x5f, 0x72, 0x65, - 0x61, 0x64, 0x5f, 0x6c, 0x6f, 0x67, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x68, - 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x44, 0x62, 0x52, 0x65, 0x61, 0x64, 0x4c, 0x6f, 0x67, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x52, 0x09, 0x64, 0x62, 0x52, 0x65, 0x61, 0x64, 0x4c, 0x6f, 0x67, 0x12, 0x2d, - 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, - 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x1a, 0x53, 0x0a, - 0x0d, 0x53, 0x69, 0x62, 0x6c, 0x69, 0x6e, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, - 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x2c, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x16, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x69, 0x62, 0x6c, - 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, - 0x38, 0x01, 0x1a, 0x4f, 0x0a, 0x0e, 0x44, 0x62, 0x52, 0x65, 0x61, 0x64, 0x4c, 0x6f, 0x67, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x27, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, - 0x31, 0x2e, 0x46, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, - 0x02, 0x38, 0x01, 0x22, 0xd4, 0x04, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x22, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x0e, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x65, - 0x61, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x74, 0x12, 0x20, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, - 0x2e, 0x46, 0x65, 0x61, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x40, 0x0a, 0x08, 0x73, 0x69, 0x62, - 0x6c, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x68, 0x61, - 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x69, 0x62, 0x6c, 0x69, 0x6e, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x52, 0x08, 0x73, 0x69, 0x62, 0x6c, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x27, 0x0a, 0x07, 0x69, - 0x6e, 0x73, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x68, - 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x65, 0x61, 0x52, 0x06, 0x69, 0x6e, - 0x73, 0x4b, 0x65, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x6e, 0x73, 0x5f, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x69, 0x6e, 0x73, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x73, 0x5f, 0x6f, 0x6c, 0x64, 0x30, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x06, 0x69, 0x73, 0x4f, 0x6c, 0x64, 0x30, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x12, 0x2c, 0x0a, 0x12, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x5f, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x70, 0x72, - 0x6f, 0x6f, 0x66, 0x48, 0x61, 0x73, 0x68, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x12, 0x45, - 0x0a, 0x0b, 0x64, 0x62, 0x5f, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x6c, 0x6f, 0x67, 0x18, 0x09, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, - 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x44, 0x62, 0x52, 0x65, - 0x61, 0x64, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, 0x64, 0x62, 0x52, 0x65, - 0x61, 0x64, 0x4c, 0x6f, 0x67, 0x12, 0x2d, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, - 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, - 0x31, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x06, 0x72, 0x65, - 0x73, 0x75, 0x6c, 0x74, 0x1a, 0x53, 0x0a, 0x0d, 0x53, 0x69, 0x62, 0x6c, 0x69, 0x6e, 0x67, 0x73, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, - 0x76, 0x31, 0x2e, 0x53, 0x69, 0x62, 0x6c, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x4f, 0x0a, 0x0e, 0x44, 0x62, 0x52, - 0x65, 0x61, 0x64, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, - 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x27, 0x0a, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x68, - 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x43, 0x0a, 0x12, 0x53, 0x65, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x43, 0x0a, 0x12, 0x53, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x67, + 0x72, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x06, 0x72, + 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x61, + 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x43, 0x6f, + 0x64, 0x65, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x57, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x2d, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x15, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, - 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, - 0x57, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x2d, 0x0a, 0x06, 0x72, 0x65, 0x73, - 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x61, 0x73, 0x68, - 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x64, 0x65, - 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x81, 0x01, 0x0a, 0x0d, 0x46, 0x6c, 0x75, - 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x6c, - 0x75, 0x73, 0x68, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x66, 0x6c, - 0x75, 0x73, 0x68, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x5f, - 0x66, 0x6c, 0x75, 0x73, 0x68, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, - 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x49, 0x64, 0x12, 0x2d, 0x0a, - 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, - 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, - 0x43, 0x6f, 0x64, 0x65, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0xe7, 0x02, 0x0a, - 0x16, 0x47, 0x65, 0x74, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x74, 0x6f, 0x72, 0x65, - 0x64, 0x5f, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x0d, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x49, 0x64, 0x12, - 0x28, 0x0a, 0x10, 0x73, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x6c, 0x75, 0x73, 0x68, - 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x73, 0x74, 0x6f, 0x72, 0x69, - 0x6e, 0x67, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x6c, 0x61, 0x73, - 0x74, 0x5f, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x0b, 0x6c, 0x61, 0x73, 0x74, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x49, 0x64, 0x12, 0x33, 0x0a, - 0x16, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x6f, 0x5f, 0x66, 0x6c, 0x75, 0x73, - 0x68, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x13, 0x70, - 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x6f, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x4e, 0x6f, 0x64, - 0x65, 0x73, 0x12, 0x37, 0x0a, 0x18, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x6f, - 0x5f, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x15, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x6f, 0x46, - 0x6c, 0x75, 0x73, 0x68, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x12, 0x23, 0x0a, 0x0d, 0x73, - 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x0c, 0x73, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x64, 0x65, 0x73, - 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x72, 0x6f, 0x67, - 0x72, 0x61, 0x6d, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x73, 0x74, 0x6f, 0x72, 0x69, - 0x6e, 0x67, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x72, 0x6f, - 0x76, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, - 0x6f, 0x76, 0x65, 0x72, 0x49, 0x64, 0x22, 0xe9, 0x02, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x46, 0x6c, - 0x75, 0x73, 0x68, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x26, 0x0a, 0x0f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x5f, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, - 0x46, 0x6c, 0x75, 0x73, 0x68, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x05, 0x6e, 0x6f, 0x64, 0x65, 0x73, - 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, - 0x76, 0x31, 0x2e, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x44, 0x61, 0x74, 0x61, 0x52, 0x05, 0x6e, 0x6f, - 0x64, 0x65, 0x73, 0x12, 0x37, 0x0a, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x5f, 0x75, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x61, 0x73, 0x68, - 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x44, 0x61, 0x74, 0x61, 0x52, - 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x2e, 0x0a, 0x07, - 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, - 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x44, - 0x61, 0x74, 0x61, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x12, 0x3b, 0x0a, 0x0e, - 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x05, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, - 0x2e, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x44, 0x61, 0x74, 0x61, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x67, - 0x72, 0x61, 0x6d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x6e, 0x6f, 0x64, - 0x65, 0x73, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, - 0x6f, 0x6f, 0x74, 0x12, 0x2d, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, - 0x6c, 0x74, 0x22, 0x4d, 0x0a, 0x03, 0x46, 0x65, 0x61, 0x12, 0x10, 0x0a, 0x03, 0x66, 0x65, 0x30, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x66, 0x65, 0x30, 0x12, 0x10, 0x0a, 0x03, 0x66, - 0x65, 0x31, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x66, 0x65, 0x31, 0x12, 0x10, 0x0a, - 0x03, 0x66, 0x65, 0x32, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x66, 0x65, 0x32, 0x12, - 0x10, 0x0a, 0x03, 0x66, 0x65, 0x33, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x66, 0x65, - 0x33, 0x22, 0x18, 0x0a, 0x06, 0x46, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x66, - 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x04, 0x52, 0x02, 0x66, 0x65, 0x22, 0x27, 0x0a, 0x0b, 0x53, - 0x69, 0x62, 0x6c, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x69, - 0x62, 0x6c, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x03, 0x28, 0x04, 0x52, 0x07, 0x73, 0x69, 0x62, - 0x6c, 0x69, 0x6e, 0x67, 0x22, 0x33, 0x0a, 0x09, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x44, 0x61, 0x74, - 0x61, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, - 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xd4, 0x01, 0x0a, 0x0a, 0x52, 0x65, - 0x73, 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x2e, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, - 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x2e, 0x43, 0x6f, - 0x64, 0x65, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x95, 0x01, 0x0a, 0x04, 0x43, 0x6f, 0x64, - 0x65, 0x12, 0x14, 0x0a, 0x10, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, - 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x43, 0x4f, 0x44, 0x45, 0x5f, - 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x01, 0x12, 0x19, 0x0a, 0x15, 0x43, 0x4f, 0x44, - 0x45, 0x5f, 0x44, 0x42, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x46, 0x4f, 0x55, - 0x4e, 0x44, 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x44, 0x42, 0x5f, - 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x17, 0x0a, 0x13, 0x43, 0x4f, 0x44, 0x45, 0x5f, - 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, - 0x12, 0x1e, 0x0a, 0x1a, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x53, 0x4d, 0x54, 0x5f, 0x49, 0x4e, 0x56, - 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x5f, 0x53, 0x49, 0x5a, 0x45, 0x10, 0x0e, - 0x32, 0x82, 0x05, 0x0a, 0x0d, 0x48, 0x61, 0x73, 0x68, 0x44, 0x42, 0x53, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x12, 0x36, 0x0a, 0x03, 0x53, 0x65, 0x74, 0x12, 0x15, 0x2e, 0x68, 0x61, 0x73, 0x68, - 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x16, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x36, 0x0a, 0x03, 0x47, 0x65, - 0x74, 0x12, 0x15, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, - 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0a, 0x53, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, - 0x12, 0x1c, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, - 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, - 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x50, 0x72, - 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, - 0x4b, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x12, 0x1c, 0x2e, - 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, - 0x67, 0x72, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x68, 0x61, - 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x67, 0x72, - 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3c, 0x0a, 0x06, - 0x4c, 0x6f, 0x61, 0x64, 0x44, 0x42, 0x12, 0x18, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, - 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x61, 0x64, 0x44, 0x42, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0d, 0x4c, 0x6f, - 0x61, 0x64, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x44, 0x42, 0x12, 0x1f, 0x2e, 0x68, 0x61, - 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x61, 0x64, 0x50, 0x72, 0x6f, 0x67, - 0x72, 0x61, 0x6d, 0x44, 0x42, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, - 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x05, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x12, - 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x18, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, - 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x00, 0x12, 0x4d, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x53, + 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, + 0x64, 0x61, 0x74, 0x61, 0x12, 0x2d, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x06, 0x72, 0x65, 0x73, + 0x75, 0x6c, 0x74, 0x22, 0x81, 0x01, 0x0a, 0x0d, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x49, 0x64, + 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x5f, 0x66, 0x6c, 0x75, 0x73, 0x68, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x73, 0x74, 0x6f, 0x72, 0x65, + 0x64, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x49, 0x64, 0x12, 0x2d, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, + 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x52, + 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0xe7, 0x02, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x46, + 0x6c, 0x75, 0x73, 0x68, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x5f, 0x66, 0x6c, 0x75, + 0x73, 0x68, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x73, 0x74, 0x6f, + 0x72, 0x65, 0x64, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x74, + 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x5f, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x73, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x46, 0x6c, 0x75, + 0x73, 0x68, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x66, 0x6c, 0x75, + 0x73, 0x68, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6c, 0x61, 0x73, + 0x74, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x49, 0x64, 0x12, 0x33, 0x0a, 0x16, 0x70, 0x65, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x6f, 0x5f, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x5f, 0x6e, 0x6f, 0x64, + 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x13, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x54, 0x6f, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x12, 0x37, 0x0a, + 0x18, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x6f, 0x5f, 0x66, 0x6c, 0x75, 0x73, + 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x15, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x6f, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x50, + 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x74, 0x6f, 0x72, 0x69, 0x6e, + 0x67, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x73, + 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x73, + 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x73, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x50, 0x72, 0x6f, + 0x67, 0x72, 0x61, 0x6d, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x69, + 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x72, 0x49, + 0x64, 0x22, 0x97, 0x03, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x44, 0x61, + 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x74, + 0x6f, 0x72, 0x65, 0x64, 0x5f, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x0d, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x46, 0x6c, 0x75, 0x73, 0x68, + 0x49, 0x64, 0x12, 0x40, 0x0a, 0x05, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x2a, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, + 0x74, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05, 0x6e, + 0x6f, 0x64, 0x65, 0x73, 0x12, 0x46, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x18, + 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, + 0x31, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x12, 0x28, 0x0a, 0x10, + 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x74, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x53, 0x74, 0x61, + 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x2d, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x06, 0x72, + 0x65, 0x73, 0x75, 0x6c, 0x74, 0x1a, 0x38, 0x0a, 0x0a, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, + 0x3a, 0x0a, 0x0c, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x4d, 0x0a, 0x03, 0x46, + 0x65, 0x61, 0x12, 0x10, 0x0a, 0x03, 0x66, 0x65, 0x30, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x03, 0x66, 0x65, 0x30, 0x12, 0x10, 0x0a, 0x03, 0x66, 0x65, 0x31, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x03, 0x66, 0x65, 0x31, 0x12, 0x10, 0x0a, 0x03, 0x66, 0x65, 0x32, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x03, 0x66, 0x65, 0x32, 0x12, 0x10, 0x0a, 0x03, 0x66, 0x65, 0x33, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x66, 0x65, 0x33, 0x22, 0x18, 0x0a, 0x06, 0x46, 0x65, + 0x4c, 0x69, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x66, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x04, + 0x52, 0x02, 0x66, 0x65, 0x22, 0x27, 0x0a, 0x0b, 0x53, 0x69, 0x62, 0x6c, 0x69, 0x6e, 0x67, 0x4c, + 0x69, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x69, 0x62, 0x6c, 0x69, 0x6e, 0x67, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x04, 0x52, 0x07, 0x73, 0x69, 0x62, 0x6c, 0x69, 0x6e, 0x67, 0x22, 0xd4, 0x01, + 0x0a, 0x0a, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x2e, 0x0a, 0x04, + 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x68, 0x61, 0x73, + 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x64, + 0x65, 0x2e, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x95, 0x01, 0x0a, + 0x04, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x55, 0x4e, + 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x43, + 0x4f, 0x44, 0x45, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x01, 0x12, 0x19, 0x0a, + 0x15, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x44, 0x42, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x4e, 0x4f, 0x54, + 0x5f, 0x46, 0x4f, 0x55, 0x4e, 0x44, 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x43, 0x4f, 0x44, 0x45, + 0x5f, 0x44, 0x42, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x17, 0x0a, 0x13, 0x43, + 0x4f, 0x44, 0x45, 0x5f, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x5f, 0x45, 0x52, 0x52, + 0x4f, 0x52, 0x10, 0x04, 0x12, 0x1e, 0x0a, 0x1a, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x53, 0x4d, 0x54, + 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x5f, 0x53, 0x49, + 0x5a, 0x45, 0x10, 0x0e, 0x2a, 0x65, 0x0a, 0x0b, 0x50, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, + 0x6e, 0x63, 0x65, 0x12, 0x21, 0x0a, 0x1d, 0x50, 0x45, 0x52, 0x53, 0x49, 0x53, 0x54, 0x45, 0x4e, + 0x43, 0x45, 0x5f, 0x43, 0x41, 0x43, 0x48, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, + 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x50, 0x45, 0x52, 0x53, 0x49, 0x53, + 0x54, 0x45, 0x4e, 0x43, 0x45, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x10, 0x01, + 0x12, 0x19, 0x0a, 0x15, 0x50, 0x45, 0x52, 0x53, 0x49, 0x53, 0x54, 0x45, 0x4e, 0x43, 0x45, 0x5f, + 0x54, 0x45, 0x4d, 0x50, 0x4f, 0x52, 0x41, 0x52, 0x59, 0x10, 0x02, 0x32, 0xc7, 0x05, 0x0a, 0x0d, + 0x48, 0x61, 0x73, 0x68, 0x44, 0x42, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x36, 0x0a, + 0x03, 0x53, 0x65, 0x74, 0x12, 0x15, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, + 0x2e, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x68, 0x61, + 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x36, 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x15, 0x2e, 0x68, + 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, + 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, + 0x0a, 0x53, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x12, 0x1c, 0x2e, 0x68, 0x61, + 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x67, 0x72, + 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x68, 0x61, 0x73, 0x68, + 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0a, 0x47, 0x65, + 0x74, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x12, 0x1c, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, + 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, + 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3c, 0x0a, 0x06, 0x4c, 0x6f, 0x61, 0x64, 0x44, + 0x42, 0x12, 0x18, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, + 0x61, 0x64, 0x44, 0x42, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0d, 0x4c, 0x6f, 0x61, 0x64, 0x50, 0x72, 0x6f, + 0x67, 0x72, 0x61, 0x6d, 0x44, 0x42, 0x12, 0x1f, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, + 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x61, 0x64, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x44, 0x42, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, + 0x00, 0x12, 0x3c, 0x0a, 0x05, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x12, 0x17, 0x2e, 0x68, 0x61, 0x73, + 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, + 0x46, 0x6c, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, + 0x42, 0x0a, 0x09, 0x53, 0x65, 0x6d, 0x69, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x12, 0x1b, 0x2e, 0x68, + 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6d, 0x69, 0x46, 0x6c, 0x75, + 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, + 0x79, 0x22, 0x00, 0x12, 0x4d, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x21, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x6c, 0x75, @@ -1835,11 +1980,11 @@ var file_hashdb_proto_rawDesc = []byte{ 0x65, 0x74, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x35, 0x5a, 0x33, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x39, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x30, 0x78, 0x50, 0x6f, 0x6c, 0x79, 0x67, 0x6f, 0x6e, 0x48, 0x65, 0x72, 0x6d, 0x65, 0x7a, 0x2f, 0x7a, 0x6b, 0x65, 0x76, 0x6d, 0x2d, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x6d, - 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x74, 0x72, 0x65, 0x65, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x74, 0x72, 0x65, 0x65, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x64, 0x62, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1854,94 +1999,100 @@ func file_hashdb_proto_rawDescGZIP() []byte { return file_hashdb_proto_rawDescData } -var file_hashdb_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_hashdb_proto_msgTypes = make([]protoimpl.MessageInfo, 26) +var file_hashdb_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_hashdb_proto_msgTypes = make([]protoimpl.MessageInfo, 29) var file_hashdb_proto_goTypes = []interface{}{ - (ResultCode_Code)(0), // 0: hashdb.v1.ResultCode.Code - (*Version)(nil), // 1: hashdb.v1.Version - (*SetRequest)(nil), // 2: hashdb.v1.SetRequest - (*GetRequest)(nil), // 3: hashdb.v1.GetRequest - (*SetProgramRequest)(nil), // 4: hashdb.v1.SetProgramRequest - (*GetProgramRequest)(nil), // 5: hashdb.v1.GetProgramRequest - (*LoadDBRequest)(nil), // 6: hashdb.v1.LoadDBRequest - (*LoadProgramDBRequest)(nil), // 7: hashdb.v1.LoadProgramDBRequest - (*GetFlushDataRequest)(nil), // 8: hashdb.v1.GetFlushDataRequest - (*SetResponse)(nil), // 9: hashdb.v1.SetResponse - (*GetResponse)(nil), // 10: hashdb.v1.GetResponse - (*SetProgramResponse)(nil), // 11: hashdb.v1.SetProgramResponse - (*GetProgramResponse)(nil), // 12: hashdb.v1.GetProgramResponse - (*FlushResponse)(nil), // 13: hashdb.v1.FlushResponse - (*GetFlushStatusResponse)(nil), // 14: hashdb.v1.GetFlushStatusResponse - (*GetFlushDataResponse)(nil), // 15: hashdb.v1.GetFlushDataResponse - (*Fea)(nil), // 16: hashdb.v1.Fea - (*FeList)(nil), // 17: hashdb.v1.FeList - (*SiblingList)(nil), // 18: hashdb.v1.SiblingList - (*FlushData)(nil), // 19: hashdb.v1.FlushData - (*ResultCode)(nil), // 20: hashdb.v1.ResultCode - nil, // 21: hashdb.v1.LoadDBRequest.InputDbEntry - nil, // 22: hashdb.v1.LoadProgramDBRequest.InputProgramDbEntry - nil, // 23: hashdb.v1.SetResponse.SiblingsEntry - nil, // 24: hashdb.v1.SetResponse.DbReadLogEntry - nil, // 25: hashdb.v1.GetResponse.SiblingsEntry - nil, // 26: hashdb.v1.GetResponse.DbReadLogEntry - (*emptypb.Empty)(nil), // 27: google.protobuf.Empty + (Persistence)(0), // 0: hashdb.v1.Persistence + (ResultCode_Code)(0), // 1: hashdb.v1.ResultCode.Code + (*Version)(nil), // 2: hashdb.v1.Version + (*SetRequest)(nil), // 3: hashdb.v1.SetRequest + (*GetRequest)(nil), // 4: hashdb.v1.GetRequest + (*SetProgramRequest)(nil), // 5: hashdb.v1.SetProgramRequest + (*GetProgramRequest)(nil), // 6: hashdb.v1.GetProgramRequest + (*LoadDBRequest)(nil), // 7: hashdb.v1.LoadDBRequest + (*LoadProgramDBRequest)(nil), // 8: hashdb.v1.LoadProgramDBRequest + (*FlushRequest)(nil), // 9: hashdb.v1.FlushRequest + (*SemiFlushRequest)(nil), // 10: hashdb.v1.SemiFlushRequest + (*GetFlushDataRequest)(nil), // 11: hashdb.v1.GetFlushDataRequest + (*SetResponse)(nil), // 12: hashdb.v1.SetResponse + (*GetResponse)(nil), // 13: hashdb.v1.GetResponse + (*SetProgramResponse)(nil), // 14: hashdb.v1.SetProgramResponse + (*GetProgramResponse)(nil), // 15: hashdb.v1.GetProgramResponse + (*FlushResponse)(nil), // 16: hashdb.v1.FlushResponse + (*GetFlushStatusResponse)(nil), // 17: hashdb.v1.GetFlushStatusResponse + (*GetFlushDataResponse)(nil), // 18: hashdb.v1.GetFlushDataResponse + (*Fea)(nil), // 19: hashdb.v1.Fea + (*FeList)(nil), // 20: hashdb.v1.FeList + (*SiblingList)(nil), // 21: hashdb.v1.SiblingList + (*ResultCode)(nil), // 22: hashdb.v1.ResultCode + nil, // 23: hashdb.v1.LoadDBRequest.InputDbEntry + nil, // 24: hashdb.v1.LoadProgramDBRequest.InputProgramDbEntry + nil, // 25: hashdb.v1.SetResponse.SiblingsEntry + nil, // 26: hashdb.v1.SetResponse.DbReadLogEntry + nil, // 27: hashdb.v1.GetResponse.SiblingsEntry + nil, // 28: hashdb.v1.GetResponse.DbReadLogEntry + nil, // 29: hashdb.v1.GetFlushDataResponse.NodesEntry + nil, // 30: hashdb.v1.GetFlushDataResponse.ProgramEntry + (*emptypb.Empty)(nil), // 31: google.protobuf.Empty } var file_hashdb_proto_depIdxs = []int32{ - 16, // 0: hashdb.v1.SetRequest.old_root:type_name -> hashdb.v1.Fea - 16, // 1: hashdb.v1.SetRequest.key:type_name -> hashdb.v1.Fea - 16, // 2: hashdb.v1.GetRequest.root:type_name -> hashdb.v1.Fea - 16, // 3: hashdb.v1.GetRequest.key:type_name -> hashdb.v1.Fea - 16, // 4: hashdb.v1.SetProgramRequest.key:type_name -> hashdb.v1.Fea - 16, // 5: hashdb.v1.GetProgramRequest.key:type_name -> hashdb.v1.Fea - 21, // 6: hashdb.v1.LoadDBRequest.input_db:type_name -> hashdb.v1.LoadDBRequest.InputDbEntry - 22, // 7: hashdb.v1.LoadProgramDBRequest.input_program_db:type_name -> hashdb.v1.LoadProgramDBRequest.InputProgramDbEntry - 16, // 8: hashdb.v1.SetResponse.old_root:type_name -> hashdb.v1.Fea - 16, // 9: hashdb.v1.SetResponse.new_root:type_name -> hashdb.v1.Fea - 16, // 10: hashdb.v1.SetResponse.key:type_name -> hashdb.v1.Fea - 23, // 11: hashdb.v1.SetResponse.siblings:type_name -> hashdb.v1.SetResponse.SiblingsEntry - 16, // 12: hashdb.v1.SetResponse.ins_key:type_name -> hashdb.v1.Fea - 24, // 13: hashdb.v1.SetResponse.db_read_log:type_name -> hashdb.v1.SetResponse.DbReadLogEntry - 20, // 14: hashdb.v1.SetResponse.result:type_name -> hashdb.v1.ResultCode - 16, // 15: hashdb.v1.GetResponse.root:type_name -> hashdb.v1.Fea - 16, // 16: hashdb.v1.GetResponse.key:type_name -> hashdb.v1.Fea - 25, // 17: hashdb.v1.GetResponse.siblings:type_name -> hashdb.v1.GetResponse.SiblingsEntry - 16, // 18: hashdb.v1.GetResponse.ins_key:type_name -> hashdb.v1.Fea - 26, // 19: hashdb.v1.GetResponse.db_read_log:type_name -> hashdb.v1.GetResponse.DbReadLogEntry - 20, // 20: hashdb.v1.GetResponse.result:type_name -> hashdb.v1.ResultCode - 20, // 21: hashdb.v1.SetProgramResponse.result:type_name -> hashdb.v1.ResultCode - 20, // 22: hashdb.v1.GetProgramResponse.result:type_name -> hashdb.v1.ResultCode - 20, // 23: hashdb.v1.FlushResponse.result:type_name -> hashdb.v1.ResultCode - 19, // 24: hashdb.v1.GetFlushDataResponse.nodes:type_name -> hashdb.v1.FlushData - 19, // 25: hashdb.v1.GetFlushDataResponse.nodes_update:type_name -> hashdb.v1.FlushData - 19, // 26: hashdb.v1.GetFlushDataResponse.program:type_name -> hashdb.v1.FlushData - 19, // 27: hashdb.v1.GetFlushDataResponse.program_update:type_name -> hashdb.v1.FlushData - 20, // 28: hashdb.v1.GetFlushDataResponse.result:type_name -> hashdb.v1.ResultCode - 0, // 29: hashdb.v1.ResultCode.code:type_name -> hashdb.v1.ResultCode.Code - 17, // 30: hashdb.v1.LoadDBRequest.InputDbEntry.value:type_name -> hashdb.v1.FeList - 18, // 31: hashdb.v1.SetResponse.SiblingsEntry.value:type_name -> hashdb.v1.SiblingList - 17, // 32: hashdb.v1.SetResponse.DbReadLogEntry.value:type_name -> hashdb.v1.FeList - 18, // 33: hashdb.v1.GetResponse.SiblingsEntry.value:type_name -> hashdb.v1.SiblingList - 17, // 34: hashdb.v1.GetResponse.DbReadLogEntry.value:type_name -> hashdb.v1.FeList - 2, // 35: hashdb.v1.HashDBService.Set:input_type -> hashdb.v1.SetRequest - 3, // 36: hashdb.v1.HashDBService.Get:input_type -> hashdb.v1.GetRequest - 4, // 37: hashdb.v1.HashDBService.SetProgram:input_type -> hashdb.v1.SetProgramRequest - 5, // 38: hashdb.v1.HashDBService.GetProgram:input_type -> hashdb.v1.GetProgramRequest - 6, // 39: hashdb.v1.HashDBService.LoadDB:input_type -> hashdb.v1.LoadDBRequest - 7, // 40: hashdb.v1.HashDBService.LoadProgramDB:input_type -> hashdb.v1.LoadProgramDBRequest - 27, // 41: hashdb.v1.HashDBService.Flush:input_type -> google.protobuf.Empty - 27, // 42: hashdb.v1.HashDBService.GetFlushStatus:input_type -> google.protobuf.Empty - 8, // 43: hashdb.v1.HashDBService.GetFlushData:input_type -> hashdb.v1.GetFlushDataRequest - 9, // 44: hashdb.v1.HashDBService.Set:output_type -> hashdb.v1.SetResponse - 10, // 45: hashdb.v1.HashDBService.Get:output_type -> hashdb.v1.GetResponse - 11, // 46: hashdb.v1.HashDBService.SetProgram:output_type -> hashdb.v1.SetProgramResponse - 12, // 47: hashdb.v1.HashDBService.GetProgram:output_type -> hashdb.v1.GetProgramResponse - 27, // 48: hashdb.v1.HashDBService.LoadDB:output_type -> google.protobuf.Empty - 27, // 49: hashdb.v1.HashDBService.LoadProgramDB:output_type -> google.protobuf.Empty - 13, // 50: hashdb.v1.HashDBService.Flush:output_type -> hashdb.v1.FlushResponse - 14, // 51: hashdb.v1.HashDBService.GetFlushStatus:output_type -> hashdb.v1.GetFlushStatusResponse - 15, // 52: hashdb.v1.HashDBService.GetFlushData:output_type -> hashdb.v1.GetFlushDataResponse - 44, // [44:53] is the sub-list for method output_type - 35, // [35:44] is the sub-list for method input_type + 19, // 0: hashdb.v1.SetRequest.old_root:type_name -> hashdb.v1.Fea + 19, // 1: hashdb.v1.SetRequest.key:type_name -> hashdb.v1.Fea + 0, // 2: hashdb.v1.SetRequest.persistence:type_name -> hashdb.v1.Persistence + 19, // 3: hashdb.v1.GetRequest.root:type_name -> hashdb.v1.Fea + 19, // 4: hashdb.v1.GetRequest.key:type_name -> hashdb.v1.Fea + 19, // 5: hashdb.v1.SetProgramRequest.key:type_name -> hashdb.v1.Fea + 19, // 6: hashdb.v1.GetProgramRequest.key:type_name -> hashdb.v1.Fea + 23, // 7: hashdb.v1.LoadDBRequest.input_db:type_name -> hashdb.v1.LoadDBRequest.InputDbEntry + 24, // 8: hashdb.v1.LoadProgramDBRequest.input_program_db:type_name -> hashdb.v1.LoadProgramDBRequest.InputProgramDbEntry + 0, // 9: hashdb.v1.SemiFlushRequest.persistence:type_name -> hashdb.v1.Persistence + 19, // 10: hashdb.v1.SetResponse.old_root:type_name -> hashdb.v1.Fea + 19, // 11: hashdb.v1.SetResponse.new_root:type_name -> hashdb.v1.Fea + 19, // 12: hashdb.v1.SetResponse.key:type_name -> hashdb.v1.Fea + 25, // 13: hashdb.v1.SetResponse.siblings:type_name -> hashdb.v1.SetResponse.SiblingsEntry + 19, // 14: hashdb.v1.SetResponse.ins_key:type_name -> hashdb.v1.Fea + 26, // 15: hashdb.v1.SetResponse.db_read_log:type_name -> hashdb.v1.SetResponse.DbReadLogEntry + 22, // 16: hashdb.v1.SetResponse.result:type_name -> hashdb.v1.ResultCode + 19, // 17: hashdb.v1.GetResponse.root:type_name -> hashdb.v1.Fea + 19, // 18: hashdb.v1.GetResponse.key:type_name -> hashdb.v1.Fea + 27, // 19: hashdb.v1.GetResponse.siblings:type_name -> hashdb.v1.GetResponse.SiblingsEntry + 19, // 20: hashdb.v1.GetResponse.ins_key:type_name -> hashdb.v1.Fea + 28, // 21: hashdb.v1.GetResponse.db_read_log:type_name -> hashdb.v1.GetResponse.DbReadLogEntry + 22, // 22: hashdb.v1.GetResponse.result:type_name -> hashdb.v1.ResultCode + 22, // 23: hashdb.v1.SetProgramResponse.result:type_name -> hashdb.v1.ResultCode + 22, // 24: hashdb.v1.GetProgramResponse.result:type_name -> hashdb.v1.ResultCode + 22, // 25: hashdb.v1.FlushResponse.result:type_name -> hashdb.v1.ResultCode + 29, // 26: hashdb.v1.GetFlushDataResponse.nodes:type_name -> hashdb.v1.GetFlushDataResponse.NodesEntry + 30, // 27: hashdb.v1.GetFlushDataResponse.program:type_name -> hashdb.v1.GetFlushDataResponse.ProgramEntry + 22, // 28: hashdb.v1.GetFlushDataResponse.result:type_name -> hashdb.v1.ResultCode + 1, // 29: hashdb.v1.ResultCode.code:type_name -> hashdb.v1.ResultCode.Code + 20, // 30: hashdb.v1.LoadDBRequest.InputDbEntry.value:type_name -> hashdb.v1.FeList + 21, // 31: hashdb.v1.SetResponse.SiblingsEntry.value:type_name -> hashdb.v1.SiblingList + 20, // 32: hashdb.v1.SetResponse.DbReadLogEntry.value:type_name -> hashdb.v1.FeList + 21, // 33: hashdb.v1.GetResponse.SiblingsEntry.value:type_name -> hashdb.v1.SiblingList + 20, // 34: hashdb.v1.GetResponse.DbReadLogEntry.value:type_name -> hashdb.v1.FeList + 3, // 35: hashdb.v1.HashDBService.Set:input_type -> hashdb.v1.SetRequest + 4, // 36: hashdb.v1.HashDBService.Get:input_type -> hashdb.v1.GetRequest + 5, // 37: hashdb.v1.HashDBService.SetProgram:input_type -> hashdb.v1.SetProgramRequest + 6, // 38: hashdb.v1.HashDBService.GetProgram:input_type -> hashdb.v1.GetProgramRequest + 7, // 39: hashdb.v1.HashDBService.LoadDB:input_type -> hashdb.v1.LoadDBRequest + 8, // 40: hashdb.v1.HashDBService.LoadProgramDB:input_type -> hashdb.v1.LoadProgramDBRequest + 9, // 41: hashdb.v1.HashDBService.Flush:input_type -> hashdb.v1.FlushRequest + 10, // 42: hashdb.v1.HashDBService.SemiFlush:input_type -> hashdb.v1.SemiFlushRequest + 31, // 43: hashdb.v1.HashDBService.GetFlushStatus:input_type -> google.protobuf.Empty + 11, // 44: hashdb.v1.HashDBService.GetFlushData:input_type -> hashdb.v1.GetFlushDataRequest + 12, // 45: hashdb.v1.HashDBService.Set:output_type -> hashdb.v1.SetResponse + 13, // 46: hashdb.v1.HashDBService.Get:output_type -> hashdb.v1.GetResponse + 14, // 47: hashdb.v1.HashDBService.SetProgram:output_type -> hashdb.v1.SetProgramResponse + 15, // 48: hashdb.v1.HashDBService.GetProgram:output_type -> hashdb.v1.GetProgramResponse + 31, // 49: hashdb.v1.HashDBService.LoadDB:output_type -> google.protobuf.Empty + 31, // 50: hashdb.v1.HashDBService.LoadProgramDB:output_type -> google.protobuf.Empty + 16, // 51: hashdb.v1.HashDBService.Flush:output_type -> hashdb.v1.FlushResponse + 31, // 52: hashdb.v1.HashDBService.SemiFlush:output_type -> google.protobuf.Empty + 17, // 53: hashdb.v1.HashDBService.GetFlushStatus:output_type -> hashdb.v1.GetFlushStatusResponse + 18, // 54: hashdb.v1.HashDBService.GetFlushData:output_type -> hashdb.v1.GetFlushDataResponse + 45, // [45:55] is the sub-list for method output_type + 35, // [35:45] is the sub-list for method input_type 35, // [35:35] is the sub-list for extension type_name 35, // [35:35] is the sub-list for extension extendee 0, // [0:35] is the sub-list for field type_name @@ -2038,7 +2189,7 @@ func file_hashdb_proto_init() { } } file_hashdb_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetFlushDataRequest); i { + switch v := v.(*FlushRequest); i { case 0: return &v.state case 1: @@ -2050,7 +2201,7 @@ func file_hashdb_proto_init() { } } file_hashdb_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SetResponse); i { + switch v := v.(*SemiFlushRequest); i { case 0: return &v.state case 1: @@ -2062,7 +2213,7 @@ func file_hashdb_proto_init() { } } file_hashdb_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetResponse); i { + switch v := v.(*GetFlushDataRequest); i { case 0: return &v.state case 1: @@ -2074,7 +2225,7 @@ func file_hashdb_proto_init() { } } file_hashdb_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SetProgramResponse); i { + switch v := v.(*SetResponse); i { case 0: return &v.state case 1: @@ -2086,7 +2237,7 @@ func file_hashdb_proto_init() { } } file_hashdb_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetProgramResponse); i { + switch v := v.(*GetResponse); i { case 0: return &v.state case 1: @@ -2098,7 +2249,7 @@ func file_hashdb_proto_init() { } } file_hashdb_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FlushResponse); i { + switch v := v.(*SetProgramResponse); i { case 0: return &v.state case 1: @@ -2110,7 +2261,7 @@ func file_hashdb_proto_init() { } } file_hashdb_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetFlushStatusResponse); i { + switch v := v.(*GetProgramResponse); i { case 0: return &v.state case 1: @@ -2122,7 +2273,7 @@ func file_hashdb_proto_init() { } } file_hashdb_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetFlushDataResponse); i { + switch v := v.(*FlushResponse); i { case 0: return &v.state case 1: @@ -2134,7 +2285,7 @@ func file_hashdb_proto_init() { } } file_hashdb_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Fea); i { + switch v := v.(*GetFlushStatusResponse); i { case 0: return &v.state case 1: @@ -2146,7 +2297,7 @@ func file_hashdb_proto_init() { } } file_hashdb_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FeList); i { + switch v := v.(*GetFlushDataResponse); i { case 0: return &v.state case 1: @@ -2158,7 +2309,7 @@ func file_hashdb_proto_init() { } } file_hashdb_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SiblingList); i { + switch v := v.(*Fea); i { case 0: return &v.state case 1: @@ -2170,7 +2321,7 @@ func file_hashdb_proto_init() { } } file_hashdb_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FlushData); i { + switch v := v.(*FeList); i { case 0: return &v.state case 1: @@ -2182,6 +2333,18 @@ func file_hashdb_proto_init() { } } file_hashdb_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SiblingList); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_hashdb_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ResultCode); i { case 0: return &v.state @@ -2199,8 +2362,8 @@ func file_hashdb_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_hashdb_proto_rawDesc, - NumEnums: 1, - NumMessages: 26, + NumEnums: 2, + NumMessages: 29, NumExtensions: 0, NumServices: 1, }, diff --git a/merkletree/pb/hashdb_grpc.pb.go b/merkletree/hashdb/hashdb_grpc.pb.go similarity index 88% rename from merkletree/pb/hashdb_grpc.pb.go rename to merkletree/hashdb/hashdb_grpc.pb.go index 10ec44f17a..41d28b5654 100644 --- a/merkletree/pb/hashdb_grpc.pb.go +++ b/merkletree/hashdb/hashdb_grpc.pb.go @@ -4,11 +4,10 @@ // - protoc v3.21.12 // source: hashdb.proto -package pb +package hashdb import ( context "context" - grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" @@ -28,6 +27,7 @@ const ( HashDBService_LoadDB_FullMethodName = "/hashdb.v1.HashDBService/LoadDB" HashDBService_LoadProgramDB_FullMethodName = "/hashdb.v1.HashDBService/LoadProgramDB" HashDBService_Flush_FullMethodName = "/hashdb.v1.HashDBService/Flush" + HashDBService_SemiFlush_FullMethodName = "/hashdb.v1.HashDBService/SemiFlush" HashDBService_GetFlushStatus_FullMethodName = "/hashdb.v1.HashDBService/GetFlushStatus" HashDBService_GetFlushData_FullMethodName = "/hashdb.v1.HashDBService/GetFlushData" ) @@ -42,7 +42,8 @@ type HashDBServiceClient interface { GetProgram(ctx context.Context, in *GetProgramRequest, opts ...grpc.CallOption) (*GetProgramResponse, error) LoadDB(ctx context.Context, in *LoadDBRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) LoadProgramDB(ctx context.Context, in *LoadProgramDBRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) - Flush(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*FlushResponse, error) + Flush(ctx context.Context, in *FlushRequest, opts ...grpc.CallOption) (*FlushResponse, error) + SemiFlush(ctx context.Context, in *SemiFlushRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) GetFlushStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*GetFlushStatusResponse, error) GetFlushData(ctx context.Context, in *GetFlushDataRequest, opts ...grpc.CallOption) (*GetFlushDataResponse, error) } @@ -109,7 +110,7 @@ func (c *hashDBServiceClient) LoadProgramDB(ctx context.Context, in *LoadProgram return out, nil } -func (c *hashDBServiceClient) Flush(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*FlushResponse, error) { +func (c *hashDBServiceClient) Flush(ctx context.Context, in *FlushRequest, opts ...grpc.CallOption) (*FlushResponse, error) { out := new(FlushResponse) err := c.cc.Invoke(ctx, HashDBService_Flush_FullMethodName, in, out, opts...) if err != nil { @@ -118,6 +119,15 @@ func (c *hashDBServiceClient) Flush(ctx context.Context, in *emptypb.Empty, opts return out, nil } +func (c *hashDBServiceClient) SemiFlush(ctx context.Context, in *SemiFlushRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, HashDBService_SemiFlush_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *hashDBServiceClient) GetFlushStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*GetFlushStatusResponse, error) { out := new(GetFlushStatusResponse) err := c.cc.Invoke(ctx, HashDBService_GetFlushStatus_FullMethodName, in, out, opts...) @@ -146,7 +156,8 @@ type HashDBServiceServer interface { GetProgram(context.Context, *GetProgramRequest) (*GetProgramResponse, error) LoadDB(context.Context, *LoadDBRequest) (*emptypb.Empty, error) LoadProgramDB(context.Context, *LoadProgramDBRequest) (*emptypb.Empty, error) - Flush(context.Context, *emptypb.Empty) (*FlushResponse, error) + Flush(context.Context, *FlushRequest) (*FlushResponse, error) + SemiFlush(context.Context, *SemiFlushRequest) (*emptypb.Empty, error) GetFlushStatus(context.Context, *emptypb.Empty) (*GetFlushStatusResponse, error) GetFlushData(context.Context, *GetFlushDataRequest) (*GetFlushDataResponse, error) mustEmbedUnimplementedHashDBServiceServer() @@ -174,9 +185,12 @@ func (UnimplementedHashDBServiceServer) LoadDB(context.Context, *LoadDBRequest) func (UnimplementedHashDBServiceServer) LoadProgramDB(context.Context, *LoadProgramDBRequest) (*emptypb.Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method LoadProgramDB not implemented") } -func (UnimplementedHashDBServiceServer) Flush(context.Context, *emptypb.Empty) (*FlushResponse, error) { +func (UnimplementedHashDBServiceServer) Flush(context.Context, *FlushRequest) (*FlushResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Flush not implemented") } +func (UnimplementedHashDBServiceServer) SemiFlush(context.Context, *SemiFlushRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method SemiFlush not implemented") +} func (UnimplementedHashDBServiceServer) GetFlushStatus(context.Context, *emptypb.Empty) (*GetFlushStatusResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetFlushStatus not implemented") } @@ -305,7 +319,7 @@ func _HashDBService_LoadProgramDB_Handler(srv interface{}, ctx context.Context, } func _HashDBService_Flush_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(emptypb.Empty) + in := new(FlushRequest) if err := dec(in); err != nil { return nil, err } @@ -317,7 +331,25 @@ func _HashDBService_Flush_Handler(srv interface{}, ctx context.Context, dec func FullMethod: HashDBService_Flush_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(HashDBServiceServer).Flush(ctx, req.(*emptypb.Empty)) + return srv.(HashDBServiceServer).Flush(ctx, req.(*FlushRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HashDBService_SemiFlush_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SemiFlushRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HashDBServiceServer).SemiFlush(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: HashDBService_SemiFlush_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HashDBServiceServer).SemiFlush(ctx, req.(*SemiFlushRequest)) } return interceptor(ctx, in, info, handler) } @@ -393,6 +425,10 @@ var HashDBService_ServiceDesc = grpc.ServiceDesc{ MethodName: "Flush", Handler: _HashDBService_Flush_Handler, }, + { + MethodName: "SemiFlush", + Handler: _HashDBService_SemiFlush_Handler, + }, { MethodName: "GetFlushStatus", Handler: _HashDBService_GetFlushStatus_Handler, diff --git a/merkletree/tree.go b/merkletree/tree.go index 36cefa572d..c6da917417 100644 --- a/merkletree/tree.go +++ b/merkletree/tree.go @@ -7,18 +7,17 @@ import ( "strings" "github.com/0xPolygonHermez/zkevm-node/hex" - "github.com/0xPolygonHermez/zkevm-node/merkletree/pb" + "github.com/0xPolygonHermez/zkevm-node/merkletree/hashdb" "github.com/ethereum/go-ethereum/common" - "google.golang.org/protobuf/types/known/emptypb" ) // StateTree provides methods to access and modify state in merkletree type StateTree struct { - grpcClient pb.HashDBServiceClient + grpcClient hashdb.HashDBServiceClient } // NewStateTree creates new StateTree. -func NewStateTree(client pb.HashDBServiceClient) *StateTree { +func NewStateTree(client hashdb.HashDBServiceClient) *StateTree { return &StateTree{ grpcClient: client, } @@ -125,7 +124,7 @@ func (tree *StateTree) GetStorageAt(ctx context.Context, address common.Address, } // SetBalance sets balance. -func (tree *StateTree) SetBalance(ctx context.Context, address common.Address, balance *big.Int, root []byte) (newRoot []byte, proof *UpdateProof, err error) { +func (tree *StateTree) SetBalance(ctx context.Context, address common.Address, balance *big.Int, root []byte, uuid string) (newRoot []byte, proof *UpdateProof, err error) { if balance.Cmp(big.NewInt(0)) == -1 { return nil, nil, fmt.Errorf("invalid balance") } @@ -139,7 +138,7 @@ func (tree *StateTree) SetBalance(ctx context.Context, address common.Address, b k := new(big.Int).SetBytes(key) balanceH8 := scalar2fea(balance) - updateProof, err := tree.set(ctx, scalarToh4(r), scalarToh4(k), balanceH8) + updateProof, err := tree.set(ctx, scalarToh4(r), scalarToh4(k), balanceH8, uuid) if err != nil { return nil, nil, err } @@ -148,7 +147,7 @@ func (tree *StateTree) SetBalance(ctx context.Context, address common.Address, b } // SetNonce sets nonce. -func (tree *StateTree) SetNonce(ctx context.Context, address common.Address, nonce *big.Int, root []byte) (newRoot []byte, proof *UpdateProof, err error) { +func (tree *StateTree) SetNonce(ctx context.Context, address common.Address, nonce *big.Int, root []byte, uuid string) (newRoot []byte, proof *UpdateProof, err error) { if nonce.Cmp(big.NewInt(0)) == -1 { return nil, nil, fmt.Errorf("invalid nonce") } @@ -163,7 +162,7 @@ func (tree *StateTree) SetNonce(ctx context.Context, address common.Address, non nonceH8 := scalar2fea(nonce) - updateProof, err := tree.set(ctx, scalarToh4(r), scalarToh4(k), nonceH8) + updateProof, err := tree.set(ctx, scalarToh4(r), scalarToh4(k), nonceH8, uuid) if err != nil { return nil, nil, err } @@ -172,7 +171,7 @@ func (tree *StateTree) SetNonce(ctx context.Context, address common.Address, non } // SetCode sets smart contract code. -func (tree *StateTree) SetCode(ctx context.Context, address common.Address, code []byte, root []byte) (newRoot []byte, proof *UpdateProof, err error) { +func (tree *StateTree) SetCode(ctx context.Context, address common.Address, code []byte, root []byte, uuid string) (newRoot []byte, proof *UpdateProof, err error) { // calculating smart contract code hash scCodeHash4, err := hashContractBytecode(code) if err != nil { @@ -201,7 +200,7 @@ func (tree *StateTree) SetCode(ctx context.Context, address common.Address, code scCodeHashBI := new(big.Int).SetBytes(scCodeHash[:]) scCodeHashH8 := scalar2fea(scCodeHashBI) - updateProof, err := tree.set(ctx, scalarToh4(r), scalarToh4(k), scCodeHashH8) + updateProof, err := tree.set(ctx, scalarToh4(r), scalarToh4(k), scCodeHashH8, uuid) if err != nil { return nil, nil, err } @@ -215,7 +214,7 @@ func (tree *StateTree) SetCode(ctx context.Context, address common.Address, code scCodeLengthBI := new(big.Int).SetInt64(int64(len(code))) scCodeLengthH8 := scalar2fea(scCodeLengthBI) - updateProof, err = tree.set(ctx, updateProof.NewRoot, scalarToh4(k), scCodeLengthH8) + updateProof, err = tree.set(ctx, updateProof.NewRoot, scalarToh4(k), scCodeLengthH8, uuid) if err != nil { return nil, nil, err } @@ -224,7 +223,7 @@ func (tree *StateTree) SetCode(ctx context.Context, address common.Address, code } // SetStorageAt sets storage value at specified position. -func (tree *StateTree) SetStorageAt(ctx context.Context, address common.Address, position *big.Int, value *big.Int, root []byte) (newRoot []byte, proof *UpdateProof, err error) { +func (tree *StateTree) SetStorageAt(ctx context.Context, address common.Address, position *big.Int, value *big.Int, root []byte, uuid string) (newRoot []byte, proof *UpdateProof, err error) { r := new(big.Int).SetBytes(root) key, err := KeyContractStorage(address, position.Bytes()) if err != nil { @@ -233,7 +232,7 @@ func (tree *StateTree) SetStorageAt(ctx context.Context, address common.Address, k := new(big.Int).SetBytes(key[:]) valueH8 := scalar2fea(value) - updateProof, err := tree.set(ctx, scalarToh4(r), scalarToh4(k), valueH8) + updateProof, err := tree.set(ctx, scalarToh4(r), scalarToh4(k), valueH8, uuid) if err != nil { return nil, nil, err } @@ -242,9 +241,9 @@ func (tree *StateTree) SetStorageAt(ctx context.Context, address common.Address, } func (tree *StateTree) get(ctx context.Context, root, key []uint64) (*Proof, error) { - result, err := tree.grpcClient.Get(ctx, &pb.GetRequest{ - Root: &pb.Fea{Fe0: root[0], Fe1: root[1], Fe2: root[2], Fe3: root[3]}, - Key: &pb.Fea{Fe0: key[0], Fe1: key[1], Fe2: key[2], Fe3: key[3]}, + result, err := tree.grpcClient.Get(ctx, &hashdb.GetRequest{ + Root: &hashdb.Fea{Fe0: root[0], Fe1: root[1], Fe2: root[2], Fe3: root[3]}, + Key: &hashdb.Fea{Fe0: key[0], Fe1: key[1], Fe2: key[2], Fe3: key[3]}, }) if err != nil { return nil, err @@ -262,8 +261,8 @@ func (tree *StateTree) get(ctx context.Context, root, key []uint64) (*Proof, err } func (tree *StateTree) getProgram(ctx context.Context, key []uint64) (*ProgramProof, error) { - result, err := tree.grpcClient.GetProgram(ctx, &pb.GetProgramRequest{ - Key: &pb.Fea{Fe0: key[0], Fe1: key[1], Fe2: key[2], Fe3: key[3]}, + result, err := tree.grpcClient.GetProgram(ctx, &hashdb.GetProgramRequest{ + Key: &hashdb.Fea{Fe0: key[0], Fe1: key[1], Fe2: key[2], Fe3: key[3]}, }) if err != nil { return nil, err @@ -274,16 +273,17 @@ func (tree *StateTree) getProgram(ctx context.Context, key []uint64) (*ProgramPr }, nil } -func (tree *StateTree) set(ctx context.Context, oldRoot, key, value []uint64) (*UpdateProof, error) { +func (tree *StateTree) set(ctx context.Context, oldRoot, key, value []uint64, uuid string) (*UpdateProof, error) { feaValue := fea2string(value) if strings.HasPrefix(feaValue, "0x") { // nolint feaValue = feaValue[2:] } - result, err := tree.grpcClient.Set(ctx, &pb.SetRequest{ - OldRoot: &pb.Fea{Fe0: oldRoot[0], Fe1: oldRoot[1], Fe2: oldRoot[2], Fe3: oldRoot[3]}, - Key: &pb.Fea{Fe0: key[0], Fe1: key[1], Fe2: key[2], Fe3: key[3]}, - Value: feaValue, - Persistent: true, + result, err := tree.grpcClient.Set(ctx, &hashdb.SetRequest{ + OldRoot: &hashdb.Fea{Fe0: oldRoot[0], Fe1: oldRoot[1], Fe2: oldRoot[2], Fe3: oldRoot[3]}, + Key: &hashdb.Fea{Fe0: key[0], Fe1: key[1], Fe2: key[2], Fe3: key[3]}, + Value: feaValue, + Persistence: hashdb.Persistence_PERSISTENCE_DATABASE, + BatchUuid: uuid, }) if err != nil { return nil, err @@ -306,8 +306,8 @@ func (tree *StateTree) set(ctx context.Context, oldRoot, key, value []uint64) (* } func (tree *StateTree) setProgram(ctx context.Context, key []uint64, data []byte, persistent bool) error { - _, err := tree.grpcClient.SetProgram(ctx, &pb.SetProgramRequest{ - Key: &pb.Fea{Fe0: key[0], Fe1: key[1], Fe2: key[2], Fe3: key[3]}, + _, err := tree.grpcClient.SetProgram(ctx, &hashdb.SetProgramRequest{ + Key: &hashdb.Fea{Fe0: key[0], Fe1: key[1], Fe2: key[2], Fe3: key[3]}, Data: data, Persistent: persistent, }) @@ -315,7 +315,8 @@ func (tree *StateTree) setProgram(ctx context.Context, key []uint64, data []byte } // Flush flushes all changes to the persistent storage. -func (tree *StateTree) Flush(ctx context.Context) error { - _, err := tree.grpcClient.Flush(ctx, &emptypb.Empty{}) +func (tree *StateTree) Flush(ctx context.Context, uuid string) error { + flushRequest := &hashdb.FlushRequest{BatchUuid: uuid} + _, err := tree.grpcClient.Flush(ctx, flushRequest) return err } diff --git a/proto/src/proto/aggregator/v1/aggregator.proto b/proto/src/proto/aggregator/v1/aggregator.proto index 66be538775..4c22903571 100644 --- a/proto/src/proto/aggregator/v1/aggregator.proto +++ b/proto/src/proto/aggregator/v1/aggregator.proto @@ -2,7 +2,7 @@ syntax = "proto3"; package aggregator.v1; -option go_package = "github.com/0xPolygonHermez/zkevm-node/aggregator/pb"; +option go_package = "github.com/0xPolygonHermez/zkevm-node/aggregator/prover"; message Version { string v0_0_1 = 1; diff --git a/proto/src/proto/hashdb/v1/hashdb.proto b/proto/src/proto/hashdb/v1/hashdb.proto index cd41cf7c23..1f887839d7 100644 --- a/proto/src/proto/hashdb/v1/hashdb.proto +++ b/proto/src/proto/hashdb/v1/hashdb.proto @@ -4,7 +4,7 @@ import "google/protobuf/empty.proto"; package hashdb.v1; -option go_package = "github.com/0xPolygonHermez/zkevm-node/merkletree/pb"; +option go_package = "github.com/0xPolygonHermez/zkevm-node/merkletree/hashdb"; message Version { string v0_0_1 = 1; @@ -27,7 +27,8 @@ service HashDBService { rpc GetProgram(GetProgramRequest) returns (GetProgramResponse) {} rpc LoadDB(LoadDBRequest) returns (google.protobuf.Empty) {} rpc LoadProgramDB(LoadProgramDBRequest) returns (google.protobuf.Empty) {} - rpc Flush (google.protobuf.Empty) returns (FlushResponse) {} + rpc Flush (FlushRequest) returns (FlushResponse) {} + rpc SemiFlush (SemiFlushRequest) returns (google.protobuf.Empty) {} rpc GetFlushStatus (google.protobuf.Empty) returns (GetFlushStatusResponse) {} rpc GetFlushData (GetFlushDataRequest) returns (GetFlushDataResponse) {} } @@ -36,22 +37,33 @@ service HashDBService { // Request messages /////////////////// +enum Persistence { + PERSISTENCE_CACHE_UNSPECIFIED = 0; + PERSISTENCE_DATABASE = 1; + PERSISTENCE_TEMPORARY = 2; +} + /** * @dev SetRequest * @param {old_root} - merkle-tree root * @param {key} - key to set * @param {value} - scalar value to set (HEX string format) - * @param {persistent} - indicates if it should be stored in the SQL database (true) or only in the memory cache (false) + * @param {persistence} - indicates if it should be stored only in CACHE, in the SQL DATABASE, or it is just TEMPORARY and should be deleted at the flush of this batch UUID * @param {details} - indicates if it should return all response parameters (true) or just the new root (false) * @param {get_db_read_log} - indicates if it should return the DB reads generated during the execution of the request + * @param {batch_uuid} - indicates a unique identifier of the current batch or session; data for this batch can be stored in memory until flushed to database + * @param {tx} - current transaction ordinal number: 0, 1, 2... */ message SetRequest { Fea old_root = 1; Fea key = 2; string value = 3; - bool persistent = 4; + Persistence persistence = 4; bool details = 5; bool get_db_read_log = 6; + string batch_uuid = 7; + uint64 tx = 8; + } /** @@ -60,12 +72,14 @@ message SetRequest { * @param {key} - key to look for * @param {details} - indicates if it should return all response parameters (true) or just the new root (false) * @param {get_db_read_log} - indicates if it should return the DB reads generated during the execution of the request + * @param {batch_uuid} - indicates a unique identifier of the current batch or session; data for this batch can be stored in memory until flushed to database */ message GetRequest { Fea root = 1; Fea key = 2; bool details = 3; bool get_db_read_log = 4; + string batch_uuid = 5; } /** @@ -108,6 +122,26 @@ message LoadProgramDBRequest { bool persistent = 2; } +/** + * @dev FlushRequest + * @param {batch_uuid} - indicates a unique identifier of the current batch or session which data will be flushed to cache (and database if required) + */ +message FlushRequest { + string batch_uuid = 1; +} + +/** + * @dev SemiFlushRequest + * @param {batch_uuid} - indicates a unique identifier of the current batch or session which data will be semi-flushed + * @param {new_state_root} - state root at this point of the execution + * @param {persistence} - indicates if it should be stored only in CACHE, in the SQL DATABASE, or it is just TEMPORARY and should be deleted at the flush of this batch UUID + */ +message SemiFlushRequest { + string batch_uuid = 1; + string new_state_root = 2; + Persistence persistence = 3; +} + /** * @dev GetFlushDataRequest * @param {flush_id} - last stored flush ID got using this method, or 0 if it never was called before @@ -234,20 +268,16 @@ message GetFlushStatusResponse { * @dev GetFlushDataResponse * @param {stored_flush_id} - id of the last flush data sent to database * @param {nodes} - data to insert in the nodes table - * @param {nodes_update} - data to update in the nodes table * @param {program} - data to insert in the program table - * @param {program_update} - data to update in the program table * @param {nodes_state_root} - nodes state root to update in the nodes table * @param {result} - result code */ message GetFlushDataResponse { uint64 stored_flush_id = 1; - repeated FlushData nodes = 2; - repeated FlushData nodes_update = 3; - repeated FlushData program = 4; - repeated FlushData program_update = 5; - string nodes_state_root = 6; - ResultCode result = 7; + map nodes = 2; + map program = 3; + string nodes_state_root = 4; + ResultCode result = 5; } /** @@ -280,16 +310,6 @@ message SiblingList { repeated uint64 sibling = 1; } -/** - * @dev Flush Data - * @param {key} - hash key - * @param {value} - string value -*/ -message FlushData { - string key = 1; - string value = 2; -} - /** * @dev Result code * @param {code} - result code diff --git a/sequencer/closingsignalsmanager_test.go b/sequencer/closingsignalsmanager_test.go index cb09f40e34..e6eca56cef 100644 --- a/sequencer/closingsignalsmanager_test.go +++ b/sequencer/closingsignalsmanager_test.go @@ -11,7 +11,7 @@ import ( "github.com/0xPolygonHermez/zkevm-node/event/nileventstorage" "github.com/0xPolygonHermez/zkevm-node/log" "github.com/0xPolygonHermez/zkevm-node/merkletree" - mtDBclientpb "github.com/0xPolygonHermez/zkevm-node/merkletree/pb" + "github.com/0xPolygonHermez/zkevm-node/merkletree/hashdb" "github.com/0xPolygonHermez/zkevm-node/state" "github.com/0xPolygonHermez/zkevm-node/state/runtime/executor" "github.com/0xPolygonHermez/zkevm-node/test/dbutils" @@ -30,7 +30,7 @@ var ( localTestDbManager *dbManager localCtx context.Context localMtDBCancel, localExecutorCancel context.CancelFunc - localMtDBServiceClient mtDBclientpb.HashDBServiceClient + localMtDBServiceClient hashdb.HashDBServiceClient localMtDBClientConn, localExecutorClientConn *grpc.ClientConn localState *state.State localExecutorClient executor.ExecutorServiceClient diff --git a/sequencer/dbmanager_test.go b/sequencer/dbmanager_test.go index 647a134dbf..0a8cf82c23 100644 --- a/sequencer/dbmanager_test.go +++ b/sequencer/dbmanager_test.go @@ -12,7 +12,7 @@ import ( "github.com/0xPolygonHermez/zkevm-node/event/nileventstorage" "github.com/0xPolygonHermez/zkevm-node/log" "github.com/0xPolygonHermez/zkevm-node/merkletree" - mtDBclientpb "github.com/0xPolygonHermez/zkevm-node/merkletree/pb" + "github.com/0xPolygonHermez/zkevm-node/merkletree/hashdb" "github.com/0xPolygonHermez/zkevm-node/state" "github.com/0xPolygonHermez/zkevm-node/state/runtime/executor" "github.com/0xPolygonHermez/zkevm-node/test/dbutils" @@ -37,7 +37,7 @@ var ( } dbManagerCfg = DBManagerCfg{PoolRetrievalInterval: types.NewDuration(500 * time.Millisecond)} executorClient executor.ExecutorServiceClient - mtDBServiceClient mtDBclientpb.HashDBServiceClient + mtDBServiceClient hashdb.HashDBServiceClient mtDBClientConn *grpc.ClientConn testDbManager *dbManager ) diff --git a/state/genesis.go b/state/genesis.go index e48a89a3b6..ab05bbed17 100644 --- a/state/genesis.go +++ b/state/genesis.go @@ -12,6 +12,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/trie" + "github.com/google/uuid" "github.com/jackc/pgx/v4" ) @@ -50,6 +51,8 @@ func (s *State) SetGenesis(ctx context.Context, block Block, genesis Genesis, db return newRoot, ErrStateTreeNil } + uuid := uuid.New().String() + for _, action := range genesis.GenesisActions { address := common.HexToAddress(action.Address) switch action.Type { @@ -58,7 +61,7 @@ func (s *State) SetGenesis(ctx context.Context, block Block, genesis Genesis, db if err != nil { return newRoot, err } - newRoot, _, err = s.tree.SetBalance(ctx, address, balance, newRoot) + newRoot, _, err = s.tree.SetBalance(ctx, address, balance, newRoot, uuid) if err != nil { return newRoot, err } @@ -67,7 +70,7 @@ func (s *State) SetGenesis(ctx context.Context, block Block, genesis Genesis, db if err != nil { return newRoot, err } - newRoot, _, err = s.tree.SetNonce(ctx, address, nonce, newRoot) + newRoot, _, err = s.tree.SetNonce(ctx, address, nonce, newRoot, uuid) if err != nil { return newRoot, err } @@ -76,7 +79,7 @@ func (s *State) SetGenesis(ctx context.Context, block Block, genesis Genesis, db if err != nil { return newRoot, fmt.Errorf("could not decode SC bytecode for address %q: %v", address, err) } - newRoot, _, err = s.tree.SetCode(ctx, address, code, newRoot) + newRoot, _, err = s.tree.SetCode(ctx, address, code, newRoot, uuid) if err != nil { return newRoot, err } @@ -91,7 +94,7 @@ func (s *State) SetGenesis(ctx context.Context, block Block, genesis Genesis, db return newRoot, err } // Store - newRoot, _, err = s.tree.SetStorageAt(ctx, address, positionBI, valueBI, newRoot) + newRoot, _, err = s.tree.SetStorageAt(ctx, address, positionBI, valueBI, newRoot, uuid) if err != nil { return newRoot, err } @@ -105,7 +108,7 @@ func (s *State) SetGenesis(ctx context.Context, block Block, genesis Genesis, db root.SetBytes(newRoot) // flush state db - err = s.tree.Flush(ctx) + err = s.tree.Flush(ctx, uuid) if err != nil { log.Errorf("error flushing state tree after genesis: %v", err) return newRoot, err diff --git a/state/state.go b/state/state.go index caeb538759..487c19f726 100644 --- a/state/state.go +++ b/state/state.go @@ -139,7 +139,7 @@ func (s *State) FlushMerkleTree(ctx context.Context) error { if s.tree == nil { return ErrStateTreeNil } - return s.tree.Flush(ctx) + return s.tree.Flush(ctx, "") } // GetStoredFlushID returns the stored flush ID and Prover ID diff --git a/state/state_test.go b/state/state_test.go index d02ed66227..d72c82e89d 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -21,7 +21,7 @@ import ( "github.com/0xPolygonHermez/zkevm-node/hex" "github.com/0xPolygonHermez/zkevm-node/log" "github.com/0xPolygonHermez/zkevm-node/merkletree" - mtDBclientpb "github.com/0xPolygonHermez/zkevm-node/merkletree/pb" + "github.com/0xPolygonHermez/zkevm-node/merkletree/hashdb" state "github.com/0xPolygonHermez/zkevm-node/state" "github.com/0xPolygonHermez/zkevm-node/state/metrics" "github.com/0xPolygonHermez/zkevm-node/state/runtime" @@ -67,7 +67,7 @@ var ( } forkID uint64 = 5 executorClient executor.ExecutorServiceClient - mtDBServiceClient mtDBclientpb.HashDBServiceClient + mtDBServiceClient hashdb.HashDBServiceClient executorClientConn, mtDBClientConn *grpc.ClientConn batchResources = state.BatchResources{ ZKCounters: state.ZKCounters{ @@ -726,7 +726,7 @@ func TestGenesis(t *testing.T) { } } - err = testState.GetTree().Flush(ctx) + err = testState.GetTree().Flush(ctx, "") require.NoError(t, err) } diff --git a/synchronizer/mock_state.go b/synchronizer/mock_state.go index a65375c1d8..052f4f848e 100644 --- a/synchronizer/mock_state.go +++ b/synchronizer/mock_state.go @@ -711,12 +711,13 @@ func (_m *stateMock) UpdateForkIDIntervals(intervals []state.ForkIDInterval) { _m.Called(intervals) } -// newStateMock creates a new instance of stateMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func newStateMock(t interface { +type mockConstructorTestingTnewStateMock interface { mock.TestingT Cleanup(func()) -}) *stateMock { +} + +// newStateMock creates a new instance of stateMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func newStateMock(t mockConstructorTestingTnewStateMock) *stateMock { mock := &stateMock{} mock.Mock.Test(t) diff --git a/test/config/test.permissionless.prover.config.json b/test/config/test.permissionless.prover.config.json index 0f879c9c17..52f7bc3744 100644 --- a/test/config/test.permissionless.prover.config.json +++ b/test/config/test.permissionless.prover.config.json @@ -81,9 +81,8 @@ "maxExecutorThreads": 20, "maxProverThreads": 8, "maxHashDBThreads": 8, - "ECRecoverPrecalc": true, "ECRecoverPrecalcNThreads": 16, - "dbMultiWriteSinglePosition": false + "stateManager": true } diff --git a/test/config/test.prover.config.json b/test/config/test.prover.config.json index 86f511f0c2..d1e0bc5da2 100644 --- a/test/config/test.prover.config.json +++ b/test/config/test.prover.config.json @@ -83,9 +83,8 @@ "maxExecutorThreads": 20, "maxProverThreads": 8, "maxHashDBThreads": 8, - "ECRecoverPrecalc": true, "ECRecoverPrecalcNThreads": 16, - "dbMultiWriteSinglePosition": false + "stateManager": true } diff --git a/test/docker-compose.yml b/test/docker-compose.yml index 6c50ab1853..ef4c453a45 100644 --- a/test/docker-compose.yml +++ b/test/docker-compose.yml @@ -338,7 +338,7 @@ services: zkevm-prover: container_name: zkevm-prover - image: hermeznetwork/zkevm-prover:v2.0.1 + image: hermeznetwork/zkevm-prover:v2.1.0-RC2 ports: # - 50051:50051 # Prover - 50052:50052 # Mock prover @@ -414,7 +414,7 @@ services: zkevm-permissionless-prover: container_name: zkevm-permissionless-prover - image: hermeznetwork/zkevm-prover:v2.0.1 + image: hermeznetwork/zkevm-prover:v2.1.0-RC2 ports: # - 50058:50058 # Prover - 50059:50052 # Mock prover diff --git a/test/scripts/send_transfers/main.go b/test/scripts/send_transfers/main.go index 136252b275..4a56b48c3f 100644 --- a/test/scripts/send_transfers/main.go +++ b/test/scripts/send_transfers/main.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "math/big" + "time" "github.com/0xPolygonHermez/zkevm-node/hex" "github.com/0xPolygonHermez/zkevm-node/log" @@ -22,8 +23,9 @@ func main() { ChainID uint64 PrivateKey string }{ - //{Name: "Local L1", URL: operations.DefaultL1NetworkURL, ChainID: operations.DefaultL1ChainID, PrivateKey: operations.DefaultSequencerPrivateKey}, - {Name: "Local L2", URL: operations.DefaultL2NetworkURL, ChainID: operations.DefaultL2ChainID, PrivateKey: operations.DefaultSequencerPrivateKey}, + // {Name: "Local L1", URL: operations.DefaultL1NetworkURL, ChainID: operations.DefaultL1ChainID, PrivateKey: operations.DefaultSequencerPrivateKey}, + // {Name: "Local L2", URL: "http://34.90.16.102:8545/", ChainID: 1234, PrivateKey: operations.DefaultSequencerPrivateKey}, + {Name: "Local L2", URL: "https://rpc.public.zkevm-test.net/", ChainID: 1442, PrivateKey: "5d120f76469fb621f9b74587096f9bb292a27ebe346a2c84071010030356c20c"}, } for _, network := range networks { @@ -37,7 +39,7 @@ func main() { auth := operations.MustGetAuth(network.PrivateKey, network.ChainID) chkErr(err) - const receiverAddr = "0x617b3a3528F9cDd6630fd3301B9c8911F7Bf063D" + const receiverAddr = "0xdc6Bf73BC0A688bC11D5234Cb0F2719672Babd30" balance, err := client.BalanceAt(ctx, auth.From, nil) chkErr(err) @@ -53,13 +55,14 @@ func main() { nonce, err := client.NonceAt(ctx, auth.From, nil) chkErr(err) // var lastTxHash common.Hash - for i := 0; i < 1000; i++ { + for i := 0; i < 100000; i++ { nonce := nonce + uint64(i) log.Debugf("Sending TX to transfer ETH") to := common.HexToAddress(receiverAddr) tx := ethTransfer(ctx, client, auth, to, transferAmount, &nonce) fmt.Println("tx sent: ", tx.Hash().String()) // lastTxHash = tx.Hash() + time.Sleep(500 * time.Millisecond) } // err = operations.WaitTxToBeMined(client, lastTxHash, txTimeout) From 1f895980166bf9aefecdc7a363bdbc2a649058bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Aug 2023 09:49:22 +0200 Subject: [PATCH 09/15] build(deps): bump golang.org/x/net from 0.13.0 to 0.14.0 (#2380) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.13.0 to 0.14.0. - [Commits](https://github.com/golang/net/compare/v0.13.0...v0.14.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 6cd2cb8941..4945a51e5b 100644 --- a/go.mod +++ b/go.mod @@ -26,8 +26,8 @@ require ( github.com/umbracle/ethgo v0.1.3 github.com/urfave/cli/v2 v2.25.7 go.uber.org/zap v1.25.0 - golang.org/x/crypto v0.11.0 - golang.org/x/net v0.13.0 + golang.org/x/crypto v0.12.0 + golang.org/x/net v0.14.0 golang.org/x/sync v0.3.0 google.golang.org/grpc v1.57.0 google.golang.org/protobuf v1.31.0 @@ -127,9 +127,9 @@ require ( go.uber.org/multierr v1.10.0 // indirect golang.org/x/exp v0.0.0-20230206171751-46f607a40771 // indirect golang.org/x/mod v0.9.0 // indirect - golang.org/x/sys v0.10.0 // indirect - golang.org/x/term v0.10.0 // indirect - golang.org/x/text v0.11.0 // indirect + golang.org/x/sys v0.11.0 // indirect + golang.org/x/term v0.11.0 // indirect + golang.org/x/text v0.12.0 // indirect golang.org/x/time v0.1.0 // indirect golang.org/x/tools v0.7.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect diff --git a/go.sum b/go.sum index 0c7a7732ee..4c0ce71305 100644 --- a/go.sum +++ b/go.sum @@ -782,8 +782,8 @@ golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= -golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -878,8 +878,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY= -golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -984,8 +984,8 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -993,8 +993,8 @@ golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= -golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= +golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1007,8 +1007,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From b1e8994d04e67f41c2f49829e6ff51c84e5a5c82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20Ram=C3=ADrez?= <58293609+ToniRamirezM@users.noreply.github.com> Date: Mon, 7 Aug 2023 15:38:19 +0200 Subject: [PATCH 10/15] fix script (#2383) --- test/scripts/send_transfers/main.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/test/scripts/send_transfers/main.go b/test/scripts/send_transfers/main.go index 4a56b48c3f..136252b275 100644 --- a/test/scripts/send_transfers/main.go +++ b/test/scripts/send_transfers/main.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "math/big" - "time" "github.com/0xPolygonHermez/zkevm-node/hex" "github.com/0xPolygonHermez/zkevm-node/log" @@ -23,9 +22,8 @@ func main() { ChainID uint64 PrivateKey string }{ - // {Name: "Local L1", URL: operations.DefaultL1NetworkURL, ChainID: operations.DefaultL1ChainID, PrivateKey: operations.DefaultSequencerPrivateKey}, - // {Name: "Local L2", URL: "http://34.90.16.102:8545/", ChainID: 1234, PrivateKey: operations.DefaultSequencerPrivateKey}, - {Name: "Local L2", URL: "https://rpc.public.zkevm-test.net/", ChainID: 1442, PrivateKey: "5d120f76469fb621f9b74587096f9bb292a27ebe346a2c84071010030356c20c"}, + //{Name: "Local L1", URL: operations.DefaultL1NetworkURL, ChainID: operations.DefaultL1ChainID, PrivateKey: operations.DefaultSequencerPrivateKey}, + {Name: "Local L2", URL: operations.DefaultL2NetworkURL, ChainID: operations.DefaultL2ChainID, PrivateKey: operations.DefaultSequencerPrivateKey}, } for _, network := range networks { @@ -39,7 +37,7 @@ func main() { auth := operations.MustGetAuth(network.PrivateKey, network.ChainID) chkErr(err) - const receiverAddr = "0xdc6Bf73BC0A688bC11D5234Cb0F2719672Babd30" + const receiverAddr = "0x617b3a3528F9cDd6630fd3301B9c8911F7Bf063D" balance, err := client.BalanceAt(ctx, auth.From, nil) chkErr(err) @@ -55,14 +53,13 @@ func main() { nonce, err := client.NonceAt(ctx, auth.From, nil) chkErr(err) // var lastTxHash common.Hash - for i := 0; i < 100000; i++ { + for i := 0; i < 1000; i++ { nonce := nonce + uint64(i) log.Debugf("Sending TX to transfer ETH") to := common.HexToAddress(receiverAddr) tx := ethTransfer(ctx, client, auth, to, transferAmount, &nonce) fmt.Println("tx sent: ", tx.Hash().String()) // lastTxHash = tx.Hash() - time.Sleep(500 * time.Millisecond) } // err = operations.WaitTxToBeMined(client, lastTxHash, txTimeout) From 1ea7e692efe61f6ebffe82c706362ee3cc05792b Mon Sep 17 00:00:00 2001 From: icydark <54430021+icydark@users.noreply.github.com> Date: Tue, 8 Aug 2023 23:30:12 +0800 Subject: [PATCH 11/15] avoid double unlock in sequencer AddTxTracker (#2285) --- sequencer/worker.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sequencer/worker.go b/sequencer/worker.go index 6e96f05349..06b14234fd 100644 --- a/sequencer/worker.go +++ b/sequencer/worker.go @@ -58,7 +58,6 @@ func (w *Worker) NewTxTracker(tx types.Transaction, counters state.ZKCounters, i // AddTxTracker adds a new Tx to the Worker func (w *Worker) AddTxTracker(ctx context.Context, tx *TxTracker) (replacedTx *TxTracker, dropReason error) { w.workerMutex.Lock() - defer w.workerMutex.Unlock() addr, found := w.pool[tx.FromStr] @@ -111,6 +110,7 @@ func (w *Worker) AddTxTracker(ctx context.Context, tx *TxTracker) (replacedTx *T newReadyTx, prevReadyTx, repTx, dropReason = addr.addTx(tx) if dropReason != nil { log.Infof("AddTx tx(%s) dropped from addrQueue(%s), reason: %s", tx.HashStr, tx.FromStr, dropReason.Error()) + w.workerMutex.Unlock() return repTx, dropReason } @@ -127,7 +127,7 @@ func (w *Worker) AddTxTracker(ctx context.Context, tx *TxTracker) (replacedTx *T if repTx != nil { log.Infof("AddTx replacedTx(%s) nonce(%d) cost(%s) has been replaced", repTx.HashStr, repTx.Nonce, repTx.Cost.String()) } - + w.workerMutex.Unlock() return repTx, nil } From de58703a993800f7ea588ecee964f91f579902f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Criado-P=C3=A9rez?= Date: Tue, 8 Aug 2023 21:12:20 +0200 Subject: [PATCH 12/15] Fix typos (#2267) --- CONTRIBUTING.md | 8 ++++---- docs/modes.md | 2 +- sequencer/addrqueue.go | 2 +- sequencer/txtracker.go | 2 +- sequencer/worker_test.go | 8 ++++---- .../js/internal/tracers/4byte_tracer_legacy.js | 2 +- .../js/internal/tracers/call_tracer_legacy.js | 2 +- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 872be190d5..3393d643e4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,22 +2,22 @@ This document addresses how we should create PRs, give and receive reviews. The motivation is to have better code, reduce the time from creation to merge while sharing knowledge and insights that help everyone becoming better developers. -Note that non of this is a hard rule, but suggestions / guidelines. Although everyone is encouraged to stick to this points as much as posible. Use your common sense if some of this do not apply well on a particular PR +Note that non of this is a hard rule, but suggestions / guidelines. Although everyone is encouraged to stick to this points as much as possible. Use your common sense if some of this do not apply well on a particular PR ## How to create a good PR - Follow the template, unless for some reason it doesn't fit the content of the PR - Try hard on doing small PRs (> ~400 lines), in general is better to have 2 small PRs rather than a big one - Indicate clearly who should review it, ideally 2 team mates -- Author of the PR is responsible for merging. Never do it until you have the aproval of the specified reviewers unless you have their explicit permision +- Author of the PR is responsible for merging. Never do it until you have the approval of the specified reviewers unless you have their explicit permission - Introduce the purpose of the PR, for example: `Fixes the handle of ...` - Give brief context on why this is being done and link it to any relevant issue - Feel free to ask to specific team mates to review specific parts of the PR ## How to do a good review -- In general it's hard to set a quality treshold for changes. A good measure for when to approve is to accept changes once the overall quality of the code has been improved (compared to the code base before the PR) -- Try hard to avoid taking things personaly. For instance avoid using `I`, `you`, `I (don't) like`, ... +- In general it's hard to set a quality threshold for changes. A good measure for when to approve is to accept changes once the overall quality of the code has been improved (compared to the code base before the PR) +- Try hard to avoid taking things personally. For instance avoid using `I`, `you`, `I (don't) like`, ... - Ask, don’t tell. ("What about trying...?" rather than "Don’t do...") - Try to use positive language. You can even use emoji to clarify tone. - Be super clear on how confident you are when requesting changes. One way to do it is by starting the message like this: diff --git a/docs/modes.md b/docs/modes.md index 3d264912c6..b5a2a34b1d 100644 --- a/docs/modes.md +++ b/docs/modes.md @@ -17,7 +17,7 @@ By default the config files found in the repository will spin up the Node in JSO **This is considered to be the base, all modes require a Synchronizer Node container to be spun up* -This will syncronize with the Trusted Sequencer (run by Polygon). +This will synchronize with the Trusted Sequencer (run by Polygon). Use the default [testnet config file](https://github.com/0xPolygonHermez/zkevm-node/blob/develop/config/environments/testnet/node.config.toml), and make sure the following values are set to: diff --git a/sequencer/addrqueue.go b/sequencer/addrqueue.go index 684a060aed..73408c282c 100644 --- a/sequencer/addrqueue.go +++ b/sequencer/addrqueue.go @@ -162,7 +162,7 @@ func (a *addrQueue) updateCurrentNonceBalance(nonce *uint64, balance *big.Int) ( } } - // We check if we have a new readyTx from the notReadyTxs (at this point, to optmize the code, + // We check if we have a new readyTx from the notReadyTxs (at this point, to optimize the code, // we are not including the oldReadyTx in notReadyTxs, as it can match again if the nonce has not changed) if a.readyTx == nil { nrTx, found := a.notReadyTxs[a.currentNonce] diff --git a/sequencer/txtracker.go b/sequencer/txtracker.go index bfc67f5b7b..a0425a90e5 100644 --- a/sequencer/txtracker.go +++ b/sequencer/txtracker.go @@ -122,7 +122,7 @@ func (tx *TxTracker) calculateEfficiency(constraints batchConstraintsFloat64, we totalWeight := float64(weights.WeightArithmetics + weights.WeightBatchBytesSize + weights.WeightBinaries + weights.WeightCumulativeGasUsed + weights.WeightKeccakHashes + weights.WeightMemAligns + weights.WeightPoseidonHashes + weights.WeightPoseidonPaddings + weights.WeightSteps) - // TODO: Optmize tx.Efficiency calculation (precalculate constansts values) + // TODO: Optimize tx.Efficiency calculation (precalculate constansts values) // TODO: Evaluate avoid type conversion (performance impact?) resourceCost := (float64(tx.BatchResources.ZKCounters.CumulativeGasUsed)/constraints.maxCumulativeGasUsed)*float64(weights.WeightCumulativeGasUsed)/totalWeight + (float64(tx.BatchResources.ZKCounters.UsedArithmetics)/constraints.maxArithmetics)*float64(weights.WeightArithmetics)/totalWeight + diff --git a/sequencer/worker_test.go b/sequencer/worker_test.go index 1d254984f5..1cc736e457 100644 --- a/sequencer/worker_test.go +++ b/sequencer/worker_test.go @@ -146,7 +146,7 @@ func TestWorkerAddTx(t *testing.T) { }, }, { - name: "Readding from:0x02, tx:0x02/ef:4", from: common.Address{2}, txHash: common.Hash{2}, nonce: 1, + name: "Reading from:0x02, tx:0x02/ef:4", from: common.Address{2}, txHash: common.Hash{2}, nonce: 1, benefit: 2000, cost: new(big.Int).SetInt64(5), counters: state.ZKCounters{CumulativeGasUsed: 5, UsedKeccakHashes: 5, UsedPoseidonHashes: 5, UsedPoseidonPaddings: 5, UsedMemAligns: 5, UsedArithmetics: 5, UsedBinaries: 5, UsedSteps: 5}, usedBytes: 5, @@ -155,7 +155,7 @@ func TestWorkerAddTx(t *testing.T) { }, }, { - name: "Readding from:0x03, tx:0x03/ef:25", from: common.Address{3}, txHash: common.Hash{3}, nonce: 1, + name: "Reading from:0x03, tx:0x03/ef:25", from: common.Address{3}, txHash: common.Hash{3}, nonce: 1, benefit: 5000, cost: new(big.Int).SetInt64(5), counters: state.ZKCounters{CumulativeGasUsed: 2, UsedKeccakHashes: 2, UsedPoseidonHashes: 2, UsedPoseidonPaddings: 2, UsedMemAligns: 2, UsedArithmetics: 2, UsedBinaries: 2, UsedSteps: 2}, usedBytes: 2, @@ -167,7 +167,7 @@ func TestWorkerAddTx(t *testing.T) { processWorkerAddTxTestCases(t, worker, addTxsTC) - // Change counters fpr tx:0x03/ef:9.61 + // Change counters for tx:0x03/ef:9.61 counters := state.ZKCounters{CumulativeGasUsed: 6, UsedKeccakHashes: 6, UsedPoseidonHashes: 6, UsedPoseidonPaddings: 6, UsedMemAligns: 6, UsedArithmetics: 6, UsedBinaries: 6, UsedSteps: 6} worker.UpdateTx(common.Hash{3}, common.Address{3}, counters) @@ -257,7 +257,7 @@ func TestWorkerGetBestTx(t *testing.T) { }, }, { - name: "Readding from:0x03, tx:0x03/ef:25", from: common.Address{3}, txHash: common.Hash{3}, nonce: 1, + name: "Reading from:0x03, tx:0x03/ef:25", from: common.Address{3}, txHash: common.Hash{3}, nonce: 1, benefit: 5000, cost: new(big.Int).SetInt64(5), counters: state.ZKCounters{CumulativeGasUsed: 2, UsedKeccakHashes: 2, UsedPoseidonHashes: 2, UsedPoseidonPaddings: 2, UsedMemAligns: 2, UsedArithmetics: 2, UsedBinaries: 2, UsedSteps: 2}, usedBytes: 2, diff --git a/state/runtime/instrumentation/js/internal/tracers/4byte_tracer_legacy.js b/state/runtime/instrumentation/js/internal/tracers/4byte_tracer_legacy.js index 462b4ad4cb..e4714b8bfb 100644 --- a/state/runtime/instrumentation/js/internal/tracers/4byte_tracer_legacy.js +++ b/state/runtime/instrumentation/js/internal/tracers/4byte_tracer_legacy.js @@ -46,7 +46,7 @@ return false; }, - // store save the given indentifier and datasize. + // store save the given identifier and datasize. store: function(id, size){ var key = "" + toHex(id) + "-" + size; this.ids[key] = this.ids[key] + 1 || 1; diff --git a/state/runtime/instrumentation/js/internal/tracers/call_tracer_legacy.js b/state/runtime/instrumentation/js/internal/tracers/call_tracer_legacy.js index 3ca7377738..0545127354 100644 --- a/state/runtime/instrumentation/js/internal/tracers/call_tracer_legacy.js +++ b/state/runtime/instrumentation/js/internal/tracers/call_tracer_legacy.js @@ -220,7 +220,7 @@ return this.finalize(result); }, - // finalize recreates a call object using the final desired field oder for json + // finalize recreates a call object using the final desired field order for json // serialization. This is a nicety feature to pass meaningfully ordered results // to users who don't interpret it, just display it. finalize: function(call) { From b18316ecf2d234c1342f3224bbd1c02227ae58d6 Mon Sep 17 00:00:00 2001 From: Thiago Coimbra Lemos Date: Wed, 9 Aug 2023 09:32:06 -0300 Subject: [PATCH 13/15] fix http request instance null for websocket requests (#2385) --- jsonrpc/handler.go | 7 ++++--- jsonrpc/server.go | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/jsonrpc/handler.go b/jsonrpc/handler.go index cd78075f37..e738700e0e 100644 --- a/jsonrpc/handler.go +++ b/jsonrpc/handler.go @@ -156,15 +156,16 @@ func (h *Handler) Handle(req handleRequest) types.Response { } // HandleWs handle websocket requests -func (h *Handler) HandleWs(reqBody []byte, wsConn *websocket.Conn) ([]byte, error) { +func (h *Handler) HandleWs(reqBody []byte, wsConn *websocket.Conn, httpReq *http.Request) ([]byte, error) { var req types.Request if err := json.Unmarshal(reqBody, &req); err != nil { return types.NewResponse(req, nil, types.NewRPCError(types.InvalidRequestErrorCode, "Invalid json request")).Bytes() } handleReq := handleRequest{ - Request: req, - wsConn: wsConn, + Request: req, + wsConn: wsConn, + HttpRequest: httpReq, } return h.Handle(handleReq).Bytes() diff --git a/jsonrpc/server.go b/jsonrpc/server.go index 8d28ded549..bbfe5f0639 100644 --- a/jsonrpc/server.go +++ b/jsonrpc/server.go @@ -366,7 +366,7 @@ func (s *Server) handleWs(w http.ResponseWriter, req *http.Request) { go func() { mu.Lock() defer mu.Unlock() - resp, err := s.handler.HandleWs(message, wsConn) + resp, err := s.handler.HandleWs(message, wsConn, req) if err != nil { log.Error(fmt.Sprintf("Unable to handle WS request, %s", err.Error())) _ = wsConn.WriteMessage(msgType, []byte(fmt.Sprintf("WS Handle error: %s", err.Error()))) From a0fb26a241d47a1453648da2bd2533db0530038e Mon Sep 17 00:00:00 2001 From: Joan Esteban <129153821+joanestebanr@users.noreply.github.com> Date: Wed, 9 Aug 2023 15:00:02 +0200 Subject: [PATCH 14/15] Feature/2362 state use field batch l2data of processing context v2 (#2390) * close #2390: removed field encodedTxs from func ProcessAndStoreClosedBatch --- state/batch.go | 16 ++++++++++---- synchronizer/interfaces.go | 2 +- synchronizer/mock_state.go | 35 +++++++++++++++---------------- synchronizer/synchronizer.go | 8 ++++--- synchronizer/synchronizer_test.go | 3 ++- 5 files changed, 37 insertions(+), 27 deletions(-) diff --git a/state/batch.go b/state/batch.go index ee457bcef6..d9f6efa0b9 100644 --- a/state/batch.go +++ b/state/batch.go @@ -410,10 +410,16 @@ func (s *State) CloseBatch(ctx context.Context, receipt ProcessingReceipt, dbTx // ProcessAndStoreClosedBatch is used by the Synchronizer to add a closed batch into the data base. Values returned are the new stateRoot, // the flushID (incremental value returned by executor), // the ProverID (executor running ID) the result of closing the batch. -func (s *State) ProcessAndStoreClosedBatch(ctx context.Context, processingCtx ProcessingContext, encodedTxs []byte, dbTx pgx.Tx, caller metrics.CallerLabel) (common.Hash, uint64, string, error) { +func (s *State) ProcessAndStoreClosedBatch(ctx context.Context, processingCtx ProcessingContext, dbTx pgx.Tx, caller metrics.CallerLabel) (common.Hash, uint64, string, error) { + BatchL2Data := processingCtx.BatchL2Data + if BatchL2Data == nil { + log.Warnf("Batch %v: ProcessAndStoreClosedBatch: processingCtx.BatchL2Data is nil, assuming is empty", processingCtx.BatchNumber) + var BatchL2DataEmpty []byte + BatchL2Data = &BatchL2DataEmpty + } // Decode transactions forkID := s.GetForkIDByBatchNumber(processingCtx.BatchNumber) - decodedTransactions, _, _, err := DecodeTxs(encodedTxs, forkID) + decodedTransactions, _, _, err := DecodeTxs(*BatchL2Data, forkID) if err != nil && !errors.Is(err, ErrInvalidData) { log.Debugf("error decoding transactions: %v", err) return common.Hash{}, noFlushID, noProverID, err @@ -423,10 +429,12 @@ func (s *State) ProcessAndStoreClosedBatch(ctx context.Context, processingCtx Pr if dbTx == nil { return common.Hash{}, noFlushID, noProverID, ErrDBTxNil } + // Avoid writing twice to the DB the BatchL2Data that is going to be written also in the call closeBatch + processingCtx.BatchL2Data = nil if err := s.OpenBatch(ctx, processingCtx, dbTx); err != nil { return common.Hash{}, noFlushID, noProverID, err } - processed, err := s.processBatch(ctx, processingCtx.BatchNumber, encodedTxs, caller, dbTx) + processed, err := s.processBatch(ctx, processingCtx.BatchNumber, *BatchL2Data, caller, dbTx) if err != nil { return common.Hash{}, noFlushID, noProverID, err } @@ -476,7 +484,7 @@ func (s *State) ProcessAndStoreClosedBatch(ctx context.Context, processingCtx Pr StateRoot: processedBatch.NewStateRoot, LocalExitRoot: processedBatch.NewLocalExitRoot, AccInputHash: processedBatch.NewAccInputHash, - BatchL2Data: encodedTxs, + BatchL2Data: *BatchL2Data, }, dbTx) } diff --git a/synchronizer/interfaces.go b/synchronizer/interfaces.go index e40c11193e..f3488b54eb 100644 --- a/synchronizer/interfaces.go +++ b/synchronizer/interfaces.go @@ -40,7 +40,7 @@ type stateInterface interface { AddVirtualBatch(ctx context.Context, virtualBatch *state.VirtualBatch, dbTx pgx.Tx) error GetNextForcedBatches(ctx context.Context, nextForcedBatches int, dbTx pgx.Tx) ([]state.ForcedBatch, error) AddVerifiedBatch(ctx context.Context, verifiedBatch *state.VerifiedBatch, dbTx pgx.Tx) error - ProcessAndStoreClosedBatch(ctx context.Context, processingCtx state.ProcessingContext, encodedTxs []byte, dbTx pgx.Tx, caller metrics.CallerLabel) (common.Hash, uint64, string, error) + ProcessAndStoreClosedBatch(ctx context.Context, processingCtx state.ProcessingContext, dbTx pgx.Tx, caller metrics.CallerLabel) (common.Hash, uint64, string, error) SetGenesis(ctx context.Context, block state.Block, genesis state.Genesis, dbTx pgx.Tx) ([]byte, error) OpenBatch(ctx context.Context, processingContext state.ProcessingContext, dbTx pgx.Tx) error CloseBatch(ctx context.Context, receipt state.ProcessingReceipt, dbTx pgx.Tx) error diff --git a/synchronizer/mock_state.go b/synchronizer/mock_state.go index 052f4f848e..fe88b02906 100644 --- a/synchronizer/mock_state.go +++ b/synchronizer/mock_state.go @@ -516,39 +516,39 @@ func (_m *stateMock) OpenBatch(ctx context.Context, processingContext state.Proc return r0 } -// ProcessAndStoreClosedBatch provides a mock function with given fields: ctx, processingCtx, encodedTxs, dbTx, caller -func (_m *stateMock) ProcessAndStoreClosedBatch(ctx context.Context, processingCtx state.ProcessingContext, encodedTxs []byte, dbTx pgx.Tx, caller metrics.CallerLabel) (common.Hash, uint64, string, error) { - ret := _m.Called(ctx, processingCtx, encodedTxs, dbTx, caller) +// ProcessAndStoreClosedBatch provides a mock function with given fields: ctx, processingCtx, dbTx, caller +func (_m *stateMock) ProcessAndStoreClosedBatch(ctx context.Context, processingCtx state.ProcessingContext, dbTx pgx.Tx, caller metrics.CallerLabel) (common.Hash, uint64, string, error) { + ret := _m.Called(ctx, processingCtx, dbTx, caller) var r0 common.Hash var r1 uint64 var r2 string var r3 error - if rf, ok := ret.Get(0).(func(context.Context, state.ProcessingContext, []byte, pgx.Tx, metrics.CallerLabel) (common.Hash, uint64, string, error)); ok { - return rf(ctx, processingCtx, encodedTxs, dbTx, caller) + if rf, ok := ret.Get(0).(func(context.Context, state.ProcessingContext, pgx.Tx, metrics.CallerLabel) (common.Hash, uint64, string, error)); ok { + return rf(ctx, processingCtx, dbTx, caller) } - if rf, ok := ret.Get(0).(func(context.Context, state.ProcessingContext, []byte, pgx.Tx, metrics.CallerLabel) common.Hash); ok { - r0 = rf(ctx, processingCtx, encodedTxs, dbTx, caller) + if rf, ok := ret.Get(0).(func(context.Context, state.ProcessingContext, pgx.Tx, metrics.CallerLabel) common.Hash); ok { + r0 = rf(ctx, processingCtx, dbTx, caller) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(common.Hash) } } - if rf, ok := ret.Get(1).(func(context.Context, state.ProcessingContext, []byte, pgx.Tx, metrics.CallerLabel) uint64); ok { - r1 = rf(ctx, processingCtx, encodedTxs, dbTx, caller) + if rf, ok := ret.Get(1).(func(context.Context, state.ProcessingContext, pgx.Tx, metrics.CallerLabel) uint64); ok { + r1 = rf(ctx, processingCtx, dbTx, caller) } else { r1 = ret.Get(1).(uint64) } - if rf, ok := ret.Get(2).(func(context.Context, state.ProcessingContext, []byte, pgx.Tx, metrics.CallerLabel) string); ok { - r2 = rf(ctx, processingCtx, encodedTxs, dbTx, caller) + if rf, ok := ret.Get(2).(func(context.Context, state.ProcessingContext, pgx.Tx, metrics.CallerLabel) string); ok { + r2 = rf(ctx, processingCtx, dbTx, caller) } else { r2 = ret.Get(2).(string) } - if rf, ok := ret.Get(3).(func(context.Context, state.ProcessingContext, []byte, pgx.Tx, metrics.CallerLabel) error); ok { - r3 = rf(ctx, processingCtx, encodedTxs, dbTx, caller) + if rf, ok := ret.Get(3).(func(context.Context, state.ProcessingContext, pgx.Tx, metrics.CallerLabel) error); ok { + r3 = rf(ctx, processingCtx, dbTx, caller) } else { r3 = ret.Error(3) } @@ -711,13 +711,12 @@ func (_m *stateMock) UpdateForkIDIntervals(intervals []state.ForkIDInterval) { _m.Called(intervals) } -type mockConstructorTestingTnewStateMock interface { +// newStateMock creates a new instance of stateMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func newStateMock(t interface { mock.TestingT Cleanup(func()) -} - -// newStateMock creates a new instance of stateMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func newStateMock(t mockConstructorTestingTnewStateMock) *stateMock { +}) *stateMock { mock := &stateMock{} mock.Mock.Test(t) diff --git a/synchronizer/synchronizer.go b/synchronizer/synchronizer.go index cc5f5aa5eb..1e86be825a 100644 --- a/synchronizer/synchronizer.go +++ b/synchronizer/synchronizer.go @@ -843,6 +843,7 @@ func (s *ClientSynchronizer) processSequenceBatches(sequencedBatches []etherman. Timestamp: batch.Timestamp, GlobalExitRoot: batch.GlobalExitRoot, ForcedBatchNum: batch.ForcedBatchNum, + BatchL2Data: &batch.BatchL2Data, } var newRoot common.Hash @@ -853,7 +854,7 @@ func (s *ClientSynchronizer) processSequenceBatches(sequencedBatches []etherman. if errors.Is(err, state.ErrNotFound) || errors.Is(err, state.ErrStateNotSynchronized) { log.Debugf("BatchNumber: %d, not found in trusted state. Storing it...", batch.BatchNumber) // If it is not found, store batch - newStateRoot, flushID, proverID, err := s.state.ProcessAndStoreClosedBatch(s.ctx, processCtx, batch.BatchL2Data, dbTx, stateMetrics.SynchronizerCallerLabel) + newStateRoot, flushID, proverID, err := s.state.ProcessAndStoreClosedBatch(s.ctx, processCtx, dbTx, stateMetrics.SynchronizerCallerLabel) if err != nil { log.Errorf("error storing trustedBatch. BatchNumber: %d, BlockNumber: %d, error: %v", batch.BatchNumber, blockNumber, err) rollbackErr := dbTx.Rollback(s.ctx) @@ -935,7 +936,7 @@ func (s *ClientSynchronizer) processSequenceBatches(sequencedBatches []etherman. log.Errorf("error resetting trusted state. BatchNumber: %d, BlockNumber: %d, error: %v", batch.BatchNumber, blockNumber, err) return err } - _, flushID, proverID, err := s.state.ProcessAndStoreClosedBatch(s.ctx, processCtx, batch.BatchL2Data, dbTx, stateMetrics.SynchronizerCallerLabel) + _, flushID, proverID, err := s.state.ProcessAndStoreClosedBatch(s.ctx, processCtx, dbTx, stateMetrics.SynchronizerCallerLabel) if err != nil { log.Errorf("error storing trustedBatch. BatchNumber: %d, BlockNumber: %d, error: %v", batch.BatchNumber, blockNumber, err) rollbackErr := dbTx.Rollback(s.ctx) @@ -1058,9 +1059,10 @@ func (s *ClientSynchronizer) processSequenceForceBatch(sequenceForceBatch []ethe Timestamp: block.ReceivedAt, Coinbase: fbatch.Coinbase, ForcedBatchNum: &forcedBatches[i].ForcedBatchNumber, + BatchL2Data: &forcedBatches[i].RawTxsData, } // Process batch - _, flushID, proverID, err := s.state.ProcessAndStoreClosedBatch(s.ctx, batch, forcedBatches[i].RawTxsData, dbTx, stateMetrics.SynchronizerCallerLabel) + _, flushID, proverID, err := s.state.ProcessAndStoreClosedBatch(s.ctx, batch, dbTx, stateMetrics.SynchronizerCallerLabel) if err != nil { log.Errorf("error processing batch in processSequenceForceBatch. BatchNumber: %d, BlockNumber: %d, error: %v", batch.BatchNumber, block.BlockNumber, err) rollbackErr := dbTx.Rollback(s.ctx) diff --git a/synchronizer/synchronizer_test.go b/synchronizer/synchronizer_test.go index 08fc343f08..ca74feec15 100644 --- a/synchronizer/synchronizer_test.go +++ b/synchronizer/synchronizer_test.go @@ -509,10 +509,11 @@ func TestSequenceForcedBatch(t *testing.T) { Timestamp: ethBlock.ReceivedAt, GlobalExitRoot: sequencedForceBatch.GlobalExitRoot, ForcedBatchNum: &f, + BatchL2Data: &sequencedForceBatch.Transactions, } m.State. - On("ProcessAndStoreClosedBatch", ctx, processingContext, sequencedForceBatch.Transactions, m.DbTx, metrics.SynchronizerCallerLabel). + On("ProcessAndStoreClosedBatch", ctx, processingContext, m.DbTx, metrics.SynchronizerCallerLabel). Return(common.Hash{}, uint64(1), cProverIDExecution, nil). Once() From d045bd5e7a1444e58f1b0d14c9a116377061a879 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Aug 2023 09:55:41 +0200 Subject: [PATCH 15/15] build(deps): bump github.com/ethereum/go-ethereum from 1.12.0 to 1.12.1 (#2402) Bumps [github.com/ethereum/go-ethereum](https://github.com/ethereum/go-ethereum) from 1.12.0 to 1.12.1. - [Release notes](https://github.com/ethereum/go-ethereum/releases) - [Commits](https://github.com/ethereum/go-ethereum/compare/v1.12.0...v1.12.1) --- updated-dependencies: - dependency-name: github.com/ethereum/go-ethereum dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 21 +++++++++++++++------ go.sum | 51 +++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 54 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index 4945a51e5b..4c3fbfa87e 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,8 @@ go 1.19 require ( github.com/didip/tollbooth/v6 v6.1.2 - github.com/dop251/goja v0.0.0-20230122112309-96b1610dd4f7 - github.com/ethereum/go-ethereum v1.12.0 + github.com/dop251/goja v0.0.0-20230605162241-28ee0ee714f3 + github.com/ethereum/go-ethereum v1.12.1 github.com/go-git/go-billy/v5 v5.4.1 github.com/go-git/go-git/v5 v5.8.1 github.com/gobuffalo/packr/v2 v2.8.3 @@ -44,6 +44,7 @@ require ( github.com/VictoriaMetrics/fastcache v1.6.0 // indirect github.com/acomagu/bufpipe v1.0.4 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/bits-and-blooms/bitset v1.7.0 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cloudflare/circl v1.3.3 // indirect @@ -51,12 +52,16 @@ require ( github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 // indirect github.com/cockroachdb/redact v1.1.3 // indirect + github.com/consensys/bavard v0.1.13 // indirect + github.com/consensys/gnark-crypto v0.10.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/crate-crypto/go-kzg-4844 v0.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/deckarep/golang-set/v2 v2.1.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect github.com/dlclark/regexp2 v1.7.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect + github.com/ethereum/c-kzg-4844 v0.3.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff // indirect github.com/getsentry/sentry-go v0.18.0 // indirect @@ -74,6 +79,7 @@ require ( github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect github.com/google/gofuzz v1.2.0 // indirect + github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/huin/goupnp v1.0.3 // indirect @@ -99,6 +105,7 @@ require ( github.com/markbates/safe v1.0.1 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect @@ -117,6 +124,7 @@ require ( github.com/status-im/keycard-go v0.2.0 // indirect github.com/stretchr/objx v0.5.0 // indirect github.com/subosito/gotenv v1.4.2 // indirect + github.com/supranational/blst v0.3.11-0.20230406105308-e9dfc5ee724b // indirect github.com/tklauser/go-sysconf v0.3.10 // indirect github.com/tklauser/numcpus v0.4.0 // indirect github.com/tyler-smith/go-bip39 v1.1.0 // indirect @@ -125,17 +133,18 @@ require ( github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect go.uber.org/multierr v1.10.0 // indirect - golang.org/x/exp v0.0.0-20230206171751-46f607a40771 // indirect - golang.org/x/mod v0.9.0 // indirect + golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect + golang.org/x/mod v0.10.0 // indirect golang.org/x/sys v0.11.0 // indirect golang.org/x/term v0.11.0 // indirect golang.org/x/text v0.12.0 // indirect - golang.org/x/time v0.1.0 // indirect - golang.org/x/tools v0.7.0 // indirect + golang.org/x/time v0.3.0 // indirect + golang.org/x/tools v0.9.1 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/warnings.v0 v0.1.2 // indirect + rsc.io/tmplfunc v0.0.3 // indirect ) require ( diff --git a/go.sum b/go.sum index 4c0ce71305..12a6a1d7e7 100644 --- a/go.sum +++ b/go.sum @@ -81,6 +81,8 @@ github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bits-and-blooms/bitset v1.7.0 h1:YjAGVd3XmtK9ktAbX8Zg2g2PwLIMjGREZJHlV4j7NEo= +github.com/bits-and-blooms/bitset v1.7.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/btcsuite/btcd v0.22.1 h1:CnwP9LM/M9xuRrGSCGeMVs9iv09uMqwsVX7EeIpgV2c= github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= @@ -94,8 +96,11 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= @@ -116,6 +121,10 @@ github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811/go.mod h1:Nb5lg github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= +github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= +github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= +github.com/consensys/gnark-crypto v0.10.0 h1:zRh22SR7o4K35SoNqouS9J/TKHTyU2QWaj5ldehyXtA= +github.com/consensys/gnark-crypto v0.10.0/go.mod h1:Iq/P3HHl0ElSjsg2E1gsMwhAyxnxoKK5nVyZKd+/KhU= github.com/containerd/continuity v0.0.0-20191214063359-1097c8bae83b h1:pik3LX++5O3UiNWv45wfP/WT81l7ukBJzd3uUiifbSU= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= @@ -128,6 +137,8 @@ github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwc github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/crate-crypto/go-kzg-4844 v0.3.0 h1:UBlWE0CgyFqqzTI+IFyCzA7A3Zw4iip6uzRv5NIXG0A= +github.com/crate-crypto/go-kzg-4844 v0.3.0/go.mod h1:SBP7ikXEgDnUPONgm33HtuDZEDtWa3L4QtN1ocJSEQ4= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -148,8 +159,8 @@ github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnm github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= -github.com/dop251/goja v0.0.0-20230122112309-96b1610dd4f7 h1:kgvzE5wLsLa7XKfV85VZl40QXaMCaeFtHpPwJ8fhotY= -github.com/dop251/goja v0.0.0-20230122112309-96b1610dd4f7/go.mod h1:yRkwfj0CBpOGre+TwBsqPV0IH0Pk73e4PXJOeNDboGs= +github.com/dop251/goja v0.0.0-20230605162241-28ee0ee714f3 h1:+3HCtB74++ClLy8GgjUQYeC8R4ILzVcIe8+5edAJJnE= +github.com/dop251/goja v0.0.0-20230605162241-28ee0ee714f3/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4= github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= @@ -165,8 +176,10 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= -github.com/ethereum/go-ethereum v1.12.0 h1:bdnhLPtqETd4m3mS8BGMNvBTf36bO5bx/hxE2zljOa0= -github.com/ethereum/go-ethereum v1.12.0/go.mod h1:/oo2X/dZLJjf2mJ6YT9wcWxa4nNJDBKDBU6sFIpx1Gs= +github.com/ethereum/c-kzg-4844 v0.3.0 h1:3Y3hD6l5i0dEYsBL50C+Om644kve3pNqoAcvE26o9zI= +github.com/ethereum/c-kzg-4844 v0.3.0/go.mod h1:WI2Nd82DMZAAZI1wV2neKGost9EKjvbpQR9OqE5Qqa8= +github.com/ethereum/go-ethereum v1.12.1 h1:1kXDPxhLfyySuQYIfRxVBGYuaHdxNNxevA73vjIwsgk= +github.com/ethereum/go-ethereum v1.12.1/go.mod h1:zKetLweqBR8ZS+1O9iJWI8DvmmD2NzD19apjEWDCsnw= github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= @@ -315,7 +328,10 @@ github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20230207041349-798e818bf904 h1:4/hN5RUoecvl+RmJRE2YxKWtnnQls6rQjjW5oV7qg2U= +github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -355,6 +371,7 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hermeznetwork/tracerr v0.3.2 h1:QB3TlQxO/4XHyixsg+nRZPuoel/FFQlQ7oAoHDD5l1c= github.com/hermeznetwork/tracerr v0.3.2/go.mod h1:nsWC1+tc4qUEbUGRv4DcPJJTjLsedlPajlFmpJoohK4= +github.com/holiman/billy v0.0.0-20230718173358-1c7e68d277a7 h1:3JQNjnMRil1yD0IfZKHF9GxxWKDJGj8I0IqOUol//sw= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/uint256 v1.2.3 h1:K8UWO1HUJpRMXBxbmaY1Y8IAMZC/RsKB+ArEnnK4l5o= @@ -368,6 +385,7 @@ github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 h1:i462o439Z github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/iden3/go-iden3-crypto v0.0.15 h1:4MJYlrot1l31Fzlo2sF56u7EVFeHHJkxGXXZCtESgK4= github.com/iden3/go-iden3-crypto v0.0.15/go.mod h1:dLpM4vEPJ3nDHzhWFXDjzkn1qHoBeOT/3UEhXsEsP3E= github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= @@ -531,6 +549,9 @@ github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= +github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= +github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= +github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= @@ -679,6 +700,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/supranational/blst v0.3.11-0.20230406105308-e9dfc5ee724b h1:u49mjRnygnB34h8OKbnNJFVUtWSKIKb1KukdV8bILUM= +github.com/supranational/blst v0.3.11-0.20230406105308-e9dfc5ee724b/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a h1:1ur3QoCqvE5fl+nylMaIr9PVV1w343YRDtsy+Rwu7XI= github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw= @@ -794,8 +817,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230206171751-46f607a40771 h1:xP7rWLUr1e1n2xkK5YB4LI0hPEy3LJC6Wk+D4pGlOJg= -golang.org/x/exp v0.0.0-20230206171751-46f607a40771/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU= +golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -823,8 +846,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= -golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -975,6 +998,7 @@ golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1004,6 +1028,7 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= @@ -1014,8 +1039,8 @@ golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA= -golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1080,8 +1105,8 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= +golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1247,3 +1272,5 @@ mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= +rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA=