diff --git a/Makefile b/Makefile index 84647c96d..cc4200281 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ CONFIG_FILE?=config-files/config.yaml export OPERATOR_ADDRESS ?= $(shell yq -r '.operator.address' $(CONFIG_FILE)) AGG_CONFIG_FILE?=config-files/config-aggregator.yaml -OPERATOR_VERSION=v0.12.2 +OPERATOR_VERSION=v0.13.0 ifeq ($(OS),Linux) BUILD_ALL_FFI = $(MAKE) build_all_ffi_linux @@ -557,7 +557,13 @@ generate_groth16_ineq_proof: ## Run the gnark_plonk_bn254_script @go run scripts/test_files/gnark_groth16_bn254_infinite_script/cmd/main.go 1 __METRICS__: -# Prometheus and graphana +# Prometheus and Grafana +metrics_remove_containers: + @docker stop prometheus grafana + @docker rm prometheus grafana +metrics_clean_db: metrics_remove_containers + @docker volume rm aligned_layer_grafana_data aligned_layer_prometheus_data + run_metrics: ## Run metrics using metrics-docker-compose.yaml @echo "Running metrics..." @docker compose -f metrics-docker-compose.yaml up @@ -604,6 +610,16 @@ upgrade_add_aggregator: ## Add Aggregator to Aligned Contracts @echo "Adding Aggregator to Aligned Contracts..." @. contracts/scripts/.env && . contracts/scripts/upgrade_add_aggregator_to_service_manager.sh +set_aggregator_address: + @echo "Setting Aggregator Address in Aligned Service Manager Contract on $(NETWORK) network..." + @echo "Aggregator address: $(AGGREGATOR_ADDRESS)" + @. contracts/scripts/.env.$(NETWORK) && . contracts/scripts/set_aggregator_address.sh $(AGGREGATOR_ADDRESS) + +set_aggregator_address_devnet: + @echo "Setting Aggregator Address in Aligned Service Manager Contract..." + @echo "Aggregator address: $(AGGREGATOR_ADDRESS)" + RPC_URL="http://localhost:8545" PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" OUTPUT_PATH=./script/output/devnet/alignedlayer_deployment_output.json ./contracts/scripts/set_aggregator_address.sh $(AGGREGATOR_ADDRESS) + upgrade_initialize_disabled_verifiers: @echo "Adding disabled verifiers to Aligned Service Manager..." @. contracts/scripts/.env && . contracts/scripts/upgrade_disabled_verifiers_in_service_manager.sh @@ -906,7 +922,7 @@ docker_down: @echo "Everything down" docker ps -DOCKER_BURST_SIZE=2 +DOCKER_BURST_SIZE=1 DOCKER_PROOFS_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 docker_batcher_send_sp1_burst: @@ -918,7 +934,8 @@ docker_batcher_send_sp1_burst: --vm_program ./scripts/test_files/sp1/sp1_fibonacci.elf \ --repetitions $(DOCKER_BURST_SIZE) \ --proof_generator_addr $(PROOF_GENERATOR_ADDRESS) \ - --rpc_url $(DOCKER_RPC_URL) + --rpc_url $(DOCKER_RPC_URL) \ + --max_fee 0.1ether docker_batcher_send_risc0_burst: @echo "Sending Risc0 fibonacci task to Batcher..." @@ -930,7 +947,8 @@ docker_batcher_send_risc0_burst: --public_input ./scripts/test_files/risc_zero/fibonacci_proof_generator/risc_zero_fibonacci.pub \ --repetitions $(DOCKER_BURST_SIZE) \ --proof_generator_addr $(PROOF_GENERATOR_ADDRESS) \ - --rpc_url $(DOCKER_RPC_URL) + --rpc_url $(DOCKER_RPC_URL) \ + --max_fee 0.1ether docker_batcher_send_plonk_bn254_burst: @echo "Sending Groth16Bn254 1!=0 task to Batcher..." @@ -942,7 +960,8 @@ docker_batcher_send_plonk_bn254_burst: --vk ./scripts/test_files/gnark_plonk_bn254_script/plonk.vk \ --proof_generator_addr $(PROOF_GENERATOR_ADDRESS) \ --rpc_url $(DOCKER_RPC_URL) \ - --repetitions $(DOCKER_BURST_SIZE) + --repetitions $(DOCKER_BURST_SIZE) \ + --max_fee 0.1ether docker_batcher_send_plonk_bls12_381_burst: @echo "Sending Groth16 BLS12-381 1!=0 task to Batcher..." @@ -954,19 +973,21 @@ docker_batcher_send_plonk_bls12_381_burst: --vk ./scripts/test_files/gnark_plonk_bls12_381_script/plonk.vk \ --proof_generator_addr $(PROOF_GENERATOR_ADDRESS) \ --repetitions $(DOCKER_BURST_SIZE) \ - --rpc_url $(DOCKER_RPC_URL) + --rpc_url $(DOCKER_RPC_URL) \ + --max_fee 0.1ether docker_batcher_send_groth16_burst: @echo "Sending Groth16 BLS12-381 1!=0 task to Batcher..." docker exec $(shell docker ps | grep batcher | awk '{print $$1}') aligned submit \ - --private_key $(DOCKER_PROOFS_PRIVATE_KEY) \ - --proving_system Groth16Bn254 \ - --proof ./scripts/test_files/gnark_groth16_bn254_script/groth16.proof \ - --public_input ./scripts/test_files/gnark_groth16_bn254_script/plonk_pub_input.pub \ - --vk ./scripts/test_files/gnark_groth16_bn254_script/groth16.vk \ - --proof_generator_addr $(PROOF_GENERATOR_ADDRESS) \ - --repetitions $(DOCKER_BURST_SIZE) \ - --rpc_url $(DOCKER_RPC_URL) + --private_key $(DOCKER_PROOFS_PRIVATE_KEY) \ + --proving_system Groth16Bn254 \ + --proof ./scripts/test_files/gnark_groth16_bn254_script/groth16.proof \ + --public_input ./scripts/test_files/gnark_groth16_bn254_script/plonk_pub_input.pub \ + --vk ./scripts/test_files/gnark_groth16_bn254_script/groth16.vk \ + --proof_generator_addr $(PROOF_GENERATOR_ADDRESS) \ + --repetitions $(DOCKER_BURST_SIZE) \ + --rpc_url $(DOCKER_RPC_URL) \ + --max_fee 0.1ether # Update target as new proofs are supported. docker_batcher_send_all_proofs_burst: @@ -993,6 +1014,7 @@ docker_batcher_send_infinite_groth16: --public_input scripts/test_files/gnark_groth16_bn254_infinite_script/infinite_proofs/ineq_$${counter}_groth16.pub \ --vk scripts/test_files/gnark_groth16_bn254_infinite_script/infinite_proofs/ineq_$${counter}_groth16.vk \ --proof_generator_addr $(PROOF_GENERATOR_ADDRESS); \ + --max_fee 0.1ether sleep $${timer}; \ counter=$$((counter + 1)); \ done \ @@ -1010,7 +1032,7 @@ docker_verify_proofs_onchain: done \ ' -DOCKER_PROOFS_WAIT_TIME=30 +DOCKER_PROOFS_WAIT_TIME=60 docker_verify_proof_submission_success: @echo "Verifying proofs were successfully submitted..." @@ -1032,7 +1054,7 @@ docker_verify_proof_submission_success: fi; \ echo "---------------------------------------------------------------------------------------------------"; \ done; \ - if [ $$(ls -1 ./aligned_verification_data/*.cbor | wc -l) -ne 10 ]; then \ + if [ $$(ls -1 ./aligned_verification_data/*.cbor | wc -l) -ne 5 ]; then \ echo "ERROR: Some proofs were verified successfully, but some proofs are missing in the aligned_verification_data/ directory"; \ exit 1; \ fi; \ diff --git a/aggregator/pkg/aggregator.go b/aggregator/pkg/aggregator.go index 467b3b52b..00c7efd9c 100644 --- a/aggregator/pkg/aggregator.go +++ b/aggregator/pkg/aggregator.go @@ -67,6 +67,9 @@ type Aggregator struct { // Stores the TaskResponse for each batch by batchIdentifierHash batchDataByIdentifierHash map[[32]byte]BatchData + // Stores the start time for each batch of the aggregator by task index + batchStartTimeByIdx map[uint32]time.Time + // This task index is to communicate with the local BLS // Service. // Note: In case of a reboot it can start from 0 again @@ -78,6 +81,7 @@ type Aggregator struct { // - batchCreatedBlockByIdx // - batchDataByIdentifierHash // - nextBatchIndex + // - batchStartTimeByIdx taskMutex *sync.Mutex // Mutex to protect ethereum wallet @@ -124,6 +128,7 @@ func NewAggregator(aggregatorConfig config.AggregatorConfig) (*Aggregator, error batchesIdxByIdentifierHash := make(map[[32]byte]uint32) batchDataByIdentifierHash := make(map[[32]byte]BatchData) batchCreatedBlockByIdx := make(map[uint32]uint64) + batchStartTimeByIdx := make(map[uint32]time.Time) chainioConfig := sdkclients.BuildAllConfig{ EthHttpUrl: aggregatorConfig.BaseConfig.EthRpcUrl, @@ -172,6 +177,7 @@ func NewAggregator(aggregatorConfig config.AggregatorConfig) (*Aggregator, error batchesIdxByIdentifierHash: batchesIdxByIdentifierHash, batchDataByIdentifierHash: batchDataByIdentifierHash, batchCreatedBlockByIdx: batchCreatedBlockByIdx, + batchStartTimeByIdx: batchStartTimeByIdx, nextBatchIndex: nextBatchIndex, taskMutex: &sync.Mutex{}, walletMutex: &sync.Mutex{}, @@ -233,6 +239,7 @@ func (agg *Aggregator) handleBlsAggServiceResponse(blsAggServiceResp blsagg.BlsA batchIdentifierHash := agg.batchesIdentifierHashByIdx[blsAggServiceResp.TaskIndex] batchData := agg.batchDataByIdentifierHash[batchIdentifierHash] taskCreatedBlock := agg.batchCreatedBlockByIdx[blsAggServiceResp.TaskIndex] + taskCreatedAt := agg.batchStartTimeByIdx[blsAggServiceResp.TaskIndex] agg.taskMutex.Unlock() agg.AggregatorConfig.BaseConfig.Logger.Info("- Unlocked Resources: Fetching task data") @@ -266,6 +273,9 @@ func (agg *Aggregator) handleBlsAggServiceResponse(blsAggServiceResp blsagg.BlsA agg.telemetry.LogQuorumReached(batchData.BatchMerkleRoot) + // Only observe quorum reached if successful + agg.metrics.ObserveTaskQuorumReached(time.Since(taskCreatedAt)) + agg.logger.Info("Threshold reached", "taskIndex", blsAggServiceResp.TaskIndex, "batchIdentifierHash", "0x"+hex.EncodeToString(batchIdentifierHash[:])) @@ -285,10 +295,12 @@ func (agg *Aggregator) handleBlsAggServiceResponse(blsAggServiceResp blsagg.BlsA if err == nil { // In some cases, we may fail to retrieve the receipt for the transaction. txHash := "Unknown" + effectiveGasPrice := "Unknown" if receipt != nil { txHash = receipt.TxHash.String() + effectiveGasPrice = receipt.EffectiveGasPrice.String() } - agg.telemetry.TaskSentToEthereum(batchData.BatchMerkleRoot, txHash) + agg.telemetry.TaskSentToEthereum(batchData.BatchMerkleRoot, txHash, effectiveGasPrice) agg.logger.Info("Aggregator successfully responded to task", "taskIndex", blsAggServiceResp.TaskIndex, "batchIdentifierHash", "0x"+hex.EncodeToString(batchIdentifierHash[:])) @@ -316,10 +328,11 @@ func (agg *Aggregator) sendAggregatedResponse(batchIdentifierHash [32]byte, batc "batchIdentifierHash", hex.EncodeToString(batchIdentifierHash[:])) // This function is a callback that is called when the gas price is bumped on the avsWriter.SendAggregatedResponse - onGasPriceBumped := func(bumpedGasPrice *big.Int) { - agg.metrics.IncBumpedGasPriceForAggregatedResponse() - agg.telemetry.BumpedTaskGasPrice(batchMerkleRoot, bumpedGasPrice.String()) + onSetGasPrice := func(gasPrice *big.Int) { + agg.telemetry.TaskSetGasPrice(batchMerkleRoot, gasPrice.String()) } + + startTime := time.Now() receipt, err := agg.avsWriter.SendAggregatedResponse( batchIdentifierHash, batchMerkleRoot, @@ -329,15 +342,18 @@ func (agg *Aggregator) sendAggregatedResponse(batchIdentifierHash [32]byte, batc agg.AggregatorConfig.Aggregator.GasBumpIncrementalPercentage, agg.AggregatorConfig.Aggregator.GasBumpPercentageLimit, agg.AggregatorConfig.Aggregator.TimeToWaitBeforeBump, - onGasPriceBumped, + agg.metrics, + onSetGasPrice, ) if err != nil { agg.walletMutex.Unlock() agg.logger.Infof("- Unlocked Wallet Resources: Error sending aggregated response for batch %s. Error: %s", hex.EncodeToString(batchIdentifierHash[:]), err) - agg.telemetry.LogTaskError(batchMerkleRoot, err) return nil, err } + // We only send the latency metric if the response is successul + agg.metrics.ObserveLatencyForRespondToTask(time.Since(startTime)) + agg.walletMutex.Unlock() agg.logger.Infof("- Unlocked Wallet Resources: Sending aggregated response for batch %s", hex.EncodeToString(batchIdentifierHash[:])) @@ -383,6 +399,7 @@ func (agg *Aggregator) AddNewTask(batchMerkleRoot [32]byte, senderAddress [20]by BatchMerkleRoot: batchMerkleRoot, SenderAddress: senderAddress, } + agg.batchStartTimeByIdx[batchIndex] = time.Now() agg.logger.Info( "Task Info added in aggregator:", "Task", batchIndex, @@ -447,6 +464,7 @@ func (agg *Aggregator) ClearTasksFromMaps() { delete(agg.batchCreatedBlockByIdx, i) delete(agg.batchesIdentifierHashByIdx, i) delete(agg.batchDataByIdentifierHash, batchIdentifierHash) + delete(agg.batchStartTimeByIdx, i) } else { agg.logger.Warn("Task not found in maps", "taskIndex", i) } diff --git a/aggregator/pkg/telemetry.go b/aggregator/pkg/telemetry.go index 94737d2ba..01dd132a3 100644 --- a/aggregator/pkg/telemetry.go +++ b/aggregator/pkg/telemetry.go @@ -30,14 +30,15 @@ type TaskErrorMessage struct { TaskError string `json:"error"` } -type TaskGasPriceBumpMessage struct { - MerkleRoot string `json:"merkle_root"` - BumpedGasPrice string `json:"bumped_gas_price"` +type TaskSetGasPriceMessage struct { + MerkleRoot string `json:"merkle_root"` + GasPrice string `json:"gas_price"` } type TaskSentToEthereumMessage struct { - MerkleRoot string `json:"merkle_root"` - TxHash string `json:"tx_hash"` + MerkleRoot string `json:"merkle_root"` + TxHash string `json:"tx_hash"` + EffectiveGasPrice string `json:"effective_gas_price"` } type Telemetry struct { @@ -101,20 +102,21 @@ func (t *Telemetry) LogTaskError(batchMerkleRoot [32]byte, taskError error) { } } -func (t *Telemetry) BumpedTaskGasPrice(batchMerkleRoot [32]byte, bumpedGasPrice string) { - body := TaskGasPriceBumpMessage{ - MerkleRoot: fmt.Sprintf("0x%s", hex.EncodeToString(batchMerkleRoot[:])), - BumpedGasPrice: bumpedGasPrice, +func (t *Telemetry) TaskSetGasPrice(batchMerkleRoot [32]byte, gasPrice string) { + body := TaskSetGasPriceMessage{ + MerkleRoot: fmt.Sprintf("0x%s", hex.EncodeToString(batchMerkleRoot[:])), + GasPrice: gasPrice, } - if err := t.sendTelemetryMessage("/api/aggregatorTaskGasPriceBump", body); err != nil { + if err := t.sendTelemetryMessage("/api/aggregatorTaskSetGasPrice", body); err != nil { t.logger.Warn("[Telemetry] Error in LogOperatorResponse", "error", err) } } -func (t *Telemetry) TaskSentToEthereum(batchMerkleRoot [32]byte, txHash string) { +func (t *Telemetry) TaskSentToEthereum(batchMerkleRoot [32]byte, txHash string, effectiveGasPrice string) { body := TaskSentToEthereumMessage{ - MerkleRoot: fmt.Sprintf("0x%s", hex.EncodeToString(batchMerkleRoot[:])), - TxHash: txHash, + MerkleRoot: fmt.Sprintf("0x%s", hex.EncodeToString(batchMerkleRoot[:])), + TxHash: txHash, + EffectiveGasPrice: effectiveGasPrice, } if err := t.sendTelemetryMessage("/api/aggregatorTaskSent", body); err != nil { t.logger.Warn("[Telemetry] Error in TaskSentToEthereum", "error", err) diff --git a/batcher/Cargo.lock b/batcher/Cargo.lock index 9de1a9452..f04cb8c63 100644 --- a/batcher/Cargo.lock +++ b/batcher/Cargo.lock @@ -72,7 +72,7 @@ dependencies = [ [[package]] name = "aligned" -version = "0.12.2" +version = "0.13.0" dependencies = [ "aligned-sdk", "clap", @@ -147,9 +147,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy-primitives" @@ -194,7 +194,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -210,7 +210,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", "syn-solidity", "tiny-keccak", ] @@ -226,7 +226,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", "syn-solidity", ] @@ -569,7 +569,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -597,7 +597,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -676,9 +676,9 @@ dependencies = [ [[package]] name = "aws-sdk-s3" -version = "1.60.0" +version = "1.64.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0506cc60e392e33712d47717d5ae5760a3b134bf8ee7aea7e43df3d7e2669ae0" +checksum = "35fe5e7f71b1cc6274e905d3bcc7daf94099ac2d4cba83447ffb959b5b27b3c1" dependencies = [ "aws-credential-types", "aws-runtime", @@ -754,9 +754,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.49.0" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53dcf5e7d9bd1517b8b998e170e650047cea8a2b85fe1835abe3210713e541b7" +checksum = "6ada54e5f26ac246dc79727def52f7f8ed38915cb47781e2a72213957dc3a7d5" dependencies = [ "aws-credential-types", "aws-runtime", @@ -982,9 +982,9 @@ dependencies = [ [[package]] name = "axum" -version = "0.7.7" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" dependencies = [ "async-trait", "axum-core", @@ -993,7 +993,7 @@ dependencies = [ "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper 1.5.0", + "hyper 1.5.1", "hyper-util", "itoa", "matchit", @@ -1006,7 +1006,7 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", - "sync_wrapper 1.0.1", + "sync_wrapper 1.0.2", "tokio", "tower", "tower-layer", @@ -1029,7 +1029,7 @@ dependencies = [ "mime", "pin-project-lite", "rustversion", - "sync_wrapper 1.0.1", + "sync_wrapper 1.0.2", "tower-layer", "tower-service", "tracing", @@ -1037,9 +1037,9 @@ dependencies = [ [[package]] name = "backon" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4fa97bb310c33c811334143cf64c5bb2b7b3c06e453db6b095d7061eff8f113" +checksum = "ba5289ec98f68f28dd809fd601059e6aa908bb8f6108620930828283d4ee23d7" dependencies = [ "fastrand", "gloo-timers 0.3.0", @@ -1140,7 +1140,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -1204,9 +1204,9 @@ dependencies = [ [[package]] name = "blake3" -version = "1.5.4" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7" +checksum = "b8ee0c1824c4dea5b5f81736aff91bae041d2c07ee1192bec91054e10e3e601e" dependencies = [ "arrayref", "arrayvec", @@ -1252,14 +1252,14 @@ dependencies = [ "maybe-async", "reqwest 0.12.9", "serde", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "borsh" -version = "1.5.2" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5327f6c99920069d1fe374aa743be1af0031dea9f250852cdf1ae6a0861ee24" +checksum = "2506947f73ad44e344215ccd6403ac2ae18cd8e046e581a441bf8d199f257f03" dependencies = [ "borsh-derive", "cfg_aliases", @@ -1267,15 +1267,15 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.5.2" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10aedd8f1a81a8aafbfde924b0e3061cd6fedd6f6bbcfc6a76e6fd426d7bfe26" +checksum = "c2593a3b8b938bd68373196c9832f516be11fa487ef4ae745eb282e6a56a7244" dependencies = [ "once_cell", "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -1302,9 +1302,9 @@ checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" [[package]] name = "bytemuck" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" +checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" dependencies = [ "bytemuck_derive", ] @@ -1317,7 +1317,7 @@ checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -1328,9 +1328,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" dependencies = [ "serde", ] @@ -1377,9 +1377,9 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" dependencies = [ "serde", ] @@ -1395,14 +1395,14 @@ dependencies = [ "semver 1.0.23", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "cc" -version = "1.1.37" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40545c26d092346d8a8dab71ee48e7685a7a9cba76e634790c215b41a4a7b4cf" +checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" dependencies = [ "jobserver", "libc", @@ -1489,9 +1489,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.20" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" dependencies = [ "clap_builder", "clap_derive", @@ -1499,9 +1499,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.20" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" dependencies = [ "anstream", "anstyle", @@ -1518,14 +1518,14 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" [[package]] name = "coins-bip32" @@ -1540,7 +1540,7 @@ dependencies = [ "k256", "serde", "sha2", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -1556,7 +1556,7 @@ dependencies = [ "pbkdf2 0.12.2", "rand", "sha2", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -1576,7 +1576,7 @@ dependencies = [ "serde_derive", "sha2", "sha3", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -1594,15 +1594,15 @@ dependencies = [ "encode_unicode", "lazy_static", "libc", - "unicode-width", + "unicode-width 0.1.14", "windows-sys 0.52.0", ] [[package]] name = "const-hex" -version = "1.13.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0121754e84117e65f9d90648ee6aa4882a6e63110307ab73967a4c5e7e69e586" +checksum = "4b0485bab839b018a8f1723fc5391819fea5f8f0f32288ef8a735fd096b6160c" dependencies = [ "cfg-if", "cpufeatures", @@ -1664,9 +1664,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -1786,7 +1786,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -1923,7 +1923,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.1", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -1943,7 +1943,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -1955,7 +1955,7 @@ dependencies = [ "console", "shell-words", "tempfile", - "thiserror", + "thiserror 1.0.69", "zeroize", ] @@ -2030,7 +2030,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -2204,7 +2204,7 @@ checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -2238,12 +2238,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2264,7 +2264,7 @@ dependencies = [ "serde_json", "sha2", "sha3", - "thiserror", + "thiserror 1.0.69", "uuid 0.8.2", ] @@ -2281,7 +2281,7 @@ dependencies = [ "serde", "serde_json", "sha3", - "thiserror", + "thiserror 1.0.69", "uint", ] @@ -2360,7 +2360,7 @@ dependencies = [ "pin-project", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -2382,7 +2382,7 @@ dependencies = [ "reqwest 0.11.27", "serde", "serde_json", - "syn 2.0.87", + "syn 2.0.90", "toml", "walkdir", ] @@ -2400,7 +2400,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -2426,9 +2426,9 @@ dependencies = [ "serde", "serde_json", "strum", - "syn 2.0.87", + "syn 2.0.90", "tempfile", - "thiserror", + "thiserror 1.0.69", "tiny-keccak", "unicode-xid", ] @@ -2445,7 +2445,7 @@ dependencies = [ "semver 1.0.23", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", "tracing", ] @@ -2469,7 +2469,7 @@ dependencies = [ "reqwest 0.11.27", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", "tokio", "tracing", "tracing-futures", @@ -2502,7 +2502,7 @@ dependencies = [ "reqwest 0.11.27", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-tungstenite 0.20.1", "tracing", @@ -2529,7 +2529,7 @@ dependencies = [ "ethers-core", "rand", "sha2", - "thiserror", + "thiserror 1.0.69", "tracing", ] @@ -2557,7 +2557,7 @@ dependencies = [ "serde_json", "solang-parser", "svm-rs", - "thiserror", + "thiserror 1.0.69", "tiny-keccak", "tokio", "tracing", @@ -2658,9 +2658,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.34" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", "miniz_oxide", @@ -2705,7 +2705,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -2811,7 +2811,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -2882,9 +2882,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96512db27971c2c3eece70a1e106fbe6c87760234e31e8f7e5634912fe52794a" +checksum = "2cb8bc4c28d15ade99c7e90b219f30da4be5c88e586277e8cbe886beeb868ab2" dependencies = [ "serde", "typenum", @@ -2897,8 +2897,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -2994,9 +2996,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" dependencies = [ "atomic-waker", "bytes", @@ -3066,9 +3068,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ "allocator-api2", "equivalent", @@ -3256,14 +3258,14 @@ dependencies = [ [[package]] name = "hyper" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" +checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.6", + "h2 0.4.7", "http 1.1.0", "http-body 1.0.1", "httparse", @@ -3299,14 +3301,14 @@ checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http 1.1.0", - "hyper 1.5.0", + "hyper 1.5.1", "hyper-util", - "rustls 0.23.16", + "rustls 0.23.19", "rustls-pki-types", "tokio", "tokio-rustls 0.26.0", "tower-service", - "webpki-roots 0.26.6", + "webpki-roots 0.26.7", ] [[package]] @@ -3330,7 +3332,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.5.0", + "hyper 1.5.1", "hyper-util", "native-tls", "tokio", @@ -3349,7 +3351,7 @@ dependencies = [ "futures-util", "http 1.1.0", "http-body 1.0.1", - "hyper 1.5.0", + "hyper 1.5.1", "pin-project-lite", "socket2", "tokio", @@ -3472,7 +3474,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -3525,13 +3527,13 @@ dependencies = [ [[package]] name = "impl-trait-for-tuples" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.90", ] [[package]] @@ -3542,25 +3544,25 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", - "hashbrown 0.15.1", + "hashbrown 0.15.2", ] [[package]] name = "indicatif" -version = "0.17.8" +version = "0.17.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" +checksum = "cbf675b85ed934d3c67b5c5469701eec7db22689d0a2139d856e0925fa28b281" dependencies = [ "console", - "instant", "number_prefix", "portable-atomic", - "unicode-width", + "unicode-width 0.2.0", + "web-time", ] [[package]] @@ -3631,9 +3633,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jobserver" @@ -3646,10 +3648,11 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.72" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -3784,7 +3787,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -3798,9 +3801,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.162" +version = "0.2.167" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" +checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" [[package]] name = "libgit2-sys" @@ -3816,9 +3819,9 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", "windows-targets 0.52.6", @@ -3860,9 +3863,9 @@ checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "litemap" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" [[package]] name = "lock_api" @@ -3886,7 +3889,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.15.1", + "hashbrown 0.15.2", ] [[package]] @@ -3921,7 +3924,7 @@ checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -3994,11 +3997,10 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ - "hermit-abi", "libc", "wasi", "windows-sys 0.52.0", @@ -4235,7 +4237,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -4325,7 +4327,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -4789,7 +4791,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" dependencies = [ "memchr", - "thiserror", + "thiserror 1.0.69", "ucd-trie", ] @@ -4843,7 +4845,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -4881,7 +4883,7 @@ checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -4924,9 +4926,9 @@ checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "portable-atomic" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" +checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" [[package]] name = "powerfmt" @@ -4956,7 +4958,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" dependencies = [ "proc-macro2", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -5029,9 +5031,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.89" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -5073,7 +5075,7 @@ dependencies = [ "parking_lot", "procfs", "protobuf", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -5116,7 +5118,7 @@ dependencies = [ "itertools 0.13.0", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -5133,37 +5135,40 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quinn" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" +checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" dependencies = [ "bytes", "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 2.0.0", - "rustls 0.23.16", + "rustc-hash 2.1.0", + "rustls 0.23.19", "socket2", - "thiserror", + "thiserror 2.0.3", "tokio", "tracing", ] [[package]] name = "quinn-proto" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" +checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" dependencies = [ "bytes", + "getrandom", "rand", "ring 0.17.8", - "rustc-hash 2.0.0", - "rustls 0.23.16", + "rustc-hash 2.1.0", + "rustls 0.23.19", + "rustls-pki-types", "slab", - "thiserror", + "thiserror 2.0.3", "tinyvec", "tracing", + "web-time", ] [[package]] @@ -5280,7 +5285,7 @@ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom", "libredox", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -5389,11 +5394,11 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2 0.4.6", + "h2 0.4.7", "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper 1.5.0", + "hyper 1.5.1", "hyper-rustls 0.27.3", "hyper-tls 0.6.0", "hyper-util", @@ -5406,13 +5411,13 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.16", + "rustls 0.23.19", "rustls-pemfile 2.2.0", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 1.0.1", + "sync_wrapper 1.0.2", "system-configuration 0.6.1", "tokio", "tokio-native-tls", @@ -5424,7 +5429,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 0.26.6", + "webpki-roots 0.26.7", "windows-registry", ] @@ -5439,7 +5444,7 @@ dependencies = [ "http 1.1.0", "reqwest 0.12.9", "serde", - "thiserror", + "thiserror 1.0.69", "tower-service", ] @@ -5766,9 +5771,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" [[package]] name = "rustc-hex" @@ -5796,9 +5801,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.40" +version = "0.38.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" +checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" dependencies = [ "bitflags 2.6.0", "errno", @@ -5821,9 +5826,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.16" +version = "0.23.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e" +checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" dependencies = [ "once_cell", "ring 0.17.8", @@ -5868,6 +5873,9 @@ name = "rustls-pki-types" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" +dependencies = [ + "web-time", +] [[package]] name = "rustls-webpki" @@ -5934,9 +5942,9 @@ dependencies = [ [[package]] name = "scale-info" -version = "2.11.5" +version = "2.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aa7ffc1c0ef49b0452c6e2986abf2b07743320641ffd5fc63d552458e3b779b" +checksum = "346a3b32eba2640d17a9cb5927056b08f3de90f65b72fe09402c2ad07d684d0b" dependencies = [ "cfg-if", "derive_more 1.0.0", @@ -5946,30 +5954,30 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.11.5" +version = "2.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46385cc24172cf615450267463f937c10072516359b3ff1cb24228a4a08bf951" +checksum = "c6630024bf739e2179b91fb424b28898baf819414262c5d376677dbff1fe7ebf" dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "scc" -version = "2.2.4" +version = "2.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8d25269dd3a12467afe2e510f69fb0b46b698e5afb296b59f2145259deaf8e8" +checksum = "66b202022bb57c049555430e11fc22fea12909276a80a4c3d368da36ac1d88ed" dependencies = [ "sdd", ] [[package]] name = "schannel" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ "windows-sys 0.59.0", ] @@ -6085,9 +6093,9 @@ dependencies = [ [[package]] name = "semver-parser" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +checksum = "9900206b54a3527fdc7b8a938bffd94a568bac4f4aa8113b209df75a09c0dec2" dependencies = [ "pest", ] @@ -6106,29 +6114,29 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "serde_json" -version = "1.0.132" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "itoa", "memchr", @@ -6154,7 +6162,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -6213,7 +6221,7 @@ checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -6316,7 +6324,7 @@ checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" dependencies = [ "num-bigint 0.4.6", "num-traits", - "thiserror", + "thiserror 1.0.69", "time", ] @@ -6359,9 +6367,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", @@ -6377,7 +6385,7 @@ dependencies = [ "lalrpop", "lalrpop-util", "phf", - "thiserror", + "thiserror 1.0.69", "unicode-xid", ] @@ -6407,7 +6415,7 @@ dependencies = [ "sp1-stark", "strum", "strum_macros", - "thiserror", + "thiserror 1.0.69", "tiny-keccak", "tracing", "typenum", @@ -6422,7 +6430,7 @@ dependencies = [ "bincode", "cfg-if", "elliptic-curve 0.13.8", - "generic-array 1.1.0", + "generic-array 1.1.1", "hashbrown 0.14.5", "hex", "itertools 0.13.0", @@ -6453,10 +6461,10 @@ dependencies = [ "strum", "strum_macros", "tempfile", - "thiserror", + "thiserror 1.0.69", "tracing", "tracing-forest", - "tracing-subscriber 0.3.18", + "tracing-subscriber 0.3.19", "typenum", "web-time", ] @@ -6470,7 +6478,7 @@ dependencies = [ "curve25519-dalek", "dashu", "elliptic-curve 0.13.8", - "generic-array 1.1.0", + "generic-array 1.1.1", "itertools 0.13.0", "k256", "num", @@ -6544,9 +6552,9 @@ dependencies = [ "sp1-stark", "subtle-encoding", "tempfile", - "thiserror", + "thiserror 1.0.69", "tracing", - "tracing-subscriber 0.3.18", + "tracing-subscriber 0.3.19", ] [[package]] @@ -6632,7 +6640,7 @@ dependencies = [ "sp1-primitives", "sp1-stark", "static_assertions", - "thiserror", + "thiserror 1.0.69", "tracing", "vec_map", "zkhash", @@ -6705,7 +6713,7 @@ dependencies = [ "strum", "strum_macros", "tempfile", - "thiserror", + "thiserror 1.0.69", "tokio", "tracing", "twirp-rs", @@ -6743,7 +6751,7 @@ dependencies = [ "strum", "strum_macros", "sysinfo", - "thiserror", + "thiserror 1.0.69", "tracing", ] @@ -6786,7 +6794,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac" dependencies = [ "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -6839,7 +6847,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -6872,7 +6880,7 @@ dependencies = [ "serde", "serde_json", "sha2", - "thiserror", + "thiserror 1.0.69", "url", "zip", ] @@ -6890,9 +6898,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.87" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -6908,7 +6916,7 @@ dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -6919,9 +6927,9 @@ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "sync_wrapper" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ "futures-core", ] @@ -6934,7 +6942,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -7049,7 +7057,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" +dependencies = [ + "thiserror-impl 2.0.3", ] [[package]] @@ -7060,7 +7077,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", ] [[package]] @@ -7166,7 +7194,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -7195,7 +7223,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.16", + "rustls 0.23.19", "rustls-pki-types", "tokio", ] @@ -7236,13 +7264,13 @@ dependencies = [ "futures-util", "log", "native-tls", - "rustls 0.23.16", + "rustls 0.23.19", "rustls-pki-types", "tokio", "tokio-native-tls", "tokio-rustls 0.26.0", "tungstenite 0.23.0", - "webpki-roots 0.26.6", + "webpki-roots 0.26.7", ] [[package]] @@ -7254,13 +7282,13 @@ dependencies = [ "futures-util", "log", "native-tls", - "rustls 0.23.16", + "rustls 0.23.19", "rustls-pki-types", "tokio", "tokio-native-tls", "tokio-rustls 0.26.0", "tungstenite 0.24.0", - "webpki-roots 0.26.6", + "webpki-roots 0.26.7", ] [[package]] @@ -7351,9 +7379,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", @@ -7363,20 +7391,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", @@ -7390,9 +7418,9 @@ checksum = "ee40835db14ddd1e3ba414292272eddde9dad04d3d4b65509656414d1c42592f" dependencies = [ "ansi_term", "smallvec", - "thiserror", + "thiserror 1.0.69", "tracing", - "tracing-subscriber 0.3.18", + "tracing-subscriber 0.3.19", ] [[package]] @@ -7427,9 +7455,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "matchers", "nu-ansi-term", @@ -7464,7 +7492,7 @@ dependencies = [ "rand", "rustls 0.21.12", "sha1", - "thiserror", + "thiserror 1.0.69", "url", "utf-8", ] @@ -7483,7 +7511,7 @@ dependencies = [ "log", "rand", "sha1", - "thiserror", + "thiserror 1.0.69", "url", "utf-8", ] @@ -7502,10 +7530,10 @@ dependencies = [ "log", "native-tls", "rand", - "rustls 0.23.16", + "rustls 0.23.19", "rustls-pki-types", "sha1", - "thiserror", + "thiserror 1.0.69", "utf-8", ] @@ -7523,10 +7551,10 @@ dependencies = [ "log", "native-tls", "rand", - "rustls 0.23.16", + "rustls 0.23.19", "rustls-pki-types", "sha1", - "thiserror", + "thiserror 1.0.69", "utf-8", ] @@ -7541,12 +7569,12 @@ dependencies = [ "futures", "http 1.1.0", "http-body-util", - "hyper 1.5.0", + "hyper 1.5.1", "prost", "reqwest 0.12.9", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", "tokio", "tower", "url", @@ -7590,9 +7618,9 @@ checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-width" @@ -7600,6 +7628,12 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + [[package]] name = "unicode-xid" version = "0.2.6" @@ -7626,9 +7660,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.3" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", @@ -7792,9 +7826,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" dependencies = [ "cfg-if", "once_cell", @@ -7803,36 +7837,37 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.45" +version = "0.4.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +checksum = "9dfaf8f50e5f293737ee323940c7d8b08a66a95a419223d9f41610ca08b0833d" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -7840,22 +7875,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" [[package]] name = "wasm-streams" @@ -7872,9 +7907,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.72" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +checksum = "a98bc3c33f0fe7e59ad7cd041b89034fa82a7c2d4365ca538dda6cdaf513863c" dependencies = [ "js-sys", "wasm-bindgen", @@ -7898,9 +7933,9 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "webpki-roots" -version = "0.26.6" +version = "0.26.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" +checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" dependencies = [ "rustls-pki-types", ] @@ -8186,7 +8221,7 @@ dependencies = [ "pharos", "rustc_version 0.4.1", "send_wrapper 0.6.0", - "thiserror", + "thiserror 1.0.69", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -8215,9 +8250,9 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "yoke" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ "serde", "stable_deref_trait", @@ -8227,13 +8262,13 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", "synstructure", ] @@ -8255,27 +8290,27 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "zerofrom" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", "synstructure", ] @@ -8296,7 +8331,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -8318,7 +8353,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] diff --git a/batcher/aligned-batcher/src/connection.rs b/batcher/aligned-batcher/src/connection.rs index 9d7192522..9a87230ab 100644 --- a/batcher/aligned-batcher/src/connection.rs +++ b/batcher/aligned-batcher/src/connection.rs @@ -21,7 +21,9 @@ pub(crate) async fn send_batch_inclusion_data_responses( finalized_batch: Vec, batch_merkle_tree: &MerkleTree, ) -> Result<(), BatcherError> { - for (vd_batch_idx, entry) in finalized_batch.iter().enumerate() { + // Finalized_batch is ordered as the PriorityQueue, ordered by: ascending max_fee && if max_fee is equal, by descending nonce. + // We iter it in reverse because each sender wants to receive responses in ascending nonce order + for (vd_batch_idx, entry) in finalized_batch.iter().enumerate().rev() { let batch_inclusion_data = BatchInclusionData::new( vd_batch_idx, batch_merkle_tree, diff --git a/batcher/aligned-batcher/src/lib.rs b/batcher/aligned-batcher/src/lib.rs index 365f22421..c6c8af65b 100644 --- a/batcher/aligned-batcher/src/lib.rs +++ b/batcher/aligned-batcher/src/lib.rs @@ -8,10 +8,11 @@ use ethers::contract::ContractError; use ethers::signers::Signer; use retry::batcher_retryables::{ cancel_create_new_task_retryable, create_new_task_retryable, get_user_balance_retryable, - get_user_nonce_from_ethereum_retryable, user_balance_is_unlocked_retryable, + get_user_nonce_from_ethereum_retryable, simulate_create_new_task_retryable, + user_balance_is_unlocked_retryable, }; use retry::{retry_function, RetryError}; -use tokio::time::timeout; +use tokio::time::{timeout, Instant}; use types::batch_state::BatchState; use types::user_state::UserState; @@ -47,7 +48,7 @@ use tokio::net::{TcpListener, TcpStream}; use tokio::sync::{Mutex, MutexGuard, RwLock}; use tokio_tungstenite::tungstenite::{Error, Message}; use types::batch_queue::{self, BatchQueueEntry, BatchQueueEntryPriority}; -use types::errors::BatcherError; +use types::errors::{BatcherError, TransactionSendError}; use crate::config::{ConfigFromYaml, ContractDeploymentOutput}; use crate::telemetry::sender::TelemetrySender; @@ -1115,12 +1116,13 @@ impl Batcher { /// an empty batch, even if the block interval has been reached. /// Once the batch meets the conditions for submission, the finalized batch is then passed to the /// `finalize_batch` function. + /// This function doesn't remove the proofs from the queue. async fn is_batch_ready( &self, block_number: u64, gas_price: U256, ) -> Option> { - let mut batch_state_lock = self.batch_state.lock().await; + let batch_state_lock = self.batch_state.lock().await; let current_batch_len = batch_state_lock.batch_queue.len(); let last_uploaded_batch_block_lock = self.last_uploaded_batch_block.lock().await; @@ -1152,7 +1154,7 @@ impl Batcher { // Set the batch posting flag to true *batch_posting = true; let batch_queue_copy = batch_state_lock.batch_queue.clone(); - let (resulting_batch_queue, finalized_batch) = batch_queue::try_build_batch( + let finalized_batch = batch_queue::try_build_batch( batch_queue_copy, gas_price, self.max_batch_byte_size, @@ -1172,7 +1174,26 @@ impl Batcher { }) .ok()?; - batch_state_lock.batch_queue = resulting_batch_queue; + Some(finalized_batch) + } + + /// Takes the submitted proofs and removes them from the queue. + /// This function should be called only AFTER the submission was confirmed onchain + async fn remove_proofs_from_queue( + &self, + finalized_batch: Vec, + ) -> Result<(), BatcherError> { + info!("Removing proofs from queue..."); + let mut batch_state_lock = self.batch_state.lock().await; + + finalized_batch.iter().for_each(|entry| { + if batch_state_lock.batch_queue.remove(entry).is_none() { + // If this happens, we have a bug in our code + error!("Some proofs were not found in the queue. This should not happen."); + } + }); + + // now we calculate the new user_states let new_user_states = // proofs, max_fee_limit, total_fees_in_queue batch_state_lock.calculate_new_user_states_data(); @@ -1188,17 +1209,33 @@ impl Batcher { // informative error. // Now we update the user states related to the batch (proof count in batch and min fee in batch) - batch_state_lock.update_user_proof_count(addr, *proof_count)?; - batch_state_lock.update_user_max_fee_limit(addr, *max_fee_limit)?; - batch_state_lock.update_user_total_fees_in_queue(addr, *total_fees_in_queue)?; + batch_state_lock + .update_user_proof_count(addr, *proof_count) + .ok_or(BatcherError::QueueRemoveError( + "Could not update_user_proof_count".into(), + ))?; + batch_state_lock + .update_user_max_fee_limit(addr, *max_fee_limit) + .ok_or(BatcherError::QueueRemoveError( + "Could not update_user_max_fee_limit".into(), + ))?; + batch_state_lock + .update_user_total_fees_in_queue(addr, *total_fees_in_queue) + .ok_or(BatcherError::QueueRemoveError( + "Could not update_user_total_fees_in_queue".into(), + ))?; } - Some(finalized_batch) + Ok(()) } - /// Takes the finalized batch as input and builds the merkle tree, posts verification data batch - /// to s3, creates new task in Aligned contract and sends responses to all clients that added proofs - /// to the batch. The last uploaded batch block is updated once the task is created in Aligned. + /// Takes the finalized batch as input and: + /// builds the merkle tree + /// posts verification data batch to s3 + /// creates new task in Aligned contract + /// removes the proofs from the queue, once they are succesfully submitted on-chain + /// sends responses to all clients that added proofs to the batch. + /// The last uploaded batch block is updated once the task is created in Aligned. async fn finalize_batch( &self, block_number: u64, @@ -1256,6 +1293,7 @@ impl Batcher { warn!("Failed to initialize task trace on telemetry: {:?}", e); } + // Here we submit the batch on-chain if let Err(e) = self .submit_batch( &batch_bytes, @@ -1274,27 +1312,30 @@ impl Batcher { { warn!("Failed to send task status to telemetry: {:?}", e); } - for entry in finalized_batch.into_iter() { - if let Some(ws_sink) = entry.messaging_sink { - let merkle_root = hex::encode(batch_merkle_tree.root); - send_message( - ws_sink.clone(), - SubmitProofResponseMessage::CreateNewTaskError( - merkle_root, - format!("{:?}", e), - ), - ) - .await - } else { - warn!("Websocket sink was found empty. This should only happen in tests"); + + // decide if i want to flush the queue: + match e { + BatcherError::TransactionSendError( + TransactionSendError::SubmissionInsufficientBalance, + ) => { + // TODO calling remove_proofs_from_queue here is a better solution, flushing only the failed batch + // this would also need a message sent to the clients + self.flush_queue_and_clear_nonce_cache().await; + } + _ => { + // Add more cases here if we want in the future } } - self.flush_queue_and_clear_nonce_cache().await; - return Err(e); }; + // Once the submit is succesfull, we remove the submitted proofs from the queue + // TODO handle error case: + if let Err(e) = self.remove_proofs_from_queue(finalized_batch.clone()).await { + error!("Unexpected error while updating queue: {:?}", e); + } + connection::send_batch_inclusion_data_responses(finalized_batch, &batch_merkle_tree).await } @@ -1387,28 +1428,12 @@ impl Batcher { let batch_merkle_root_hex = hex::encode(batch_merkle_root); info!("Batch merkle root: 0x{}", batch_merkle_root_hex); let file_name = batch_merkle_root_hex.clone() + ".json"; - - info!("Uploading batch to S3..."); - self.upload_batch_to_s3(batch_bytes, &file_name).await?; - - if let Err(e) = self - .telemetry - .task_uploaded_to_s3(&batch_merkle_root_hex) - .await - { - warn!("Failed to send task status to telemetry: {:?}", e); - }; - info!("Batch sent to S3 with name: {}", file_name); - - info!("Uploading batch to contract"); let batch_data_pointer: String = "".to_owned() + &self.download_endpoint + "/" + &file_name; let num_proofs_in_batch = leaves.len(); - let gas_per_proof = (CONSTANT_GAS_COST + ADDITIONAL_SUBMISSION_GAS_COST_PER_PROOF * num_proofs_in_batch as u128) / num_proofs_in_batch as u128; - let fee_per_proof = U256::from(gas_per_proof) * gas_price; let fee_for_aggregator = (U256::from(AGGREGATOR_GAS_COST) * gas_price @@ -1424,12 +1449,31 @@ impl Batcher { respond_to_task_fee_limit, ); - let proof_submitters = finalized_batch.iter().map(|entry| entry.sender).collect(); + let proof_submitters: Vec
= + finalized_batch.iter().map(|entry| entry.sender).collect(); + + self.simulate_create_new_task( + *batch_merkle_root, + batch_data_pointer.clone(), + proof_submitters.clone(), + fee_params.clone(), + ) + .await?; self.metrics .gas_price_used_on_latest_batch .set(gas_price.as_u64() as i64); + info!("Uploading batch to S3..."); + self.upload_batch_to_s3(batch_bytes, &file_name).await?; + if let Err(e) = self + .telemetry + .task_uploaded_to_s3(&batch_merkle_root_hex) + .await + { + warn!("Failed to send task status to telemetry: {:?}", e); + }; + info!("Batch sent to S3 with name: {}", file_name); if let Err(e) = self .telemetry .task_created( @@ -1442,6 +1486,7 @@ impl Batcher { warn!("Failed to send task status to telemetry: {:?}", e); }; + info!("Submitting batch to contract"); match self .create_new_task( *batch_merkle_root, @@ -1479,6 +1524,7 @@ impl Batcher { proof_submitters: Vec
, fee_params: CreateNewTaskFeeParams, ) -> Result { + let start = Instant::now(); let result = retry_function( || { create_new_task_retryable( @@ -1497,6 +1543,11 @@ impl Batcher { ETHEREUM_CALL_MAX_RETRY_DELAY, ) .await; + self.metrics + .create_new_task_duration + .set(start.elapsed().as_millis() as i64); + // Set to zero since it is not always executed + self.metrics.cancel_create_new_task_duration.set(0); match result { Ok(receipt) => { if let Err(e) = self @@ -1506,6 +1557,10 @@ impl Batcher { { warn!("Failed to send task status to telemetry: {:?}", e); } + let gas_cost = Self::gas_cost_in_eth(receipt.effective_gas_price, receipt.gas_used); + self.metrics + .batcher_gas_cost_create_task_total + .inc_by(gas_cost); Ok(receipt) } Err(RetryError::Permanent(BatcherError::ReceiptNotFoundError)) => { @@ -1517,6 +1572,37 @@ impl Batcher { } } + /// Simulates the `create_new_task` transaction by sending an `eth_call` to the RPC node. + /// This function does not mutate the state but verifies if it will revert under the given conditions. + async fn simulate_create_new_task( + &self, + batch_merkle_root: [u8; 32], + batch_data_pointer: String, + proof_submitters: Vec
, + fee_params: CreateNewTaskFeeParams, + ) -> Result<(), BatcherError> { + retry_function( + || { + simulate_create_new_task_retryable( + batch_merkle_root, + batch_data_pointer.clone(), + proof_submitters.clone(), + fee_params.clone(), + &self.payment_service, + &self.payment_service_fallback, + ) + }, + ETHEREUM_CALL_MIN_RETRY_DELAY, + ETHEREUM_CALL_BACKOFF_FACTOR, + ETHEREUM_CALL_MAX_RETRIES, + ETHEREUM_CALL_MAX_RETRY_DELAY, + ) + .await + .map_err(|e| e.inner())?; + + Ok(()) + } + /// Sends a transaction to Ethereum with the same nonce as the previous one to override it. /// Retries on recoverable errors with exponential backoff. /// Bumps the fee if not included in 6 blocks, using `calculate_bumped_gas_price`. @@ -1524,10 +1610,11 @@ impl Batcher { /// After 2 hours (attempt 13), retries occur hourly for 1 day (33 retries). pub async fn cancel_create_new_task_tx(&self, old_tx_gas_price: U256) { info!("Cancelling createNewTask transaction..."); + let start = Instant::now(); let iteration = Arc::new(Mutex::new(0)); let previous_gas_price = Arc::new(Mutex::new(old_tx_gas_price)); - if let Err(e) = retry_function( + match retry_function( || async { let mut iteration = iteration.lock().await; let mut previous_gas_price = previous_gas_price.lock().await; @@ -1563,11 +1650,38 @@ impl Batcher { ) .await { - error!("Could not cancel createNewTask transaction: {e}"); - return; + Ok(receipt) => { + info!("createNewTask transaction successfully canceled"); + let gas_cost = Self::gas_cost_in_eth(receipt.effective_gas_price, receipt.gas_used); + self.metrics + .batcher_gas_cost_cancel_task_total + .inc_by(gas_cost); + } + Err(e) => error!("Could not cancel createNewTask transaction: {e}"), }; + self.metrics + .cancel_create_new_task_duration + .set(start.elapsed().as_millis() as i64); + } + + fn gas_cost_in_eth(gas_price: Option, gas_used: Option) -> f64 { + if let (Some(gas_price), Some(gas_used)) = (gas_price, gas_used) { + let wei_gas_cost = gas_price + .checked_mul(gas_used) + .unwrap_or_else(U256::max_value); + + // f64 is typically sufficient for transaction gas costs. + let max_f64_u256 = U256::from(f64::MAX as u64); + if wei_gas_cost > max_f64_u256 { + return f64::MAX; + } + + let wei_gas_cost_f64 = wei_gas_cost.low_u128() as f64; + let eth_gas_cost = wei_gas_cost_f64 / 1e18; - info!("createNewTask transaction successfully canceled"); + return eth_gas_cost; + } + 0.0 } /// Only relevant for testing and for users to easily use Aligned @@ -1709,7 +1823,8 @@ impl Batcher { batch_bytes: &[u8], file_name: &str, ) -> Result<(), BatcherError> { - retry_function( + let start = Instant::now(); + let result = retry_function( || { Self::upload_batch_to_s3_retryable( batch_bytes, @@ -1724,7 +1839,13 @@ impl Batcher { ETHEREUM_CALL_MAX_RETRY_DELAY, ) .await - .map_err(|e| BatcherError::BatchUploadError(e.to_string())) + .map_err(|e| BatcherError::BatchUploadError(e.to_string())); + + self.metrics + .s3_duration + .set(start.elapsed().as_micros() as i64); + + result } async fn upload_batch_to_s3_retryable( diff --git a/batcher/aligned-batcher/src/metrics.rs b/batcher/aligned-batcher/src/metrics.rs index 231a4795f..9d0f0e30f 100644 --- a/batcher/aligned-batcher/src/metrics.rs +++ b/batcher/aligned-batcher/src/metrics.rs @@ -2,8 +2,9 @@ use std::{thread, time::Duration}; // Prometheus use prometheus::{ - opts, register_int_counter, register_int_counter_vec, register_int_gauge, IntCounter, - IntCounterVec, IntGauge, + core::{AtomicF64, GenericCounter}, + opts, register_counter, register_int_counter, register_int_counter_vec, register_int_gauge, + IntCounter, IntCounterVec, IntGauge, }; use warp::{Filter, Rejection, Reply}; @@ -19,30 +20,58 @@ pub struct BatcherMetrics { pub batcher_started: IntCounter, pub gas_price_used_on_latest_batch: IntGauge, pub broken_ws_connections: IntCounter, + pub s3_duration: IntGauge, + pub create_new_task_duration: IntGauge, + pub cancel_create_new_task_duration: IntGauge, + pub batcher_gas_cost_create_task_total: GenericCounter, + pub batcher_gas_cost_cancel_task_total: GenericCounter, } impl BatcherMetrics { pub fn start(metrics_port: u16) -> anyhow::Result { let registry = prometheus::Registry::new(); - let open_connections = register_int_gauge!(opts!("open_connections", "Open Connections"))?; - let received_proofs = register_int_counter!(opts!("received_proofs", "Received Proofs"))?; - let sent_batches = register_int_counter!(opts!("sent_batches", "Sent Batches"))?; + let open_connections = + register_int_gauge!(opts!("open_connections_count", "Open Connections"))?; + let received_proofs = + register_int_counter!(opts!("received_proofs_count", "Received Proofs"))?; + let sent_batches = register_int_counter!(opts!("sent_batches_count", "Sent Batches"))?; let reverted_batches = - register_int_counter!(opts!("reverted_batches", "Reverted Batches"))?; + register_int_counter!(opts!("reverted_batches_count", "Reverted Batches"))?; let canceled_batches = - register_int_counter!(opts!("canceled_batches", "Canceled Batches"))?; + register_int_counter!(opts!("canceled_batches_count", "Canceled Batches"))?; let user_errors = register_int_counter_vec!( - opts!("user_errors", "User Errors"), + opts!("user_errors_count", "User Errors"), &["error_type", "proving_system"] )?; - let batcher_started = register_int_counter!(opts!("batcher_started", "Batcher Started"))?; + let batcher_started = + register_int_counter!(opts!("batcher_started_count", "Batcher Started"))?; let gas_price_used_on_latest_batch = register_int_gauge!(opts!("gas_price_used_on_latest_batch", "Gas Price"))?; let broken_ws_connections = register_int_counter!(opts!( - "broken_ws_connections", + "broken_ws_connections_count", "Broken websocket connections" ))?; + let s3_duration = register_int_gauge!(opts!("s3_duration", "S3 Duration"))?; + let create_new_task_duration = register_int_gauge!(opts!( + "create_new_task_duration", + "Create New Task Duration" + ))?; + let cancel_create_new_task_duration = register_int_gauge!(opts!( + "cancel_create_new_task_duration", + "Cancel create New Task Duration" + ))?; + + let batcher_gas_cost_create_task_total: GenericCounter = + register_counter!(opts!( + "batcher_gas_cost_create_task_total", + "Batcher Gas Cost Create Task Total" + ))?; + let batcher_gas_cost_cancel_task_total: GenericCounter = + register_counter!(opts!( + "batcher_gas_cost_cancel_task_total", + "Batcher Gas Cost Cancel Task Total" + ))?; registry.register(Box::new(open_connections.clone()))?; registry.register(Box::new(received_proofs.clone()))?; @@ -53,6 +82,11 @@ impl BatcherMetrics { registry.register(Box::new(gas_price_used_on_latest_batch.clone()))?; registry.register(Box::new(batcher_started.clone()))?; registry.register(Box::new(broken_ws_connections.clone()))?; + registry.register(Box::new(s3_duration.clone()))?; + registry.register(Box::new(create_new_task_duration.clone()))?; + registry.register(Box::new(cancel_create_new_task_duration.clone()))?; + registry.register(Box::new(batcher_gas_cost_create_task_total.clone()))?; + registry.register(Box::new(batcher_gas_cost_cancel_task_total.clone()))?; let metrics_route = warp::path!("metrics") .and(warp::any().map(move || registry.clone())) @@ -74,6 +108,11 @@ impl BatcherMetrics { batcher_started, gas_price_used_on_latest_batch, broken_ws_connections, + s3_duration, + create_new_task_duration, + cancel_create_new_task_duration, + batcher_gas_cost_create_task_total, + batcher_gas_cost_cancel_task_total, }) } diff --git a/batcher/aligned-batcher/src/retry/batcher_retryables.rs b/batcher/aligned-batcher/src/retry/batcher_retryables.rs index d63637abe..d5f1aaef0 100644 --- a/batcher/aligned-batcher/src/retry/batcher_retryables.rs +++ b/batcher/aligned-batcher/src/retry/batcher_retryables.rs @@ -10,7 +10,7 @@ use crate::{ utils::get_current_nonce, }, retry::RetryError, - types::errors::BatcherError, + types::errors::{BatcherError, TransactionSendError}, }; pub async fn get_user_balance_retryable( @@ -130,7 +130,7 @@ pub async fn create_new_task_retryable( // Since transaction was reverted, we don't want to retry with fallback. warn!("Transaction reverted {:?}", err); return Err(RetryError::Permanent(BatcherError::TransactionSendError( - err.to_string(), + TransactionSendError::from(err), ))); } _ => { @@ -149,12 +149,12 @@ pub async fn create_new_task_retryable( Err(ContractError::Revert(err)) => { warn!("Transaction reverted {:?}", err); return Err(RetryError::Permanent(BatcherError::TransactionSendError( - err.to_string(), + TransactionSendError::from(err), ))); } Err(err) => { return Err(RetryError::Transient(BatcherError::TransactionSendError( - err.to_string(), + TransactionSendError::Generic(err.to_string()), ))) } } @@ -175,6 +175,69 @@ pub async fn create_new_task_retryable( .ok_or(RetryError::Permanent(BatcherError::ReceiptNotFoundError)) } +pub async fn simulate_create_new_task_retryable( + batch_merkle_root: [u8; 32], + batch_data_pointer: String, + proofs_submitters: Vec
, + fee_params: CreateNewTaskFeeParams, + payment_service: &BatcherPaymentService, + payment_service_fallback: &BatcherPaymentService, +) -> Result<(), RetryError> { + info!("Simulating task for: 0x{}", hex::encode(batch_merkle_root)); + let simulation_fallback; + let simulation = payment_service + .create_new_task( + batch_merkle_root, + batch_data_pointer.clone(), + proofs_submitters.clone(), + fee_params.fee_for_aggregator, + fee_params.fee_per_proof, + fee_params.respond_to_task_fee_limit, + ) + .gas_price(fee_params.gas_price); + // sends an `eth_call` request to the node + match simulation.call().await { + Ok(_) => { + info!( + "Simulation task for: 0x{} succeeded.", + hex::encode(batch_merkle_root) + ); + Ok(()) + } + Err(ContractError::Revert(err)) => { + // Since transaction was reverted, we don't want to retry with fallback. + warn!("Simulated transaction reverted {:?}", err); + Err(RetryError::Permanent(BatcherError::TransactionSendError( + TransactionSendError::from(err), + ))) + } + _ => { + simulation_fallback = payment_service_fallback + .create_new_task( + batch_merkle_root, + batch_data_pointer, + proofs_submitters, + fee_params.fee_for_aggregator, + fee_params.fee_per_proof, + fee_params.respond_to_task_fee_limit, + ) + .gas_price(fee_params.gas_price); + match simulation_fallback.call().await { + Ok(_) => Ok(()), + Err(ContractError::Revert(err)) => { + warn!("Simulated transaction reverted {:?}", err); + Err(RetryError::Permanent(BatcherError::TransactionSendError( + TransactionSendError::from(err), + ))) + } + Err(err) => Err(RetryError::Transient(BatcherError::TransactionSendError( + TransactionSendError::Generic(err.to_string()), + ))), + } + } + } +} + pub async fn cancel_create_new_task_retryable( batcher_signer: &SignerMiddlewareT, batcher_signer_fallback: &SignerMiddlewareT, diff --git a/batcher/aligned-batcher/src/types/batch_queue.rs b/batcher/aligned-batcher/src/types/batch_queue.rs index 17918e7a2..2d9ce2225 100644 --- a/batcher/aligned-batcher/src/types/batch_queue.rs +++ b/batcher/aligned-batcher/src/types/batch_queue.rs @@ -100,9 +100,16 @@ impl PartialOrd for BatchQueueEntryPriority { impl Ord for BatchQueueEntryPriority { fn cmp(&self, other: &Self) -> std::cmp::Ordering { - let ord = other.max_fee.cmp(&self.max_fee); + // Implementation of lowest-first: + let ord: std::cmp::Ordering = other.max_fee.cmp(&self.max_fee); + // This means, less max_fee will go first + // We want this because we will .pop() to remove unwanted elements, low fee submitions. + if ord == std::cmp::Ordering::Equal { - self.nonce.cmp(&other.nonce).reverse() + // Case of same max_fee: + // Implementation of biggest-first: + // Since we want to .pop() entries with biggest nonce first, because we want to submit low nonce first + self.nonce.cmp(&other.nonce) } else { ord } @@ -134,29 +141,28 @@ pub(crate) fn calculate_batch_size(batch_queue: &BatchQueue) -> Result Result<(BatchQueue, Vec), BatcherError> { - let mut batch_queue = batch_queue; - let mut batch_size = calculate_batch_size(&batch_queue)?; - let mut resulting_priority_queue = BatchQueue::new(); +) -> Result, BatcherError> { + let mut finalized_batch = batch_queue; + let mut batch_size = calculate_batch_size(&finalized_batch)?; - while let Some((entry, _)) = batch_queue.peek() { - let batch_len = batch_queue.len(); + while let Some((entry, _)) = finalized_batch.peek() { + let batch_len = finalized_batch.len(); let fee_per_proof = calculate_fee_per_proof(batch_len, gas_price); + // if batch is not acceptable: if batch_size > max_batch_byte_size || fee_per_proof > entry.nonced_verification_data.max_fee || batch_len > max_batch_proof_qty @@ -173,8 +179,7 @@ pub(crate) fn try_build_batch( .len(); batch_size -= verification_data_size; - let (not_working_entry, not_working_priority) = batch_queue.pop().unwrap(); - resulting_priority_queue.push(not_working_entry, not_working_priority); + finalized_batch.pop(); continue; } @@ -183,16 +188,13 @@ pub(crate) fn try_build_batch( break; } - // If `batch_queue_copy` is empty, this means that all the batch queue was traversed and we didn't find + // If `finalized_batch` is empty, this means that all the batch queue was traversed and we didn't find // any user willing to pay fot the fee per proof. - if batch_queue.is_empty() { + if finalized_batch.is_empty() { return Err(BatcherError::BatchCostTooHigh); } - Ok(( - resulting_priority_queue, - batch_queue.clone().into_sorted_vec(), - )) + Ok(finalized_batch.clone().into_sorted_vec()) } fn calculate_fee_per_proof(batch_len: usize, gas_price: U256) -> U256 { @@ -301,14 +303,222 @@ mod test { batch_queue.push(entry_3, batch_priority_3); let gas_price = U256::from(1); - let (resulting_batch_queue, batch) = - try_build_batch(batch_queue, gas_price, 5000000, 50).unwrap(); + let finalized_batch = try_build_batch(batch_queue, gas_price, 5000000, 50).unwrap(); + + assert_eq!( + finalized_batch[0].nonced_verification_data.max_fee, + max_fee_3 + ); + assert_eq!( + finalized_batch[1].nonced_verification_data.max_fee, + max_fee_2 + ); + assert_eq!( + finalized_batch[2].nonced_verification_data.max_fee, + max_fee_1 + ); + } + + #[test] + fn batch_finalization_algorithm_works_from_same_sender_same_fee() { + // The following information will be the same for each entry, it is just some dummy data to see + // algorithm working. + + let proof_generator_addr = Address::random(); + let payment_service_addr = Address::random(); + let sender_addr = Address::random(); + let bytes_for_verification_data = vec![42_u8; 10]; + let dummy_signature = Signature { + r: U256::from(1), + s: U256::from(2), + v: 3, + }; + let verification_data = VerificationData { + proving_system: ProvingSystemId::Risc0, + proof: bytes_for_verification_data.clone(), + pub_input: Some(bytes_for_verification_data.clone()), + verification_key: Some(bytes_for_verification_data.clone()), + vm_program_code: Some(bytes_for_verification_data), + proof_generator_addr, + }; + let chain_id = U256::from(42); + + // Here we create different entries for the batch queue. + // All with the same fee + + let max_fee = U256::from(130000000000000u128); + + // Entry 1 + let nonce_1 = U256::from(1); + let nonced_verification_data_1 = NoncedVerificationData::new( + verification_data.clone(), + nonce_1, + max_fee, + chain_id, + payment_service_addr, + ); + let vd_commitment_1: VerificationDataCommitment = nonced_verification_data_1.clone().into(); + let entry_1 = BatchQueueEntry::new_for_testing( + nonced_verification_data_1, + vd_commitment_1, + dummy_signature, + sender_addr, + ); + let batch_priority_1 = BatchQueueEntryPriority::new(max_fee, nonce_1); + + // Entry 2 + let nonce_2 = U256::from(2); + let nonced_verification_data_2 = NoncedVerificationData::new( + verification_data.clone(), + nonce_2, + max_fee, + chain_id, + payment_service_addr, + ); + let vd_commitment_2: VerificationDataCommitment = nonced_verification_data_2.clone().into(); + let entry_2 = BatchQueueEntry::new_for_testing( + nonced_verification_data_2, + vd_commitment_2, + dummy_signature, + sender_addr, + ); + let batch_priority_2 = BatchQueueEntryPriority::new(max_fee, nonce_2); - assert!(resulting_batch_queue.is_empty()); + // Entry 3 + let nonce_3 = U256::from(3); + let nonced_verification_data_3 = NoncedVerificationData::new( + verification_data.clone(), + nonce_3, + max_fee, + chain_id, + payment_service_addr, + ); + let vd_commitment_3: VerificationDataCommitment = nonced_verification_data_3.clone().into(); + let entry_3 = BatchQueueEntry::new_for_testing( + nonced_verification_data_3, + vd_commitment_3, + dummy_signature, + sender_addr, + ); + let batch_priority_3 = BatchQueueEntryPriority::new(max_fee, nonce_3); - assert_eq!(batch[0].nonced_verification_data.max_fee, max_fee_3); - assert_eq!(batch[1].nonced_verification_data.max_fee, max_fee_2); - assert_eq!(batch[2].nonced_verification_data.max_fee, max_fee_1); + let mut batch_queue = BatchQueue::new(); + batch_queue.push(entry_1, batch_priority_1); + batch_queue.push(entry_2, batch_priority_2); + batch_queue.push(entry_3, batch_priority_3); + + let gas_price = U256::from(1); + let finalized_batch = try_build_batch(batch_queue.clone(), gas_price, 5000000, 50).unwrap(); + + // All entries from the batch queue should be in + // the finalized batch. + assert!(batch_queue.len() == 3); + + assert_eq!(finalized_batch[0].nonced_verification_data.nonce, nonce_3); + assert_eq!(finalized_batch[1].nonced_verification_data.nonce, nonce_2); + assert_eq!(finalized_batch[2].nonced_verification_data.nonce, nonce_1); + + // sanity check + assert_eq!(finalized_batch[2].nonced_verification_data.max_fee, max_fee); + } + + #[test] + fn batch_finalization_algorithm_works_from_same_sender_same_fee_nonempty_resulting_queue() { + // The following information will be the same for each entry, it is just some dummy data to see + // algorithm working. + + let proof_generator_addr = Address::random(); + let payment_service_addr = Address::random(); + let sender_addr = Address::random(); + let bytes_for_verification_data = vec![42_u8; 10]; + let dummy_signature = Signature { + r: U256::from(1), + s: U256::from(2), + v: 3, + }; + let verification_data = VerificationData { + proving_system: ProvingSystemId::Risc0, + proof: bytes_for_verification_data.clone(), + pub_input: Some(bytes_for_verification_data.clone()), + verification_key: Some(bytes_for_verification_data.clone()), + vm_program_code: Some(bytes_for_verification_data), + proof_generator_addr, + }; + let chain_id = U256::from(42); + + // Here we create different entries for the batch queue. + // All with the same fee + + let max_fee = U256::from(130000000000000u128); + + // Entry 1 + let nonce_1 = U256::from(1); + let nonced_verification_data_1 = NoncedVerificationData::new( + verification_data.clone(), + nonce_1, + max_fee, + chain_id, + payment_service_addr, + ); + let vd_commitment_1: VerificationDataCommitment = nonced_verification_data_1.clone().into(); + let entry_1 = BatchQueueEntry::new_for_testing( + nonced_verification_data_1, + vd_commitment_1, + dummy_signature, + sender_addr, + ); + let batch_priority_1 = BatchQueueEntryPriority::new(max_fee, nonce_1); + + // Entry 2 + let nonce_2 = U256::from(2); + let nonced_verification_data_2 = NoncedVerificationData::new( + verification_data.clone(), + nonce_2, + max_fee, + chain_id, + payment_service_addr, + ); + let vd_commitment_2: VerificationDataCommitment = nonced_verification_data_2.clone().into(); + let entry_2 = BatchQueueEntry::new_for_testing( + nonced_verification_data_2, + vd_commitment_2, + dummy_signature, + sender_addr, + ); + let batch_priority_2 = BatchQueueEntryPriority::new(max_fee, nonce_2); + + // Entry 3 + let nonce_3 = U256::from(3); + let nonced_verification_data_3 = NoncedVerificationData::new( + verification_data.clone(), + nonce_3, + max_fee, + chain_id, + payment_service_addr, + ); + let vd_commitment_3: VerificationDataCommitment = nonced_verification_data_3.clone().into(); + let entry_3 = BatchQueueEntry::new_for_testing( + nonced_verification_data_3, + vd_commitment_3, + dummy_signature, + sender_addr, + ); + let batch_priority_3 = BatchQueueEntryPriority::new(max_fee, nonce_3); + + let mut batch_queue = BatchQueue::new(); + batch_queue.push(entry_1, batch_priority_1); + batch_queue.push(entry_2, batch_priority_2); + batch_queue.push(entry_3.clone(), batch_priority_3.clone()); + + let gas_price = U256::from(1); + let finalized_batch = try_build_batch(batch_queue.clone(), gas_price, 5000000, 2).unwrap(); + + // One Entry from the batch_queue should not be in the finalized batch + // Particularly, nonce_3 is not in the finalized batch + assert!(batch_queue.len() == 3); + + assert_eq!(finalized_batch[0].nonced_verification_data.nonce, nonce_2); + assert_eq!(finalized_batch[1].nonced_verification_data.nonce, nonce_1); } #[test] @@ -404,13 +614,11 @@ mod test { batch_queue.push(entry_3, batch_priority_3); let gas_price = U256::from(1); - let (resulting_batch_queue, finalized_batch) = - try_build_batch(batch_queue, gas_price, 5000000, 50).unwrap(); + let finalized_batch = try_build_batch(batch_queue.clone(), gas_price, 5000000, 50).unwrap(); - // The resulting batch queue (entries from the old batch queue that were not willing to pay - // in this batch), should be empty and hence, all entries from the batch queue should be in + // All entries from the batch queue should be in // the finalized batch. - assert!(resulting_batch_queue.is_empty()); + assert_eq!(batch_queue.len(), 3); assert_eq!(finalized_batch.len(), 3); assert_eq!( finalized_batch[0].nonced_verification_data.max_fee, @@ -515,14 +723,10 @@ mod test { batch_queue.push(entry_3, batch_priority_3); let gas_price = U256::from(1); - let (resulting_batch_queue, finalized_batch) = - try_build_batch(batch_queue, gas_price, 5000000, 50).unwrap(); - - // The resulting batch queue (entries from the old batch queue that were not willing to pay - // in this batch), should be empty and hence, all entries from the batch queue should be in - // the finalized batch. + let finalized_batch = try_build_batch(batch_queue.clone(), gas_price, 5000000, 50).unwrap(); - assert_eq!(resulting_batch_queue.len(), 1); + // All but one entries from the batch queue should be in the finalized batch. + assert_eq!(batch_queue.len(), 3); assert_eq!(finalized_batch.len(), 2); assert_eq!( finalized_batch[0].nonced_verification_data.max_fee, @@ -628,10 +832,10 @@ mod test { // The max batch len is 2, so the algorithm should stop at the second entry. let max_batch_proof_qty = 2; - let (resulting_batch_queue, finalized_batch) = - try_build_batch(batch_queue, gas_price, 5000000, max_batch_proof_qty).unwrap(); + let finalized_batch = + try_build_batch(batch_queue.clone(), gas_price, 5000000, max_batch_proof_qty).unwrap(); - assert_eq!(resulting_batch_queue.len(), 1); + assert_eq!(batch_queue.len(), 3); assert_eq!(finalized_batch.len(), 2); assert_eq!( finalized_batch[0].nonced_verification_data.max_fee, diff --git a/batcher/aligned-batcher/src/types/errors.rs b/batcher/aligned-batcher/src/types/errors.rs index b20fc45bb..1262045d6 100644 --- a/batcher/aligned-batcher/src/types/errors.rs +++ b/batcher/aligned-batcher/src/types/errors.rs @@ -1,8 +1,45 @@ use std::fmt; -use ethers::types::{Address, SignatureError}; +use ethers::types::{Address, Bytes, SignatureError}; use tokio_tungstenite::tungstenite; +pub enum TransactionSendError { + NoProofSubmitters, + NoFeePerProof, + InsufficientFeeForAggregator, + SubmissionInsufficientBalance, + BatchAlreadySubmitted, + InsufficientFunds, + OnlyBatcherAllowed, + Generic(String), +} + +impl From for TransactionSendError { + fn from(e: Bytes) -> Self { + let byte_string = e.to_string(); + let str_code = if byte_string.len() >= 10 { + &byte_string[..10] // Extract the error code only + } else { + "" // Not gonna match + }; + match str_code { + "0xc43ac290" => TransactionSendError::NoProofSubmitters, // can't happen, don't flush + "0xa3a8658a" => TransactionSendError::NoFeePerProof, // can't happen, don't flush + "0x7899ec71" => TransactionSendError::InsufficientFeeForAggregator, // shouldn't happen, don't flush + // returning the proofs and retrying later may help + "0x3102f10c" => TransactionSendError::BatchAlreadySubmitted, // can happen, don't flush + "0x5c54305e" => TransactionSendError::InsufficientFunds, // shouldn't happen, don't flush + "0x152bc288" => TransactionSendError::OnlyBatcherAllowed, // won't happen, don't flush + "0x4f779ceb" => TransactionSendError::SubmissionInsufficientBalance, // shouldn't happen, + // flush can help if something went wrong + _ => { + // flush because unkown error + TransactionSendError::Generic(format!("Unknown bytestring error: {}", byte_string)) + } + } + } +} + pub enum BatcherError { TcpListenerError(String), ConnectionError(tungstenite::Error), @@ -12,7 +49,7 @@ pub enum BatcherError { BatchUploadError(String), TaskCreationError(String), ReceiptNotFoundError, - TransactionSendError(String), + TransactionSendError(TransactionSendError), MaxRetriesReachedError, SerializationError(String), GasPriceError, @@ -20,6 +57,7 @@ pub enum BatcherError { BatchCostTooHigh, WsSinkEmpty, AddressNotFoundInUserStates(Address), + QueueRemoveError(String), } impl From for BatcherError { @@ -98,6 +136,40 @@ impl fmt::Debug for BatcherError { reason ) } + BatcherError::QueueRemoveError(e) => { + write!(f, "Error while removing entry from queue: {}", e) + } + } + } +} + +impl fmt::Display for TransactionSendError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + TransactionSendError::NoProofSubmitters => { + write!(f, "No proof submitter error") + } + TransactionSendError::NoFeePerProof => { + write!(f, "No fee per proof") + } + TransactionSendError::InsufficientFeeForAggregator => { + write!(f, "Insufficient fee for aggregator") + } + TransactionSendError::SubmissionInsufficientBalance => { + write!(f, "Submission insufficient balance") + } + TransactionSendError::BatchAlreadySubmitted => { + write!(f, "Batch already submitted") + } + TransactionSendError::InsufficientFunds => { + write!(f, "Insufficient funds") + } + TransactionSendError::OnlyBatcherAllowed => { + write!(f, "Only batcher allowed") + } + TransactionSendError::Generic(e) => { + write!(f, "Generic error: {}", e) + } } } } diff --git a/batcher/aligned-sdk/src/core/constants.rs b/batcher/aligned-sdk/src/core/constants.rs index d45189000..b7a253f7a 100644 --- a/batcher/aligned-sdk/src/core/constants.rs +++ b/batcher/aligned-sdk/src/core/constants.rs @@ -8,7 +8,7 @@ pub const CONSTANT_GAS_COST: u128 = + BATCHER_SUBMISSION_BASE_GAS_COST; pub const DEFAULT_MAX_FEE_PER_PROOF: u128 = ADDITIONAL_SUBMISSION_GAS_COST_PER_PROOF * 100_000_000_000; // gas_price = 100 Gwei = 0.0000001 ether (high gas price) -pub const CONNECTION_TIMEOUT: u64 = 5; // 5 secs +pub const CONNECTION_TIMEOUT: u64 = 30; // 30 secs // % modifiers: (100% is x1, 10% is x0.1, 1000% is x10) pub const RESPOND_TO_TASK_FEE_LIMIT_PERCENTAGE_MULTIPLIER: u128 = 250; // fee_for_aggregator -> respondToTaskFeeLimit modifier diff --git a/batcher/aligned/Cargo.toml b/batcher/aligned/Cargo.toml index 462f9ec50..8b55824cd 100644 --- a/batcher/aligned/Cargo.toml +++ b/batcher/aligned/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aligned" -version = "0.12.2" +version = "0.13.0" edition = "2021" [dependencies] diff --git a/batcher/aligned/src/main.rs b/batcher/aligned/src/main.rs index ea2870efb..1153cacbd 100644 --- a/batcher/aligned/src/main.rs +++ b/batcher/aligned/src/main.rs @@ -108,9 +108,9 @@ pub struct SubmitArgs { #[arg(name = "Private key", long = "private_key")] private_key: Option, #[arg( - name = "Max Fee", + name = "Max Fee (ether)", long = "max_fee", - default_value = "1300000000000000" // 13_000 gas per proof * 100 gwei gas price (upper bound) + default_value = "0.0013ether" // 13_000 gas per proof * 100 gwei gas price (upper bound) )] max_fee: String, // String because U256 expects hex #[arg(name = "Nonce", long = "nonce")] @@ -279,8 +279,16 @@ async fn main() -> Result<(), AlignedError> { SubmitError::IoError(batch_inclusion_data_directory_path.clone(), e) })?; - let max_fee = - U256::from_dec_str(&submit_args.max_fee).map_err(|_| SubmitError::InvalidMaxFee)?; + if !submit_args.max_fee.ends_with("ether") { + error!("`max_fee` should be in the format XX.XXether"); + return Ok(()); + } + + let max_fee_ether = submit_args.max_fee.replace("ether", ""); + + let max_fee_wei: U256 = parse_ether(&max_fee_ether).map_err(|e| { + SubmitError::EthereumProviderError(format!("Error while parsing amount: {}", e)) + })?; let repetitions = submit_args.repetitions; let connect_addr = submit_args.batcher_url.clone(); @@ -360,7 +368,7 @@ async fn main() -> Result<(), AlignedError> { &connect_addr, submit_args.network.into(), &verification_data_arr, - max_fee, + max_fee_wei, wallet.clone(), nonce, ) @@ -445,13 +453,13 @@ async fn main() -> Result<(), AlignedError> { } DepositToBatcher(deposit_to_batcher_args) => { if !deposit_to_batcher_args.amount.ends_with("ether") { - error!("Amount should be in the format XX.XXether"); + error!("`amount` should be in the format XX.XXether"); return Ok(()); } - let amount = deposit_to_batcher_args.amount.replace("ether", ""); + let amount_ether = deposit_to_batcher_args.amount.replace("ether", ""); - let amount_ether = parse_ether(&amount).map_err(|e| { + let amount_wei = parse_ether(&amount_ether).map_err(|e| { SubmitError::EthereumProviderError(format!("Error while parsing amount: {}", e)) })?; @@ -482,7 +490,7 @@ async fn main() -> Result<(), AlignedError> { let client = SignerMiddleware::new(eth_rpc_provider.clone(), wallet.clone()); - match deposit_to_aligned(amount_ether, client, deposit_to_batcher_args.network.into()) + match deposit_to_aligned(amount_wei, client, deposit_to_batcher_args.network.into()) .await { Ok(receipt) => { diff --git a/contracts/script/output/holesky/alignedlayer_deployment_output.stage.json b/contracts/script/output/holesky/alignedlayer_deployment_output.stage.json index fd1fa126c..85d255d79 100644 --- a/contracts/script/output/holesky/alignedlayer_deployment_output.stage.json +++ b/contracts/script/output/holesky/alignedlayer_deployment_output.stage.json @@ -14,7 +14,7 @@ "stakeRegistry": "0x1b0C9b87b094d821911500F91914B1A1D2856F14", "stakeRegistryImplementation": "0x52A9e264b98fe2d53805937Bc094a981E7eB6BeE", "batcherPaymentService": "0x7577Ec4ccC1E6C529162ec8019A49C13F6DAd98b", - "batcherPaymentServiceImplementation": "0x230FF8F073C188732856990b070485FD1A9b1039" + "batcherPaymentServiceImplementation": "0x230FF8F073C188732856990b070485FD1A9b1039", "pauserRegistry": "0xBCd09a605906a13F917230F82BdAC15B778A5B03" }, "chainInfo": { diff --git a/contracts/scripts/set_aggregator_address.sh b/contracts/scripts/set_aggregator_address.sh new file mode 100755 index 000000000..d4839be10 --- /dev/null +++ b/contracts/scripts/set_aggregator_address.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +# cd to the directory of this script so that this can be run from anywhere +parent_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) +# At this point we are in contracts/scripts +cd "$parent_path" + +# At this point we are in contracts +cd ../ + +# Check if the number of arguments is correct +if [ "$#" -ne 1 ]; then + echo "Usage: set_aggregator_address.sh " + exit 1 +fi + +AGGREGATOR_ADDRESS=$1 + +# Read the service manager address from the JSON file +SERVICE_MANAGER=$(jq -r '.addresses.alignedLayerServiceManager' "$OUTPUT_PATH") + +# Check if the servide manager address is empty +if [ -z "$SERVICE_MANAGER" ]; then + echo "Service manager address is empty" + exit 1 +fi + +# Check if the Ethereum RPC URL is empty +if [ -z "$RPC_URL" ]; then + echo "Ethereum RPC URL is empty" + exit 1 +fi + +# Check if the private key is empty +if [ -z "$PRIVATE_KEY" ]; then + echo "Private key is empty" + exit 1 +fi + +# Call the setAggregator(address _alignedAggregator) function on the contract +cast send \ + --private-key=$PRIVATE_KEY \ + --rpc-url=$RPC_URL \ + $SERVICE_MANAGER "setAggregator(address)" \ + $AGGREGATOR_ADDRESS diff --git a/core/chainio/avs_writer.go b/core/chainio/avs_writer.go index cd379ee23..bf39c0c6a 100644 --- a/core/chainio/avs_writer.go +++ b/core/chainio/avs_writer.go @@ -89,7 +89,7 @@ func NewAvsWriterFromConfig(baseConfig *config.BaseConfig, ecdsaConfig *config.E // - If no receipt is found, but the batch state indicates the response has already been processed, it exits // without an error (returning `nil, nil`). // - An error if the process encounters a fatal issue (e.g., permanent failure in verifying balances or state). -func (w *AvsWriter) SendAggregatedResponse(batchIdentifierHash [32]byte, batchMerkleRoot [32]byte, senderAddress [20]byte, nonSignerStakesAndSignature servicemanager.IBLSSignatureCheckerNonSignerStakesAndSignature, gasBumpPercentage uint, gasBumpIncrementalPercentage uint, gasBumpPercentageLimit uint, timeToWaitBeforeBump time.Duration, onGasPriceBumped func(*big.Int)) (*types.Receipt, error) { +func (w *AvsWriter) SendAggregatedResponse(batchIdentifierHash [32]byte, batchMerkleRoot [32]byte, senderAddress [20]byte, nonSignerStakesAndSignature servicemanager.IBLSSignatureCheckerNonSignerStakesAndSignature, gasBumpPercentage uint, gasBumpIncrementalPercentage uint, gasBumpPercentageLimit uint, timeToWaitBeforeBump time.Duration, metrics *metrics.Metrics, onSetGasPrice func(*big.Int)) (*types.Receipt, error) { txOpts := *w.Signer.GetTxOpts() txOpts.NoSend = true // simulate the transaction simTx, err := w.RespondToTaskV2Retryable(&txOpts, batchMerkleRoot, senderAddress, nonSignerStakesAndSignature, retry.SendToChainRetryParams()) @@ -141,6 +141,8 @@ func (w *AvsWriter) SendAggregatedResponse(batchIdentifierHash [32]byte, batchMe txOpts.GasPrice = minimumGasPriceBump } + onSetGasPrice(txOpts.GasPrice) + if i > 0 { w.logger.Infof("Trying to get old sent transaction receipt before sending a new transaction", "merkle root", batchMerkleRootHashString) for _, tx := range sentTxs { @@ -148,7 +150,7 @@ func (w *AvsWriter) SendAggregatedResponse(batchIdentifierHash [32]byte, batchMe if receipt == nil { receipt, _ = w.ClientFallback.TransactionReceipt(context.Background(), tx.Hash()) if receipt != nil { - w.checkIfAggregatorHadToPaidForBatcher(tx, batchIdentifierHash) + w.updateAggregatorGasCostMetrics(receipt, batchIdentifierHash) return receipt, nil } } @@ -161,7 +163,7 @@ func (w *AvsWriter) SendAggregatedResponse(batchIdentifierHash [32]byte, batchMe } w.logger.Infof("Batch state has not been responded yet, will send a new tx", "merkle root", batchMerkleRootHashString) - onGasPriceBumped(txOpts.GasPrice) + metrics.IncBumpedGasPriceForAggregatedResponse() } // We compare both Aggregator funds and Batcher balance in Aligned against respondToTaskFeeLimit @@ -183,7 +185,7 @@ func (w *AvsWriter) SendAggregatedResponse(batchIdentifierHash [32]byte, batchMe w.logger.Infof("Transaction sent, waiting for receipt", "merkle root", batchMerkleRootHashString) receipt, err := utils.WaitForTransactionReceiptRetryable(w.Client, w.ClientFallback, realTx.Hash(), retry.WaitForTxRetryParams(timeToWaitBeforeBump)) if receipt != nil { - w.checkIfAggregatorHadToPaidForBatcher(realTx, batchIdentifierHash) + w.updateAggregatorGasCostMetrics(receipt, batchIdentifierHash) return receipt, nil } @@ -204,18 +206,20 @@ func (w *AvsWriter) SendAggregatedResponse(batchIdentifierHash [32]byte, batchMe return retry.RetryWithData(respondToTaskV2Func, retry.RespondToTaskV2()) } -// Calculates the transaction cost from the receipt and compares it with the batcher respondToTaskFeeLimit -// if the tx cost was higher, then it means the aggregator has paid the difference for the batcher (txCost - respondToTaskFeeLimit) and so metrics are updated accordingly. -// otherwise nothing is done. -func (w *AvsWriter) checkIfAggregatorHadToPaidForBatcher(tx *types.Transaction, batchIdentifierHash [32]byte) { +// Calculates the transaction cost from the receipt and updates the total amount paid by the aggregator metric +// Then, it compares that tx cost with the batcher respondToTaskFeeLimit. +// If the tx cost was higher, it means the aggregator has paid the difference for the batcher (txCost - respondToTaskFeeLimit) and so metrics are updated accordingly. +func (w *AvsWriter) updateAggregatorGasCostMetrics(receipt *types.Receipt, batchIdentifierHash [32]byte) { batchState, err := w.BatchesStateRetryable(&bind.CallOpts{}, batchIdentifierHash, retry.NetworkRetryParams()) if err != nil { return } respondToTaskFeeLimit := batchState.RespondToTaskFeeLimit - // NOTE we are not using tx.Cost() because tx.Cost() includes tx.Value() - txCost := new(big.Int).Mul(big.NewInt(int64(tx.Gas())), tx.GasPrice()) + txCost := new(big.Int).Mul(big.NewInt(int64(receipt.GasUsed)), receipt.EffectiveGasPrice) + + txCostInEth := utils.WeiToEth(txCost) + w.metrics.AddAggregatorGasCostPaidTotal(txCostInEth) if respondToTaskFeeLimit.Cmp(txCost) < 0 { aggregatorDifferencePaid := new(big.Int).Sub(txCost, respondToTaskFeeLimit) diff --git a/docs/0_internal/4_b_1_propose_pause.md b/docs/0_internal/4_b_1_propose_pause.md index b86927663..3d2236175 100644 --- a/docs/0_internal/4_b_1_propose_pause.md +++ b/docs/0_internal/4_b_1_propose_pause.md @@ -18,7 +18,7 @@ To propose the pause transaction you can follow the steps below: ![Transaction Builder](./images/4_b_1_pause_2.png) -3. . Get the `AlignedLayerServiceManager` address from ```contracts/script/output/mainnet/alignedlayer_deployment_output.json``` or ```contracts/script/output/holesky/alignedlayer_deployment_output.json``` or ```contracts/script/output/sepolia/alignedlayer_deployment_output.json``` +3. Get the `AlignedLayerServiceManager` address from ```contracts/script/output/mainnet/alignedlayer_deployment_output.json``` or ```contracts/script/output/holesky/alignedlayer_deployment_output.json``` or ```contracts/script/output/sepolia/alignedlayer_deployment_output.json``` 4. Paste the `AlignedLayerServiceManager` address on `Enter Address or ENS Name` diff --git a/docs/0_internal/4_b_3_propose_unpause.md b/docs/0_internal/4_b_3_propose_unpause.md new file mode 100644 index 000000000..76cfc2ac6 --- /dev/null +++ b/docs/0_internal/4_b_3_propose_unpause.md @@ -0,0 +1,100 @@ +# Propose the Transaction for UnPause using Multisig + +If you want to unpause the contracts, you can propose the unpause transaction using the multisig wallet. + +## Prerequisites + +- You need to have deployed the contracts following the [Deploy Contracts Guide](./2_deploy_contracts.md). + +## Propose transaction for UnPause AlignedLayerServiceManager + +To propose the unpause transaction you can follow the steps below: + +1. Go to [Safe](https://app.safe.global/home) + +2. Click on `New transaction` -> `Transaction Builder` + + ![New transaction](./images/4_b_1_unpause_1.png) + + ![Transaction Builder](./images/4_b_1_unpause_2.png) + +3. Get the `AlignedLayerServiceManager` address from ```contracts/script/output/mainnet/alignedlayer_deployment_output.json``` or ```contracts/script/output/holesky/alignedlayer_deployment_output.json``` or ```contracts/script/output/sepolia/alignedlayer_deployment_output.json``` + +4. Paste the `AlignedLayerServiceManager` address on `Enter Address or ENS Name` + + ![Enter Address](./images/4_b_1_unpause_3.png) + +5. As this is a Proxy contract, choose `Use Implementation ABI` + + ![Use Implementation ABI](./images/4_b_1_unpause_4.png) + +6. In `contract method selector` choose `unpause()` and within `newPausedStatus(uint256)` enter the desired pause status as described in [pausable.md](./pausable.md). Use pause status `0` for pausable contracts. + + ![Choose pause](./images/4_b_1_unpause_5.png) + +7. Click on `+ Add new transaction` + + You should see the new transaction to be executed + +8. Click on `Create batch` to create the transaction. + + ![Choose pause](./images/4_b_1_unpause_6.png) + +9. Simulate the transaction by clicking on `Simulate` + +10. If everything is correct, click on `Send batch` to send the transaction. + +11. Simulate the transaction, and if everything is correct, click on `Sign`. + + ![Send batch](./images/4_b_1_unpause_7.png) + +> [!NOTE] +> In the `call` field, you will see `fallback`. +#### 12. Wait for the transaction to be executed. You can check the transaction status on the `Transactions` tab. + + +## Propose transaction for UnPause BatcherPaymentService + +To propose the unpause transaction you can follow the steps below: + +1. Create the unpause transaction on [Safe](https://app.safe.global/home) + +2. Click on `New transaction` -> `Transaction Builder` + + ![New transaction](./images/4_b_1_unpause_1.png) + + ![Transaction Builder](./images/4_b_1_unpause_2.png) + +3. Get the `BatcherPaymentService` address from ```contracts/script/output/mainnet/alignedlayer_deployment_output.json``` or ```contracts/script/output/holesky/alignedlayer_deployment_output.json``` or ```contracts/script/output/sepolia/alignedlayer_deployment_output.json``` + +4. Paste the `BatcherPaymentService` address on `Enter Address or ENS Name` + + ![Enter Address](./images/4_b_1_unpause_3.png) + +5. As this is a Proxy contract, choose `Use Implementation ABI` + + ![Use Implementation ABI](./images/4_b_1_unpause_4.png) + +6. In `contract method selector` choose `unpause()` + + ![Choose pause](./images/4_b_1_unpause_8.png) + +7. Then click on `+ Add new transaction` + + You should see the new transaction to be executed. Then click on `Create batch` to create the transaction. + + ![Add new transaction](./images/4_b_1_unpause_9.png) + +8. Review and confirm you are interacting with the correct `BatcherPaymentService` contract and you are calling the `unpause` function. + + ![Review transaction](./images/4_b_1_unpause_10.png) + +9. Simulate the transaction by clicking on `Simulate` + +10. If everything is correct, click on `Send batch` to send the transaction. + +11. Review the transaction and click on `Sign` to sign the transaction. + + ![Send batch](./images/4_b_1_unpause_11.png) + +12. If the transaction is correctly created, you have to wait until the required Multisig member signs the transaction to send it. For this, you can follow [the following guide](./4_b_4_approve_unpause.md) diff --git a/docs/0_internal/4_b_4_approve_unpause.md b/docs/0_internal/4_b_4_approve_unpause.md new file mode 100644 index 000000000..9a3c25154 --- /dev/null +++ b/docs/0_internal/4_b_4_approve_unpause.md @@ -0,0 +1,55 @@ +# Approve the UnPause Transaction + +Once the transaction is proposed, the multisig owners must approve the transaction. + +## Approve the UnPause for AlignedLayerServiceManager + +1. Go to [Safe](https://app.safe.global/home) and connect your wallet. + +2. Go to the `Transactions` tab and find the transaction that was proposed. + +3. Get the ```unpause(uint256)``` signature by running: + + ```bash + cast calldata "unpause(uint256)" + ``` + + for ```PAUSE_STATE=3``` + + It must show you ```0xfabc1cbc0000000000000000000000000000000000000000000000000000000000000003```, with ```0xfabc1cbc``` being the function identifier. + +4. Click on the transaction, and then click on ```Advanced Details```. + + ![Check details](images/4_b_2_approve_unpause_1.png) + +5. Copy the ```Raw Data```, paste it in a text editor and verify it is the same value as the one you got in step 3. + +6. If the data is correct, click on the `Confirm` button. + +7. Simulate the transaction. If everything is correct, click on the `Sign` button. + + ![Sign transaction](images/4_b_2_approve_unpause_2.png) + +8. Once the transaction is executed, the unpause will be effective. + +## Approve the UnPause for BatcherPaymentService + +1. Go to [Safe](https://app.safe.global/home) and connect your wallet. + +2. Go to the `Transactions` tab and find the transaction that was proposed. + +3. Click on the transaction and validate the data is correct. + + The called function must be `unpause()` and the contract address must be the `BatcherPaymentService` address. + + ![Check details](images/4_b_2_approve_unpause_3.png) + + Get the `BatcherPaymentService` address from ```contracts/script/output/mainnet/alignedlayer_deployment_output.json``` or ```contracts/script/output/holesky/alignedlayer_deployment_output.json``` or ```contracts/script/output/sepolia/alignedlayer_deployment_output.json``` + +4. If the data is correct, click on the `Confirm` button. + +5. Simulate the transaction. If everything is correct, click on the `Sign` button. + + ![Sign transaction](images/4_b_2_approve_unpause_4.png) + +6. Once the transaction is executed, the unpause will be effective. \ No newline at end of file diff --git a/docs/0_internal/5_a_whitelist_operators.md b/docs/0_internal/5_a_whitelist_operators.md new file mode 100644 index 000000000..fdb331d5b --- /dev/null +++ b/docs/0_internal/5_a_whitelist_operators.md @@ -0,0 +1,60 @@ +# Whitelist Operators +This doc contains a guide on how to Whitelist Operators in Aligned. + +To run the make targets specified in this guide, you must first set the following env vars within `./contracts/scripts/.env.`: +``` +RPC_URL= +OUTPUT_PATH= +PRIVATE_KEY= +``` + +## Without Multisig + +### Adding to Whitelist + +You can whitelist a single Operator as following: +``` +make operator_whitelist OPERATOR_ADDRESS= +``` + +Or you can whitelist multiple Operators as following: +``` +make operator_whitelist OPERATOR_ADDRESS= +``` + +Note how there are no spaces between the commas that separate each Operator address. + +For example: +``` +make operator_whitelist OPERATOR_ADDRESS=0x70997970C51812dc3A010C7d01b50e0d17dc79C8,0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC,0x90F79bf6EB2c4f870365E785982E1f101E93b906 +``` + +### Removing from Whitelist + +You can remove from whitelist a single Operator as following: +``` +make operator_remove_from_whitelist OPERATOR_ADDRESS= +``` + +Or you can remove from whitelist multiple Operators as following: +``` +make operator_remove_from_whitelist OPERATOR_ADDRESS= +``` + +Note how there are no spaces between the commas that separate each Operator address. + +For example: +``` +make operator_remove_from_whitelist OPERATOR_ADDRESS=0x70997970C51812dc3A010C7d01b50e0d17dc79C8,0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC,0x90F79bf6EB2c4f870365E785982E1f101E93b906 +``` + +### Viewing Operator Whitelist status + +```bash +cast call --rpc-url "isWhitelisted(address)" +``` + + +## With Multisig + +To add or remove Operators from the Whitelist using a Multisig, you can follow the next [guide](./5_b_1_propose_whitelist.md) diff --git a/docs/0_internal/5_b_1_propose_whitelist.md b/docs/0_internal/5_b_1_propose_whitelist.md new file mode 100644 index 000000000..b5283182c --- /dev/null +++ b/docs/0_internal/5_b_1_propose_whitelist.md @@ -0,0 +1,59 @@ +# Propose the Transaction for Whitelisting Operators using Multisig + +If you want to Whitelist Operators, you can propose the whitelist operator transaction using the multisig wallet. + +## Prerequisites + +- You need to have deployed the contracts following the [Deploy Contracts Guide](./2_deploy_contracts.md). + +## Propose transaction for Whitelist Operators + +To propose the whitelist transaction you can follow the steps below: + +1. Go to [Safe](https://app.safe.global/home) + +2. Click on `New transaction` -> `Transaction Builder` + + ![New transaction](./images/5_b_1_whitelist_operator_1.png) + + ![Transaction Builder](./images/5_b_1_whitelist_operator_2.png) + +3. Get the `registryCoordinator` address from ```contracts/script/output/mainnet/alignedlayer_deployment_output.json``` or ```contracts/script/output/holesky/alignedlayer_deployment_output.json``` or ```contracts/script/output/sepolia/alignedlayer_deployment_output.json``` + +4. Paste the `registryCoordinator` address on `Enter Address or ENS Name` + + ![](./images/5_b_1_whitelist_operator_3.png) + +5. As this is a Proxy contract, choose `Use Implementation ABI` + + ![Use Implementation ABI](./images/5_b_1_whitelist_operator_4.png) + +If `Use Implementation ABI`, did not show up you will need to submit the call via raw calldata. Consult this alternative [guide](./5_b_1b_propose_whitelist_with_call_data.md) + +6. In `Contract Method Selector` choose `add_multiple()` in the `_addresses(address[])` field, enter the operator addresses in the following format `[, ..., ]` for example, `[0000000000000000000000000000000000000009, 0000000000000000000000000000000000000003]` + + ![Choose the add_multiple()](./images/5_b_1_whitelist_operator_5.png) + +7. Click on `+ Add new transaction` + + You should see the new transaction to be executed + + ![alt text](./images/5_b_1_whitelist_operator_6.png) + +8. Click on `Create batch` to create the transaction. + + ![alt text](./images/5_b_1_whitelist_operator_7.png) + +9. Simulate the transaction by clicking on `Simulate` + + ![alt text](./images/5_b_1_whitelist_operator_8.png) + +10. If everything is correct, click on `Send batch` to send the transaction. + +11. Simulate the transaction, and if everything is correct, click on `Sign`. + + ![alt text](./images/5_b_1_whitelist_operator_9.png) + +12. Wait for the transaction to be executed. You can check the transaction status on the `Transactions` tab. + +13. If the transaction is correctly created, you have to wait until the required Multisig member signs the transaction to send it. For this, you can follow [the following guide](./5_b_2_approve_whitelist.md) diff --git a/docs/0_internal/5_b_1b_propose_whitelist_with_call_data.md b/docs/0_internal/5_b_1b_propose_whitelist_with_call_data.md new file mode 100644 index 000000000..e08e9e7c0 --- /dev/null +++ b/docs/0_internal/5_b_1b_propose_whitelist_with_call_data.md @@ -0,0 +1,20 @@ +Turn on the `Custom Data` flag. + ![alt text](./images/5_b_1b_whitelist_operator_with_call_data_1.png) + +Generate the call data for function to white list an operator ```add_multiple(address[])``` by running: + +```bash +cast calldata "add_multiple(address[])" "[, ...]" +``` + +For example: +```bash +cast calldata "add_multiple(address[])" "[0x0000000000000000000000000000000000000009, 0x0000000000000000000000000000000000000003]" +``` +will display ```0x6c7089040000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000003``` + +Confirm the calldata starts with the correct function identifier ```0x6c708904```. + +In `Data` field paste the previously generated call data. Also check the `To Address` is the correct `registryCoordinator` address, and the `ETH value` should be set to `0`. + + ![alt text](./images/5_b_1b_whitelist_operator_with_call_data_2.png) diff --git a/docs/0_internal/5_b_2_approve_whitelist.md b/docs/0_internal/5_b_2_approve_whitelist.md new file mode 100644 index 000000000..d9af01079 --- /dev/null +++ b/docs/0_internal/5_b_2_approve_whitelist.md @@ -0,0 +1,37 @@ +# Approve the Whitelist Transaction + +Once the transaction is proposed, the multisig owners must approve the transaction. + +## Approve the Whitelist for registryCoordinator + +1. Go to [Safe](https://app.safe.global/home) and connect your wallet. + +2. Go to the `Transactions` tab and find the transaction that was proposed. + +3. Get the ```add_multiple(address[])``` signature by running: + + ```bash + cast calldata "add_multiple(address[])" "[, ...]" + ``` + + For example: + ```bash + cast calldata "add_multiple(address[])" "[0x0000000000000000000000000000000000000009, 0x0000000000000000000000000000000000000003]" + ``` + will display ```0x6c7089040000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000003``` + + Confirm the calldata starts with the correct function identifier ```0x6c708904```. + +4. Click on the transaction, and then click on ```Advanced Details```. + + ![Check details](images/5_b_2_whitelist_operator_1.png) + +5. Copy the ```Raw Data```, paste it in a text editor and verify it is the same value as the one you got in step 3. + +6. If the data is correct, click on the `Confirm` button. + +7. Simulate the transaction. If everything is correct, click on the `Sign` button. + + ![Sign transaction](images/5_b_2_whitelist_operator_2.png) + +8. Once the transaction is executed, the operator will be whitelisted within the `registryCoordinator` contract. \ No newline at end of file diff --git a/docs/0_internal/5_b_3_propose_remove_whitelist.md b/docs/0_internal/5_b_3_propose_remove_whitelist.md new file mode 100644 index 000000000..c95f4a125 --- /dev/null +++ b/docs/0_internal/5_b_3_propose_remove_whitelist.md @@ -0,0 +1,59 @@ +# Propose the Transaction for Removing Operators using Multisig + +If you want to Removing Operators, you can propose the remove operator transaction using the multisig wallet. + +## Prerequisites + +- You need to have deployed the contracts following the [Deploy Contracts Guide](./2_deploy_contracts.md). + +## Propose transaction for Removing Operators + +To propose a remove operator from whitelist transaction you can follow the steps below: + +1. Go to [Safe](https://app.safe.global/home) + +2. Click on `New transaction` -> `Transaction Builder` + + ![New transaction](./images/5_b_3_remove_operator_1.png) + + ![Transaction Builder](./images/5_b_3_remove_operator_2.png) + +3. Get the `registryCoordinator` address from ```contracts/script/output/mainnet/alignedlayer_deployment_output.json``` or ```contracts/script/output/holesky/alignedlayer_deployment_output.json``` or ```contracts/script/output/sepolia/alignedlayer_deployment_output.json``` + +4. Paste the `registryCoordinator` address on `Enter Address or ENS Name` + + ![](./images/5_b_3_remove_operator_3.png) + +5. As this is a Proxy contract, choose `Use Implementation ABI` + + ![Use Implementation ABI](./images/5_b_3_remove_operator_4.png) + +If `Use Implementation ABI`, did not show up you will need to submit the call via raw calldata. Consult this this alternative [guide](./5_b_3b_propose_remove_operator_with_call_data.md) + +6. In `contract method selector` choose `remove_multiple()` in the `_addresses(address[])` field, enter the operator addresses in the following format `[, ..., ]` for example, `[0000000000000000000000000000000000000009, 0000000000000000000000000000000000000003]` + + ![Choose the add_multiple()](./images/5_b_3_remove_operator_5.png) + +7. Click on `+ Add new transaction` + + You should see the new transaction to be executed + + ![alt text](./images/5_b_3_remove_operator_6.png) + +8. Click on `Create Batch` to create the transaction. + + ![alt text](./images/5_b_3_remove_operator_7.png) + +9. Simulate the transaction by clicking on `Simulate` + + ![alt text](./images/5_b_3_remove_operator_8.png) + +10. If everything is correct, click on `Send batch` to send the transaction. + +11. Simulate the transaction, and if everything is correct, click on `Sign`. + + ![alt text](./images/5_b_3_remove_operator_9.png) + +12. Wait for the transaction to be executed. You can check the transaction status on the `Transactions` tab. + +13. If the transaction is correctly created, you have to wait until the required Multisig member signs the transaction to send it. For this, you can follow [the following guide](./5_b_4_approve_remove_operator.md) diff --git a/docs/0_internal/5_b_3b_propose_remove_whitelist_with_call_data.md b/docs/0_internal/5_b_3b_propose_remove_whitelist_with_call_data.md new file mode 100644 index 000000000..c3da16434 --- /dev/null +++ b/docs/0_internal/5_b_3b_propose_remove_whitelist_with_call_data.md @@ -0,0 +1,21 @@ +Turn on the `Custom Data` flag. + ![alt text](./images/5_b_3b_remove_operator_with_call_data_1.png) + +Generate the call data for function to white list an operator ```remove_multiple(address[])``` by running: + +```bash +cast calldata "remove_multiple(address[])" "[, ...]" +``` + +For example: +```bash +cast calldata "remove_multiple(address[])" "[0x0000000000000000000000000000000000000009, 0x0000000000000000000000000000000000000003]" +``` + +will display ```0x53abfad40000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000003``` + +Confirm the calldata starts with the correct function identifier ```0x53abfad4```. + +In `Data` field paste the previously generated call data. Also check the `To Address` is the correct `registryCoordinator` address, and the `ETH value` should be set to `0`. + + ![alt text](./images/5_b_3b_remove_operator_with_call_data_2.png) diff --git a/docs/0_internal/5_b_4_approve_remove_whitelist.md b/docs/0_internal/5_b_4_approve_remove_whitelist.md new file mode 100644 index 000000000..1f9b9522f --- /dev/null +++ b/docs/0_internal/5_b_4_approve_remove_whitelist.md @@ -0,0 +1,37 @@ +# Approve the Remove Operator Transaction + +Once the transaction is proposed, the multisig owners must approve the transaction. + +## Approve the Remove Operator for registryCoordinator + +1. Go to [Safe](https://app.safe.global/home) and connect your wallet. + +2. Go to the `Transactions` tab and find the transaction that was proposed. + +3. Get the ```remove_multiple(address[])``` signature by running: + + ```bash + cast calldata "remove_multiple(address[])" "[, ...]" + ``` + + For example: + ```bash + cast calldata "remove_multiple(address[])" "[0x0000000000000000000000000000000000000009, 0x0000000000000000000000000000000000000003]" + ``` + will display ```0x53abfad40000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000003``` + + Confirm the calldata starts with the correct function identifier ```0x53abfad4```. + +4. Click on the transaction, and then click on ```Advanced Details```. + + ![Check details](images/5_b_4_remove_operator_1.png) + +5. Copy the ```Raw Data```, paste it in a text editor and verify it is the same value as the one you got in step 3. + +6. If the data is correct, click on the `Confirm` button. + +7. Simulate the transaction. If everything is correct, click on the `Sign` or `Execute` (if you are the last signer) button. + + ![Sign transaction](images/5_b_4_remove_operator_2.png) + +8. Once the transaction is executed, the operator will be removed within the `registryCoordinator` contract. \ No newline at end of file diff --git a/docs/0_internal/5_b_whitelist_operators_with_multisig.md b/docs/0_internal/5_b_whitelist_operators_with_multisig.md new file mode 100644 index 000000000..332bcb693 --- /dev/null +++ b/docs/0_internal/5_b_whitelist_operators_with_multisig.md @@ -0,0 +1,47 @@ +# Whitelist Operators with a Multisig + +> [!WARNING] +> Safe Multisig Wallet is not currently supported in Holesky Testnet. +> For this reason, we deployed EigenLayer contracts in Sepolia to test the upgrade on AlignedLayer Contracts. + +> [!NOTE] +> EigenLayer Sepolia contracts information is available in `contracts/script/output/sepolia/eigenlayer_deployment_output.json`. + +> [!NOTE] +> You can find a guide on how to Deploy the contracts [here](./2_deploy_contracts.md). + +This guide is for Whitelisting Operators using a Multisig wallet. If you deployed the contract without a Multisig wallet, you can follow the [Whitelist Operators](./5_a_whitelist_operators.md) tutorial. + +In this guide we add and remove Operators from Aligned's Whitelist using a multisig wallet. + +## Prerequisites + +- You need to have installed + - git + - make + - [jq](https://jqlang.github.io/jq/download/) + +- Clone the repository + + ``` + git clone https://github.com/yetanotherco/aligned_layer.git + ``` + +- Install foundry + + ```shell + make install_foundry + foundryup -v nightly-a428ba6ad8856611339a6319290aade3347d25d9 + ``` + +## Steps for Whitelisting an Operator + +1. Propose the transaction with the multisig following the [Propose Whitelist Guide](./5_b_1_propose_whitelist.md). + +2. After the Whitelist is proposed, multisig participants can approve the whitelist transaction following the [Approve Whitelist Guide](./5_b_2_approve_whitelist.md). + +## Steps for Removing an Operator + +1. Propose the transaction with the multisig following the [Propose Remove Whitelist Guide](./5_b_3_propose_remove_whitelist.md). + +2. After the unpause is proposed, multisig participants can approve the remove following the [Approve Remove Whitelist Guide](./5_b_4_approve_remove_whitelist.md). diff --git a/docs/0_internal/7_a_setting_aggregator_address.md b/docs/0_internal/7_a_setting_aggregator_address.md new file mode 100644 index 000000000..c14dad131 --- /dev/null +++ b/docs/0_internal/7_a_setting_aggregator_address.md @@ -0,0 +1,59 @@ +# Setting Aggregator Address +This doc contains a guide on how to call `setAggregator(address)` to set the `alignedAggregator` value of the AlignedServiceManager.sol contract. + +### NOTE: +- This guide assumes the Aligned layer contracts have been sucessfully deployed and the deployment outputs are within `./contracts/script/output/` + +1. Locate the deployed Aligned Aggregator Address + +The address of Aligned Aggregator can be found in `./contracts/script/output//aligned_deployment_output.json` within: +``` + "permissions": { + ... + "alignedLayerAggregator": "/aligned_deployment_output.json` within: +``` + "addresses": { + ... + "alignedLayerServiceManager": "", + ... + } +``` + +3. Set Environment Variables + +To run the make targets specified in this guide, you must first set the following env vars within `./contracts/scripts/.env.`: +``` +RPC_URL= +PRIVATE_KEY= +ALIGNED_SERVICE_MANAGER_ADDRESS= +``` + +4. Check the current value of `alignedAggregator` within AlignedServiceManager.sol + +``` +cast call --rpc-url $ALIGNED_SERVICE_MANAGER_ADDRESS "alignedAggregator()(address)" +``` + +You should see that the returned address matches the address from `./contracts/script/output//aligned_deployment_output.json` + +5. Change the value of `alignedAggregator` within AlignedServiceManager.sol + +Set the environment variable `AGGREGATOR_ADDRESS` to the new address of the aggregator. +``` +export AGGREGATOR_ADDRESS= +make set_aggregator_address +``` + +6. Verify the Aligned Aggreagtor Address has changed +``` +cast call --rpc-url $ALIGNED_SERVICE_MANAGER_ADDRESS "alignedAggregator()(address)" +``` + +You should observe that the printed address matches the address in `AGGREGATOR_ADDRESS`. \ No newline at end of file diff --git a/docs/0_internal/7_b_1_propose_setting_aggregator_address_multisig.md b/docs/0_internal/7_b_1_propose_setting_aggregator_address_multisig.md new file mode 100644 index 000000000..1763ece2d --- /dev/null +++ b/docs/0_internal/7_b_1_propose_setting_aggregator_address_multisig.md @@ -0,0 +1,62 @@ +# Propose the Transaction for Setting Aggregator Address using Multisig + +If you want to set the Aggregator Address, you can propose the set Aggregator Address transaction using the multisig wallet. + +## Prerequisites + +- You need to have deployed the contracts following the [Deploy Contracts Guide](./2_deploy_contracts.md). + +## Propose transaction for Set Aggregator Address + +To propose the set aggregator address transaction you can follow the steps below: + +1. Go to [Safe](https://app.safe.global/home) + +2. Click on `New transaction` -> `Transaction Builder` + + ![New transaction](./images/7_b_1_set_aggregator_address_1.png) + + ![Transaction Builder](./images/7_b_1_set_aggregator_address_2.png) + +3. Get the `AlignedLayerServiceManager` address from ```contracts/script/output/mainnet/alignedlayer_deployment_output.json``` or ```contracts/script/output/holesky/alignedlayer_deployment_output.json``` or ```contracts/script/output/sepolia/alignedlayer_deployment_output.json``` + +4. Paste the `AlignedLayerServiceManager` address on `Enter Address or ENS Name` + + ![Enter Address](./images/7_b_1_set_aggregator_address_3.png) + +5. As this is a Proxy contract, choose `Use Implementation ABI` + + ![Use Implementation ABI](./images/7_b_1_set_aggregator_address_4.png) + +6. In `contract method selector` choose `setAggregator()` and within `_alignedAggregator(address)` enter the ethereum new address of the aggregator. + + ![Choose set_aggregator](./images/7_b_1_set_aggregator_address_5.png) + +7. Click on `+ Add new transaction` + + You should see the new transaction to be executed + +8. Click on `Create batch` to create the transaction. + + ![create batch](./images/7_b_1_set_aggregator_address_6.png) + +9. Simulate the transaction by clicking on `Simulate` + +10. If everything is correct, click on `Send batch` to send the transaction. + + ![Send batch](./images/7_b_1_set_aggregator_address_7.png) + +11. Simulate the transaction, and if everything is correct, click on `Sign`. + + ![Send batch](./images/7_b_1_set_aggregator_address_8.png) + +> [!NOTE] +> In the `call` field, you will see `fallback`. +12. Wait for the transaction to be executed. You can check the transaction status on the `Transactions` tab. + +13. Verify the Aligned Aggreagtor Address has changed +``` +cast call --rpc-url $ALIGNED_SERVICE_MANAGER_ADDRESS "alignedAggregator()(address)" +``` + +You should observe that the printed address matches the address in `AGGREGATOR_ADDRESS`. diff --git a/docs/0_internal/7_b_2_approve_setting_aggregator_address_multisig.md b/docs/0_internal/7_b_2_approve_setting_aggregator_address_multisig.md new file mode 100644 index 000000000..2e3a0a7ae --- /dev/null +++ b/docs/0_internal/7_b_2_approve_setting_aggregator_address_multisig.md @@ -0,0 +1,29 @@ +# Approve the Set Aggregator Address Transaction + +Once the transaction is proposed, the multisig owners must approve the transaction. + +## Approve the new Address for the Aggregator + +1. Go to [Safe](https://app.safe.global/home) and connect your wallet. + +2. Go to the `Transactions` tab and find the transaction that was proposed. + +3. Get the expected raw data by running by running: + +```bash +cast calldata "setAggregator(address)()" +``` + +4. Click on the transaction, and then click on ```Advanced Details```. + + ![Check details](images/7_b_2_set_aggregator_address_1.png) + +5. Copy the ```Raw Data```, paste it in a text editor and verify it is the same value as the one you got in step 3. + +6. If the data is correct, click on the `Confirm` button. + +7. Simulate the transaction. If everything is correct, click on the `Sign` button (or `Execute`, if you are the last one signing the transaction). + + ![Sign transaction](images/7_b_2_set_aggregator_address_2.png) + +8. Once the transaction is executed, the change will be effective. diff --git a/docs/0_internal/images/4_b_1_unpause_1.png b/docs/0_internal/images/4_b_1_unpause_1.png new file mode 100644 index 000000000..cff06afa8 Binary files /dev/null and b/docs/0_internal/images/4_b_1_unpause_1.png differ diff --git a/docs/0_internal/images/4_b_1_unpause_10.png b/docs/0_internal/images/4_b_1_unpause_10.png new file mode 100644 index 000000000..e72812967 Binary files /dev/null and b/docs/0_internal/images/4_b_1_unpause_10.png differ diff --git a/docs/0_internal/images/4_b_1_unpause_11.png b/docs/0_internal/images/4_b_1_unpause_11.png new file mode 100644 index 000000000..1e6bf5cf2 Binary files /dev/null and b/docs/0_internal/images/4_b_1_unpause_11.png differ diff --git a/docs/0_internal/images/4_b_1_unpause_2.png b/docs/0_internal/images/4_b_1_unpause_2.png new file mode 100644 index 000000000..59b0d5856 Binary files /dev/null and b/docs/0_internal/images/4_b_1_unpause_2.png differ diff --git a/docs/0_internal/images/4_b_1_unpause_3.png b/docs/0_internal/images/4_b_1_unpause_3.png new file mode 100644 index 000000000..29c457fe0 Binary files /dev/null and b/docs/0_internal/images/4_b_1_unpause_3.png differ diff --git a/docs/0_internal/images/4_b_1_unpause_4.png b/docs/0_internal/images/4_b_1_unpause_4.png new file mode 100644 index 000000000..02997be33 Binary files /dev/null and b/docs/0_internal/images/4_b_1_unpause_4.png differ diff --git a/docs/0_internal/images/4_b_1_unpause_5.png b/docs/0_internal/images/4_b_1_unpause_5.png new file mode 100644 index 000000000..fb023f8d0 Binary files /dev/null and b/docs/0_internal/images/4_b_1_unpause_5.png differ diff --git a/docs/0_internal/images/4_b_1_unpause_6.png b/docs/0_internal/images/4_b_1_unpause_6.png new file mode 100644 index 000000000..02d376a92 Binary files /dev/null and b/docs/0_internal/images/4_b_1_unpause_6.png differ diff --git a/docs/0_internal/images/4_b_1_unpause_7.png b/docs/0_internal/images/4_b_1_unpause_7.png new file mode 100644 index 000000000..4b2ba49a1 Binary files /dev/null and b/docs/0_internal/images/4_b_1_unpause_7.png differ diff --git a/docs/0_internal/images/4_b_1_unpause_8.png b/docs/0_internal/images/4_b_1_unpause_8.png new file mode 100644 index 000000000..8729d1a5d Binary files /dev/null and b/docs/0_internal/images/4_b_1_unpause_8.png differ diff --git a/docs/0_internal/images/4_b_1_unpause_9.png b/docs/0_internal/images/4_b_1_unpause_9.png new file mode 100644 index 000000000..702b23666 Binary files /dev/null and b/docs/0_internal/images/4_b_1_unpause_9.png differ diff --git a/docs/0_internal/images/4_b_2_approve_unpause_1.png b/docs/0_internal/images/4_b_2_approve_unpause_1.png new file mode 100644 index 000000000..3995be78e Binary files /dev/null and b/docs/0_internal/images/4_b_2_approve_unpause_1.png differ diff --git a/docs/0_internal/images/4_b_2_approve_unpause_2.png b/docs/0_internal/images/4_b_2_approve_unpause_2.png new file mode 100644 index 000000000..6b18ad5f8 Binary files /dev/null and b/docs/0_internal/images/4_b_2_approve_unpause_2.png differ diff --git a/docs/0_internal/images/4_b_2_approve_unpause_3.png b/docs/0_internal/images/4_b_2_approve_unpause_3.png new file mode 100644 index 000000000..b6d188ccf Binary files /dev/null and b/docs/0_internal/images/4_b_2_approve_unpause_3.png differ diff --git a/docs/0_internal/images/4_b_2_approve_unpause_4.png b/docs/0_internal/images/4_b_2_approve_unpause_4.png new file mode 100644 index 000000000..5177ec83f Binary files /dev/null and b/docs/0_internal/images/4_b_2_approve_unpause_4.png differ diff --git a/docs/0_internal/images/5_b_1_whitelist_operator_1.png b/docs/0_internal/images/5_b_1_whitelist_operator_1.png new file mode 100644 index 000000000..bf368670e Binary files /dev/null and b/docs/0_internal/images/5_b_1_whitelist_operator_1.png differ diff --git a/docs/0_internal/images/5_b_1_whitelist_operator_2.png b/docs/0_internal/images/5_b_1_whitelist_operator_2.png new file mode 100644 index 000000000..25c00502a Binary files /dev/null and b/docs/0_internal/images/5_b_1_whitelist_operator_2.png differ diff --git a/docs/0_internal/images/5_b_1_whitelist_operator_3.png b/docs/0_internal/images/5_b_1_whitelist_operator_3.png new file mode 100644 index 000000000..d0cb61e0e Binary files /dev/null and b/docs/0_internal/images/5_b_1_whitelist_operator_3.png differ diff --git a/docs/0_internal/images/5_b_1_whitelist_operator_4.png b/docs/0_internal/images/5_b_1_whitelist_operator_4.png new file mode 100644 index 000000000..4a38a68c4 Binary files /dev/null and b/docs/0_internal/images/5_b_1_whitelist_operator_4.png differ diff --git a/docs/0_internal/images/5_b_1_whitelist_operator_5.png b/docs/0_internal/images/5_b_1_whitelist_operator_5.png new file mode 100644 index 000000000..8ed093e62 Binary files /dev/null and b/docs/0_internal/images/5_b_1_whitelist_operator_5.png differ diff --git a/docs/0_internal/images/5_b_1_whitelist_operator_6.png b/docs/0_internal/images/5_b_1_whitelist_operator_6.png new file mode 100644 index 000000000..130fb654d Binary files /dev/null and b/docs/0_internal/images/5_b_1_whitelist_operator_6.png differ diff --git a/docs/0_internal/images/5_b_1_whitelist_operator_7.png b/docs/0_internal/images/5_b_1_whitelist_operator_7.png new file mode 100644 index 000000000..1f215669b Binary files /dev/null and b/docs/0_internal/images/5_b_1_whitelist_operator_7.png differ diff --git a/docs/0_internal/images/5_b_1_whitelist_operator_8.png b/docs/0_internal/images/5_b_1_whitelist_operator_8.png new file mode 100644 index 000000000..5ba52dfd9 Binary files /dev/null and b/docs/0_internal/images/5_b_1_whitelist_operator_8.png differ diff --git a/docs/0_internal/images/5_b_1_whitelist_operator_9.png b/docs/0_internal/images/5_b_1_whitelist_operator_9.png new file mode 100644 index 000000000..870b48978 Binary files /dev/null and b/docs/0_internal/images/5_b_1_whitelist_operator_9.png differ diff --git a/docs/0_internal/images/5_b_1b_whitelist_operator_with_call_data_1.png b/docs/0_internal/images/5_b_1b_whitelist_operator_with_call_data_1.png new file mode 100644 index 000000000..988dfb5fe Binary files /dev/null and b/docs/0_internal/images/5_b_1b_whitelist_operator_with_call_data_1.png differ diff --git a/docs/0_internal/images/5_b_1b_whitelist_operator_with_call_data_2.png b/docs/0_internal/images/5_b_1b_whitelist_operator_with_call_data_2.png new file mode 100644 index 000000000..2864a776d Binary files /dev/null and b/docs/0_internal/images/5_b_1b_whitelist_operator_with_call_data_2.png differ diff --git a/docs/0_internal/images/5_b_2_whitelist_operator_1.png b/docs/0_internal/images/5_b_2_whitelist_operator_1.png new file mode 100644 index 000000000..f3cb78f40 Binary files /dev/null and b/docs/0_internal/images/5_b_2_whitelist_operator_1.png differ diff --git a/docs/0_internal/images/5_b_2_whitelist_operator_2.png b/docs/0_internal/images/5_b_2_whitelist_operator_2.png new file mode 100644 index 000000000..1eda2a41c Binary files /dev/null and b/docs/0_internal/images/5_b_2_whitelist_operator_2.png differ diff --git a/docs/0_internal/images/5_b_3_remove_operator_1.png b/docs/0_internal/images/5_b_3_remove_operator_1.png new file mode 100644 index 000000000..27955857e Binary files /dev/null and b/docs/0_internal/images/5_b_3_remove_operator_1.png differ diff --git a/docs/0_internal/images/5_b_3_remove_operator_2.png b/docs/0_internal/images/5_b_3_remove_operator_2.png new file mode 100644 index 000000000..d186c2459 Binary files /dev/null and b/docs/0_internal/images/5_b_3_remove_operator_2.png differ diff --git a/docs/0_internal/images/5_b_3_remove_operator_3.png b/docs/0_internal/images/5_b_3_remove_operator_3.png new file mode 100644 index 000000000..124eeeb0b Binary files /dev/null and b/docs/0_internal/images/5_b_3_remove_operator_3.png differ diff --git a/docs/0_internal/images/5_b_3_remove_operator_4.png b/docs/0_internal/images/5_b_3_remove_operator_4.png new file mode 100644 index 000000000..9a8803c06 Binary files /dev/null and b/docs/0_internal/images/5_b_3_remove_operator_4.png differ diff --git a/docs/0_internal/images/5_b_3_remove_operator_5.png b/docs/0_internal/images/5_b_3_remove_operator_5.png new file mode 100644 index 000000000..fc28e447a Binary files /dev/null and b/docs/0_internal/images/5_b_3_remove_operator_5.png differ diff --git a/docs/0_internal/images/5_b_3_remove_operator_6.png b/docs/0_internal/images/5_b_3_remove_operator_6.png new file mode 100644 index 000000000..f82e7db2b Binary files /dev/null and b/docs/0_internal/images/5_b_3_remove_operator_6.png differ diff --git a/docs/0_internal/images/5_b_3_remove_operator_7.png b/docs/0_internal/images/5_b_3_remove_operator_7.png new file mode 100644 index 000000000..8206738a3 Binary files /dev/null and b/docs/0_internal/images/5_b_3_remove_operator_7.png differ diff --git a/docs/0_internal/images/5_b_3_remove_operator_8.png b/docs/0_internal/images/5_b_3_remove_operator_8.png new file mode 100644 index 000000000..5dd73e695 Binary files /dev/null and b/docs/0_internal/images/5_b_3_remove_operator_8.png differ diff --git a/docs/0_internal/images/5_b_3_remove_operator_9.png b/docs/0_internal/images/5_b_3_remove_operator_9.png new file mode 100644 index 000000000..1057814a8 Binary files /dev/null and b/docs/0_internal/images/5_b_3_remove_operator_9.png differ diff --git a/docs/0_internal/images/5_b_3b_remove_operator_with_call_data_1.png b/docs/0_internal/images/5_b_3b_remove_operator_with_call_data_1.png new file mode 100644 index 000000000..7d639d30d Binary files /dev/null and b/docs/0_internal/images/5_b_3b_remove_operator_with_call_data_1.png differ diff --git a/docs/0_internal/images/5_b_3b_remove_operator_with_call_data_2.png b/docs/0_internal/images/5_b_3b_remove_operator_with_call_data_2.png new file mode 100644 index 000000000..c0ed388b1 Binary files /dev/null and b/docs/0_internal/images/5_b_3b_remove_operator_with_call_data_2.png differ diff --git a/docs/0_internal/images/5_b_4_remove_operator_1.png b/docs/0_internal/images/5_b_4_remove_operator_1.png new file mode 100644 index 000000000..4ea4371fb Binary files /dev/null and b/docs/0_internal/images/5_b_4_remove_operator_1.png differ diff --git a/docs/0_internal/images/5_b_4_remove_operator_2.png b/docs/0_internal/images/5_b_4_remove_operator_2.png new file mode 100644 index 000000000..3fc233247 Binary files /dev/null and b/docs/0_internal/images/5_b_4_remove_operator_2.png differ diff --git a/docs/0_internal/images/7_b_1_set_aggregator_address_1.png b/docs/0_internal/images/7_b_1_set_aggregator_address_1.png new file mode 100644 index 000000000..acef452f7 Binary files /dev/null and b/docs/0_internal/images/7_b_1_set_aggregator_address_1.png differ diff --git a/docs/0_internal/images/7_b_1_set_aggregator_address_2.png b/docs/0_internal/images/7_b_1_set_aggregator_address_2.png new file mode 100644 index 000000000..ae48b2075 Binary files /dev/null and b/docs/0_internal/images/7_b_1_set_aggregator_address_2.png differ diff --git a/docs/0_internal/images/7_b_1_set_aggregator_address_3.png b/docs/0_internal/images/7_b_1_set_aggregator_address_3.png new file mode 100644 index 000000000..f05191e41 Binary files /dev/null and b/docs/0_internal/images/7_b_1_set_aggregator_address_3.png differ diff --git a/docs/0_internal/images/7_b_1_set_aggregator_address_4.png b/docs/0_internal/images/7_b_1_set_aggregator_address_4.png new file mode 100644 index 000000000..02997be33 Binary files /dev/null and b/docs/0_internal/images/7_b_1_set_aggregator_address_4.png differ diff --git a/docs/0_internal/images/7_b_1_set_aggregator_address_5.png b/docs/0_internal/images/7_b_1_set_aggregator_address_5.png new file mode 100644 index 000000000..6f0e7a711 Binary files /dev/null and b/docs/0_internal/images/7_b_1_set_aggregator_address_5.png differ diff --git a/docs/0_internal/images/7_b_1_set_aggregator_address_6.png b/docs/0_internal/images/7_b_1_set_aggregator_address_6.png new file mode 100644 index 000000000..3d2393468 Binary files /dev/null and b/docs/0_internal/images/7_b_1_set_aggregator_address_6.png differ diff --git a/docs/0_internal/images/7_b_1_set_aggregator_address_7.png b/docs/0_internal/images/7_b_1_set_aggregator_address_7.png new file mode 100644 index 000000000..052aa7d33 Binary files /dev/null and b/docs/0_internal/images/7_b_1_set_aggregator_address_7.png differ diff --git a/docs/0_internal/images/7_b_1_set_aggregator_address_8.png b/docs/0_internal/images/7_b_1_set_aggregator_address_8.png new file mode 100644 index 000000000..3f86d9548 Binary files /dev/null and b/docs/0_internal/images/7_b_1_set_aggregator_address_8.png differ diff --git a/docs/0_internal/images/7_b_2_set_aggregator_address_1.png b/docs/0_internal/images/7_b_2_set_aggregator_address_1.png new file mode 100644 index 000000000..1ca759570 Binary files /dev/null and b/docs/0_internal/images/7_b_2_set_aggregator_address_1.png differ diff --git a/docs/0_internal/images/7_b_2_set_aggregator_address_2.png b/docs/0_internal/images/7_b_2_set_aggregator_address_2.png new file mode 100644 index 000000000..9e4a13104 Binary files /dev/null and b/docs/0_internal/images/7_b_2_set_aggregator_address_2.png differ diff --git a/docs/3_guides/1_SDK_how_to.md b/docs/3_guides/1_SDK_how_to.md index 0be228618..6cdd1fb2b 100644 --- a/docs/3_guides/1_SDK_how_to.md +++ b/docs/3_guides/1_SDK_how_to.md @@ -12,7 +12,7 @@ To use this SDK in your Rust project, add the following to your `Cargo.toml`: ```toml [dependencies] -aligned-sdk = { git = "https://github.com/yetanotherco/aligned_layer", tag="v0.12.2" } +aligned-sdk = { git = "https://github.com/yetanotherco/aligned_layer", tag="v0.13.0" } ``` To find the latest release tag go to [releases](https://github.com/yetanotherco/aligned_layer/releases) and copy the diff --git a/docs/3_guides/6_setup_aligned.md b/docs/3_guides/6_setup_aligned.md index eaf488ecc..536deac04 100644 --- a/docs/3_guides/6_setup_aligned.md +++ b/docs/3_guides/6_setup_aligned.md @@ -7,63 +7,32 @@ Ensure you have the following installed: - [Go](https://go.dev/doc/install) - [Rust](https://www.rust-lang.org/tools/install) - [Foundry](https://book.getfoundry.sh/getting-started/installation) -- [zap-pretty](https://github.com/maoueh/zap-pretty) -- [abigen](https://geth.ethereum.org/docs/tools/abigen) -- [eigenlayer-cli](https://github.com/Layr-Labs/eigenlayer-cli.git) - [jq](https://jqlang.github.io/jq/) - [yq](https://github.com/mikefarah/yq) -To -install [Go](https://go.dev/doc/install), -[Rust](https://www.rust-lang.org/tools/install), [jq](https://jqlang.github.io/jq/) -and [yq](https://github.com/mikefarah/yq) go to the provided links and follow the instructions. +After installing foundryup, you need to install a specific Foundry version: -Install Go -dependencies ([zap-pretty](https://github.com/maoueh/zap-pretty), [abigen](https://geth.ethereum.org/docs/tools/abigen), [eigenlayer-cli](https://github.com/Layr-Labs/eigenlayer-cli.git)): - -```bash -make go_deps -``` - -Install [Foundry](https://book.getfoundry.sh/getting-started/installation): - -```bash -make install_foundry +```shell foundryup -i nightly-a428ba6ad8856611339a6319290aade3347d25d9 ``` -Install the necessary submodules and build all the FFIs for your OS: +Then run: -```bash +```shell make deps ``` -If you want to rebuild the FFIs, you can use: - -```bash -make build_all_ffi -``` - -### Booting Devnet with Default configs - -Before starting, you need to set up an S3 bucket. More data storage will be tested in the future. - -You need to fill the data in: - -`batcher/aligned-batcher/.env` - -And you can use this file as an example of how to fill it: - -`batcher/aligned-batcher/.env.example` +This will: -After having the env setup, run in different terminals the following commands to boot Aligned locally: +- Initialize git submodules +- Install: `eigenlayer-cli`, `zap-pretty` and `abigen` +- Build ffis for your os. -## Anvil +## Contracts and eth node -To start anvil, a local Ethereum devnet with all necessary contracts already deployed and ready to be interacted with, -run: +To start anvil, a local Ethereum devnet with all necessary contracts already deployed and ready to be interacted with, run: -```bash +```shell make anvil_start_with_block_time ``` @@ -92,36 +61,14 @@ When changing Aligned contracts, the anvil state needs to be updated with: make anvil_deploy_aligned_contracts ``` -To test the upgrade script for ServiceManager in the local devnet, run: - -```bash -make anvil_upgrade_aligned_contracts -``` - -To test the upgrade script for RegistryCoordintator in the local devnet, run: +Note that when changing the contracts, you must also re-generate the Go smart contract bindings: ```bash -make anvil_upgrade_registry_coordinator +make bindings ``` -Note that when upgrading the contracts, you must also: - -1. Re-generate the Go smart contract bindings: - - ```bash - make bindings - ``` - -2. Rebuild Aggregator and Operator Go binaries: - - ```bash - make build_binaries - ``` - ---- - ## Aggregator To start the [Aggregator](../2_architecture/components/5_aggregator.md): @@ -130,393 +77,72 @@ To start the [Aggregator](../2_architecture/components/5_aggregator.md): make aggregator_start ``` -
-To start the aggregator with a custom configuration: +or with a custom config: ```bash make aggregator_start CONFIG_FILE= ``` -
- ---- - ## Operator -To start an [Operator](../2_architecture/components/4_operator.md) -(note it also registers it): +To setup an [Operator](../2_architecture/components/4_operator.md) run: ```bash make operator_register_and_start ``` -If you need to start again the operator, and it's already registered, you can use: +or with a custom config: ```bash -make operator_start +make operator_register_and_start CONFIG_FILE= ``` +Different configs for operators can be found in `config-files/config-operator`. +
More information about Operator registration: -Operator needs to register in both EigenLayer and Aligned. Then it can start verifying proofs. - -### Register into EigenLayer - -To register an operator in EigenLayer Devnet with the default configuration, run: - -```bash -make operator_register_with_eigen_layer -``` - -To register an operator in EigenLayer with a custom configuration, run: - -```bash -make operator_register_with_eigen_layer CONFIG_FILE= -``` - -### Register into Aligned - -To register an operator in Aligned with the default configuration, run: - -```bash -make operator_register_with_aligned_layer -``` - -To register an operator in Aligned with a custom configuration, run: - -```bash -make operator_register_with_aligned_layer CONFIG_FILE= -``` - -### Full Registration in Anvil with one command - -To register an operator in EigenLayer and Aligned and deposit strategy tokens in EigenLayer with the default -configuration, run: +If you wish to only register an operator you can run: ```bash -make operator_full_registration +make operator_full_registration CONFIG_FILE ``` -To register an operator in EigenLayer and Aligned and deposit strategy tokens in EigenLayer with a custom configuration, -run: +and to start it once it has been registered: ```bash -make operator_full_registration CONFIG_FILE= -``` - -### Deposit Strategy Tokens in Anvil local devnet - -There is an ERC20 token deployed in the Anvil chain to use as a strategy token with EigenLayer. - -To deposit strategy tokens in the Anvil chain with the default configuration, run: - -```bash -make operator_mint_mock_tokens -make operator_deposit_into_mock_strategy -``` - -To deposit strategy tokens in the Anvil chain with a custom configuration, run: - -```bash -make operator_mint_mock_tokens CONFIG_FILE= -make operator_deposit_into_mock_strategy CONFIG_FILE= -``` - -### Deposit Strategy tokens in Holesky/Mainnet - -EigenLayer strategies are available in [eigenlayer-strategies](https://holesky.eigenlayer.xyz/restake). - -For Holesky, we are using [WETH](https://holesky.eigenlayer.xyz/restake/WETH) as the strategy token. - -To get HolETH and swap it for different strategies, you can use the -following [guide](https://docs.eigenlayer.xyz/eigenlayer/restaking-guides/restaking-user-guide/testnet/obtaining-testnet-eth-and-liquid-staking-tokens-lsts). - -### Config - -There is a default configuration for devnet purposes in `config-files/config.yaml`. -Also, there are three different configurations for the operator -in `config-files/devnet/operator-1.yaml`, `config-files/devnet/operator-2.yaml` -and `config-files/devnet/operator-3.yaml`. - -The configuration file has the following structure: - -```yaml -# Common variables for all the services -# 'production' only prints info and above. 'development' also prints debug -environment: -aligned_layer_deployment_config_file_path: -eigen_layer_deployment_config_file_path: -eth_rpc_url: -eth_ws_url: -eigen_metrics_ip_port_address: - -## ECDSA Configurations -ecdsa: - private_key_store_path: - private_key_store_password: - -## BLS Configurations -bls: - private_key_store_path: - private_key_store_password: - -## Operator Configurations -operator: - aggregator_rpc_server_ip_port_address: # This is the aggregator url - address: - earnings_receiver_address: # This is the address where the operator will receive the earnings, it can be the same as the operator address - delegation_approver_address: "0x0000000000000000000000000000000000000000" - staker_opt_out_window_blocks: 0 - metadata_url: "https://yetanotherco.github.io/operator_metadata/metadata.json" - enable_metrics: - metrics_ip_port_address: - max_batch_size: -# Operators variables needed for register it in EigenLayer -el_delegation_manager_address: # This is the address of the EigenLayer delegationManager -private_key_store_path: -bls_private_key_store_path: -signer_type: local_keystore -chain_id: -``` - -Changing operator keys: - -Operator keys can be changed if needed. - -{% hint style="warning" %} -When creating a new wallet keystore and private key please use strong passwords for your own protection. -{% endhint %} - -To create a keystore, run: - -```bash -cast wallet new-mnemonic -cast wallet import --private-key -``` - -To create an ECDSA keystore, run: - -```bash -eigenlayer operator keys import --key-type ecdsa -``` - -To create a BLS keystore, run: - -```bash -eigenlayer operator keys import --key-type bls +make operator_start CONFIG_FILE= ```
---- - ## Batcher -To start the [Batcher](../2_architecture/components/1_batcher.md): +To start the [Batcher](../2_architecture/components/1_batcher.md) locally: -```bash -make batcher_start -``` - -If you are testing locally, you can run this instead: ```bash make batcher_start_local ``` -
-More information about Batcher configuration: - -To run the batcher, you will need to set environment variables in a `.env` file in the same directory as the -batcher (`batcher/aligned-batcher/`). - -The necessary environment variables are: - -| Variable Name | Description | -| --------------------- | ------------------------------------------------------------------------------------------------------------------------------ | -| AWS_SECRET_ACCESS_KEY | Secret key to authenticate and authorize API requests to the AWS S3 Bucket. | -| AWS_REGION | Geographical region where the AWS S3 Bucket will be accessed. | -| AWS_ACCESS_KEY_ID | Access key used in combination with the AWS_SECRET_ACCESS_KEY to authenticate and authorize API requests to the AWS S3 Bucket. | -| AWS_BUCKET_NAME | Name of the AWS S3 Bucket. | -| RUST_LOG | Rust log level (info, debug, error, warn, etc.). | - -You can find an example `.env` file in [.env.example](../../batcher/aligned-batcher/.env.example) - -You can configure the batcher in `config-files/config.yaml`: - -```yaml -# Common variables for all the services -eth_rpc_url: -eth_ws_url: -aligned_layer_deployment_config_file_path: - -## Batcher Configurations -batcher: - block_interval: - batch_size_interval: - max_proof_size: - max_batch_size: - pre_verification_is_enabled: - -## ECDSA Configurations -ecdsa: - private_key_store_path: - private_key_store_password: -``` +This starts a [localstack](https://www.localstack.cloud/) to act as a replacement for S3. -### Run +If you want to use the batcher under a real `S3` connection you'll need to specify the environment variables under `batcher/aligned-batcher/.env` and then run: ```bash make batcher_start ``` -or - -```bash -make batcher_start_local -``` - -The latter version sets up a [localstack](https://www.localstack.cloud/) to act as a replacement for S3, -so you don't need to interact with (and give money to) AWS for your tests. - -
- --- -## Send test proofs - -Next, you can use some of the send proofs make targets. -All these proofs are pre-generated and for testing purposes, -feel free to generate your own tests to submit to Aligned. - -
-SP1 - -Send an individual proof: - -```bash -make batcher_send_sp1_task -``` - -Send a burst of 15 proofs: - -```bash -make batcher_send_sp1_burst -``` - -Send proofs indefinitely: - -```bash -make batcher_send_infinite_sp1 -``` - -
- -
-Risc0 - -Send an individual proof: - -```bash -make batcher_send_risc0_task -``` - -Send a burst of 15 proofs: - -```bash -make batcher_send_risc0_burst -``` - -
- -
-Plonk - -Send an individual BN254 proof: - -```bash -make batcher_send_plonk_bn254_task -``` - -Send a burst of 15 BN254 proofs: +# Other components -```bash -make batcher_send_plonk_bn254_burst -``` - -Send an individual BLS12-381 proof: - -```bash -make batcher_send_plonk_bls12_381_task -``` - -Send a burst of 15 BLS12-381 proofs: - -```bash -make batcher_send_plonk_bls12_381_burst -``` - -
- -
-Groth16 - -Send an individual BN254 proof: - -```bash -make batcher_send_groth16_bn254_task -``` - -Send BN254 proofs indefinitely: - -```bash -make batcher_send_infinite_groth16 -``` - -Send BN254 proof bursts indefinitely: - -```bash -make batcher_send_burst_groth16 -``` - -
- -
-Send a specific proof: - -To install the Aligned client to send a specific proof, run: - -```bash -make install_aligned_compiling -``` - -The SP1 and Risc0 proofs need the proof file and the vm program file. -The current SP1 version used in Aligned is -`v3.0.0` and the current Risc0 version used in Aligned is `v1.1.2`. -The GnarkPlonkBn254, GnarkPlonkBls12_381 and Groth16Bn254 proofs need the proof file, the public input file and the -verification key file. - -```bash -aligned submit \ ---proving_system \ ---proof \ ---vm_program \ ---pub_input \ ---proof_generator_addr [proof_generator_addr] \ ---batch_inclusion_data_directory_path [batch_inclusion_data_directory_path] \ ---keystore_path [path_to_ecdsa_keystore] \ ---batcher_url wss://batcher.alignedlayer.com \ ---rpc_url https://ethereum-holesky-rpc.publicnode.com -``` - -
+Aligned also counts with 2 external components, which are not necessary for Aligned to work, but are useful for observability. ## Explorer -If you also want to start the explorer for the devnet, to clearly visualize your submitted and verified batches, see how -to run it using the following documentation: +### Dependencies -### Minimum Requirements +Ensure you have the following installed: - [Erlang 26](https://github.com/asdf-vm/asdf-erlang) - [Elixir 1.16.2](https://elixir-ko.github.io/install.html), compiled with OTP 26 @@ -525,58 +151,33 @@ to run it using the following documentation: - Tested with node 20 and 22 - [pnpm](https://pnpm.io/installation) -### DB Setup - -> [!NOTE] -> If you want to run the DB separately, without docker, -> you can set it up and start the explorer with the following command: -> ```bash -> make run_explorer_without_docker_db -> ``` - -To set up the explorer, an installation of the DB is necessary. - -First, you'll need to install docker if you don't have it already. -You can follow the instructions [here](https://docs.docker.com/get-docker/). +After installing the necessary deps, setup the environment variables by running: -The explorer uses a PostgreSQL database. To build and start the DB using docker, run: - -```bash -make explorer_build_db +```shell +make explorer_create_env ``` -
- - - (Optional) The steps to manually execute the database are as follows... - +Then start the explorer: -- Run the database container, opening port `5432`: - -```bash -make explorer_run_db +```shell +make explorer_build_db +make run_explorer ``` -- Configure the database with ecto running `ecto.create` and `ecto.migrate`: +This will: -```bash -make explorer_ecto_setup_db -``` +- Start a postgres docker container +- Run ecto setup +- Start the explorer on http://localhost:4000. -- Start the explorer: +If you want to run the explorer without docker run: -```bash -make run_explorer -``` - -> [!NOTE] -> If you want to run the DB separately, without docker, -> you can set it up and start the explorer with the following command: -```bash +```shell make run_explorer_without_docker_db ``` -
+
+Clean, dump and recover DB To clear the DB, you can run: @@ -598,17 +199,17 @@ Data can be recovered from a `dump.$date.sql` using the following command: make explorer_recover_db ``` -Then you'll be requested to enter the file name of the dump you want to recover already positioned in the `/explorer` -directory. +Then you'll be requested to enter the file name of the dump you want to recover already positioned in the `/explorer` directory. This will update your database with the dumped database data. -
-Extra Explorer script to fetch past batches +
+ +### Fetching batches and operators data + +If you want to fetch past batches that for any reason were not inserted into the DB, you will first need to make sure you have the `ELIXIR_HOSTNAME` in the `.env` file. -If you want to fetch past batches that for any reason were not inserted into the DB, you will first need to make sure -you have the ELIXIR_HOSTNAME .env variable configured. -You can get the hostname of your elixir by running : +You can get the hostname of your elixir by running: ```bash elixir -e 'IO.puts(:inet.gethostname() |> elem(1))' @@ -617,137 +218,86 @@ elixir -e 'IO.puts(:inet.gethostname() |> elem(1))' Then you can run: ```bash -make explorer_fetch_old_batches +make explorer_fetch_old_batches FROM_BLOCK= TO_BLOCK= ``` -You can modify which blocks are being fetched by modify the parameters the `explorer_fetch_old_batches.sh` is being -received - -
- -### Running the Explorer - -To run the explorer for the local devnet, you'll need to have the devnet running and the DB already setup. - -Additionally, you'll need to have the `.env` file in the `/explorer` directory of the project. -A base example of the `.env` file can be found in `/explorer/.env.dev`. - -Use the following command to start the Explorer: +To get operators strategies and restakes data: ```bash -make run_explorer +make explorer_fetch_old_operators_strategies_restakes FROM_BLOCK= ``` -Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. -You can access to a tasks' information by visiting `localhost:4000/batches/:merkle_root`. - -
-There's an additional Explorer script to fetch past operators and restake - -If you want to fetch past operators, strategies and restake, you will need to run: - -```bash -make explorer_fetch_old_operators_strategies_restakes -``` +## Metrics/Telemetry -This will run the script `explorer_fetch_old_operators_strategies_restakes.sh` that will fetch the operators, strategies -and restake which will later insert into the DB. +The Metrics and Telemetry are used to view more in-depth information about the network. With it, you can visualize all sort of cumulative and historical metrics of the network, of the individual components and their behaviors. Tese services are not necessary to run aligned, though you will see warnings in the rest of components as they won't be able to connect and send their status. -
+### Dependencies -### Run with custom env / other devnets +Ensure you have the following installed: -Create a `.env` file in the `/explorer` directory of the project. -The `.env` file needs to contain the following variables: +- [Go](https://go.dev/doc/install) +- [Erlang 26](https://github.com/asdf-vm/asdf-erlang) +- [Elixir 1.16.2](https://elixir-ko.github.io/install.html), compiled with OTP 26 +- [Docker](https://docs.docker.com/get-docker/) -| Variable | Description | -| --------------------- | ----------------------------------------------------------------------------------------------- | -| `RPC_URL` | The RPC URL of the network you want to connect to. | -| `ENVIRONMENT` | The environment you want to run the application in. It can be `devnet`, `holesky` or `mainnet`. | -| `ALIGNED_CONFIG_FILE` | The config file containing Aligned contracts' deployment information | -| `PHX_HOST` | The host URL where the Phoenix server will be running. | -| `DB_NAME` | The name of the postgres database. | -| `DB_USER` | The username of the postgres database. | -| `DB_PASS` | The password of the postgres database. | -| `DB_HOST` | The host URL where the postgres database will be running. | -| `ELIXIR_HOSTNAME` | The hostname of your running elixir. | -| `DEBUG_ERRORS` | If you want to enable phoenix errors on your browser instead of a 500 page, set this to `true`. | -| `TRACKER_API_URL` | The URL of the aligned version each operator is running. | +### Metrics service -Then you can run the explorer with this env file config by entering the following command: +To run Prometheus and Grafana, run: ```bash -make run_explorer +make run_metrics ``` -This will start the explorer with the configuration set in the `.env` file on port 4000. -Visit [`localhost:4000`](http://localhost:4000) from your browser. +This will start containers for Prometheus and Grafana. You can access Grafana on `http://localhost:3000` with the default credentials `admin:admin`. -## Metrics +Alternately, you can access the raw scrapped metrics collected with Prometheus on `http://localhost:9091/metrics`. -### Aggregator Metrics +### Telemetry service -Aggregator metrics are exposed on the `/metrics` endpoint. +To setup the telemetry service run: -If you are using the default config, you can access the metrics on `http://localhost:9091/metrics`. - -To run Prometheus and Grafana, run: +If it is your first time first you'll need to execute the following commands: ```bash -make run_metrics +make telemetry_create_env +make telemetry_build_db ``` -Then you can access Grafana on `http://localhost:3000` with the default credentials `admin:admin`. - -If you want to install Prometheus and Grafana manually, you can follow the instructions below. +Then, to start the service: -To install Prometheus, you can follow the instructions on -the [official website](https://prometheus.io/docs/prometheus/latest/getting_started/). - -To install Grafana, you can follow the instructions on -the [official website](https://grafana.com/docs/grafana/latest/setup-grafana/installation/). - -## Notes on project creation - -EigenLayer middleware was installed as a submodule with: - -```sh -mkdir contracts -cd contacts -forge init . --no-commit -forge install Layr-Labs/eigenlayer-middleware@mainnet +```bash +make telemetry_full_start ``` -Then, to solve the issue, we changed it to: - -`forge install yetanotherco/eigenlayer-middleware@yac-mainnet --no-commit` +This will: -As soon as it gets fixed in mainnet, we can revert it. +- Start OpenJaeger container for the traces: available at `http://localhost:16686/` +- Start telemetry server: available at `http://localhost:4001/` -Base version of middleware used is `7229f2b`. +--- -The script to initialize the devnet can be found on `contracts/scripts/anvil`. +## Send test proofs -The addresses of the relevant contracts after running the anvil script are dumped -on `contracts/script/output/devnet`. +To send proofs quickly you can run any of the targets that have the prefix `batcher_send` for example: -The state is backed up on `contracts/scripts/anvil/state`. +Send a single plonk proof: -EigenLayer contract deployment is almost the same as the EigenLayer contract deployment on mainnet. -Changes are described in the file. +```shell +make batcher_send_plonk_bn254_task +``` -## Running Fuzzers: +Send a burst of `` risc0 proofs: -Fuzzing for the operator can be done by executing the following make commands from the root directory of the project. +```shell +make batcher_send_risc0_burst BURST_SIZE= +``` -macOS: +Send an infinite stream of groth_16 proofs: -``` -make operator_verification_data_fuzz_macos +```shell +make batcher_send_burst_groth16 BURST_SIZE=2 ``` -Linux: +Feel free to explore the rest of targets. -``` -operator_verification_data_fuzz_linux -``` +--- diff --git a/docs/operator_guides/0_running_an_operator.md b/docs/operator_guides/0_running_an_operator.md index f0278593c..b43785663 100644 --- a/docs/operator_guides/0_running_an_operator.md +++ b/docs/operator_guides/0_running_an_operator.md @@ -1,7 +1,7 @@ # Register as an Aligned operator in testnet > **CURRENT VERSION:** -> Aligned Operator [v0.12.2](https://github.com/yetanotherco/aligned_layer/releases/tag/v0.12.2) +> Aligned Operator [v0.13.0](https://github.com/yetanotherco/aligned_layer/releases/tag/v0.13.0) > **IMPORTANT:** > You must be [whitelisted](https://docs.google.com/forms/d/e/1FAIpQLSdH9sgfTz4v33lAvwj6BvYJGAeIshQia3FXz36PFfF-WQAWEQ/viewform) to become an Aligned operator. @@ -30,7 +30,7 @@ The list of supported strategies can be found [here](../3_guides/7_contract_addr To start with, clone the Aligned repository and move inside it ```bash -git clone https://github.com/yetanotherco/aligned_layer.git --branch v0.12.2 +git clone https://github.com/yetanotherco/aligned_layer.git --branch v0.13.0 cd aligned_layer ``` diff --git a/explorer/.env.dev b/explorer/.env.dev index d594e549a..38728db84 100644 --- a/explorer/.env.dev +++ b/explorer/.env.dev @@ -22,3 +22,6 @@ DEBUG_ERRORS=true TRACKER_API_URL=http://localhost:3030 MAX_BATCH_SIZE=268435456 # 256 MiB + +# Time we wait for a batch to be verified before marking it stale +BATCH_TTL_MINUTES=5 diff --git a/explorer/.env.example b/explorer/.env.example index 290801b6a..b9a3a7a32 100644 --- a/explorer/.env.example +++ b/explorer/.env.example @@ -20,3 +20,6 @@ DEBUG_ERRORS= # Tracker API TRACKER_API_URL= + +# Time we wait for a batch to be verified before marking it stale +BATCH_TTL_MINUTES=5 diff --git a/explorer/assets/css/app.css b/explorer/assets/css/app.css index 6d95c636a..c1349f683 100644 --- a/explorer/assets/css/app.css +++ b/explorer/assets/css/app.css @@ -1,6 +1,7 @@ @import "tailwindcss/base"; @import "tailwindcss/components"; @import "tailwindcss/utilities"; +@import "./tooltip.css"; @layer base { :root { diff --git a/explorer/assets/css/tooltip.css b/explorer/assets/css/tooltip.css new file mode 100644 index 000000000..039017c72 --- /dev/null +++ b/explorer/assets/css/tooltip.css @@ -0,0 +1,61 @@ +.chart-tooltip-container { + display: flex; + flex-direction: column; + align-items: center; + position: relative; +} + +.chart-tooltip-dot { + height: 10px; + width: 10px; + bottom: -5px; + border-radius: 100%; + background-color: hsl(var(--accent)); + position: absolute; + transition: all 0.2s; +} + +.chart-tooltip-dot:hover { + transform: scale(1.2); + cursor: pointer; +} + +.chart-tooltip-items-container { + min-height: 50px; + min-width: 250px; + padding: 20px; + margin-bottom: 8px; + background-color: hsl(var(--card)); + border-radius: 8px; + border-width: 1px; + border-color: hsl(var(--foreground) / 20%); +} + +.chart-tooltip-title { + text-align: center; + color: hsl(var(--foreground)); + margin-bottom: 10px; + font-weight: 700; +} + +.chart-tooltip-items { + display: flex; + flex-direction: column; + gap: 5px; +} + +.chart-tooltip-item { + display: flex; + justify-content: space-between; + gap: 5px; + width: 100%; +} + +.chart-tooltip-item-title { + color: hsl(var(--muted-foreground)); +} + +.chart-tooltip-item-value { + color: hsl(var(--foreground)); +} + diff --git a/explorer/assets/js/app.js b/explorer/assets/js/app.js index 9d32220be..dfa19e90a 100644 --- a/explorer/assets/js/app.js +++ b/explorer/assets/js/app.js @@ -7,12 +7,14 @@ import darkModeHook from "../vendor/dark_mode"; import searchFocusHook from "../vendor/search_focus"; import tooltipHook from "../vendor/tooltip"; import copyToClipboardHook from "../vendor/clipboard"; +import chartHook from "../vendor/charts"; -let Hooks = {}; -Hooks.DarkThemeToggle = darkModeHook; -Hooks.SearchFocus = searchFocusHook; -Hooks.TooltipHook = tooltipHook; -Hooks.CopyToClipboard = copyToClipboardHook; +let hooks = {}; +hooks.DarkThemeToggle = darkModeHook; +hooks.SearchFocus = searchFocusHook; +hooks.TooltipHook = tooltipHook; +hooks.CopyToClipboard = copyToClipboardHook; +hooks.ChartHook = chartHook; let csrfToken = document .querySelector("meta[name='csrf-token']") @@ -20,19 +22,15 @@ let csrfToken = document let liveSocket = new LiveSocket("/live", Socket, { params: { _csrf_token: csrfToken }, - hooks: Hooks + hooks: hooks, }); topbar.config({ barColors: { 0: "#18FF7F" }, shadowColor: "rgba(0, 0, 0, .3)" }); -window.addEventListener("phx:page-loading-start", (_info) => - topbar.show(50) -); -window.addEventListener("phx:page-loading-stop", (_info) => - topbar.hide() -); +window.addEventListener("phx:page-loading-start", (_info) => topbar.show(50)); +window.addEventListener("phx:page-loading-stop", (_info) => topbar.hide()); liveSocket.connect(); diff --git a/explorer/assets/package.json b/explorer/assets/package.json index cf5e3b9b8..28fc7f636 100644 --- a/explorer/assets/package.json +++ b/explorer/assets/package.json @@ -1,6 +1,7 @@ { "dependencies": { - "@floating-ui/dom": "^1.6.8" + "@floating-ui/dom": "^1.6.8", + "chart.js": "^4.4.7" }, "devDependencies": { "tailwindcss-animate": "^1.0.7" diff --git a/explorer/assets/pnpm-lock.yaml b/explorer/assets/pnpm-lock.yaml index 8ffd4aa9c..50f486408 100644 --- a/explorer/assets/pnpm-lock.yaml +++ b/explorer/assets/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@floating-ui/dom': specifier: ^1.6.8 version: 1.6.10 + chart.js: + specifier: ^4.4.7 + version: 4.4.7 devDependencies: tailwindcss-animate: specifier: ^1.0.7 @@ -27,6 +30,13 @@ packages: '@floating-ui/utils@0.2.7': resolution: {integrity: sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA==} + '@kurkle/color@0.3.4': + resolution: {integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==} + + chart.js@4.4.7: + resolution: {integrity: sha512-pwkcKfdzTMAU/+jNosKhNL2bHtJc/sSmYgVbuGTEDhzkrhmyihmP7vUc/5ZK9WopidMDHNe3Wm7jOd/WhuHWuw==} + engines: {pnpm: '>=8'} + tailwindcss-animate@1.0.7: resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} peerDependencies: @@ -45,4 +55,10 @@ snapshots: '@floating-ui/utils@0.2.7': {} + '@kurkle/color@0.3.4': {} + + chart.js@4.4.7: + dependencies: + '@kurkle/color': 0.3.4 + tailwindcss-animate@1.0.7: {} diff --git a/explorer/assets/vendor/charts/batch_size.js b/explorer/assets/vendor/charts/batch_size.js new file mode 100644 index 000000000..9abbe0b5a --- /dev/null +++ b/explorer/assets/vendor/charts/batch_size.js @@ -0,0 +1,60 @@ +import { yTickCallbackShowMinAndMaxValues } from "./helpers"; +import { alignedTooltip } from "./tooltip"; + +export const batchSizeCustomOptions = (options, data) => { + // show only min and max values + options.scales.y.ticks.callback = yTickCallbackShowMinAndMaxValues( + data, + (val) => `${val} proofs` + ); + + // show age min, mean and max age in x axis + options.scales.x.ticks.callback = (_value, index, values) => { + const age = data.datasets[0].age; + if (index === 0) return age[0]; + if (index === Math.floor((age.length - 1) / 2)) + return age[Math.floor((age.length - 1) / 2)]; + if (index === values.length - 1) return age[age.length - 1]; + return ""; + }; + + options.plugins.tooltip.external = (context) => + alignedTooltip(context, { + name: "batch-size", + title: "Batch size", + items: [ + { title: "Fee per proof", id: "cost" }, + { title: "Age", id: "age" }, + { title: "Merkle root", id: "merkle_root" }, + { title: "Block number", id: "block_number" }, + { title: "Amount of proofs", id: "amount_of_proofs" }, + ], + onTooltipClick: (tooltipModel) => { + const dataset = tooltipModel.dataPoints[0].dataset; + const idx = tooltipModel.dataPoints[0].dataIndex; + const merkleRootHash = dataset.merkle_root[idx]; + window.location.href = `/batches/${merkleRootHash}`; + }, + onTooltipUpdate: (tooltipModel) => { + const dataset = tooltipModel.dataPoints[0].dataset; + const idx = tooltipModel.dataPoints[0].dataIndex; + const amount_of_proofs = dataset.data[idx].y; + const age = dataset.age[idx]; + const merkleRootHash = dataset.merkle_root[idx]; + const merkle_root = `${merkleRootHash.slice( + 0, + 6 + )}...${merkleRootHash.slice(merkleRootHash.length - 4)}`; + const block_number = dataset.data[idx].x; + const cost = `${dataset.fee_per_proof[idx]} USD`; + + return { + cost, + age, + merkle_root, + block_number, + amount_of_proofs, + }; + }, + }); +}; diff --git a/explorer/assets/vendor/charts/cost_per_proof.js b/explorer/assets/vendor/charts/cost_per_proof.js new file mode 100644 index 000000000..e60438b13 --- /dev/null +++ b/explorer/assets/vendor/charts/cost_per_proof.js @@ -0,0 +1,61 @@ +import { yTickCallbackShowMinAndMaxValues } from "./helpers"; +import { alignedTooltip } from "./tooltip"; + +export const costPerProofCustomOptions = (options, data) => { + // show only 0, min and max values + options.scales.y.ticks.callback = yTickCallbackShowMinAndMaxValues( + data, + (val) => `${val} USD` + ); + + // show age min, mean and max age in x axis + options.scales.x.ticks.callback = (_value, index, values) => { + const age = data.datasets[0].age; + if (index === 0) return age[0]; + if (index === Math.floor((age.length - 1) / 2)) + return age[Math.floor((age.length - 1) / 2)]; + if (index === values.length - 1) return age[age.length - 1]; + return ""; + }; + + options.plugins.tooltip.external = (context) => + alignedTooltip(context, { + name: "cost-per-proof", + title: "Cost per proof", + items: [ + { title: "Fee per proof", id: "cost" }, + { title: "Age", id: "age" }, + { title: "Merkle root", id: "merkle_root" }, + { title: "Block number", id: "block_number" }, + { title: "Amount of proofs", id: "amount_of_proofs" }, + ], + onTooltipClick: (tooltipModel) => { + const dataset = tooltipModel.dataPoints[0].dataset; + const idx = tooltipModel.dataPoints[0].dataIndex; + const merkleRootHash = dataset.merkle_root[idx]; + window.location.href = `/batches/${merkleRootHash}`; + }, + onTooltipUpdate: (tooltipModel) => { + const dataset = tooltipModel.dataPoints[0].dataset; + const idx = tooltipModel.dataPoints[0].dataIndex; + + const cost = `${dataset.data[idx].y} USD`; + const age = dataset.age[idx]; + const merkleRootHash = dataset.merkle_root[idx]; + const merkle_root = `${merkleRootHash.slice( + 0, + 6 + )}...${merkleRootHash.slice(merkleRootHash.length - 4)}`; + const block_number = dataset.data[idx].x; + const amount_of_proofs = dataset.amount_of_proofs[idx]; + + return { + cost, + age, + merkle_root, + block_number, + amount_of_proofs, + }; + }, + }); +}; diff --git a/explorer/assets/vendor/charts/helpers.js b/explorer/assets/vendor/charts/helpers.js new file mode 100644 index 000000000..ee73d0d2d --- /dev/null +++ b/explorer/assets/vendor/charts/helpers.js @@ -0,0 +1,43 @@ +const findClosestIndex = (target, values) => { + let closestIndex = 0; + let smallestDiff = Math.abs(values[0] - target); + for (let i = 1; i < values.length; i++) { + const diff = Math.abs(values[i] - target); + if (diff < smallestDiff) { + closestIndex = i; + smallestDiff = diff; + } + } + return closestIndex; +}; + +/** + * A callback function to customize y-axis tick labels by showing only the zero, minimum and maximum data values. + * + * @param {Object} data - The chart data object containing datasets and their values. + * @param {Function} renderText - A function to format and render text for the tick labels. + * @returns {Function} - A callback function for Chart.js tick customization. + * + * The returned function compares the current tick index with the indices of the values closest + * to the minimum and maximum data points, and displays these values formatted using the + * `renderText` function. + * + * @example + * options.scales.y.ticks.callback = yTickCallbackShowMinAndMaxValues(data, (val) => `${val} USD`); + */ +export const yTickCallbackShowMinAndMaxValues = + (data, renderText) => (_value, index, values) => { + if (index === 0) return renderText(0); + + const dataY = data.datasets[0].data.map((point) => parseFloat(point.y)); + const sortedData = dataY.sort((a, b) => b - a); + const min = sortedData[0]; + const max = sortedData[sortedData.length - 1]; + const valsData = values.map((item) => item.value); + const idxClosestToMin = findClosestIndex(min, valsData); + const idxClosestToMax = findClosestIndex(max, valsData); + + if (index == idxClosestToMin) return renderText(min); + if (index == idxClosestToMax) return renderText(max); + return ""; + }; diff --git a/explorer/assets/vendor/charts/index.js b/explorer/assets/vendor/charts/index.js new file mode 100644 index 000000000..911230632 --- /dev/null +++ b/explorer/assets/vendor/charts/index.js @@ -0,0 +1,69 @@ +import Chart from "chart.js/auto"; +import { costPerProofCustomOptions } from "./cost_per_proof"; +import { batchSizeCustomOptions } from "./batch_size"; + +const applyCommonChartOptions = (options, data) => { + // tooltip disabled by default, each chart should implement its own with alignedTooltip + options.plugins.tooltip = { + enabled: false, + }; +}; + +const applyOptionsByChartId = (id, options, data) => { + const idOptionsMap = { + cost_per_proof_chart: () => costPerProofCustomOptions(options, data), + batch_size_chart: () => batchSizeCustomOptions(options, data), + }; + + idOptionsMap[id] && idOptionsMap[id](); +}; + +export default { + mounted() { + this.initChart(); + window.addEventListener("resize", this.resizeChart.bind(this)); + }, + + updated() { + this.reinitChart(); + }, + + destroyed() { + if (this.chart) { + this.chart.destroy(); + window.removeEventListener("resize", this.resizeChart.bind(this)); + } + }, + + initChart() { + const ctx = this.el; + const type = this.el.dataset.chartType; + const data = JSON.parse(this.el.dataset.chartData); + const options = JSON.parse(this.el.dataset.chartOptions); + const chartId = this.el.id; + + applyCommonChartOptions(options, data); + applyOptionsByChartId(chartId, options, data); + + this.chart = new Chart(ctx, { + type, + data, + options, + }); + + this.resizeChart(); + }, + + reinitChart() { + if (this.chart) { + this.chart.destroy(); + } + this.initChart(); + }, + + resizeChart() { + if (this.chart) { + this.chart.resize(); + } + }, +}; diff --git a/explorer/assets/vendor/charts/tooltip.js b/explorer/assets/vendor/charts/tooltip.js new file mode 100644 index 000000000..57309fe99 --- /dev/null +++ b/explorer/assets/vendor/charts/tooltip.js @@ -0,0 +1,123 @@ +const tooltipItem = (title, valueId) => ` +
+

${title}

+

+
+`; + +const tooltipComponent = ({ title, isTooltipClickable, items }) => ` +
+
+
+
+

${title}

+
+ ${items.map((item) => tooltipItem(item.title, item.id)).join("")} +
+
+
+`; + +/** + * Displays and positions a custom tooltip on the page based on the provided chart context. + * This function creates the tooltip element if it doesn't exist, updates its content, + * and positions it correctly based on the chart's canvas and tooltip model. + * + * @param {Object} context - The chart.js context, typically passed as `this` within chart hooks. + * @param {Object} params - An object containing configuration for the tooltip. + * @param {Object} params.name - A string that serves as an identifier for the tooltip. + * @param {string} params.title - The title text to display in the tooltip. + * @param {Array} params.items - An array of items (with ids) to be displayed inside the tooltip. + * @param {Array} params.onTooltipClick - A callback that receives `tooltipModel` and gets triggered when the tooltip is clicked. + * @param {Function} params.onTooltipUpdate - A callback function that updates the tooltip values based on the tooltip model. + * It is called with the `tooltipModel` as an argument and should return an object containing updated values for each tooltip item by their respective `id`. + * + * @returns {void} - This function doesn't return any value. It manipulates the DOM directly to show the tooltip. + * + * @example + * alignedTooltip(context, { + * name: "my-chart", + * title: "Tooltip Title", + * items: [{ title: "Cost", id: "cost_id" }, { title: "Timestamp", id: "timestamp_id" }], + * onTooltipClick: (tooltipModel) => { + * const dataset = tooltipModel.dataPoints[0].dataset; + const idx = tooltipModel.dataPoints[0].dataIndex; + + * const y = tooltipModel.dataPoints[0].data[idx].y + * window.location.href = `/batches/${y}` + * } + * onTooltipUpdate: (tooltipModel) => { + * const cost = tooltipModel.dataPoints[0].raw; + * const timestamp = tooltipModel.dataPoints[0].label; + * + * return { + * cost_id: cost, + * timestamp_id: label + * }; + * } + * }); + */ +export const alignedTooltip = ( + context, + { name, title, items, onTooltipClick, onTooltipUpdate } +) => { + const tooltipModel = context.tooltip; + let tooltipEl = document.getElementById(`chartjs-tooltip-${name}`); + if (!tooltipEl) { + tooltipEl = document.createElement("div"); + tooltipEl.style = "transition: opacity 0.3s;"; + tooltipEl.style = "transition: left 0.1s;"; + tooltipEl.id = `chartjs-tooltip-${name}`; + tooltipEl.innerHTML = tooltipComponent({ + title, + isTooltipClickable: !!onTooltipClick, + items, + }); + document.body.appendChild(tooltipEl); + tooltipEl.onmouseenter = () => { + window.isTooltipBeingHovered = true; + }; + tooltipEl.onmouseleave = () => { + window.isTooltipBeingHovered = false; + tooltipEl.style.opacity = 0; + tooltipEl.style.zIndex = -1; + }; + // this is needed to maintain responsiveness + window.addEventListener("resize", () => { + tooltipEl.remove(); + }); + if (onTooltipClick) + tooltipEl.querySelector(".chart-tooltip-dot").onclick = () => + onTooltipClick(tooltipModel); + } + + // Hide element if no tooltip + if (tooltipModel.opacity == 0 && !window.isTooltipBeingHovered) { + tooltipEl.style.opacity = 0; + return; + } + + const values = onTooltipUpdate(tooltipModel); + items.forEach((item) => { + tooltipEl.querySelector(`#${item.id}`).textContent = values[item.id]; + }); + + const position = context.chart.canvas.getBoundingClientRect(); + + tooltipEl.style.opacity = 1; + tooltipEl.style.zIndex = 1; + tooltipEl.style.position = "absolute"; + tooltipEl.style.left = + position.left - + tooltipEl.offsetWidth / 2 + + window.scrollX + + tooltipModel.caretX + + "px"; + tooltipEl.style.top = + position.top - + tooltipEl.offsetHeight + + window.scrollY + + tooltipModel.caretY + + "px"; +}; diff --git a/explorer/assets/vendor/dark_mode.js b/explorer/assets/vendor/dark_mode.js index bbb1f4790..7c0ebc19b 100644 --- a/explorer/assets/vendor/dark_mode.js +++ b/explorer/assets/vendor/dark_mode.js @@ -1,10 +1,18 @@ -const localStorageKey = "theme"; +const themeCookieKey = "theme"; const isDark = () => { - if (localStorage.getItem(localStorageKey) === "dark") return true; - if (localStorage.getItem(localStorageKey) === "light") - return false; - return window.matchMedia("(prefers-color-scheme: dark)").matches; + const theme = document.cookie + .split("; ") + .find((row) => row.startsWith(`${themeCookieKey}=`)) + ?.split("=")[1]; + return ( + theme == "dark" || + window.matchMedia("(prefers-color-scheme: dark)").matches + ); +}; + +const setThemeCookie = (theme) => { + document.cookie = `${themeCookieKey}=${theme}; path=/; max-age=31536000; SameSite=Strict;`; // would expire in a yer }; const setupThemeToggle = () => { @@ -15,30 +23,18 @@ const setupThemeToggle = () => { const themeToggleLightIcon = document.getElementById( "theme-toggle-light-icon" ); - if ( - themeToggleDarkIcon == null || - themeToggleLightIcon == null - ) - return; - const show = dark - ? themeToggleDarkIcon - : themeToggleLightIcon; - const hide = dark - ? themeToggleLightIcon - : themeToggleDarkIcon; + if (themeToggleDarkIcon == null || themeToggleLightIcon == null) return; + const show = dark ? themeToggleDarkIcon : themeToggleLightIcon; + const hide = dark ? themeToggleLightIcon : themeToggleDarkIcon; show.classList.remove("hidden", "text-transparent"); hide.classList.add("hidden", "text-transparent"); if (dark) { document.documentElement.classList.add("dark"); + setThemeCookie("dark"); } else { document.documentElement.classList.remove("dark"); + setThemeCookie("light"); } - try { - localStorage.setItem( - localStorageKey, - dark ? "dark" : "light" - ); - } catch (_err) {} }; toggleVisibility(isDark()); document @@ -52,7 +48,7 @@ const darkModeHook = { mounted() { setupThemeToggle(); }, - updated() {} + updated() {}, }; export default darkModeHook; diff --git a/explorer/assets/vendor/tooltip.js b/explorer/assets/vendor/tooltip.js index 86bd1de22..80b29ba7a 100644 --- a/explorer/assets/vendor/tooltip.js +++ b/explorer/assets/vendor/tooltip.js @@ -41,7 +41,7 @@ class Tooltip { setupFloatingUI() { this.cleanup = autoUpdate(this.$parent, this.$tooltip, () => { computePosition(this.$parent, this.$tooltip, { - placement: "top", + placement: "bottom", middleware: [offset(5), flip(), shift({ padding: 5 })] }).then(({ x, y }) => { Object.assign(this.$tooltip.style, { diff --git a/explorer/lib/explorer/models/batches.ex b/explorer/lib/explorer/models/batches.ex index f2474ef48..b22dd7b68 100644 --- a/explorer/lib/explorer/models/batches.ex +++ b/explorer/lib/explorer/models/batches.ex @@ -76,24 +76,35 @@ defmodule Batches do Explorer.Repo.one(query) end - def get_latest_batches(%{amount: amount}) do + def get_latest_batches(%{amount: amount} = %{order_by: :desc}) do query = from(b in Batches, order_by: [desc: b.submission_block_number], limit: ^amount, select: b) Explorer.Repo.all(query) - end - - def get_paginated_batches(%{page: page, page_size: page_size}) do + end + + def get_latest_batches(%{amount: amount} = %{order_by: :asc}) do query = from(b in Batches, - order_by: [desc: b.submission_block_number], - limit: ^page_size, - offset: ^((page - 1) * page_size), + order_by: [asc: b.submission_block_number], + limit: ^amount, select: b) Explorer.Repo.all(query) end + + def get_paginated_batches(%{page: page, page_size: page_size}) do + query = + from(b in Batches, + order_by: [desc: b.submission_block_number], + limit: ^page_size, + offset: ^((page - 1) * page_size), + select: b + ) + + Explorer.Repo.all(query) + end def get_last_page(page_size) do total_batches = Explorer.Repo.aggregate(Batches, :count, :merkle_root) @@ -128,6 +139,19 @@ defmodule Batches do result -> result end end + + def get_avg_fee_per_proof() do + query = + from(b in Batches, + where: b.is_verified == true, + select: avg(b.fee_per_proof) + ) + + case Explorer.Repo.one(query) do + nil -> 0 + result -> result + end + end def get_amount_of_verified_proofs() do query = from(b in Batches, diff --git a/explorer/lib/explorer/periodically.ex b/explorer/lib/explorer/periodically.ex index 403d65140..adf351ca1 100644 --- a/explorer/lib/explorer/periodically.ex +++ b/explorer/lib/explorer/periodically.ex @@ -50,6 +50,8 @@ defmodule Explorer.Periodically do Task.start(&process_unverified_batches/0) end + PubSub.broadcast(Explorer.PubSub, "update_views", :block_age) + {:noreply, %{state | batches_count: new_count}} end diff --git a/explorer/lib/explorer_web/components/assets_cta.ex b/explorer/lib/explorer_web/components/assets_cta.ex index 9aeece5cc..35cc452c6 100644 --- a/explorer/lib/explorer_web/components/assets_cta.ex +++ b/explorer/lib/explorer_web/components/assets_cta.ex @@ -25,7 +25,7 @@ defmodule AssetsCTAComponent do View all active operators - <.link navigate={~p"/restake"} class="flex-1 flex flex-col justify-start gap-0.5 group"> + <.link navigate={~p"/restaked"} class="flex-1 flex flex-col justify-start gap-0.5 group">

Total Restaked diff --git a/explorer/lib/explorer_web/components/batches_table.ex b/explorer/lib/explorer_web/components/batches_table.ex new file mode 100644 index 000000000..568c0d9b6 --- /dev/null +++ b/explorer/lib/explorer_web/components/batches_table.ex @@ -0,0 +1,51 @@ +defmodule ExplorerWeb.BatchesTable do + use Phoenix.Component + use ExplorerWeb, :live_component + + attr(:batches, :list, required: true) + + def batches_table(assigns) do + ~H""" + <.table id="batches" rows={@batches}> + <:col :let={batch} label="Batch Hash" class="text-left"> + <.link navigate={~p"/batches/#{batch.merkle_root}"}> + + <%= Helpers.shorten_hash(batch.merkle_root, 6) %> + <.right_arrow /> + <.tooltip> + <%= batch.merkle_root %> + + + + + <:col :let={batch} label="Status"> + <.dynamic_badge_for_batcher status={Helpers.get_batch_status(batch)} /> + + <:col :let={batch} label="Age"> + + <%= batch.submission_timestamp |> Helpers.parse_timeago() %> + + + <:col :let={batch} label="Block Number"> + <%= batch.submission_block_number |> Helpers.format_number() %> + + + <:col :let={batch} label="Fee per proof"> + <%= case EthConverter.wei_to_usd_sf(batch.fee_per_proof, 2) do %> + <% {:ok, usd} -> %> + <%= "#{usd} USD" %> + <% {:error, _} -> %> + <%= "N/A" %> + <% end %> + <.tooltip> + ~= <%= EthConverter.wei_to_eth(batch.fee_per_proof, 6) %> ETH + + + + <:col :let={batch} label="Number of proofs"> + <%= batch.amount_of_proofs |> Helpers.format_number() %> + + + """ + end +end diff --git a/explorer/lib/explorer_web/components/charts.ex b/explorer/lib/explorer_web/components/charts.ex new file mode 100644 index 000000000..a5c1f5aa7 --- /dev/null +++ b/explorer/lib/explorer_web/components/charts.ex @@ -0,0 +1,230 @@ +defmodule ExplorerWeb.ChartComponents do + use Phoenix.Component + + attr(:id, :string, required: true) + attr(:chart_type, :string, required: true) + attr(:chart_data, :string, required: true) + attr(:chart_options, :string, required: true) + + defp basic_chart(assigns) do + ~H""" +
+ + +
+ """ + end + + @doc """ + Renders a bar chart with aligned style. + ## Examples + <.bar_chart + id="exchanges" + points={%{x: [1, 2, 3, 4], y: ["01-01-2024", "01-02-2024", "01-03-2024", "01-04-2024"]},} + show_ticks={%{x: true, y: true}} + extra_data={%{merkle_roots: [0x1, 0x2, 0x3, 0x4]}} + /> + !Note: + - id is used to reference the chart on javascript to apply custom styles, configurations, tooltip, that are possible only via javascript + - points: nil values are automatically ignored and not displayed + - extra_data: any other data you might want to retrieve via javascript later + """ + attr(:id, :string, required: true) + attr(:points, :map, required: true) + attr(:extra_data, :map, default: %{}) + attr(:show_ticks, :map, default: %{x: true, y: true}) + + def bar_chart(assigns) do + ~H""" + <.basic_chart + id={@id} + chart_type="bar" + chart_data={ + Jason.encode!(%{ + labels: @points, + datasets: [ + Map.merge( + %{ + data: @points, + fill: false, + tension: 0.1, + backgroundColor: "rgba(24, 255, 128, 0.3)", + hoverBackgroundColor: "rgba(24, 255, 128, 0.5)", + borderColor: "rgb(24, 255, 127)", + borderWidth: 1.5, + borderRadius: 4 + }, + @extra_data + ) + ] + }) + } + chart_options={ + Jason.encode!(%{ + animation: false, + responsive: false, + maintainAspectRatio: false, + interaction: %{ + mode: "index", + intersect: false + }, + plugins: %{ + legend: %{ + display: false + } + }, + elements: %{ + point: %{ + pointStyle: false + } + }, + scales: %{ + x: %{ + bounds: "data", + offset: false, + ticks: %{ + display: @show_ticks.x, + autoSkip: false, + maxRotation: 0, + font: %{ + weight: "700" + } + }, + grid: %{ + display: false + }, + border: %{ + display: false + } + }, + y: %{ + offset: false, + beginAtZero: true, + ticks: %{ + display: @show_ticks.y, + autoSkip: false, + maxRotation: 0, + font: %{ + weight: "700" + } + }, + grid: %{ + display: false + }, + border: %{ + display: false + } + } + } + }) + } + /> + """ + end + + @doc """ + Renders a linear chart with aligned style. + ## Examples + <.line_chart + id="exchanges" + points={%{x: [1, 2, 3, 4], y: ["01-01-2024", "01-02-2024", "01-03-2024", "01-04-2024"]},} + show_ticks={%{x: true, y: true}} + extra_data={%{merkle_roots: [0x1, 0x2, 0x3, 0x4]}} + /> + !Note: + - id is used to reference the chart on javascript to apply custom styles, configurations, tooltip, that are possible only via javascript + - points: nil values are automatically ignored and not displayed + - extra_data: any other data you might want to retrieve via javascript later + """ + attr(:id, :string, required: true) + attr(:points, :map, required: true) + attr(:extra_data, :map, default: %{}) + attr(:show_ticks, :map, default: %{x: true, y: true}) + + def line_chart(assigns) do + ~H""" + <.basic_chart + id={@id} + chart_type="line" + chart_data={ + Jason.encode!(%{ + labels: @points, + datasets: [ + Map.merge( + %{data: @points, borderColor: "rgb(24, 255, 127)", fill: false, tension: 0.1}, + @extra_data + ) + ] + }) + } + chart_options={ + Jason.encode!(%{ + animation: false, + responsive: false, + maintainAspectRatio: false, + interaction: %{ + mode: "index", + intersect: false + }, + plugins: %{ + legend: %{ + display: false + } + }, + elements: %{ + point: %{ + pointStyle: false + } + }, + scales: %{ + x: %{ + type: "linear", + bounds: "data", + offset: false, + ticks: %{ + display: @show_ticks.x, + autoSkip: false, + maxRotation: 0, + font: %{ + weight: "700" + } + }, + grid: %{ + display: false + }, + border: %{ + display: false + } + }, + y: %{ + type: "linear", + offset: false, + ticks: %{ + display: @show_ticks.y, + autoSkip: false, + maxRotation: 0, + font: %{ + weight: "700" + } + }, + grid: %{ + display: false + }, + border: %{ + display: false + } + } + } + }) + } + /> + """ + end +end diff --git a/explorer/lib/explorer_web/components/contracts.ex b/explorer/lib/explorer_web/components/contracts.ex index 6f353d69a..48652bf21 100644 --- a/explorer/lib/explorer_web/components/contracts.ex +++ b/explorer/lib/explorer_web/components/contracts.ex @@ -1,17 +1,45 @@ defmodule ContractsComponent do use ExplorerWeb, :live_component - attr :class, :string, default: nil + attr(:class, :string, default: nil) + attr(:host, :string, default: nil) @impl true def mount(socket) do + addresses = Helpers.get_aligned_contracts_addresses() + {:ok, assign(socket, - service_manager_address: - AlignedLayerServiceManager.get_aligned_layer_service_manager_address(), - batcher_payment_service_address: - BatcherPaymentServiceManager.get_batcher_payment_service_address(), - network: System.get_env("ENVIRONMENT") + contracts: [ + %{ + contract_name: "AlignedServiceManager", + address: addresses["alignedLayerServiceManager"] + }, + %{ + contract_name: "BatcherPaymentService", + address: addresses["batcherPaymentService"] + }, + %{ + contract_name: "BlsApkRegistry", + address: addresses["blsApkRegistry"] + }, + %{ + contract_name: "IndexRegistry", + address: addresses["indexRegistry"] + }, + %{ + contract_name: "OperatorStateRetriever", + address: addresses["operatorStateRetriever"] + }, + %{ + contract_name: "RegistryCoordinator", + address: addresses["registryCoordinator"] + }, + %{ + contract_name: "StakeRegistry", + address: addresses["stakeRegistry"] + } + ] )} end @@ -22,6 +50,7 @@ defmodule ContractsComponent do <.card inner_class="text-base leading-9 flex flex-wrap sm:flex-row overflow-x-auto gap-x-2" title="Contract Addresses" + subtitle={"All Aligned contracts addresses on #{Helpers.get_current_network_from_host(@host)}"} > <.link href="https://docs.alignedlayer.com/guides/6_contract_addresses" @@ -29,32 +58,36 @@ defmodule ContractsComponent do target="_blank" rel="noopener noreferrer" > - View All <.icon name="hero-arrow-top-right-on-square-solid" class="size-3.5 mb-1" /> + See more <.icon name="hero-arrow-top-right-on-square-solid" class="size-3.5 mb-1" /> -

- <.icon name="hero-cpu-chip" class="size-4 mb-0.5" /> Service Manager: -

- <.a - href={"#{Helpers.get_etherescan_url()}/address/#{@service_manager_address}"} - class="hover:text-foreground/80" - target="_blank" - rel="noopener noreferrer" - > - <%= @service_manager_address %> - -

- <.icon name="hero-wallet" class="size-4 mb-0.5" /> Batcher Payment Service: -

- <.a - href={"#{Helpers.get_etherescan_url()}/address/#{@batcher_payment_service_address}"} - class="hover:text-foreground/80" - target="_blank" - rel="noopener noreferrer" - > - <%= @batcher_payment_service_address %> - +
+ <%= for %{contract_name: contract_name, address: address} <- @contracts do %> + <.contract contract_name={contract_name} address={address} /> + <% end %> +
""" end + + attr(:contract_name, :string) + attr(:address, :string) + + def contract(assigns) do + ~H""" +
+

+ <%= @contract_name %> +

+ <.a + href={"#{Helpers.get_etherescan_url()}/address/#{@address}"} + class="hover:text-foreground/80" + target="_blank" + rel="noopener noreferrer" + > + <%= @address %> + +
+ """ + end end diff --git a/explorer/lib/explorer_web/components/core_components.ex b/explorer/lib/explorer_web/components/core_components.ex index a8d529d37..e9b93e9c8 100644 --- a/explorer/lib/explorer_web/components/core_components.ex +++ b/explorer/lib/explorer_web/components/core_components.ex @@ -305,19 +305,23 @@ defmodule ExplorerWeb.CoreComponents do """ attr(:class, :string, default: nil) attr(:title, :string, default: nil) + attr(:subtitle, :string, default: nil) attr(:inner_class, :string, default: nil) - + attr(:header_container_class, :string, default: nil) slot(:inner_block, default: nil) def card(assigns) do ~H""" - <.card_background class={@class}> -

- <%= @title %> -

- + <.card_background class={classes(["px-10 py-8", @class])}> +
+

+ <%= @title %> +

+

<%= @subtitle %>

+
+
<%= render_slot(@inner_block) %> - +
""" end @@ -473,6 +477,7 @@ defmodule ExplorerWeb.CoreComponents do :invalid -> "destructive" :verified -> "accent" :pending -> "foreground" + :stale -> "destructive" end } class={ @@ -485,12 +490,95 @@ defmodule ExplorerWeb.CoreComponents do :invalid -> "Invalid" :verified -> "Verified" :pending -> "Pending" + :stale -> "Unverified" end %> <%= render_slot(@inner_block) %> """ end + @doc """ + Renders a selector dropdown on hover component with buttons that trigger actions on click. + + The selector allows you to display a list of options and enables the user to select a value by clicking on a button. + The component supports different styling variants, and the current selected value can be displayed as part of the selector. + + ## Attributes + + - `:class` (optional, :string) - Additional custom CSS classes to apply to the outermost div container. + + - `:variant` (optional, :string, default: "accent") - The style variant of the selector. + - `:current_value` (required, :string) - The current value being displayed in the selector. This is usually the name of the currently selected option. + - `:icon` (required, :string) - The icon to show on small devices instead of `current_value`. + - `:options` (required, :list of tuples) - A list of options to render. Each option is a tuple containing: + - `name`: The display name of the option (string). + - `on_click`: The JavaScript `onclick` action to trigger when the option is clicked (string). + """ + attr(:class, :string, default: nil) + attr(:variant, :string, default: "accent") + attr(:current_value, :list, doc: "the current selector value") + attr(:options, :string, doc: "the options to render") + attr(:icon, :string, doc: "hero icon to render on small devices") + + def hover_dropdown_selector(assigns) do + ~H""" +
+ "color-accent text-accent-foreground bg-accent group-hover:bg-accent/80" + + "primary" -> + "color-primary text-primary-foreground bg-primary group-hover:bg-primary/80" + + "secondary" -> + "color-secondary text-secondary-foreground bg-secondary group-hover:bg-secondary/80" + + "destructive" -> + "color-destructive text-destructive-foreground bg-destructive group-hover:bg-destructive/80" + + "foreground" -> + "color-foreground text-background bg-foreground group-hover:bg-foreground/80" + + "card" -> + "color-card text-card-foreground bg-card group-hover:bg-card/80" + + _ -> + "color-accent text-accent-foreground bg-accent group-hover:bg-accent/80" + end, + @class + ]) + }> + <.icon name={@icon} class="h-5 w-5 hidden max-md:block" /> + + +
+ """ + end + @doc """ Renders an input with label and error messages. @@ -713,6 +801,7 @@ defmodule ExplorerWeb.CoreComponents do attr(:rows, :list, required: true) attr(:row_id, :any, default: nil, doc: "the function for generating the row id") attr(:row_click, :any, default: nil, doc: "the function for handling phx-click on each row") + attr(:class, :any, default: nil, doc: "css class attributes for the card background") attr(:row_item, :any, default: &Function.identity/1, @@ -733,56 +822,46 @@ defmodule ExplorerWeb.CoreComponents do end ~H""" - <.card_background class="overflow-x-auto"> - - - - - - - - - + + + + + + + + + - - - -
- <%= col[:label] %> - - <%= gettext("Actions") %> -
+ <%= col[:label] %> + + <%= gettext("Actions") %> +
- -
- <%= render_slot(col, @row_item.(row)) %> -
-
-
- - <%= render_slot(action, @row_item.(row)) %> - -
-
- +
+ <%= render_slot(col, @row_item.(row)) %> +
+ + +
+ + <%= render_slot(action, @row_item.(row)) %> + +
+ + + + """ end diff --git a/explorer/lib/explorer_web/components/dark_mode.ex b/explorer/lib/explorer_web/components/dark_mode.ex index 939e36e67..09640f9a1 100644 --- a/explorer/lib/explorer_web/components/dark_mode.ex +++ b/explorer/lib/explorer_web/components/dark_mode.ex @@ -1,6 +1,8 @@ defmodule DarkMode do use Phoenix.Component + attr(:theme, :string, required: true) + def button(assigns) do ~H""" - - """ end end diff --git a/explorer/lib/explorer_web/components/footer.ex b/explorer/lib/explorer_web/components/footer.ex new file mode 100644 index 000000000..44cfac3d9 --- /dev/null +++ b/explorer/lib/explorer_web/components/footer.ex @@ -0,0 +1,74 @@ +defmodule FooterComponent do + use ExplorerWeb, :live_component + + @impl true + def mount(socket) do + {:ok, + assign(socket, + headers: [ + {"General", + [ + {"Batches", "/batches"}, + {"Operators", "/operators"}, + {"Restake", "/restaked"} + ]}, + {"Social", + [ + {"Twitter", "https://x.com/alignedlayer"}, + {"Telegram", "https://t.me/aligned_layer"}, + {"Discord", "https://discord.gg/alignedlayer"}, + {"Youtube", "https://youtube.com/@alignedlayer"} + ]}, + {"Developers", + [ + {"Docs", "https://docs.alignedlayer.com/"}, + {"Supported verifiers", + "https://docs.alignedlayer.com/architecture/0_supported_verifiers"}, + {"Whitepaper", "https://alignedlayer.com/whitepaper/"}, + {"Github", "https://github.com/yetanotherco/aligned_layer"} + ]}, + {"Resources", + [ + {"Blog", "https://blog.alignedlayer.com/"}, + {"Contact", "https://alignedlayer.com/contact/"} + ]} + ] + )} + end + + @impl true + def render(assigns) do + ~H""" +
+
+ + +
+
+ <%= for {title, links} <- @headers do %> +
+

<%= title %>

+ <%= for {value, link} <- links do %> + <.link class="text-md text-foreground/80 hover:underline" href={link}> + <%= value %> + + <% end %> +
+ <% end %> +
+
+
+
+ """ + end +end diff --git a/explorer/lib/explorer_web/components/layouts/app.html.heex b/explorer/lib/explorer_web/components/layouts/app.html.heex index eab1e52b8..a7591881a 100644 --- a/explorer/lib/explorer_web/components/layouts/app.html.heex +++ b/explorer/lib/explorer_web/components/layouts/app.html.heex @@ -1,5 +1,6 @@ -<.live_component module={NavComponent} id="navbar" /> +<.live_component module={NavComponent} id="navbar" host={@host} theme={@theme} /> <.root_background> <.flash_group flash={@flash} /> <%= @inner_content %> +<.live_component module={FooterComponent} id="footer" /> diff --git a/explorer/lib/explorer_web/components/layouts/root.html.heex b/explorer/lib/explorer_web/components/layouts/root.html.heex index 284684de3..ffb87f2c5 100644 --- a/explorer/lib/explorer_web/components/layouts/root.html.heex +++ b/explorer/lib/explorer_web/components/layouts/root.html.heex @@ -1,5 +1,5 @@ - + diff --git a/explorer/lib/explorer_web/components/nav.ex b/explorer/lib/explorer_web/components/nav.ex index a93b9565e..ff29adb71 100644 --- a/explorer/lib/explorer_web/components/nav.ex +++ b/explorer/lib/explorer_web/components/nav.ex @@ -1,9 +1,27 @@ defmodule NavComponent do use ExplorerWeb, :live_component + def get_networks(current_network) do + Helpers.get_aligned_networks() + |> Enum.filter(fn {name, _link} -> + case current_network do + # Filter dev networks if we are in mainnet or holesky + "Mainnet" -> name in ["Mainnet", "Holesky"] + "Holesky" -> name in ["Mainnet", "Holesky"] + _ -> true + end + end) + |> Enum.map(fn {name, link} -> + {name, "window.location.href='#{link}'"} + end) + end + @impl true def mount(socket) do - {:ok, assign(socket, latest_release: ReleasesHelper.get_latest_release())} + {:ok, + assign(socket, + latest_release: ReleasesHelper.get_latest_release() + )} end @impl true @@ -11,35 +29,61 @@ defmodule NavComponent do ~H""" """ end @@ -109,4 +184,10 @@ defmodule NavComponent do |> JS.toggle(to: ".toggle-open") |> JS.toggle(to: ".toggle-close") end + + defp active_view_class(current_view, target_views) do + if current_view in target_views, + do: "text-green-500 font-bold", + else: "text-foreground/80 hover:text-foreground font-semibold" + end end diff --git a/explorer/lib/explorer_web/components/search.ex b/explorer/lib/explorer_web/components/search.ex index bf119ef66..7babfcbd5 100644 --- a/explorer/lib/explorer_web/components/search.ex +++ b/explorer/lib/explorer_web/components/search.ex @@ -22,7 +22,7 @@ defmodule SearchComponent do end end - attr :class, :string, default: nil + attr(:class, :string, default: nil) @impl true def render(assigns) do @@ -32,7 +32,7 @@ defmodule SearchComponent do phx-submit="search_batch" class={ classes([ - "relative flex items-center gap-2 z-10 px-5 sm:px-0 drop-shadow-sm max-w-md", + "relative flex items-center gap-2 sm:px-0 w-full", @class ]) } @@ -40,18 +40,12 @@ defmodule SearchComponent do - <.button - type="submit" - class="absolute right-5 sm:right-1 top-0.5 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-transparent border-none size-10 shadow-none hover:bg-transparent text-muted-foreground" - > - <.icon name="hero-magnifying-glass-solid" class="size-5 hover:text-foreground" /> - Search - + <.icon name="hero-magnifying-glass-solid" class="absolute right-3 text-foreground/20 size-5 hover:text-foreground" /> """ end diff --git a/explorer/lib/explorer_web/endpoint.ex b/explorer/lib/explorer_web/endpoint.ex index fbc6824d9..7b66fad6f 100644 --- a/explorer/lib/explorer_web/endpoint.ex +++ b/explorer/lib/explorer_web/endpoint.ex @@ -12,7 +12,7 @@ defmodule ExplorerWeb.Endpoint do ] socket "/live", Phoenix.LiveView.Socket, - websocket: [connect_info: [session: @session_options]], + websocket: [connect_info: [:uri, session: @session_options]], longpoll: [connect_info: [session: @session_options]] # Serve at "/" the static files from "priv/static" directory. diff --git a/explorer/lib/explorer_web/hooks.ex b/explorer/lib/explorer_web/hooks.ex new file mode 100644 index 000000000..74a896fd4 --- /dev/null +++ b/explorer/lib/explorer_web/hooks.ex @@ -0,0 +1,15 @@ +defmodule ExplorerWeb.Hooks do + def on_mount(:add_host, _params, _session, socket) do + %URI{host: host} = Phoenix.LiveView.get_connect_info(socket, :uri) + + socket = Phoenix.Component.assign(socket, host: host) + + {:cont, socket} + end + + def on_mount(:add_theme, _params, session, socket) do + socket = Phoenix.Component.assign(socket, theme: session["theme"]) + + {:cont, socket} + end +end diff --git a/explorer/lib/explorer_web/live/eth_converter.ex b/explorer/lib/explorer_web/live/eth_converter.ex index 0576b72bf..24009e531 100644 --- a/explorer/lib/explorer_web/live/eth_converter.ex +++ b/explorer/lib/explorer_web/live/eth_converter.ex @@ -40,10 +40,41 @@ defmodule EthConverter do {:ok, Decimal.round(usd_value, decimal_places) |> Decimal.to_string(:normal)} else {:error, reason} -> {:error, reason} - _ -> {:error, "Failed to convert wei to USD"} end end + # rounds to significant figures, instead of decimal places + def wei_to_usd_sf(wei, significant_figures \\ 3) do + with eth_amount <- wei_to_eth(wei, 18), + {:ok, eth_price} <- get_eth_price_usd() do + usd_value = + Decimal.mult(Decimal.new(to_string(eth_amount)), Decimal.new(to_string(eth_price))) + + rounded_value = round_to_sf(usd_value, significant_figures) + + {:ok, Decimal.to_string(rounded_value, :normal)} + else + {:error, reason} -> {:error, reason} + end + end + + defp round_to_sf(value, significant_figures) do + # Convert the value to a float and calculate the magnitude + value_float = Decimal.to_float(value) + magnitude = :math.log10(abs(value_float)) |> floor() + + # Calculate the factor to multiply by for shifting the decimal point + factor = :math.pow(10, significant_figures - 1 - magnitude) + + # Round, then shift back + rounded_value = value_float + |> Kernel.*(factor) + |> round() + |> Kernel./(factor) + + Decimal.new(Float.to_string(rounded_value)) + end + def multiply_eth_by_usd(eth, usd_price) do eth_float = to_float(eth) usd_float = to_float(usd_price) diff --git a/explorer/lib/explorer_web/live/pages/batch/index.ex b/explorer/lib/explorer_web/live/pages/batch/index.ex index ea7d72a63..275eac729 100644 --- a/explorer/lib/explorer_web/live/pages/batch/index.ex +++ b/explorer/lib/explorer_web/live/pages/batch/index.ex @@ -12,13 +12,16 @@ defmodule ExplorerWeb.Batch.Index do :empty %Batches{fee_per_proof: fee_per_proof} = batch -> - {_, fee_per_proof_usd} = EthConverter.wei_to_usd(fee_per_proof) + {_, fee_per_proof_usd} = EthConverter.wei_to_usd_sf(fee_per_proof, 2) %{ batch | fee_per_proof: EthConverter.wei_to_eth(fee_per_proof) } - |> Map.put(:fee_per_proof_usd, fee_per_proof_usd) + |> Map.merge(%{ + fee_per_proof_usd: fee_per_proof_usd, + status: batch |> Helpers.get_batch_status + }) end { @@ -55,13 +58,16 @@ defmodule ExplorerWeb.Batch.Index do :empty %{fee_per_proof: fee_per_proof} = batch -> - {_, fee_per_proof_usd} = EthConverter.wei_to_usd(fee_per_proof) + {_, fee_per_proof_usd} = EthConverter.wei_to_usd_sf(fee_per_proof, 2) %{ batch | fee_per_proof: EthConverter.wei_to_eth(fee_per_proof) } - |> Map.put(:fee_per_proof_usd, fee_per_proof_usd) + |> Map.merge(%{ + fee_per_proof_usd: fee_per_proof_usd, + status: batch |> Helpers.get_batch_status + }) end diff --git a/explorer/lib/explorer_web/live/pages/batch/index.html.heex b/explorer/lib/explorer_web/live/pages/batch/index.html.heex index 2f6294874..4422d0c8c 100644 --- a/explorer/lib/explorer_web/live/pages/batch/index.html.heex +++ b/explorer/lib/explorer_web/live/pages/batch/index.html.heex @@ -25,7 +25,7 @@

Status:

- <.dynamic_badge_for_batcher class="w-fit" status={Helpers.get_batch_status(@current_batch)} /> + <.dynamic_badge_for_batcher class="w-fit" status={@current_batch.status} />

diff --git a/explorer/lib/explorer_web/live/pages/batches/index.ex b/explorer/lib/explorer_web/live/pages/batches/index.ex index f6c649c8e..0367ffd70 100644 --- a/explorer/lib/explorer_web/live/pages/batches/index.ex +++ b/explorer/lib/explorer_web/live/pages/batches/index.ex @@ -1,6 +1,7 @@ defmodule ExplorerWeb.Batches.Index do alias Phoenix.PubSub require Logger + import ExplorerWeb.BatchesTable use ExplorerWeb, :live_view @page_size 15 @@ -10,6 +11,7 @@ defmodule ExplorerWeb.Batches.Index do current_page = get_current_page(params) batches = Batches.get_paginated_batches(%{page: current_page, page_size: @page_size}) + |> Helpers.enrich_batches() if connected?(socket), do: PubSub.subscribe(Explorer.PubSub, "update_views") @@ -27,6 +29,7 @@ defmodule ExplorerWeb.Batches.Index do current_page = socket.assigns.current_page batches = Batches.get_paginated_batches(%{page: current_page, page_size: @page_size}) + |> Helpers.enrich_batches() {:noreply, assign(socket, batches: batches, last_page: Batches.get_last_page(@page_size))} end diff --git a/explorer/lib/explorer_web/live/pages/batches/index.html.heex b/explorer/lib/explorer_web/live/pages/batches/index.html.heex index 26ce5ac2e..8172a2961 100644 --- a/explorer/lib/explorer_web/live/pages/batches/index.html.heex +++ b/explorer/lib/explorer_web/live/pages/batches/index.html.heex @@ -1,32 +1,11 @@
<.card_preheding>Batches <%= if @batches != :empty and @batches != [] do %> - <.table id="batches" rows={@batches}> - <:col :let={batch} label="Batch Hash" class="text-left"> - <.link navigate={~p"/batches/#{batch.merkle_root}"}> - - <%= Helpers.shorten_hash(batch.merkle_root, 6) %> - <.right_arrow /> - <.tooltip> - <%= batch.merkle_root %> - - - - - <:col :let={batch} label="Status"> - <.dynamic_badge_for_batcher status={Helpers.get_batch_status(batch)} /> - - <:col :let={batch} label="Age"> - - <%= batch.submission_timestamp |> Helpers.parse_timeago() %> - - - <:col :let={batch} label="Submission Block Number"> - <%= batch.submission_block_number |> Helpers.format_number() %> - - + <.card_background class="w-full overflow-x-auto sm:col-span-2"> + <.batches_table batches={@batches} /> + <% else %> - <.empty_card_background text="No batches found." /> + <.empty_card_background text="No Batches To Display." class="sm:col-span-2" /> <% end %>
<%= if @current_page >= 2 do %> @@ -39,9 +18,9 @@ <%= if @current_page > 1 do %> <.link navigate={~p"/batches?page=#{@current_page - 1}"}> <.button - icon="arrow-left-solid" - icon_class="group-hover:-translate-x-1 transition-all duration-150" - class="text-muted-foreground size-10 group" + icon="arrow-right-solid" + icon_class="group-hover:translate-x-1 transition-all duration-150" + class="text-muted-foreground size-10 group rotate-180" > Previous Page diff --git a/explorer/lib/explorer_web/live/pages/home/index.ex b/explorer/lib/explorer_web/live/pages/home/index.ex index 0b2af668f..2638a5c28 100644 --- a/explorer/lib/explorer_web/live/pages/home/index.ex +++ b/explorer/lib/explorer_web/live/pages/home/index.ex @@ -1,80 +1,190 @@ defmodule ExplorerWeb.Home.Index do require Logger + import ExplorerWeb.ChartComponents + import ExplorerWeb.BatchesTable use ExplorerWeb, :live_view - defp set_empty_values(socket) do - Logger.info("Setting empty values") - socket |> assign( - verified_batches: :empty, - operators_registered: :empty, - latest_batches: :empty, - verified_proofs: :empty, - restaked_amount_eth: :empty, - restaked_amount_usd: :empty - ) - end - - @impl true - def handle_info(_, socket) do + def get_stats() do verified_batches = Batches.get_amount_of_verified_batches() + avg_fee_per_proof = Batches.get_avg_fee_per_proof() - operators_registered = Operators.get_amount_of_operators() + avg_fee_per_proof_usd = + case EthConverter.wei_to_usd_sf(avg_fee_per_proof, 2) do + {:ok, value} -> value + _ -> 0 + end - latest_batches = - Batches.get_latest_batches(%{amount: 5}) - # extract only the merkle root - |> Enum.map(fn %Batches{merkle_root: merkle_root} -> merkle_root end) + avg_fee_per_proof_eth = EthConverter.wei_to_eth(avg_fee_per_proof, 4) verified_proofs = Batches.get_amount_of_verified_proofs() + operators_registered = Operators.get_amount_of_operators() restaked_amount_eth = Restakings.get_restaked_amount_eth() - restaked_amount_usd = Restakings.get_restaked_amount_usd() + + restaked_amount_usd = + case Restakings.get_restaked_amount_usd() do + nil -> + "0" + + amount -> + amount + end + + restaked_amount_usd_shorthand = + restaked_amount_usd + |> Decimal.new() + |> Decimal.to_integer() + |> Helpers.convert_number_to_shorthand() + + operator_latest_release = ReleasesHelper.get_latest_release() + + [ + %{ + title: "Proofs verified", + value: Helpers.convert_number_to_shorthand(verified_proofs), + tooltip_text: "= #{Helpers.format_number(verified_proofs)} proofs", + link: nil + }, + %{ + title: "Total batches", + value: Helpers.convert_number_to_shorthand(verified_batches), + tooltip_text: "= #{Helpers.format_number(verified_batches)} batches", + link: nil + }, + %{ + title: "AVG proof cost", + value: "#{avg_fee_per_proof_usd} USD", + tooltip_text: "~= #{avg_fee_per_proof_eth} ETH", + link: nil + }, + %{ + title: "Operators", + value: operators_registered, + tooltip_text: "Current version #{operator_latest_release}", + link: "/operators" + }, + %{ + title: "Total restaked", + value: "#{restaked_amount_usd_shorthand} USD", + # Using HTML.raw to break paragraph line with
+ tooltip_text: + Phoenix.HTML.raw( + "= #{Helpers.format_number(restaked_amount_usd)} USD
~= #{restaked_amount_eth} ETH" + ), + link: "/restaked" + } + ] + end + + def get_cost_per_proof_chart_data(amount) do + batches = + Enum.reverse(Batches.get_latest_batches(%{amount: amount, order_by: :desc})) + + extra_data = + %{ + merkle_root: Enum.map(batches, fn b -> b.merkle_root end), + amount_of_proofs: Enum.map(batches, fn b -> b.amount_of_proofs end), + age: Enum.map(batches, fn b -> Helpers.parse_timeago(b.submission_timestamp) end) + } + + points = + Enum.map(batches, fn b -> + fee_per_proof = + case EthConverter.wei_to_usd_sf(b.fee_per_proof, 2) do + {:ok, value} -> + value + + # Nil values are ignored by the chart + {:error, _} -> + nil + end + + %{x: b.submission_block_number, y: fee_per_proof} + end) + + %{ + points: points, + extra_data: extra_data + } + end + + def get_batch_size_chart_data(amount) do + batches = + Enum.reverse(Batches.get_latest_batches(%{amount: amount, order_by: :desc})) + + extra_data = + %{ + merkle_root: Enum.map(batches, fn b -> b.merkle_root end), + fee_per_proof: + Enum.map(batches, fn b -> + case EthConverter.wei_to_usd_sf(b.fee_per_proof, 2) do + {:ok, value} -> + value + + {:error, _} -> + nil + end + end), + age: Enum.map(batches, fn b -> Helpers.parse_timeago(b.submission_timestamp) end) + } + + points = + Enum.map(batches, fn b -> + %{x: b.submission_block_number, y: b.amount_of_proofs} + end) + + %{ + points: points, + extra_data: extra_data + } + end + + defp set_empty_values(socket) do + Logger.info("Setting empty values") + + socket + |> assign( + stats: [], + latest_batches: [], + cost_per_proof_data: %{points: [], extra_data: %{}}, + batch_size_chart_data: %{points: [], extra_data: %{}} + ) + end + + @impl true + def handle_info(_, socket) do + latest_batches = Batches.get_latest_batches(%{amount: 10, order_by: :desc}) + charts_query_limit = 20 {:noreply, assign( socket, - verified_batches: verified_batches, - operators_registered: operators_registered, + stats: get_stats(), latest_batches: latest_batches, - verified_proofs: verified_proofs, - restaked_amount_eth: restaked_amount_eth, - restaked_amount_usd: restaked_amount_usd + cost_per_proof_chart: get_cost_per_proof_chart_data(charts_query_limit), + batch_size_chart_data: get_batch_size_chart_data(charts_query_limit) )} end @impl true def mount(_, _, socket) do - verified_batches = Batches.get_amount_of_verified_batches() - - operators_registered = Operators.get_amount_of_operators() - - latest_batches = - Batches.get_latest_batches(%{amount: 5}) - # extract only the merkle root - |> Enum.map(fn %Batches{merkle_root: merkle_root} -> merkle_root end) - - verified_proofs = Batches.get_amount_of_verified_proofs() - - restaked_amount_eth = Restakings.get_restaked_amount_eth() - restaked_amount_usd = Restakings.get_restaked_amount_usd() + latest_batches = Batches.get_latest_batches(%{amount: 10, order_by: :desc}) + charts_query_limit = 20 if connected?(socket), do: Phoenix.PubSub.subscribe(Explorer.PubSub, "update_views") {:ok, assign(socket, - verified_batches: verified_batches, - operators_registered: operators_registered, + stats: get_stats(), latest_batches: latest_batches, - verified_proofs: verified_proofs, - service_manager_address: - AlignedLayerServiceManager.get_aligned_layer_service_manager_address(), - restaked_amount_eth: restaked_amount_eth, - restaked_amount_usd: restaked_amount_usd, + cost_per_proof_chart: get_cost_per_proof_chart_data(charts_query_limit), + batch_size_chart_data: get_batch_size_chart_data(charts_query_limit), page_title: "Welcome" )} rescue e in Mint.TransportError -> Logger.error("Error: Mint.TransportError: #{inspect(e)}") + case e do %Mint.TransportError{reason: :econnrefused} -> { @@ -87,11 +197,13 @@ defmodule ExplorerWeb.Home.Index do { :ok, set_empty_values(socket) - |> put_flash(:error, "Something went wrong, please try again later.")} + |> put_flash(:error, "Something went wrong, please try again later.") + } end e in FunctionClauseError -> Logger.error("Error: FunctionClauseError: #{inspect(e)}") + case e do %FunctionClauseError{ module: ExplorerWeb.Home.Index @@ -105,10 +217,12 @@ defmodule ExplorerWeb.Home.Index do e -> Logger.error("Error: other error: #{inspect(e)}") + { :ok, set_empty_values(socket) - |> put_flash(:error, "Something went wrong, please try again later.")} + |> put_flash(:error, "Something went wrong, please try again later.") + } end embed_templates("*") diff --git a/explorer/lib/explorer_web/live/pages/home/index.html.heex b/explorer/lib/explorer_web/live/pages/home/index.html.heex index 2c94c086b..18bb7687b 100644 --- a/explorer/lib/explorer_web/live/pages/home/index.html.heex +++ b/explorer/lib/explorer_web/live/pages/home/index.html.heex @@ -1,65 +1,87 @@ -
-

- Aligned Explorer -

-
- <.card_link - icon="hero-arrow-right-solid" - navigate={~p"/operators"} - title="Registered Active Operators" - > - <%= if @operators_registered != :empty do %> - <%= @operators_registered %> - <% end %> - - <.card_link - icon="hero-arrow-right-solid" - navigate={~p"/restake"} - title="Total Restaked" - subtitle={ - if @restaked_amount_eth |> Helpers.format_number() != nil do - "(#{@restaked_amount_eth |> Helpers.format_number()} ETH)" - end - }> - <%= if @restaked_amount_usd |> Helpers.format_number() != nil do %> - <%= @restaked_amount_usd |> Helpers.format_number() %> USD - <% end %> - - <.card title="verified batches"> - <%= if @verified_batches != :empty do %> - <%= @verified_batches |> Helpers.format_number() %> - <% end %> - - <.card title="Verified Proofs" class="-mt-0.5 md:mt-0"> - <%= if @verified_proofs != :empty do %> - <%= @verified_proofs |> Helpers.format_number() %> +
+
+

+ Aligned Layer Explorer +

+
+
+ <.card_background class="flex lg:justify-between gap-10 px-20 py-8 flex-wrap"> + <%= for %{title: title, value: value, tooltip_text: tooltip_text, link: link} <- @stats do %> +
+ <.link + navigate={link} + class={[ + "group", + link == nil && "cursor-default pointer-events-none" + ]} + > +

+ <%= title %> +

+

<%= value %>

+ + <%= if tooltip_text do %> + <.tooltip> + <%= tooltip_text %> + + <% end %> +
<% end %> - - <.live_component module={ContractsComponent} id="contracts_card" class="sm:col-span-2" /> + +
+ <.card + title="Cost per proof" + subtitle="Cost of proving over time" + class="p-0 flex-1" + header_container_class="px-10 pt-8" + > +
+ <.bar_chart + id="cost_per_proof_chart" + points={@cost_per_proof_chart.points} + extra_data={@cost_per_proof_chart.extra_data} + /> +
+ + <.card + title="Batch size" + subtitle="Number of proofs in last batches" + class="p-0 flex-1" + header_container_class="px-10 pt-8" + > +
+ <.bar_chart + id="batch_size_chart" + points={@batch_size_chart_data.points} + extra_data={@batch_size_chart_data.extra_data} + /> +
+ +
+ <%= if @latest_batches != :empty and @latest_batches != [] do %> <.card - class="relative sm:col-span-2 w-full flex flex-col space-y-1" - inner_class="text-ellipsis overflow-hidden text-lg flex flex-col space-y-1" - title="Recent Batches" + title="Latest batches" + subtitle="The most recent verified batches on aligned" + class="overflow-x-auto" > - <.link - navigate={~p"/batches"} - class="absolute top-4 right-5 hover:underline font-medium text-muted-foreground capitalize text-sm" - > - View All - - <%= for batch <- @latest_batches do %> - <.link class="flex justify-between group" navigate={~p"/batches/#{batch}"}> - - <%= Helpers.shorten_hash(batch) %> - - <.right_arrow /> - <.tooltip> - <%= batch %> - + <.batches_table batches={@latest_batches} /> +
+ <.link + navigate={~p"/batches"} + class="absolute hover:underline font-medium text-muted-foreground capitalize text-sm" + > + View All <.icon name="hero-arrow-top-right-on-square-solid" class="size-3.5 mb-1" /> - <% end %> +
+ <% else %> + <.empty_card_background text="No Batches To Display." /> <% end %> + + <.live_component module={ContractsComponent} id="contracts_card" class="" host={@host} />
diff --git a/explorer/lib/explorer_web/live/pages/operators/index.ex b/explorer/lib/explorer_web/live/pages/operators/index.ex index 26e156802..c05a54aa0 100644 --- a/explorer/lib/explorer_web/live/pages/operators/index.ex +++ b/explorer/lib/explorer_web/live/pages/operators/index.ex @@ -54,46 +54,54 @@ defmodule ExplorerWeb.Operators.Index do operators_registered={@operators_registered} /> <%= if @operators != [] do %> - <.table id="operators" rows={@operators}> - <:col :let={operator} label="Name" class="[animation-delay: 3s]"> - <.link navigate={~p"/operators/#{operator.address}"} class="flex gap-x-2"> - - {operator.name} - - <%= operator.name %> - <%= if @operator_versions[operator.address] != nil do %> - <.badge class="text-xs px-1.5" variant="secondary"> - <%= @operator_versions[operator.address] %> - - <% end %> + <.card_background> + <.table id="operators" rows={@operators}> + <:col :let={operator} label="Name" class="[animation-delay: 3s]"> + <.link navigate={~p"/operators/#{operator.address}"} class="flex gap-x-2"> + + {operator.name} + + <%= operator.name %> + <%= if @operator_versions[operator.address] != nil do %> + <.badge class="text-xs px-1.5" variant="secondary"> + <%= @operator_versions[operator.address] %> + + <% end %> + + <.right_arrow /> + <.tooltip class="py-2 px-2.5 rounded-2xl"> + Id: <%= operator.id + |> Helpers.binary_to_hex_string() %> +
+ Address: <%= operator.address %> +
- <.right_arrow /> - <.tooltip class="py-2 px-2.5 rounded-2xl"> - Id: <%= operator.id - |> Helpers.binary_to_hex_string() %> -
- Address: <%= operator.address %> - -
- - - <:col :let={operator} label="Restake Concentration"> - <%= operator.weight |> Numbers.show_percentage() %> - - <:col :let={operator} label="Total Restaked"> -
-

<%= operator.total_stake_usd |> Helpers.format_number() %> USD

-

<%= operator.total_stake_eth |> Helpers.format_number() %> ETH

-
- - <:col :let={operator} label="Status"> - <.dynamic_badge_boolean status={operator.is_active} truthy_text="Active" falsy_text="Inactive" /> - - + + + <:col :let={operator} label="Restake Concentration"> + <%= operator.weight |> Numbers.show_percentage() %> + + <:col :let={operator} label="Total Restaked"> +
+

<%= operator.total_stake_usd |> Helpers.format_number() %> USD

+

+ <%= operator.total_stake_eth |> Helpers.format_number() %> ETH +

+
+ + <:col :let={operator} label="Status"> + <.dynamic_badge_boolean + status={operator.is_active} + truthy_text="Active" + falsy_text="Inactive" + /> + + + <% else %> <.empty_card_background text="No operators found." /> <% end %> diff --git a/explorer/lib/explorer_web/live/pages/restakes/index.ex b/explorer/lib/explorer_web/live/pages/restakes/index.ex index 02d184283..3bda8ad50 100644 --- a/explorer/lib/explorer_web/live/pages/restakes/index.ex +++ b/explorer/lib/explorer_web/live/pages/restakes/index.ex @@ -65,39 +65,43 @@ defmodule ExplorerWeb.Restakes.Index do operators_registered={@operators_registered} /> <%= if @assets != [] do %> - <.table id="assets" rows={@assets}> - <:col :let={asset} label="Token" class="text-left"> - <.link - navigate={~p"/restake/#{asset.strategy_address}"} - class="flex gap-x-2 items-center group-hover:text-foreground/80" - > - String.downcase()}.webp"} - alt={asset.name} - class="size-5 rounded-full object-scale-down text-xs truncate text-center" - /> - <%= if asset.name != "‎" do %> - <%= asset.name %> - <% else %> - <%= asset.strategy_address %> - <% end %> -

- <%= asset.symbol %> -

- <.right_arrow /> - - - <:col :let={asset} label="Total Restaked"> -
- <%= if asset.total_staked_eth != nil do %> -

<%= asset.total_staked_usd |> Helpers.format_number() %> USD

-

<%= asset.total_staked_eth |> Helpers.format_number() %> ETH

- <% else %> - N/A - <% end %> -
- - + <.card_background> + <.table id="assets" rows={@assets}> + <:col :let={asset} label="Token" class="text-left"> + <.link + navigate={~p"/restaked/#{asset.strategy_address}"} + class="flex gap-x-2 items-center group-hover:text-foreground/80" + > + String.downcase()}.webp"} + alt={asset.name} + class="size-5 rounded-full object-scale-down text-xs truncate text-center" + /> + <%= if asset.name != "‎" do %> + <%= asset.name %> + <% else %> + <%= asset.strategy_address %> + <% end %> +

+ <%= asset.symbol %> +

+ <.right_arrow /> + + + <:col :let={asset} label="Total Restaked"> +
+ <%= if asset.total_staked_eth != nil do %> +

<%= asset.total_staked_usd |> Helpers.format_number() %> USD

+

+ <%= asset.total_staked_eth |> Helpers.format_number() %> ETH +

+ <% else %> + N/A + <% end %> +
+ + + <% else %> <.empty_card_background text="No restaked assets found." /> <% end %> diff --git a/explorer/lib/explorer_web/live/utils.ex b/explorer/lib/explorer_web/live/utils.ex index 0df4cc3ef..79bbb4062 100644 --- a/explorer/lib/explorer_web/live/utils.ex +++ b/explorer/lib/explorer_web/live/utils.ex @@ -8,11 +8,8 @@ defmodule ExplorerWeb.Helpers do end def convert_number_to_shorthand(number) when number >= 1_000_000 do - "#{div(number, 1_000_000)}M" - end - - def convert_number_to_shorthand(number) when number >= 10_000 do - "#{div(number, 10_000)}k" + formatted_number = Float.round(number / 1_000_000, 2) + "#{remove_trailing_zeros(formatted_number)}M" end def convert_number_to_shorthand(number) when number >= 1_000 do @@ -25,6 +22,14 @@ defmodule ExplorerWeb.Helpers do def convert_number_to_shorthand(_number), do: "Invalid number" + defp remove_trailing_zeros(number) do + if Float.ceil(number) == Float.floor(number) do + Integer.to_string(trunc(number)) + else + Float.to_string(Float.round(number, 2)) + end + end + def parse_timestamp(timestamp) do %{hour: hour, minute: minute, second: second, day: day, month: month, year: year} = timestamp @@ -108,6 +113,27 @@ defmodule ExplorerWeb.Helpers do end end + @doc """ + Returns a list of available AlignedLayer networks with their names and explorer URLs. + """ + def get_aligned_networks() do + [ + {"Mainnet", "https://explorer.alignedlayer.com"}, + {"Holesky", "https://holesky.explorer.alignedlayer.com"}, + {"Stage", "https://stage.explorer.alignedlayer.com"}, + {"Devnet", "http://localhost:4000/"} + ] + end + + def get_current_network_from_host(host) do + case host do + "explorer.alignedlayer.com" -> "Mainnet" + "holesky.explorer.alignedlayer.com" -> "Holesky" + "stage.explorer.alignedlayer.com" -> "Stage" + _ -> "Devnet" + end + end + @doc """ Get the Etherscan URL based on the environment. - `holesky` -> https://holesky.etherscan.io @@ -124,17 +150,42 @@ defmodule ExplorerWeb.Helpers do end end + def get_aligned_contracts_addresses() do + aligned_config_file = System.get_env("ALIGNED_CONFIG_FILE") + {_, config_json_string} = File.read(aligned_config_file) + Jason.decode!(config_json_string) |> Map.get("addresses") + end + def binary_to_hex_string(binary) do Utils.binary_to_hex_string(binary) end + def is_stale?(batch) do + ttl = Utils.batch_ttl_minutes() + + DateTime.add(batch.submission_timestamp, ttl, :minute) + |> DateTime.before?(DateTime.utc_now()) + end + def get_batch_status(batch) do cond do not batch.is_valid -> :invalid batch.is_verified -> :verified + is_stale?(batch) -> :stale true -> :pending end end + + def enrich_batches(batches) do + batches + |> Enum.map(fn batch -> + batch + |> Map.merge(%{ + age: batch.submission_timestamp |> parse_timeago(), + status: batch |> get_batch_status + }) + end) + end end # Backend utils @@ -272,6 +323,11 @@ defmodule Utils do end end + def batch_ttl_minutes() do + System.get_env("BATCH_TTL_MINUTES") + |> String.to_integer() + end + def process_batch(%BatchDB{} = batch) do case get_proof_hashes(batch) do {:ok, proof_hashes} -> diff --git a/explorer/lib/explorer_web/plugs.ex b/explorer/lib/explorer_web/plugs.ex new file mode 100644 index 000000000..b3fb09870 --- /dev/null +++ b/explorer/lib/explorer_web/plugs.ex @@ -0,0 +1,13 @@ +defmodule ExplorerWeb.Plugs do + import Plug.Conn + + def init(default), do: default + + def load_theme_cookie_in_session(conn, _opts) do + # Default to dark if not present + theme = Map.get(conn.cookies, "theme", "dark") + + conn + |> put_session(:theme, theme) + end +end diff --git a/explorer/lib/explorer_web/router.ex b/explorer/lib/explorer_web/router.ex index 87834237a..e85cb7491 100644 --- a/explorer/lib/explorer_web/router.ex +++ b/explorer/lib/explorer_web/router.ex @@ -1,5 +1,6 @@ defmodule ExplorerWeb.Router do use ExplorerWeb, :router + import ExplorerWeb.Plugs # https://furlough.merecomplexities.com/elixir/phoenix/security/2021/02/26/content-security-policy-configuration-in-phoenix.html @@ -21,6 +22,7 @@ defmodule ExplorerWeb.Router do plug :accepts, ["html"] plug :fetch_session plug :fetch_live_flash + plug :load_theme_cookie_in_session plug :put_root_layout, html: {ExplorerWeb.Layouts, :root} plug :protect_from_forgery plug :put_secure_browser_headers, %{"content-security-policy" => @content_security_policy} @@ -34,12 +36,13 @@ defmodule ExplorerWeb.Router do pipe_through :browser # https://fly.io/phoenix-files/live-session/ - live_session :default do + live_session :default, + on_mount: [{ExplorerWeb.Hooks, :add_host}, {ExplorerWeb.Hooks, :add_theme}] do live "/", Home.Index live "/batches/:merkle_root", Batch.Index live "/batches", Batches.Index - live "/restake", Restakes.Index - live "/restake/:address", Restake.Index + live "/restaked", Restakes.Index + live "/restaked/:address", Restake.Index live "/operators", Operators.Index live "/operators/:address", Operator.Index live "/search", Search.Index diff --git a/explorer/start.sh b/explorer/start.sh index 94786e011..1763d7314 100755 --- a/explorer/start.sh +++ b/explorer/start.sh @@ -15,6 +15,7 @@ env_vars=( "DEBUG_ERRORS" "TRACKER_API_URL" "MAX_BATCH_SIZE" + "BATCH_TTL_MINUTES" ) for var in "${env_vars[@]}"; do diff --git a/grafana/provisioning/dashboards/aligned/aggregator_batcher.json b/grafana/provisioning/dashboards/aligned/aggregator_batcher.json index b4eb8f3bf..0ddda5311 100644 --- a/grafana/provisioning/dashboards/aligned/aggregator_batcher.json +++ b/grafana/provisioning/dashboards/aligned/aggregator_batcher.json @@ -23,27 +23,67 @@ "liveNow": false, "panels": [ { - "collapsed": false, + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, "gridPos": { - "h": 1, - "w": 24, + "h": 2, + "w": 15, "x": 0, "y": 0 }, - "id": 11, - "panels": [], - "title": "Batcher", - "type": "row" + "id": 34, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "

\n ETHEREUM\n

", + "mode": "html" + }, + "pluginVersion": "10.1.10", + "transparent": true, + "type": "text" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "gridPos": { + "h": 2, + "w": 8, + "x": 16, + "y": 0 + }, + "id": 35, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "

\n HISTORIC DATA\n

", + "mode": "html" + }, + "pluginVersion": "10.1.10", + "transparent": true, + "type": "text" }, { "datasource": { + "default": true, "type": "prometheus", "uid": "prometheus" }, + "description": "Ethereum Gas Price in GWEI", "fieldConfig": { "defaults": { "color": { - "mode": "thresholds" + "fixedColor": "green", + "mode": "fixed" }, "mappings": [], "thresholds": { @@ -52,9 +92,14 @@ { "color": "green", "value": null + }, + { + "color": "red", + "value": 80 } ] - } + }, + "unit": "GWEI" }, "overrides": [] }, @@ -62,15 +107,15 @@ "h": 6, "w": 4, "x": 0, - "y": 1 + "y": 2 }, - "id": 12, + "id": 32, "options": { "colorMode": "value", "graphMode": "none", "justifyMode": "auto", "orientation": "auto", - "percentChangeColorMode": "standard", + "percentChangeColorMode": "inverted", "reduceOptions": { "calcs": [ "lastNotNull" @@ -82,7 +127,7 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "11.2.2+security-01", + "pluginVersion": "10.1.10", "targets": [ { "datasource": { @@ -90,8 +135,10 @@ "uid": "prometheus" }, "disableTextWrap": false, - "editorMode": "builder", - "expr": "floor(increase(received_proofs{job=\"aligned-batcher\"}[10y]))", + "editorMode": "code", + "exemplar": false, + "expr": "gas_price{job=\"aligned-tracker\"} * 10^-9", + "format": "time_series", "fullMetaSearch": false, "includeNullMetadata": true, "instant": false, @@ -101,7 +148,7 @@ "useBackend": false } ], - "title": "Total Proofs Received", + "title": "Ethereum Gas Price [GWEI]", "type": "stat" }, { @@ -109,10 +156,44 @@ "type": "prometheus", "uid": "prometheus" }, + "description": "Ethereum Gas Price evolution in GWEI", "fieldConfig": { "defaults": { "color": { - "mode": "thresholds" + "fixedColor": "green", + "mode": "fixed" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 2, + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } }, "mappings": [], "thresholds": { @@ -121,37 +202,34 @@ { "color": "green", "value": null - }, - { - "color": "red", - "value": 256 } ] - } + }, + "unit": "GWEI" }, "overrides": [] }, "gridPos": { "h": 6, - "w": 4, + "w": 11, "x": 4, - "y": 1 + "y": 2 }, - "id": 17, + "id": 31, "options": { - "minVizHeight": 75, - "minVizWidth": 75, - "orientation": "auto", - "reduceOptions": { + "legend": { "calcs": [ - "lastNotNull" + "lastNotNull", + "max" ], - "fields": "", - "values": false + "displayMode": "list", + "placement": "bottom", + "showLegend": true }, - "showThresholdLabels": false, - "showThresholdMarkers": true, - "sizing": "auto" + "tooltip": { + "mode": "single", + "sort": "none" + } }, "pluginVersion": "11.2.2+security-01", "targets": [ @@ -161,19 +239,21 @@ "uid": "prometheus" }, "disableTextWrap": false, - "editorMode": "builder", - "expr": "open_connections{job=\"aligned-batcher\"}", + "editorMode": "code", + "exemplar": false, + "expr": "round(gas_price{job=\"aligned-tracker\"} * 10^-9 != 0, 0.00001)", + "format": "time_series", "fullMetaSearch": false, "includeNullMetadata": true, "instant": false, - "legendFormat": "__auto", + "legendFormat": ".", "range": true, "refId": "A", "useBackend": false } ], - "title": "Open Connections", - "type": "gauge" + "title": "Ethereum Gas Price [GWEI]", + "type": "timeseries" }, { "datasource": { @@ -183,40 +263,7 @@ "fieldConfig": { "defaults": { "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } + "mode": "thresholds" }, "mappings": [], "thresholds": { @@ -225,10 +272,6 @@ { "color": "green", "value": null - }, - { - "color": "red", - "value": 80 } ] } @@ -237,22 +280,27 @@ }, "gridPos": { "h": 6, - "w": 13, - "x": 8, - "y": 1 + "w": 4, + "x": 16, + "y": 2 }, - "id": 16, + "id": 12, "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false }, - "tooltip": { - "mode": "single", - "sort": "none" - } + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true }, "pluginVersion": "10.1.10", "targets": [ @@ -263,7 +311,7 @@ }, "disableTextWrap": false, "editorMode": "code", - "expr": "floor(increase(batcher_started{job=\"aligned-batcher\"}[$__range]))", + "expr": "floor(increase(received_proofs_count{job=\"aligned-batcher\"}[10y]))", "fullMetaSearch": false, "includeNullMetadata": true, "instant": false, @@ -273,8 +321,8 @@ "useBackend": false } ], - "title": "Total Batcher Restarts", - "type": "timeseries" + "title": "Total Proofs Received", + "type": "stat" }, { "datasource": { @@ -310,10 +358,10 @@ "gridPos": { "h": 6, "w": 4, - "x": 0, - "y": 7 + "x": 20, + "y": 2 }, - "id": 14, + "id": 41, "options": { "colorMode": "value", "graphMode": "none", @@ -331,7 +379,7 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "11.2.2+security-01", + "pluginVersion": "10.1.10", "targets": [ { "datasource": { @@ -340,7 +388,7 @@ }, "disableTextWrap": false, "editorMode": "code", - "expr": "floor(increase(sent_batches{job=\"aligned-batcher\"}[$__range]))", + "expr": "floor(increase(sent_batches_count{job=\"aligned-batcher\"}[10y]))", "fullMetaSearch": false, "includeNullMetadata": true, "instant": false, @@ -355,46 +403,14 @@ }, { "datasource": { + "default": true, "type": "prometheus", "uid": "prometheus" }, "fieldConfig": { "defaults": { "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } + "mode": "thresholds" }, "mappings": [], "thresholds": { @@ -403,35 +419,38 @@ { "color": "green", "value": null - }, - { - "color": "red", - "value": 80 } ] - } + }, + "unit": "GWEI" }, "overrides": [] }, "gridPos": { "h": 6, - "w": 9, - "x": 4, - "y": 7 + "w": 4, + "x": 0, + "y": 8 }, - "id": 13, + "id": 33, "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false }, - "tooltip": { - "mode": "single", - "sort": "none" - } + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true }, + "pluginVersion": "10.1.10", "targets": [ { "datasource": { @@ -440,7 +459,7 @@ }, "disableTextWrap": false, "editorMode": "code", - "expr": "floor(increase(sent_batches{job=\"aligned-batcher\"}[10y]))", + "expr": "gas_price_used_on_latest_batch{job=\"aligned-batcher\"} * 10^-9", "fullMetaSearch": false, "includeNullMetadata": true, "instant": false, @@ -450,8 +469,8 @@ "useBackend": false } ], - "title": "Batches Sent", - "type": "timeseries" + "title": "Gas Price on Latest Batch Sent [GWEI]", + "type": "stat" }, { "datasource": { @@ -464,15 +483,13 @@ "mode": "palette-classic" }, "custom": { - "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, - "barWidthFactor": 0.6, "drawStyle": "line", - "fillOpacity": 25, + "fillOpacity": 0, "gradientMode": "none", "hideFrom": { "legend": false, @@ -481,13 +498,16 @@ }, "insertNulls": false, "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", - "spanNulls": false, + "spanNulls": true, "stacking": { "group": "A", "mode": "none" @@ -503,26 +523,26 @@ { "color": "green", "value": null - }, - { - "color": "red", - "value": 80 } ] - } + }, + "unit": "GWEI" }, "overrides": [] }, "gridPos": { - "h": 12, - "w": 8, - "x": 13, - "y": 7 + "h": 6, + "w": 11, + "x": 4, + "y": 8 }, - "id": 18, + "id": 29, "options": { "legend": { - "calcs": [], + "calcs": [ + "lastNotNull", + "max" + ], "displayMode": "list", "placement": "bottom", "showLegend": true @@ -540,17 +560,19 @@ }, "disableTextWrap": false, "editorMode": "code", - "expr": "gas_price_used_on_latest_batch{job=\"aligned-batcher\"}", + "exemplar": false, + "expr": "round(gas_price_used_on_latest_batch{job=\"aligned-batcher\"} * 10^-9 != 0, 0.00001)", "fullMetaSearch": false, "includeNullMetadata": true, "instant": false, - "legendFormat": "__auto", + "interval": "", + "legendFormat": "{{job}}", "range": true, "refId": "A", "useBackend": false } ], - "title": "Gas price on latest batch", + "title": "Gas Price on Latest Batch Sent [GWEI]", "type": "timeseries" }, { @@ -558,6 +580,7 @@ "type": "prometheus", "uid": "prometheus" }, + "description": "", "fieldConfig": { "defaults": { "color": { @@ -570,14 +593,6 @@ { "color": "green", "value": null - }, - { - "color": "#FF9830", - "value": 3 - }, - { - "color": "red", - "value": 5 } ] } @@ -587,10 +602,10 @@ "gridPos": { "h": 6, "w": 4, - "x": 0, - "y": 13 + "x": 16, + "y": 8 }, - "id": 15, + "id": 8, "options": { "colorMode": "value", "graphMode": "none", @@ -608,7 +623,7 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "11.2.2+security-01", + "pluginVersion": "10.1.10", "targets": [ { "datasource": { @@ -617,7 +632,7 @@ }, "disableTextWrap": false, "editorMode": "code", - "expr": "floor(increase(reverted_batches{job=\"aligned-batcher\"}[$__range]))", + "expr": "floor(increase(aligned_aggregator_received_tasks_count{bot=\"aggregator\"}[10y]))", "fullMetaSearch": false, "includeNullMetadata": true, "instant": false, @@ -627,7 +642,7 @@ "useBackend": false } ], - "title": "Reverted Batches", + "title": "Total Tasks Received", "type": "stat" }, { @@ -638,40 +653,7 @@ "fieldConfig": { "defaults": { "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } + "mode": "thresholds" }, "mappings": [], "thresholds": { @@ -680,10 +662,6 @@ { "color": "green", "value": null - }, - { - "color": "red", - "value": 80 } ] } @@ -692,23 +670,29 @@ }, "gridPos": { "h": 6, - "w": 9, - "x": 4, - "y": 13 + "w": 4, + "x": 20, + "y": 8 }, - "id": 19, + "id": 2, "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false }, - "tooltip": { - "mode": "single", - "sort": "none" - } + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true }, + "pluginVersion": "10.1.10", "targets": [ { "datasource": { @@ -716,26 +700,29 @@ "uid": "prometheus" }, "disableTextWrap": false, - "editorMode": "builder", - "expr": "floor(increase(reverted_batches{job=\"aligned-batcher\"}[10y]))", + "editorMode": "code", + "exemplar": false, + "expr": "floor(increase(aligned_aggregated_responses_count{bot=\"aggregator\"}[10y]))", + "format": "table", "fullMetaSearch": false, "includeNullMetadata": true, "instant": false, + "interval": "", "legendFormat": "__auto", "range": true, "refId": "A", "useBackend": false } ], - "title": "Batches Reverted", - "type": "timeseries" + "title": "Total Tasks Verified", + "type": "stat" }, { "datasource": { - "default": true, "type": "prometheus", "uid": "prometheus" }, + "description": "Accumulated cost in ETH the Batcher paid when sending Create Task or Cancel Task transactions.", "fieldConfig": { "defaults": { "color": { @@ -748,27 +735,20 @@ { "color": "green", "value": null - }, - { - "color": "#EAB839", - "value": 3 - }, - { - "color": "red", - "value": 5 } ] - } + }, + "unit": "ETH" }, "overrides": [] }, "gridPos": { - "h": 6, - "w": 4, + "h": 3, + "w": 7, "x": 0, - "y": 19 + "y": 14 }, - "id": 22, + "id": 58, "options": { "colorMode": "value", "graphMode": "none", @@ -786,7 +766,7 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "11.2.2+security-01", + "pluginVersion": "10.1.10", "targets": [ { "datasource": { @@ -795,7 +775,7 @@ }, "disableTextWrap": false, "editorMode": "code", - "expr": "floor(increase(canceled_batches{job=\"aligned-batcher\"}[$__range]))", + "expr": "increase(batcher_gas_cost_create_task_total{bot=\"batcher\"}[$__range])", "fullMetaSearch": false, "includeNullMetadata": true, "instant": false, @@ -803,9 +783,38 @@ "range": true, "refId": "A", "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "increase(batcher_gas_cost_cancel_task_total{bot=\"batcher\"}[$__range]) * 10 ^ -9", + "hide": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "B" + } + ], + "title": "Accumulated Batcher Cost Paid [ETH]", + "transformations": [ + { + "id": "calculateField", + "options": {} + }, + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": [ + "Total" + ] + } + } } ], - "title": "Canceled Batches", "type": "stat" }, { @@ -813,43 +822,11 @@ "type": "prometheus", "uid": "prometheus" }, + "description": "Accumulated gas cost in ETH the Aggregator paid when sending RespondToTask transactions.", "fieldConfig": { "defaults": { "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } + "mode": "thresholds" }, "mappings": [], "thresholds": { @@ -858,35 +835,38 @@ { "color": "green", "value": null - }, - { - "color": "red", - "value": 80 } ] - } + }, + "unit": "ETH" }, "overrides": [] }, "gridPos": { - "h": 6, - "w": 9, - "x": 4, - "y": 19 + "h": 3, + "w": 7, + "x": 7, + "y": 14 }, - "id": 21, + "id": 56, "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false }, - "tooltip": { - "mode": "single", - "sort": "none" - } + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true }, + "pluginVersion": "10.1.10", "targets": [ { "datasource": { @@ -894,8 +874,8 @@ "uid": "prometheus" }, "disableTextWrap": false, - "editorMode": "builder", - "expr": "canceled_batches", + "editorMode": "code", + "expr": "increase(aligned_aggregator_gas_cost_paid_total_count{bot=\"aggregator\"}[$__range])", "fullMetaSearch": false, "includeNullMetadata": true, "instant": false, @@ -905,52 +885,19 @@ "useBackend": false } ], - "title": "Batches Canceled", - "type": "timeseries" + "title": "Accumulated Aggregator Gas Cost Paid [ETH]", + "type": "stat" }, { "datasource": { "type": "prometheus", "uid": "prometheus" }, - "description": "Measures websocket connections that were abnormally disconnected.", + "description": "Historical accumulated cost in ETH the Batcher paid when sending Create Task or Cancel Task transactions.", "fieldConfig": { "defaults": { "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } + "mode": "thresholds" }, "mappings": [], "thresholds": { @@ -959,24 +906,1978 @@ { "color": "green", "value": null - }, - { - "color": "red", - "value": 80 } ] - } + }, + "unit": "ETH" }, "overrides": [] }, "gridPos": { - "h": 8, - "w": 10, - "x": 0, - "y": 25 + "h": 3, + "w": 4, + "x": 16, + "y": 14 }, - "id": 20, - "interval": "1m", + "id": 52, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.1.10", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "increase(batcher_gas_cost_create_task_total{bot=\"batcher\"}[10y])", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "increase(batcher_gas_cost_cancel_task_total{bot=\"batcher\"}[10y])", + "hide": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "B" + } + ], + "title": "Historical - Accumulated Batcher Cost Paid [ETH]", + "transformations": [ + { + "id": "calculateField", + "options": {} + }, + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": [ + "Total" + ] + } + } + } + ], + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "Accumulated gas cost in ETH the Aggregator paid when sending RespondToTask transactions.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "ETH" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 20, + "y": 14 + }, + "id": 48, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.1.10", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "increase(aligned_aggregator_gas_cost_paid_total_count{bot=\"aggregator\"}[10y])", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Historical - Accumulated Aggregator Gas Cost Paid [ETH]", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "Accumulated cost in ETH the Batcher paid when sending Cancel Task transactions.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "ETH" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 7, + "x": 0, + "y": 17 + }, + "id": 53, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.1.10", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "increase(batcher_gas_cost_cancel_task_total{bot=\"batcher\"}[$__range])", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Accumulated Batcher Cancel Task Cost [ETH]", + "transformations": [], + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "Accumulated gas cost the aggregator paid for the batcher when the tx cost was higher than the respondToTaskFeeLimit.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "ETH" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 7, + "x": 7, + "y": 17 + }, + "id": 59, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.1.10", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "increase(aligned_aggregator_gas_cost_paid_for_batcher_sum{bot=\"aggregator\"}[$__range])", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Accumulated Aggregator Extra Cost Paid [ETH]", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "Historical accumulated cost in ETH the Batcher paid when Canceling a Task", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "ETH" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 16, + "y": 17 + }, + "id": 50, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.1.10", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "increase(batcher_gas_cost_cancel_task_total{bot=\"batcher\"}[10y])", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Historical - Accumulated Batcher Cancel Task Cost [ETH]", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "Accumulated gas cost the aggregator paid for the batcher when the tx cost was higher than the respondToTaskFeeLimit.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "ETH" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 20, + "y": 17 + }, + "id": 54, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.1.10", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "increase(aligned_aggregator_gas_cost_paid_for_batcher_sum{bot=\"aggregator\"}[10y])", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Historical - Accumulated Aggregator Extra Cost Paid [ETH]", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "Number of times the aggregator paid for the batcher when the tx cost was higher than the respondToTaskFeeLimit", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 7, + "x": 7, + "y": 20 + }, + "id": 26, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.1.10", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "floor(increase(aligned_aggregator_num_times_paid_for_batcher_count{bot=\"aggregator\"}[$__range]))", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "# Times Aggregator Paid Extra Cost", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "Number of times the aggregator paid for the batcher when the tx cost was higher than the respondToTaskFeeLimit", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 20, + "y": 20 + }, + "id": 55, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.1.10", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "floor(increase(aligned_aggregator_num_times_paid_for_batcher_count{bot=\"aggregator\"}[10y]))", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Historical - # Times Aggregator Paid Extra Cost", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 23 + }, + "id": 38, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "

\n SYSTEM STATUS\n

", + "mode": "html" + }, + "pluginVersion": "10.1.10", + "transparent": true, + "type": "text" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 256 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 3, + "x": 7, + "y": 25 + }, + "id": 17, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" + }, + "pluginVersion": "10.1.10", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "open_connections_count{job=\"aligned-batcher\"}", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Batcher Open Connections", + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "dark-red", + "value": null + }, + { + "color": "green", + "value": 0 + }, + { + "color": "yellow", + "value": 1 + }, + { + "color": "dark-red", + "value": 2 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 3, + "x": 10, + "y": 25 + }, + "id": 9, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.1.10", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "exemplar": false, + "expr": "floor(increase(aligned_aggregator_received_tasks_count{job=\"aligned-aggregator\"}[$__range]))", + "fullMetaSearch": false, + "hide": true, + "includeNullMetadata": true, + "instant": false, + "interval": "", + "legendFormat": "{{label_name}}", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "floor(increase(aligned_aggregated_responses_count{job=\"aligned-aggregator\"}[$__range]))", + "fullMetaSearch": false, + "hide": true, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "B", + "useBackend": false + }, + { + "datasource": { + "name": "Expression", + "type": "__expr__", + "uid": "__expr__" + }, + "expression": "$A - $B", + "hide": false, + "reducer": "last", + "refId": "C", + "type": "math" + } + ], + "title": "Tasks Not Verified", + "transformations": [ + { + "id": "filterByValue", + "options": { + "filters": [ + { + "config": { + "id": "lower", + "options": { + "value": 0 + } + }, + "fieldName": "C {bot=\"aggregator\", instance=\"host.docker.internal:9091\", job=\"aligned-aggregator\"}" + } + ], + "match": "any", + "type": "exclude" + } + } + ], + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 4, + "x": 13, + "y": 25 + }, + "id": 42, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.1.10", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "floor(increase(batcher_started_count{job=\"aligned-batcher\"}[10y]))", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Total Batcher Restarts", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 10, + "x": 7, + "y": 30 + }, + "id": 16, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "10.1.10", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "floor(increase(batcher_started_count{job=\"aligned-batcher\"}[10y]))", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{job}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Total Batcher Restarts", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 36 + }, + "id": 39, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "

\n SYSTEM STATUS\n

", + "mode": "html" + }, + "pluginVersion": "10.1.10", + "transparent": true, + "type": "text" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "gridPos": { + "h": 2, + "w": 12, + "x": 0, + "y": 37 + }, + "id": 36, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "

\n BATCHER\n

", + "mode": "html" + }, + "pluginVersion": "10.1.10", + "transparent": true, + "type": "text" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "gridPos": { + "h": 2, + "w": 12, + "x": 12, + "y": 37 + }, + "id": 37, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "

\n AGGREGATOR\n

", + "mode": "html" + }, + "pluginVersion": "10.1.10", + "transparent": true, + "type": "text" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "dark-red", + "value": 0 + }, + { + "color": "green", + "value": 1 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 3, + "x": 0, + "y": 39 + }, + "id": 14, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.1.10", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "floor(increase(sent_batches_count{job=\"aligned-batcher\"}[$__range]))", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Batches Sent", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 9, + "x": 3, + "y": 39 + }, + "id": 13, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "floor(increase(sent_batches_count{job=\"aligned-batcher\"}[10y]))", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Batches Sent", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 3, + "x": 12, + "y": 39 + }, + "id": 7, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.1.10", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "floor(increase(aligned_aggregator_received_tasks_count{bot=\"aggregator\"}[$__range]))", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Tasks Received", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 9, + "x": 15, + "y": 39 + }, + "id": 40, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "floor(increase(aligned_aggregated_responses_count{bot=\"aggregator\"}[10y]))", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Verified Tasks", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "#FF9830", + "value": 3 + }, + { + "color": "red", + "value": 5 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 3, + "x": 0, + "y": 45 + }, + "id": 15, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.1.10", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "floor(increase(reverted_batches_count{job=\"aligned-batcher\"}[$__range]))", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Reverted Batches", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 9, + "x": 3, + "y": 45 + }, + "id": 19, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "floor(increase(reverted_batches_count{job=\"aligned-batcher\"}[10y]))", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Batches Reverted", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 3, + "x": 12, + "y": 45 + }, + "id": 5, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.1.10", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "exemplar": false, + "expr": "floor(increase(aligned_aggregated_responses_count{bot=\"aggregator\"}[$__range]))", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Tasks Verified", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 9, + "x": 15, + "y": 45 + }, + "id": 1, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "floor(increase(aligned_aggregator_received_tasks_count{bot=\"aggregator\"}[10y]))", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Received Tasks", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "#EAB839", + "value": 3 + }, + { + "color": "red", + "value": 5 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 3, + "x": 0, + "y": 51 + }, + "id": 22, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.1.10", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "floor(increase(canceled_batches_count{job=\"aligned-batcher\"}[$__range]))", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Canceled Batches", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 9, + "x": 3, + "y": 51 + }, + "id": 21, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "floor(increase(canceled_batches_count{job=\"aligned-batcher\"}[10y]))", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Batches Canceled", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "Number of times gas price was bumped while sending an aggregated response.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 10, + "x": 12, + "y": 51 + }, + "id": 25, + "interval": "36", "options": { "legend": { "calcs": [], @@ -996,8 +2897,8 @@ "uid": "prometheus" }, "disableTextWrap": false, - "editorMode": "builder", - "expr": "broken_ws_connections{job=\"aligned-batcher\"}", + "editorMode": "code", + "expr": "floor(increase(aligned_respond_to_task_gas_price_bumped_count{bot=\"aggregator\"}[10y]))", "fullMetaSearch": false, "includeNullMetadata": true, "instant": false, @@ -1007,7 +2908,7 @@ "useBackend": false } ], - "title": "Broken websocket connections", + "title": "# Bumps Sending Responses to Ethereum", "type": "timeseries" }, { @@ -1015,19 +2916,18 @@ "type": "prometheus", "uid": "prometheus" }, + "description": "Measures websocket connections that were abnormally disconnected.", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { - "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, - "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 0, "gradientMode": "none", @@ -1039,7 +2939,7 @@ "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, - "pointSize": 5, + "pointSize": 1, "scaleDistribution": { "type": "linear" }, @@ -1058,8 +2958,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1071,17 +2970,18 @@ "overrides": [] }, "gridPos": { - "h": 8, - "w": 11, - "x": 10, - "y": 25 + "h": 7, + "w": 12, + "x": 0, + "y": 57 }, - "id": 24, + "id": 20, + "interval": "1m", "options": { "legend": { "calcs": [], "displayMode": "list", - "placement": "right", + "placement": "bottom", "showLegend": true }, "tooltip": { @@ -1096,128 +2996,118 @@ "uid": "prometheus" }, "disableTextWrap": false, - "editorMode": "builder", - "expr": "user_errors", + "editorMode": "code", + "expr": "floor(increase(broken_ws_connections_count{job=\"aligned-batcher\"}[10y]))", "fullMetaSearch": false, "includeNullMetadata": true, "instant": false, - "legendFormat": "{{error_type}}", + "legendFormat": "__auto", "range": true, "refId": "A", "useBackend": false } ], - "title": "User Error Count", - "transformations": [ - { - "id": "calculateField", - "options": { - "alias": "proof_rejected", - "mode": "reduceRow", - "reduce": { - "include": [ - "rejected_proof" - ], - "reducer": "sum" - } - } - }, - { - "id": "organize", - "options": { - "excludeByName": { - "rejected_proof": true - }, - "indexByName": {}, - "renameByName": {} - } - }, - { - "id": "calculateField", - "options": { - "alias": "total", - "mode": "reduceRow", - "reduce": { - "reducer": "sum" - } - } - } - ], + "title": "Broken websocket connections", "type": "timeseries" }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 33 - }, - "id": 10, - "panels": [], - "title": "Aggregator", - "type": "row" - }, { "datasource": { "type": "prometheus", "uid": "prometheus" }, + "description": "Time series showing the number of times the aggregator paid for the batcher when the tx cost was higher than the respondToTaskFeeLimit", "fieldConfig": { "defaults": { "color": { - "mode": "thresholds" + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { - "color": "dark-red", - "value": null - }, - { - "color": "green", - "value": 0 - }, - { - "color": "yellow", - "value": 1 - }, - { - "color": "dark-red", - "value": 2 + "color": "green" } ] } }, - "overrides": [] + "overrides": [ + { + "__systemRef": "hideSeriesFrom", + "matcher": { + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "{bot=\"aggregator\", instance=\"host.docker.internal:9091\", job=\"aligned-aggregator\"}" + ], + "prefix": "All except:", + "readOnly": true + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": false, + "viz": true + } + } + ] + } + ] }, "gridPos": { "h": 7, "w": 10, - "x": 0, - "y": 34 + "x": 12, + "y": 57 }, - "id": 9, + "id": 28, "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true + "tooltip": { + "mode": "single", + "sort": "none" + } }, - "pluginVersion": "11.2.2+security-01", "targets": [ { "datasource": { @@ -1225,71 +3115,19 @@ "uid": "prometheus" }, "disableTextWrap": false, - "editorMode": "builder", - "exemplar": false, - "expr": "floor(increase(aligned_aggregator_received_tasks{job=\"aligned-aggregator\"}[$__range]))", - "fullMetaSearch": false, - "hide": true, - "includeNullMetadata": true, - "instant": false, - "interval": "", - "legendFormat": "{{label_name}}", - "range": true, - "refId": "A", - "useBackend": false - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "disableTextWrap": false, - "editorMode": "builder", - "expr": "floor(increase(aligned_aggregated_responses{bot=\"aggregator\"}[$__range]))", + "editorMode": "code", + "expr": "floor(increase(aligned_aggregator_num_times_paid_for_batcher_count{bot=\"aggregator\"}[10y]))", "fullMetaSearch": false, - "hide": true, - "includeNullMetadata": true, - "instant": false, - "legendFormat": "__auto", - "range": true, - "refId": "B", - "useBackend": false - }, - { - "datasource": { - "name": "Expression", - "type": "__expr__", - "uid": "__expr__" - }, - "expression": "$A - $B", - "hide": false, - "reducer": "last", - "refId": "C", - "type": "math" - } - ], - "title": "Tasks Not Verified", - "transformations": [ - { - "id": "filterByValue", - "options": { - "filters": [ - { - "config": { - "id": "lower", - "options": { - "value": 0 - } - }, - "fieldName": "C {bot=\"aggregator\", instance=\"host.docker.internal:9091\", job=\"aligned-aggregator\"}" - } - ], - "match": "any", - "type": "exclude" - } + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false } ], - "type": "stat" + "title": "# Times Aggregator Paid Extra Cost", + "type": "timeseries" }, { "datasource": { @@ -1302,13 +3140,11 @@ "mode": "palette-classic" }, "custom": { - "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, - "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 0, "gradientMode": "none", @@ -1339,8 +3175,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1352,21 +3187,20 @@ "overrides": [] }, "gridPos": { - "h": 7, - "w": 10, - "x": 10, - "y": 34 + "h": 8, + "w": 12, + "x": 0, + "y": 64 }, - "id": 1, + "id": 24, "options": { "legend": { "calcs": [], "displayMode": "list", - "placement": "bottom", + "placement": "right", "showLegend": true }, "tooltip": { - "maxHeight": 600, "mode": "single", "sort": "none" } @@ -1378,108 +3212,79 @@ "uid": "prometheus" }, "disableTextWrap": false, - "editorMode": "builder", - "expr": "floor(increase(aligned_aggregated_responses{bot=\"aggregator\"}[10y]))", + "editorMode": "code", + "expr": "floor(increase(user_errors_count{job=\"aligned-batcher\"}[10y]))", "fullMetaSearch": false, "includeNullMetadata": true, "instant": false, - "legendFormat": "__auto", + "legendFormat": "{{error_type}}", "range": true, "refId": "A", "useBackend": false } ], - "title": "Verified Tasks", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] + "title": "# User Errors", + "transformations": [ + { + "id": "calculateField", + "options": { + "alias": "proof_rejected", + "mode": "reduceRow", + "reduce": { + "include": [ + "rejected_proof" + ], + "reducer": "sum" + } } }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 5, - "x": 0, - "y": 41 - }, - "id": 8, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false + { + "id": "organize", + "options": { + "excludeByName": { + "rejected_proof": true + }, + "indexByName": {}, + "renameByName": {} + } }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.2.2+security-01", - "targets": [ { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "disableTextWrap": false, - "editorMode": "builder", - "expr": "floor(increase(aligned_aggregator_received_tasks{bot=\"aggregator\"}[10y]))", - "fullMetaSearch": false, - "includeNullMetadata": true, - "instant": false, - "legendFormat": "__auto", - "range": true, - "refId": "A", - "useBackend": false + "id": "calculateField", + "options": { + "alias": "total", + "mode": "reduceRow", + "reduce": { + "reducer": "sum" + } + } } ], - "title": "Total Tasks Received", - "type": "stat" + "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "prometheus" }, - "description": "", "fieldConfig": { "defaults": { "color": { - "mode": "thresholds" + "mode": "continuous-GrYlRd" + }, + "custom": { + "align": "left", + "cellOptions": { + "type": "auto" + }, + "inspect": false }, "mappings": [], + "noValue": "none", "thresholds": { "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1488,33 +3293,43 @@ ] } }, - "overrides": [] + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Last *" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "type": "gauge" + } + } + ] + } + ] }, "gridPos": { - "h": 7, - "w": 5, - "x": 5, - "y": 41 + "h": 8, + "w": 12, + "x": 12, + "y": 64 }, - "id": 7, + "id": 49, "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], + "cellHeight": "sm", + "footer": { + "countRows": false, "fields": "", - "values": false + "reducer": [ + "sum" + ], + "show": false }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true + "showHeader": false }, - "pluginVersion": "11.2.2+security-01", + "pluginVersion": "10.1.10", "targets": [ { "datasource": { @@ -1522,70 +3337,114 @@ "uid": "prometheus" }, "disableTextWrap": false, - "editorMode": "builder", - "expr": "floor(increase(aligned_aggregator_received_tasks{bot=\"aggregator\"}[$__range]))", + "editorMode": "code", + "exemplar": false, + "expr": "floor(increase(missing_operator_count{job=\"aligned-tracker\"}[$__range]))", + "format": "time_series", "fullMetaSearch": false, "includeNullMetadata": true, "instant": false, - "legendFormat": "__auto", + "interval": "1", + "legendFormat": "{{operator}}", "range": true, "refId": "A", "useBackend": false } ], - "title": "Tasks Received", - "type": "stat" + "title": "# Operator Missing Tasks", + "transformations": [ + { + "id": "reduce", + "options": { + "labelsToFields": false, + "reducers": [ + "lastNotNull" + ] + } + }, + { + "id": "sortBy", + "options": { + "fields": {}, + "sort": [ + { + "desc": true, + "field": "Last *" + } + ] + } + } + ], + "type": "table" }, { "datasource": { "type": "prometheus", "uid": "prometheus" }, - "description": "Accumulated gas cost the aggregator paid for the batcher when the tx cost was higher than the respondToTaskFeeLimit.", "fieldConfig": { "defaults": { "color": { - "mode": "thresholds" + "fixedColor": "dark-green", + "mode": "fixed" + }, + "custom": { + "align": "left", + "cellOptions": { + "type": "auto" + }, + "inspect": false }, "mappings": [], + "noValue": "none", "thresholds": { "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] - }, - "unit": "ETH" + } }, - "overrides": [] + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Last *" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "mode": "lcd", + "type": "gauge", + "valueDisplayMode": "text" + } + } + ] + } + ] }, "gridPos": { - "h": 7, - "w": 5, - "x": 10, - "y": 41 + "h": 8, + "w": 12, + "x": 12, + "y": 72 }, - "id": 27, + "id": 51, "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], + "cellHeight": "sm", + "footer": { + "countRows": false, "fields": "", - "values": false + "reducer": [ + "sum" + ], + "show": false }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true + "showHeader": false }, - "pluginVersion": "11.2.2+security-01", + "pluginVersion": "10.1.10", "targets": [ { "datasource": { @@ -1593,89 +3452,83 @@ "uid": "prometheus" }, "disableTextWrap": false, - "editorMode": "builder", - "expr": "aligned_aggregator_gas_cost_paid_for_batcher{bot=\"aggregator\"}", + "editorMode": "code", + "exemplar": false, + "expr": "floor(increase(operator_response_count{job=\"aligned-tracker\"}[$__range]))", + "format": "time_series", "fullMetaSearch": false, "includeNullMetadata": true, "instant": false, - "legendFormat": "__auto", + "interval": "1", + "legendFormat": "{{operator}}", "range": true, "refId": "A", "useBackend": false - } - ], - "title": "Accumulated GasCost paid for batcher", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "description": "Number of times the aggregator paid for the batcher when the tx cost was higher than the respondToTaskFeeLimit", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 5, - "x": 15, - "y": 41 - }, - "id": 26, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.2.2+security-01", - "targets": [ + }, { "datasource": { "type": "prometheus", "uid": "prometheus" }, - "disableTextWrap": false, - "editorMode": "builder", - "expr": "aligned_aggregator_num_times_paid_for_batcher{bot=\"aggregator\"}", - "fullMetaSearch": false, - "includeNullMetadata": true, + "editorMode": "code", + "expr": "floor(increase(aligned_aggregator_received_tasks_count{bot=\"aggregator\"}[$__range]))", + "hide": false, "instant": false, - "legendFormat": "__auto", + "legendFormat": "Tasks Received", "range": true, - "refId": "A", - "useBackend": false + "refId": "B" } ], - "title": "Times aggregator paid for batcher", - "type": "stat" + "title": "# Operator Responses", + "transformations": [ + { + "id": "reduce", + "options": { + "labelsToFields": false, + "reducers": [ + "lastNotNull" + ] + } + }, + { + "id": "sortBy", + "options": { + "fields": {}, + "sort": [ + { + "desc": false, + "field": "Last *" + } + ] + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 80 + }, + "id": 46, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "

\n Latency\n

", + "mode": "html" + }, + "pluginVersion": "10.1.10", + "transparent": true, + "type": "text" }, { "datasource": { @@ -1685,46 +3538,71 @@ "fieldConfig": { "defaults": { "color": { - "mode": "thresholds" + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 48, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] - } + }, + "unit": "ms" }, "overrides": [] }, "gridPos": { - "h": 7, - "w": 5, + "h": 8, + "w": 12, "x": 0, - "y": 48 + "y": 82 }, - "id": 2, + "id": 47, "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true + "tooltip": { + "mode": "single", + "sort": "none" + } }, - "pluginVersion": "11.2.2+security-01", "targets": [ { "datasource": { @@ -1732,122 +3610,143 @@ "uid": "prometheus" }, "disableTextWrap": false, - "editorMode": "builder", - "exemplar": false, - "expr": "floor(increase(aligned_aggregated_responses{bot=\"aggregator\"}[10y]))", - "format": "table", + "editorMode": "code", + "expr": "s3_duration * 10 ^ (-3)", "fullMetaSearch": false, "includeNullMetadata": true, "instant": false, - "interval": "", "legendFormat": "__auto", "range": true, "refId": "A", "useBackend": false } ], - "title": "Total Tasks Verified", - "type": "stat" + "title": "Upload Batch to S3 Duration", + "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "prometheus" }, + "description": "", "fieldConfig": { "defaults": { "color": { - "mode": "thresholds" + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" + }, + { + "color": "red", + "value": 80 } ] - } + }, + "unit": "s" }, "overrides": [] }, "gridPos": { - "h": 7, - "w": 5, - "x": 5, - "y": 48 + "h": 8, + "w": 12, + "x": 12, + "y": 82 }, - "id": 5, + "id": 43, + "interval": "1s", "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "right", + "showLegend": false }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true + "tooltip": { + "mode": "single", + "sort": "none" + } }, - "pluginVersion": "11.2.2+security-01", "targets": [ { "datasource": { "type": "prometheus", "uid": "prometheus" }, - "disableTextWrap": false, - "editorMode": "builder", - "exemplar": false, - "expr": "floor(increase(aligned_aggregated_responses{bot=\"aggregator\"}[$__range]))", - "fullMetaSearch": false, - "includeNullMetadata": true, + "editorMode": "code", + "expr": "aligned_aggregator_respond_to_task_latency{bot=\"aggregator\"}", + "hide": false, "instant": false, - "legendFormat": "__auto", + "legendFormat": "Latest latency", "range": true, - "refId": "A", - "useBackend": false + "refId": "Latency" } ], - "title": "Tasks Verified", - "type": "stat" + "title": "Latest respond to task latency", + "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "prometheus" }, - "description": "Time series showing the number of times the aggregator paid for the batcher when the tx cost was higher than the respondToTaskFeeLimit", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { - "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, - "barWidthFactor": 0.6, "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", + "fillOpacity": 48, + "gradientMode": "opacity", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, - "lineInterpolation": "linear", + "lineInterpolation": "smooth", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { @@ -1868,26 +3767,30 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" + }, + { + "color": "red", + "value": 80 } ] - } + }, + "unit": "s" }, "overrides": [] }, "gridPos": { - "h": 7, - "w": 10, - "x": 10, - "y": 48 + "h": 8, + "w": 12, + "x": 0, + "y": 90 }, - "id": 28, + "id": 45, "options": { "legend": { "calcs": [], "displayMode": "list", - "placement": "bottom", + "placement": "right", "showLegend": true }, "tooltip": { @@ -1902,18 +3805,51 @@ "uid": "prometheus" }, "disableTextWrap": false, - "editorMode": "builder", - "expr": "aligned_aggregator_num_times_paid_for_batcher{bot=\"aggregator\"}", + "editorMode": "code", + "expr": "cancel_create_new_task_duration * 10 ^ (-3)", "fullMetaSearch": false, "includeNullMetadata": true, "instant": false, - "legendFormat": "__auto", + "legendFormat": "cancel_new_task", "range": true, "refId": "A", "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "create_new_task_duration * 10 ^(-3)", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "create_new_task", + "range": true, + "refId": "B", + "useBackend": false + } + ], + "title": "CreateNewTask Duration", + "transformations": [ + { + "id": "calculateField", + "options": { + "alias": "total", + "mode": "reduceRow", + "reduce": { + "include": [ + "cancel_new_task", + "create_new_task" + ], + "reducer": "sum" + } + } } ], - "title": "Aggregator paid for batcher time series", "type": "timeseries" }, { @@ -1921,20 +3857,17 @@ "type": "prometheus", "uid": "prometheus" }, - "description": "Number of times gas price was bumped while sending an aggregated response.", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { - "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, - "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 0, "gradientMode": "none", @@ -1965,8 +3898,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1978,19 +3910,19 @@ "overrides": [] }, "gridPos": { - "h": 7, - "w": 10, - "x": 0, - "y": 55 + "h": 8, + "w": 12, + "x": 12, + "y": 90 }, - "id": 25, - "interval": "36", + "id": 44, + "interval": "1s", "options": { "legend": { "calcs": [], "displayMode": "list", - "placement": "bottom", - "showLegend": true + "placement": "right", + "showLegend": false }, "tooltip": { "mode": "single", @@ -2003,35 +3935,33 @@ "type": "prometheus", "uid": "prometheus" }, - "disableTextWrap": false, - "editorMode": "builder", - "expr": "aligned_respond_to_task_gas_price_bumped{bot=\"aggregator\"}", - "fullMetaSearch": false, - "includeNullMetadata": true, + "editorMode": "code", + "expr": "aligned_aggregator_task_quorum_reached_latency{bot=\"aggregator\"}", + "hide": false, "instant": false, - "legendFormat": "__auto", + "legendFormat": "Latest latency", "range": true, - "refId": "A", - "useBackend": false + "refId": "A" } ], - "title": "Bumped gas price for aggregated responses", + "title": "Latest quorum reached latency", "type": "timeseries" } ], - "refresh": "5s", - "schemaVersion": 39, + "refresh": "", + "schemaVersion": 38, + "style": "dark", "tags": [], "templating": { "list": [] }, "time": { - "from": "now-5m", + "from": "now-1h", "to": "now" }, "timepicker": {}, "timezone": "browser", - "title": "Aggregator Data", + "title": "System Data", "uid": "aggregator", "version": 3, "weekStart": "" diff --git a/grafana/provisioning/dashboards/aligned/operator.json b/grafana/provisioning/dashboards/aligned/operator.json index bb229c98a..3b5440982 100644 --- a/grafana/provisioning/dashboards/aligned/operator.json +++ b/grafana/provisioning/dashboards/aligned/operator.json @@ -18,7 +18,7 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, - "id": 2, + "id": 3, "links": [], "liveNow": false, "panels": [ @@ -76,9 +76,9 @@ "uid": "prometheus" }, "disableTextWrap": false, - "editorMode": "builder", + "editorMode": "code", "exemplar": false, - "expr": "floor(increase(aligned_operator_responses{bot=\"operator\"}[10y]))", + "expr": "floor(increase(aligned_operator_responses_count{bot=\"operator\"}[10y]))", "format": "table", "fullMetaSearch": false, "includeNullMetadata": true, @@ -147,9 +147,9 @@ "uid": "prometheus" }, "disableTextWrap": false, - "editorMode": "builder", + "editorMode": "code", "exemplar": false, - "expr": "floor(increase(aligned_operator_responses{bot=\"operator\"}[$__range]))", + "expr": "floor(increase(aligned_operator_responses_count{bot=\"operator\"}[$__range]))", "fullMetaSearch": false, "includeNullMetadata": true, "instant": false, @@ -247,18 +247,18 @@ "uid": "prometheus" }, "disableTextWrap": false, - "editorMode": "builder", - "expr": "floor(increase(aligned_operator_responses{bot=\"operator\"}[10y]))", + "editorMode": "code", + "expr": "floor(increase(aligned_operator_responses_count{bot=\"operator\"}[10y]))", "fullMetaSearch": false, "includeNullMetadata": true, "instant": false, - "legendFormat": "__auto", + "legendFormat": "{{job}}", "range": true, "refId": "A", "useBackend": false } ], - "title": "Operator", + "title": "Operator Responses", "type": "timeseries" }, { @@ -319,7 +319,7 @@ }, "disableTextWrap": false, "editorMode": "code", - "expr": "rate(aligned_operator_responses{bot=\"operator\"}[1m]) * 60", + "expr": "rate(aligned_operator_responses_count{bot=\"operator\"}[1m]) * 60", "fullMetaSearch": false, "includeNullMetadata": true, "instant": false, @@ -385,7 +385,7 @@ "uid": "prometheus" }, "editorMode": "code", - "expr": "rate(aligned_operator_responses{bot=\"operator\"}[1m])", + "expr": "rate(aligned_operator_responses_count{bot=\"operator\"}[1m])", "instant": false, "legendFormat": "__auto", "range": true, @@ -481,7 +481,7 @@ }, "disableTextWrap": false, "editorMode": "code", - "expr": "rate(aligned_operator_responses{bot=\"operator\"}[1m]) * 60", + "expr": "rate(aligned_operator_responses_count{bot=\"operator\"}[1m]) * 60", "fullMetaSearch": false, "includeNullMetadata": true, "instant": false, @@ -495,7 +495,7 @@ "type": "timeseries" } ], - "refresh": "auto", + "refresh": "5s", "schemaVersion": 38, "style": "dark", "tags": [], @@ -510,6 +510,6 @@ "timezone": "browser", "title": "Operator Data", "uid": "operator", - "version": 3, + "version": 4, "weekStart": "" } \ No newline at end of file diff --git a/metrics/metrics.go b/metrics/metrics.go index e76d83b7b..743e7862d 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -21,6 +21,9 @@ type Metrics struct { aggregatorGasCostPaidForBatcherTotal prometheus.Gauge aggregatorNumTimesPaidForBatcher prometheus.Counter numBumpedGasPriceForAggregatedResponse prometheus.Counter + aggregatorGasCostPaidTotal prometheus.Counter + aggregatorRespondToTaskLatency prometheus.Gauge + aggregatorTaskQuorumReachedLatency prometheus.Gauge } const alignedNamespace = "aligned" @@ -31,34 +34,49 @@ func NewMetrics(ipPortAddress string, reg prometheus.Registerer, logger logging. logger: logger, numAggregatedResponses: promauto.With(reg).NewCounter(prometheus.CounterOpts{ Namespace: alignedNamespace, - Name: "aggregated_responses", + Name: "aggregated_responses_count", Help: "Number of aggregated responses sent to the Aligned Service Manager", }), numOperatorTaskResponses: promauto.With(reg).NewCounter(prometheus.CounterOpts{ Namespace: alignedNamespace, - Name: "operator_responses", + Name: "operator_responses_count", Help: "Number of proof verified by the operator and sent to the Aligned Service Manager", }), numAggregatorReceivedTasks: promauto.With(reg).NewCounter(prometheus.CounterOpts{ Namespace: alignedNamespace, - Name: "aggregator_received_tasks", + Name: "aggregator_received_tasks_count", Help: "Number of tasks received by the Service Manager", }), aggregatorGasCostPaidForBatcherTotal: promauto.With(reg).NewGauge(prometheus.GaugeOpts{ Namespace: alignedNamespace, - Name: "aggregator_gas_cost_paid_for_batcher", + Name: "aggregator_gas_cost_paid_for_batcher_sum", Help: "Accumulated gas cost the aggregator paid for the batcher when the tx cost was higher than the respondToTaskFeeLimit", }), aggregatorNumTimesPaidForBatcher: promauto.With(reg).NewCounter(prometheus.CounterOpts{ Namespace: alignedNamespace, - Name: "aggregator_num_times_paid_for_batcher", + Name: "aggregator_num_times_paid_for_batcher_count", Help: "Number of times the aggregator paid for the batcher when the tx cost was higher than the respondToTaskFeeLimit", }), + aggregatorGasCostPaidTotal: promauto.With(reg).NewCounter(prometheus.CounterOpts{ + Namespace: alignedNamespace, + Name: "aggregator_gas_cost_paid_total_count", + Help: "Total amount of gas paid by the aggregator while responding to tasks", + }), numBumpedGasPriceForAggregatedResponse: promauto.With(reg).NewCounter(prometheus.CounterOpts{ Namespace: alignedNamespace, - Name: "respond_to_task_gas_price_bumped", + Name: "respond_to_task_gas_price_bumped_count", Help: "Number of times gas price was bumped while sending aggregated response", }), + aggregatorRespondToTaskLatency: promauto.With(reg).NewGauge(prometheus.GaugeOpts{ + Namespace: alignedNamespace, + Name: "aggregator_respond_to_task_latency", + Help: "Latency of last call to respondToTask on Aligned Service Manager", + }), + aggregatorTaskQuorumReachedLatency: promauto.With(reg).NewGauge(prometheus.GaugeOpts{ + Namespace: alignedNamespace, + Name: "aggregator_task_quorum_reached_latency", + Help: "Time it takes for a task to reach quorum", + }), } } @@ -113,6 +131,18 @@ func (m *Metrics) AddAggregatorGasPaidForBatcher(value float64) { m.aggregatorGasCostPaidForBatcherTotal.Add(value) } +func (m *Metrics) AddAggregatorGasCostPaidTotal(value float64) { + m.aggregatorGasCostPaidTotal.Add(value) +} + func (m *Metrics) IncBumpedGasPriceForAggregatedResponse() { m.numBumpedGasPriceForAggregatedResponse.Inc() } + +func (m *Metrics) ObserveLatencyForRespondToTask(elapsed time.Duration) { + m.aggregatorRespondToTaskLatency.Set(elapsed.Seconds()) +} + +func (m *Metrics) ObserveTaskQuorumReached(elapsed time.Duration) { + m.aggregatorTaskQuorumReachedLatency.Set(elapsed.Seconds()) +} diff --git a/telemetry_api/lib/telemetry_api/ethereum_metrics.ex b/telemetry_api/lib/telemetry_api/ethereum_metrics.ex deleted file mode 100644 index 2db78d6d1..000000000 --- a/telemetry_api/lib/telemetry_api/ethereum_metrics.ex +++ /dev/null @@ -1,12 +0,0 @@ -defmodule TelemetryApi.EthereumMetrics do - use Prometheus.Metric - - @gauge [name: :gas_price, help: "Ethereum Gas Price.", labels: []] - - def new_gas_price(gas_price) do - Gauge.set( - [name: :gas_price, labels: []], - gas_price - ) - end -end diff --git a/telemetry_api/lib/telemetry_api/operators.ex b/telemetry_api/lib/telemetry_api/operators.ex index c3b2864f9..21ea013b2 100644 --- a/telemetry_api/lib/telemetry_api/operators.ex +++ b/telemetry_api/lib/telemetry_api/operators.ex @@ -9,6 +9,7 @@ defmodule TelemetryApi.Operators do alias TelemetryApi.Operators.Operator alias TelemetryApi.ContractManagers.OperatorStateRetriever alias TelemetryApi.ContractManagers.DelegationManager + alias TelemetryApi.PrometheusMetrics @doc """ Returns the list of operators. @@ -95,6 +96,18 @@ defmodule TelemetryApi.Operators do |> Enum.filter(fn {status, _} -> status == :ok end) |> Enum.map(fn {_, data} -> data end) + # Initialize new_operators metrics + Enum.map(new_operators, fn {_, op_data} -> + op_name_address = op_data.name <> " - " <> String.slice(op_data.address, 0..7) + PrometheusMetrics.initialize_operator_metrics(op_name_address) + end) + + # If the server was restarted, initialize old_operators metrics + Enum.map(old_operators, fn {op, _} -> + op_name_address = op.name <> " - " <> String.slice(op.address, 0..7) + PrometheusMetrics.initialize_operator_metrics(op_name_address) + end) + # Merge both lists operators = (new_operators ++ old_operators) diff --git a/telemetry_api/lib/telemetry_api/periodically.ex b/telemetry_api/lib/telemetry_api/periodically.ex index 6aae11982..21ba1639f 100644 --- a/telemetry_api/lib/telemetry_api/periodically.ex +++ b/telemetry_api/lib/telemetry_api/periodically.ex @@ -1,7 +1,7 @@ defmodule TelemetryApi.Periodically do use GenServer alias TelemetryApi.Operators - alias TelemetryApi.EthereumMetrics + alias TelemetryApi.PrometheusMetrics alias TelemetryApi.ContractManagers.RegistryCoordinatorManager require Logger @@ -45,17 +45,17 @@ defmodule TelemetryApi.Periodically do def handle_info(:gas_price, _state) do case Ethers.current_gas_price() do {:ok, gas_price} -> - EthereumMetrics.new_gas_price(gas_price) + PrometheusMetrics.new_gas_price(gas_price) {:error, error} -> - IO.inspect("Error fetching gas price: #{error}") + Logger.error("Error fetching gas price: #{inspect(error)}") end {:noreply, %{}} end defp fetch_operators_info() do case Operators.fetch_all_operators() do :ok -> :ok - {:error, message} -> IO.inspect("Couldn't fetch operators: #{IO.inspect(message)}") + {:error, message} -> Logger.error("Couldn't fetch operators: #{inspect(message)}") end end @@ -67,7 +67,7 @@ defmodule TelemetryApi.Periodically do Operators.update_operator(op, %{status: string_status(status)}) error -> - Logger.error("Error when updating status: #{error}") + Logger.error("Error when updating status: #{inspect(error)}") end end) :ok diff --git a/telemetry_api/lib/telemetry_api/prometheus_metrics.ex b/telemetry_api/lib/telemetry_api/prometheus_metrics.ex new file mode 100644 index 000000000..19fd86838 --- /dev/null +++ b/telemetry_api/lib/telemetry_api/prometheus_metrics.ex @@ -0,0 +1,48 @@ +defmodule TelemetryApi.PrometheusMetrics do + use Prometheus.Metric + + @gauge [name: :gas_price, help: "Ethereum Gas Price.", labels: []] + @counter [name: :missing_operator_count, help: "Missing Operators", labels: [:operator]] + @counter [name: :operator_response_count, help: "Operator Response Count", labels: [:operator]] + + def new_gas_price(gas_price) do + Gauge.set( + [name: :gas_price, labels: []], + gas_price + ) + end + + def missing_operator(operator) do + Counter.inc( + name: :missing_operator_count, + labels: [operator] + ) + end + + def operator_response(operator) do + Counter.inc( + name: :operator_response_count, + labels: [operator] + ) + end + + def initialize_operator_metrics(operator) do + value = + Counter.value( + name: :missing_operator_count, + labels: [operator] + ) + + if value == :undefined do + Counter.inc( + [name: :missing_operator_count, labels: [operator]], + 0 + ) + + Counter.inc( + [name: :operator_response_count, labels: [operator]], + 0 + ) + end + end +end diff --git a/telemetry_api/lib/telemetry_api/traces.ex b/telemetry_api/lib/telemetry_api/traces.ex index e611f386a..bcea07229 100644 --- a/telemetry_api/lib/telemetry_api/traces.ex +++ b/telemetry_api/lib/telemetry_api/traces.ex @@ -5,6 +5,7 @@ defmodule TelemetryApi.Traces do alias TelemetryApi.Traces.Trace alias TelemetryApi.Operators alias TelemetryApi.ContractManagers.StakeRegistry + alias TelemetryApi.PrometheusMetrics require OpenTelemetry.Tracer require OpenTelemetry.Ctx @@ -90,8 +91,12 @@ defmodule TelemetryApi.Traces do current_stake: new_stake }) + PrometheusMetrics.operator_response( + operator.name <> " - " <> String.slice(operator.address, 0..7) + ) + IO.inspect( - "Operator response included. merkle_root: #{IO.inspect(merkle_root)} operator_id: #{IO.inspect(operator_id)}" + "Operator response included. merkle_root: #{inspect(merkle_root)} operator_id: #{inspect(operator_id)}" ) :ok @@ -124,11 +129,6 @@ defmodule TelemetryApi.Traces do | subspans: Map.delete(trace.subspans, :batcher) }) - with {:ok, _trace} <- set_current_trace(merkle_root) do - Tracer.end_span() - TraceStore.delete_trace(merkle_root) - end - :ok end end @@ -208,7 +208,6 @@ defmodule TelemetryApi.Traces do :ok end end - @doc """ Registers the sending of a batcher task to Ethereum in the task trace. @@ -298,20 +297,20 @@ defmodule TelemetryApi.Traces do :ok end end - + @doc """ - Registers a bump in the gas price when the aggregator tries to respond to a task in the task trace. + Registers a set gas price when the aggregator tries to respond to a task in the task trace. ## Examples iex> merkle_root - iex> bumped_gas_price - iex> aggregator_task_gas_price_bumped(merkle_root, bumped_gas_price) + iex> gas_price + iex> aggregator_task_set_gas_price(merkle_root, gas_price) :ok """ - def aggregator_task_gas_price_bumped(merkle_root, bumped_gas_price) do + def aggregator_task_set_gas_price(merkle_root, gas_price) do with {:ok, _trace} <- set_current_trace_with_subspan(merkle_root, :aggregator) do - Tracer.add_event("Task gas price bumped", [{"bumped__gas_price", bumped_gas_price}]) + Tracer.add_event("Gas price set", [{"gas_price", gas_price}]) :ok end end @@ -323,12 +322,12 @@ defmodule TelemetryApi.Traces do iex> merkle_root iex> tx_hash - iex> aggregator_task_sent(merkle_root, tx_hash) + iex> aggregator_task_sent(merkle_root, tx_hash, effective_gas_price) :ok """ - def aggregator_task_sent(merkle_root, tx_hash) do + def aggregator_task_sent(merkle_root, tx_hash, effective_gas_price) do with {:ok, _trace} <- set_current_trace_with_subspan(merkle_root, :aggregator) do - Tracer.add_event("Task Sent to Ethereum", [{"tx_hash", tx_hash}]) + Tracer.add_event("Task Sent to Ethereum", [{"tx_hash", tx_hash}, {"effective_gas_price", effective_gas_price}]) :ok end end @@ -368,8 +367,17 @@ defmodule TelemetryApi.Traces do defp add_missing_operators([]), do: :ok defp add_missing_operators(missing_operators) do + # Concatenate name + address + missing_operators = + missing_operators + |> Enum.map(fn op -> op.name <> " - " <> String.slice(op.address, 0..7) end) + + # Send to prometheus + missing_operators + |> Enum.map(fn o -> PrometheusMetrics.missing_operator(o) end) + missing_operators = - missing_operators |> Enum.map(fn o -> o.name end) |> Enum.join(";") + missing_operators |> Enum.join(";") Tracer.add_event("Missing Operators", [{:operators, missing_operators}]) end diff --git a/telemetry_api/lib/telemetry_api_web/controllers/trace_controller.ex b/telemetry_api/lib/telemetry_api_web/controllers/trace_controller.ex index b9e334652..5abf82f36 100644 --- a/telemetry_api/lib/telemetry_api_web/controllers/trace_controller.ex +++ b/telemetry_api/lib/telemetry_api_web/controllers/trace_controller.ex @@ -123,14 +123,14 @@ defmodule TelemetryApiWeb.TraceController do end @doc """ - Registers a gas price bump in the trace of the given merkle_root - Method: POST aggregatorTaskGasPriceBump + Registers a gas price in the trace of the given merkle_root + Method: POST aggregatorTaskSetGasPrice """ - def aggregator_task_gas_price_bumped(conn, %{ + def aggregator_task_set_gas_price(conn, %{ "merkle_root" => merkle_root, - "bumped_gas_price" => bumped_gas_price + "gas_price" => gas_price }) do - with :ok <- Traces.aggregator_task_gas_price_bumped(merkle_root, bumped_gas_price) do + with :ok <- Traces.aggregator_task_set_gas_price(merkle_root, gas_price) do conn |> put_status(:ok) |> render(:show_merkle, merkle_root: merkle_root) @@ -141,8 +141,8 @@ defmodule TelemetryApiWeb.TraceController do Register a task sent, from the aggregator, to Ethereum in the trace of the given merkle_root Method: POST aggregatorTaskSent """ - def aggregator_task_sent(conn, %{"merkle_root" => merkle_root, "tx_hash" => tx_hash}) do - with :ok <- Traces.aggregator_task_sent(merkle_root, tx_hash) do + def aggregator_task_sent(conn, %{"merkle_root" => merkle_root, "tx_hash" => tx_hash, "effective_gas_price" => effective_gas_price}) do + with :ok <- Traces.aggregator_task_sent(merkle_root, tx_hash, effective_gas_price) do conn |> put_status(:ok) |> render(:show_merkle, merkle_root: merkle_root) diff --git a/telemetry_api/lib/telemetry_api_web/router.ex b/telemetry_api/lib/telemetry_api_web/router.ex index 7115cced6..6d26b5241 100644 --- a/telemetry_api/lib/telemetry_api_web/router.ex +++ b/telemetry_api/lib/telemetry_api_web/router.ex @@ -15,7 +15,7 @@ defmodule TelemetryApiWeb.Router do post "/operatorResponse", TraceController, :register_operator_response post "/quorumReached", TraceController, :quorum_reached post "/taskError", TraceController, :task_error - post "/aggregatorTaskGasPriceBump", TraceController, :aggregator_task_gas_price_bumped + post "/aggregatorTaskSetGasPrice", TraceController, :aggregator_task_set_gas_price post "/aggregatorTaskSent", TraceController, :aggregator_task_sent post "/finishTaskTrace", TraceController, :finish_task_trace