diff --git a/generator/src/generator.rs b/generator/src/generator.rs index 44343561..9bfb7dc8 100644 --- a/generator/src/generator.rs +++ b/generator/src/generator.rs @@ -56,7 +56,7 @@ pub struct TransferCis2Args { #[derive(Debug, Args)] pub struct RegisterDataArgs { - /// Size of the data to register in bytes. Max 256. + /// Size of the data to register in bytes. Max 256. #[arg(long, default_value = "32")] size: u16, } diff --git a/state-compare/Cargo.lock b/state-compare/Cargo.lock index b9cff9c8..d7a015a7 100644 --- a/state-compare/Cargo.lock +++ b/state-compare/Cargo.lock @@ -75,6 +75,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "allocator-api2" version = "0.2.16" @@ -96,6 +105,55 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "anyhow" version = "1.0.81" @@ -259,17 +317,6 @@ dependencies = [ "syn 2.0.58", ] -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.2.0" @@ -512,42 +559,49 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.25" +version = "4.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +checksum = "35723e6a11662c2afb578bcf0b88bf6ea8e21282a953428f240574fcc3a2b5b3" dependencies = [ - "atty", - "bitflags", + "clap_builder", "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49eb96cbfa7cfa35017b7cd548c75b14c3118c98b423041d70562665e07fb0fa" +dependencies = [ + "anstream", + "anstyle", "clap_lex", - "indexmap 1.9.3", - "once_cell", - "strsim", - "termcolor", - "textwrap", + "strsim 0.11.1", ] [[package]] name = "clap_derive" -version = "3.2.25" +version = "4.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" +checksum = "5d029b67f89d30bbb547c89fd5161293c0aec155fc691d7924b64550662db93e" dependencies = [ "heck", - "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.58", ] [[package]] name = "clap_lex" -version = "0.2.4" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" -dependencies = [ - "os_str_bytes", -] +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "colored" @@ -653,9 +707,12 @@ dependencies = [ "concordium-rust-sdk", "futures", "indicatif", + "pretty_assertions", "serde_json", "tokio", "tonic", + "tracing", + "tracing-subscriber", ] [[package]] @@ -859,7 +916,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim", + "strsim 0.10.0", "syn 2.0.58", ] @@ -918,6 +975,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "digest" version = "0.10.7" @@ -1210,18 +1273,9 @@ dependencies = [ [[package]] name = "heck" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" @@ -1397,6 +1451,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" version = "0.10.5" @@ -1463,6 +1523,15 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + [[package]] name = "matchit" version = "0.7.3" @@ -1517,6 +1586,16 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num" version = "0.4.1" @@ -1604,7 +1683,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.3.9", + "hermit-abi", "libc", ] @@ -1657,10 +1736,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] -name = "os_str_bytes" -version = "6.6.1" +name = "overload" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "password-hash" @@ -1775,6 +1854,16 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "pretty_assertions" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -1803,7 +1892,6 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn 1.0.109", "version_check", ] @@ -1935,6 +2023,50 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "regex" +version = "1.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + [[package]] name = "rend" version = "0.4.2" @@ -2128,6 +2260,15 @@ dependencies = [ "keccak", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "signature" version = "2.2.0" @@ -2152,6 +2293,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + [[package]] name = "socket2" version = "0.5.6" @@ -2178,6 +2325,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "subtle" version = "2.5.0" @@ -2230,21 +2383,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "textwrap" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" - [[package]] name = "thiserror" version = "1.0.58" @@ -2265,6 +2403,16 @@ dependencies = [ "syn 2.0.58", ] +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "time" version = "0.3.34" @@ -2490,6 +2638,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", ] [[package]] @@ -2526,12 +2704,24 @@ dependencies = [ "subtle", ] +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "version_check" version = "0.9.4" @@ -2623,15 +2813,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" -dependencies = [ - "winapi", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -2797,6 +2978,12 @@ dependencies = [ "tap", ] +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + [[package]] name = "zerocopy" version = "0.7.32" diff --git a/state-compare/Cargo.toml b/state-compare/Cargo.toml index ff96f7e0..da5b6745 100644 --- a/state-compare/Cargo.toml +++ b/state-compare/Cargo.toml @@ -6,13 +6,16 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -concordium-rust-sdk = { path = "../deps/concordium-rust-sdk", version = "*" } anyhow = "1" -tokio = {version = "1.20", features = ["rt-multi-thread"]} -futures = "0.3" -clap = { version = "3", features = ["derive", "env"] } chrono = "0.4" -tonic = "0.10" -serde_json = "1.0" +clap = { version = "4.5.11", features = ["derive", "env"] } colored = "2" +concordium-rust-sdk = { path = "../deps/concordium-rust-sdk", version = "*" } +futures = "0.3" indicatif = "0.17" +pretty_assertions = "1.4.0" +serde_json = "1.0" +tokio = {version = "1.20", features = ["rt-multi-thread"]} +tonic = "0.10" +tracing = "0.1.40" +tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } diff --git a/state-compare/README.md b/state-compare/README.md index e3ee27e1..7c4e7bef 100644 --- a/state-compare/README.md +++ b/state-compare/README.md @@ -1,6 +1,6 @@ # State compare -Check the state of two blocks. The main purpose of this tool is to check the +Compares the state of two blocks. The main purpose of this tool is to inspect the state just before and after the protocol update, but in principle any two blocks can be supplied to print differences. @@ -25,13 +25,10 @@ information. # State checks -The following checks are performed. +The following checks are performed and diffs are printed for any difference found: - The list of accounts in the two blocks are the same. -- For each of the accounts in the list of accounts, the accounts are the same - modulo changes from P3 to P4. If `block1` is in P3 and `block2` is in P4 then - the equality check for accounts is relaxed and we allow that in `block2` an - account has baker pool information that it does not have in `block1`. +- For each of the accounts in the list of accounts, the accounts are the same. - The list of smart contract modules is the same in both blocks, and the modules can be retrieved from both blocks, and their source code matches. - The list of smart contract instances is the same in both blocks, and for each @@ -41,58 +38,19 @@ The following checks are performed. same, and the amounts are the same. - Active bakers agree. In particular the election difficulty is the same, and the bakers are the same, and have the same lottery power. -- If both blocks are in protocols P4 and later then baker pools are checked. The - tool checks that the pools are the same, and have the same capital. It also - checks that the delegators for each pool are the same, which includes checking - that the staked amounts are the same, as well as any pending change. +- Baker pools are checked. The tool checks that the pools are the same, and have + the same capital. It also checks that the delegators for each pool are the same, + which includes checking that the staked amounts are the same, as well as any + pending change. - Update sequence numbers are migrated correctly. -- Election difficulty only checked for protocols before P6. -## Example - -On mainnet running the tool when protocol version 4 is in effect leads to - -``` -$ concordium-state-compare --node1 http://localhost:20000 -Comparings state in blocks 5af81a1cc51141617f13c37e1cdea7ebdd76fce7e6377c0e7576b7450724472a (protocol version P3) and ea4a52a04ba905c2692b4598aa344707327781d588573d374125b449a1ce0bcc (protocol version P4). -Comparing account lists. -Querying all accounts. -Comparing all modules. -Querying all contracts. -Checking passive delegators. -Checking active bakers. -Checking baker pools. -Not comparing baker pools since one of the protocol versions is before P4. -No changes in the state detected. -``` - -If two other blocks are chosen an example output shows what differs. For example -on mainnet - -``` -$ concordium-state-compare --node1 http://localhost:20000 --block1 c58f6582361589dec16d07631081be5446e58b8e3c4f13b82b86d30f2f523706 --block2 abdebbfe582744e6e55ca43dfd4aa81f74d00e3e9010af5d9717203b26fddf39 -Comparings state in blocks c58f6582361589dec16d07631081be5446e58b8e3c4f13b82b86d30f2f523706 (protocol version P4) and abdebbfe582744e6e55ca43dfd4aa81f74d00e3e9010af5d9717203b26fddf39 (protocol version P4). -Comparing account lists. -Querying all accounts. -Account 35CJPZohio6Ztii2zy1AYzJKvuxbGG44wrBn7hLHiYLoF2nxnh differs. It does not have stake either in c58f6582361589dec16d07631081be5446e58b8e3c4f13b82b86d30f2f523706 or abdebbfe582744e6e55ca43dfd4aa81f74d00e3e9010af5d9717203b26fddf39. -Comparing all modules. -Querying all contracts. -Checking passive delegators. -Checking active bakers. -Checking baker pools. -Pool 3 differs. -Error: States in the two blocks c58f6582361589dec16d07631081be5446e58b8e3c4f13b82b86d30f2f523706 and abdebbfe582744e6e55ca43dfd4aa81f74d00e3e9010af5d9717203b26fddf39 differ. -``` - -The tool will exit with a non-zero status code if it fails to query some data, -or there is a difference in state. +The tool will exit with a non-zero status code if it fails to query some data. ## Caveats The state is checked using the node's API, so this is not a completely -comprehensive check. However it provides a basic check that the state has been -migrated correctly. The output is meant to indicate if there are issues, the -tool does not print the exact difference between the states. +comprehensive diff. The output is meant to be inspected manully to ensure +that the changes make sense with regards to any protocol update. The tool at present requires a decent amount of memory since it loads the list of all accounts in memory, and also queries accounts and contract state in diff --git a/state-compare/src/main.rs b/state-compare/src/main.rs index b637f2cb..01ba8ea1 100644 --- a/state-compare/src/main.rs +++ b/state-compare/src/main.rs @@ -1,196 +1,195 @@ //! A tool to compare the state at two given blocks, potentially in two //! different nodes. //! -//! The program is structured as a set of functions for checking various parts -//! of the state. Each of these functions returns a boolean that indicates -//! whether the particular aspect of the state is changed between the two blocks -//! and/or nodes. -use anyhow::ensure; +//! The program will print a collection of diffs between the various parts of +//! the states between the two blocks. + +use std::fmt::Display; + +use anyhow::Context; use clap::Parser; -use colored::Colorize; use concordium_rust_sdk::{ endpoints, id::types::AccountAddress, types::{ - hashes::BlockHash, smart_contracts::ModuleReference, AccountInfo, ContractAddress, - ProtocolVersion, + hashes::BlockHash, smart_contracts::ModuleReference, ContractAddress, ProtocolVersion, }, v2, }; use futures::{StreamExt, TryStreamExt}; use indicatif::ProgressBar; -use std::{ - fmt::Display, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, -}; - -/// Like eprintln!, but print the provided message in yellow. -macro_rules! warn { - ($($arg:tt)*) => {{ - eprintln!("{}", format!($($arg)*).yellow()); - }}; -} - -/// Like eprintln!, but print the provided message in red. -macro_rules! diff { - ($($arg:tt)*) => {{ - eprintln!("{}", format!($($arg)*).red()); - }}; +use pretty_assertions::Comparison; +use tracing::{info, level_filters::LevelFilter, warn}; +use tracing_subscriber::EnvFilter; + +/// Compares the given values and prints a pretty diff with the given message if +/// they are not equal. +macro_rules! compare { + ($v1:expr, $v2:expr, $($arg:tt)*) => { + if $v1 != $v2 { + warn!("{} differs:\n{}", format!($($arg)*), Comparison::new(&$v1, &$v2)) + } + }; } -#[derive(clap::Parser, Debug)] -#[clap(version, author)] -struct App { - #[clap( - long = "node1", - help = "GRPC V2 interface of the node.", +#[derive(Parser, Debug)] +#[clap(version)] +struct Args { + /// GRPC V2 interface of the node. + #[arg( + long, default_value = "http://localhost:20000", env = "STATE_COMPARE_NODE1" )] - endpoint: endpoints::Endpoint, - #[clap( - long = "node2", - help = "Optionally, a GRPC V2 interface of a second node to compare state with. If not \ - provided the first node is used.", - env = "STATE_COMPARE_NODE2" - )] - endpoint2: Option, - #[clap( - long = "block1", - help = "The first block where to compare state. If not given the default is the last \ - finalized block `before` the last protocol update.", - env = "STATE_COMPARE_BLOCK1" - )] - block1: Option, - #[clap( - long = "block2", - help = "The second block where to compare state. If not given the default is the genesis \ - block of the current protocol.", - env = "STATE_COMPARE_BLOCK2" - )] - block2: Option, -} - -/// Get the protocol version for the two blocks. -/// This currently uses the rewards overview call since this is the cheapest -/// call that returns it. -async fn get_protocol_versions( - client1: &mut v2::Client, - client2: &mut v2::Client, - block1: BlockHash, - block2: BlockHash, -) -> anyhow::Result<(ProtocolVersion, ProtocolVersion)> { - let t1 = client1.get_tokenomics_info(&block1).await?; - let t2 = client2.get_tokenomics_info(&block2).await?; - let p1 = match t1.response { - concordium_rust_sdk::types::RewardsOverview::V0 { data } => data.protocol_version, - concordium_rust_sdk::types::RewardsOverview::V1 { common, .. } => common.protocol_version, - }; - let p2 = match t2.response { - concordium_rust_sdk::types::RewardsOverview::V0 { data } => data.protocol_version, - concordium_rust_sdk::types::RewardsOverview::V1 { common, .. } => common.protocol_version, - }; - Ok((p1, p2)) + node1: endpoints::Endpoint, + + /// Optionally, a GRPC V2 interface of a second node to compare state with. + /// + /// If not provided the first node is used. + #[arg(long, env = "STATE_COMPARE_NODE2")] + node2: Option, + + /// The first block to compare state against. + /// + /// If not given the default is the last finalized block `before` the last + /// protocol update. + #[arg(long, env = "STATE_COMPARE_BLOCK1")] + block1: Option, + + /// The second block where to compare state. + /// + /// If not given the default is the genesis block of the current protocol. + #[arg(long, env = "STATE_COMPARE_BLOCK2")] + block2: Option, } #[tokio::main] async fn main() -> anyhow::Result<()> { - let app = App::parse(); - - let mut client = v2::Client::new(app.endpoint).await?; - let mut client2 = match app.endpoint2 { + let args = Args::parse(); + + tracing_subscriber::fmt() + .with_env_filter( + EnvFilter::builder() + .with_default_directive(LevelFilter::INFO.into()) + .from_env()?, + ) + .init(); + + let mut client1 = v2::Client::new(args.node1).await?; + let mut client2 = match args.node2 { Some(ep) => v2::Client::new(ep).await?, - None => client.clone(), + None => client1.clone(), }; - let ci1 = client.get_consensus_info().await?; + let ci1 = client1.get_consensus_info().await?; let ci2 = client2.get_consensus_info().await?; - ensure!( - ci1.genesis_block == ci2.genesis_block, - "Genesis blocks for the two nodes differ." - ); - let block1 = match app.block1 { + + let block1 = match args.block1 { Some(bh) => bh, None => { - client + client1 .get_block_info(ci1.current_era_genesis_block) .await? .response .block_parent } }; - let block2 = match app.block2 { + + let block2 = match args.block2 { Some(bh) => bh, None => ci1.current_era_genesis_block, }; - let (pv1, pv2) = get_protocol_versions(&mut client, &mut client2, block1, block2).await?; - eprintln!( - "Comparings state in blocks {} (protocol version {}) and {} (protocol version {}).", - block1, pv1, block2, pv2 + + let (pv1, pv2) = get_protocol_versions(&mut client1, &mut client2, block1, block2).await?; + + info!( + "Comparing states in blocks {block1} (protocol version {pv1}) and {block2} (protocol \ + version {pv2})." ); - let mut found_diff = false; + compare!(ci1.genesis_block, ci2.genesis_block, "Genesis blocks"); - found_diff |= compare_accounts(&mut client, &mut client2, block1, block2, pv1, pv2).await?; + compare_accounts(&mut client1, &mut client2, block1, block2).await?; - found_diff |= compare_modules(&mut client, &mut client2, block1, block2).await?; + compare_modules(&mut client1, &mut client2, block1, block2).await?; - found_diff |= compare_instances(&mut client, &mut client2, block1, block2).await?; + compare_instances(&mut client1, &mut client2, block1, block2).await?; - found_diff |= - compare_passive_delegators(&mut client, &mut client2, block1, block2, pv1, pv2).await?; + compare_passive_delegators(&mut client1, &mut client2, block1, block2).await?; - found_diff |= - compare_active_bakers(&mut client, &mut client2, block1, block2, pv1, pv2).await?; + compare_active_bakers(&mut client1, &mut client2, block1, block2).await?; - found_diff |= compare_baker_pools(&mut client, &mut client2, block1, block2, pv1, pv2).await?; + compare_baker_pools(&mut client1, &mut client2, block1, block2).await?; - found_diff |= compare_update_queues(&mut client, &mut client2, block1, block2).await?; + compare_update_queues(&mut client1, &mut client2, block1, block2).await?; + + info!("Done!"); - if found_diff { - anyhow::bail!(format!("States in the two blocks {} and {} differ.", block1, block2).red()); - } else { - eprintln!("{}", "No changes in the state detected.".green()); - } Ok(()) } +/// Get the protocol version for the two blocks. +/// This currently uses the rewards overview call since this is the cheapest +/// call that returns it. +async fn get_protocol_versions( + client1: &mut v2::Client, + client2: &mut v2::Client, + block1: BlockHash, + block2: BlockHash, +) -> anyhow::Result<(ProtocolVersion, ProtocolVersion)> { + let t1 = client1.get_tokenomics_info(&block1).await?; + let t2 = client2.get_tokenomics_info(&block2).await?; + let p1 = match t1.response { + concordium_rust_sdk::types::RewardsOverview::V0 { data } => data.protocol_version, + concordium_rust_sdk::types::RewardsOverview::V1 { common, .. } => common.protocol_version, + }; + let p2 = match t2.response { + concordium_rust_sdk::types::RewardsOverview::V0 { data } => data.protocol_version, + concordium_rust_sdk::types::RewardsOverview::V1 { common, .. } => common.protocol_version, + }; + Ok((p1, p2)) +} + async fn compare_update_queues( client1: &mut v2::Client, client2: &mut v2::Client, block1: BlockHash, block2: BlockHash, -) -> anyhow::Result { +) -> anyhow::Result<()> { + info!("Checking update queues."); + let s1 = client1 .get_next_update_sequence_numbers(block1) .await? .response; + let s2 = client2 .get_next_update_sequence_numbers(block2) .await? .response; - if s1 != s2 { - diff!(" Sequence numbers differ: {s1:#?} {s2:#?}") - } + + compare!(s1, s2, "Sequence numbers"); + let q1 = client1 .get_block_pending_updates(block1) .await? .response .try_collect::>() .await?; + let q2 = client2 .get_block_pending_updates(block2) .await? .response .try_collect::>() .await?; - if q1.len() != q2.len() { - return Ok(false); - } - Ok(s1 != s2) + + // PendingUpdate unfortunately does not impl Eq so we'll settle for a comparison + // of their debug representations Should be good enough to produce a nice + // diff. + compare!(format!("{q1:#?}"), format!("{q2:#?}"), "Pending updates"); + + Ok(()) } async fn compare_account_lists( @@ -198,7 +197,7 @@ async fn compare_account_lists( client2: &mut v2::Client, block1: BlockHash, block2: BlockHash, -) -> anyhow::Result<(bool, Vec)> { +) -> anyhow::Result> { let mut accounts1 = client1 .get_account_list(block1) .await? @@ -213,14 +212,14 @@ async fn compare_account_lists( .try_collect::>() .await?; accounts2.sort_unstable(); - let found_diff = compare_iters( + compare_iters( "Account", block1, block2, accounts1.iter(), accounts2.iter(), ); - Ok((found_diff, accounts1)) + Ok(accounts1) } async fn compare_instance_lists( @@ -228,7 +227,7 @@ async fn compare_instance_lists( client2: &mut v2::Client, block1: BlockHash, block2: BlockHash, -) -> anyhow::Result<(bool, Vec)> { +) -> anyhow::Result> { let mut cs1 = client1 .get_instance_list(block1) .await? @@ -243,8 +242,8 @@ async fn compare_instance_lists( .try_collect::>() .await?; cs2.sort_unstable(); - let found_diff = compare_iters("Instance", block1, block2, cs1.iter(), cs2.iter()); - Ok((found_diff, cs1)) + compare_iters("Instance", block1, block2, cs1.iter(), cs2.iter()); + Ok(cs1) } async fn compare_module_lists( @@ -252,7 +251,7 @@ async fn compare_module_lists( client2: &mut v2::Client, block1: BlockHash, block2: BlockHash, -) -> anyhow::Result<(bool, Vec)> { +) -> anyhow::Result> { let mut ms1 = client1 .get_module_list(block1) .await? @@ -267,60 +266,38 @@ async fn compare_module_lists( .try_collect::>() .await?; ms2.sort_unstable(); - let found_diff = compare_iters("Module", block1, block2, ms1.iter(), ms2.iter()); - Ok((found_diff, ms1)) + compare_iters("Module", block1, block2, ms1.iter(), ms2.iter()); + Ok(ms1) } /// Compare two iterators that are assumed to yield elements in increasing -/// order. Print any discrepancies. Return +/// order. Print any discrepancies. fn compare_iters( msg: &str, block1: BlockHash, block2: BlockHash, i1: impl Iterator, i2: impl Iterator, -) -> bool { - let mut found_diff = false; +) { let mut i1 = i1.peekable(); let mut i2 = i2.peekable(); while let Some(a1) = i1.peek() { if let Some(a2) = i2.peek() { if a1 < a2 { - diff!( - " {} {} appears in {} but not in {}.", - msg, - a1, - block1, - block2 - ); - found_diff = true; - let _ = i1.next(); + warn!("{msg} {a1} appears in {block1} but not in {block2}.",); + i1.next(); } else if a2 < a1 { - diff!( - " {} {} appears in {} but not in {}.", - msg, - a2, - block2, - block1 - ); - found_diff = true; - let _ = i2.next(); + warn!("{msg} {a2} appears in {block2} but not in {block1}.",); + i2.next(); } else { - let _ = i1.next(); - let _ = i2.next(); + i1.next(); + i2.next(); } } else { - found_diff = true; - diff!( - " {} {} appears in {} but not in {}.", - msg, - a1, - block1, - block2 - ) + warn!("{msg} {a1} appears in {block1} but not in {block2}.",); + i1.next(); } } - found_diff } async fn compare_accounts( @@ -328,138 +305,41 @@ async fn compare_accounts( client2: &mut v2::Client, block1: BlockHash, block2: BlockHash, - pv1: ProtocolVersion, - pv2: ProtocolVersion, -) -> anyhow::Result { - eprintln!("Comparing account lists."); - let (found_diff, accounts1) = compare_account_lists(client1, client2, block1, block2).await?; +) -> anyhow::Result<()> { + info!("Comparing account lists."); + let accounts1 = compare_account_lists(client1, client2, block1, block2).await?; - eprintln!("Got {} accounts.", accounts1.len()); + info!("Got {} accounts.", accounts1.len()); let bar = ProgressBar::new(accounts1.len() as u64); - eprintln!("Querying and comparing all accounts."); - let flag = Arc::new(AtomicBool::new(found_diff)); + info!("Querying and comparing all accounts."); for acc in accounts1 { - let mut a_client = client1.clone(); - let mut a_client2 = client2.clone(); let accid = acc.into(); - let (mut a1, mut a2) = futures::try_join!( - a_client.get_account_info(&accid, block1), - a_client2.get_account_info(&accid, block2) + let (a1, a2) = futures::try_join!( + client1.get_account_info(&accid, block1), + client2.get_account_info(&accid, block2) )?; + let mut a1 = a1.response; + let mut a2 = a2.response; + bar.inc(1); // We ignore the order of transactions in the release schedules since they are // not guaranteed to be in any specific order. - for s in a1.response.account_release_schedule.schedule.iter_mut() { + for s in a1.account_release_schedule.schedule.iter_mut() { s.transactions.sort_unstable(); } - for s in a2.response.account_release_schedule.schedule.iter_mut() { + for s in a2.account_release_schedule.schedule.iter_mut() { s.transactions.sort_unstable(); } - if a1.response != a2.response { - match (&a1.response.account_stake, a2.response.account_stake) { - (None, None) => { - diff!( - "Account {} differs. It does not have stake either in {} or {}.", - a1.response.account_address, - block1, - block2 - ); - flag.store(true, Ordering::Release); - } - (None, Some(_)) => { - diff!( - "Account {} differs. It does not have stake in {} but does in {}.", - a1.response.account_address, - block1, - block2 - ); - flag.store(true, Ordering::Release); - } - (Some(_), None) => { - diff!( - "Account {} differs. It does have stake in {} but does not in {}.", - a1.response.account_address, - block1, - block2 - ); - flag.store(true, Ordering::Release); - } - (Some(s1), Some(s2)) => { - // This is special case handling of P3->P4 upgrade. - if pv1 == ProtocolVersion::P3 && pv2 == ProtocolVersion::P4 { - match s1 { - concordium_rust_sdk::types::AccountStakingInfo::Baker { - pool_info: None, - .. - } => match s2 { - concordium_rust_sdk::types::AccountStakingInfo::Baker { - staked_amount, - restake_earnings, - baker_info, - pending_change, - pool_info: Some(_), - } => { - let s2_no_pool = - concordium_rust_sdk::types::AccountStakingInfo::Baker { - staked_amount, - restake_earnings, - baker_info, - pending_change, - pool_info: None, - }; - let a2_no_pool = AccountInfo { - account_stake: Some(s2_no_pool), - ..a2.response - }; - if a1.response != a2_no_pool { - diff!( - "Account {} differs. It does have stake in both {} \ - and {}.", - a1.response.account_address, - block1, - block2 - ); - flag.store(true, Ordering::Release); - } - } - _ => { - diff!( - "Account {} differs. It does have stake in both {} and {}.", - a1.response.account_address, - block1, - block2 - ); - flag.store(true, Ordering::Release); - } - }, - _ => { - diff!( - "Account {} differs. It does have stake in both {} and {}.", - a1.response.account_address, - block1, - block2 - ); - flag.store(true, Ordering::Release); - } - } - } else { - diff!( - "Account {} differs. It does have stake in both {} and {}.", - a1.response.account_address, - block1, - block2 - ); - flag.store(true, Ordering::Release); - } - } - } - } + + compare!(a1, a2, "Account {accid}"); } + bar.finish_and_clear(); - Ok(found_diff | flag.load(Ordering::Acquire)) + + Ok(()) } async fn compare_modules( @@ -467,9 +347,9 @@ async fn compare_modules( client2: &mut v2::Client, block1: BlockHash, block2: BlockHash, -) -> anyhow::Result { - eprintln!("Comparing all modules."); - let (mut found_diff, ms1) = compare_module_lists(client1, client2, block1, block2).await?; +) -> anyhow::Result<()> { + info!("Comparing all modules."); + let ms1 = compare_module_lists(client1, client2, block1, block2).await?; let bar = ProgressBar::new(ms1.len() as u64); for m in ms1 { @@ -478,13 +358,13 @@ async fn compare_modules( client1.get_module_source(&m, block1), client2.get_module_source(&m, block2) )?; - if m1.response != m2.response { - found_diff = true; - diff!("Module {} differs.", m); - } + + compare!(m1.response, m2.response, "Module {m}"); } + bar.finish_and_clear(); - Ok(found_diff) + + Ok(()) } async fn compare_instances( @@ -492,9 +372,9 @@ async fn compare_instances( client2: &mut v2::Client, block1: BlockHash, block2: BlockHash, -) -> anyhow::Result { - eprintln!("Querying all contracts."); - let (mut found_diff, cs1) = compare_instance_lists(client1, client2, block1, block2).await?; +) -> anyhow::Result<()> { + info!("Querying all contracts."); + let cs1 = compare_instance_lists(client1, client2, block1, block2).await?; let bar = ProgressBar::new(cs1.len() as u64); @@ -504,34 +384,33 @@ async fn compare_instances( client1.get_instance_info(c, block1), client2.get_instance_info(c, block2) )?; - if ci1.response != ci2.response { - diff!("Instance {} differs.", c); - found_diff = true; - } - let (mut state1, mut state2) = futures::try_join!( + + compare!(ci1.response, ci2.response, "Contract instance {c}"); + + let (state1, state2) = futures::try_join!( client1.get_instance_state(c, block1), client2.get_instance_state(c, block2) )?; - while let Some(s1) = state1.response.next().await.transpose()? { - if let Some(s2) = state2.response.next().await.transpose()? { - if s1 != s2 { - diff!("State differs for {}.", c); - found_diff = true; - break; - } + + let mut state1 = state1.response; + let mut state2 = state2.response; + + while let Some(s1) = state1.next().await.transpose()? { + if let Some(s2) = state2.next().await.transpose()? { + compare!(s1, s2, "State for {c}"); } else { - diff!("State differs for {}.", c); - found_diff = true; - break; + warn!("State for {c} not present in block 2."); } } - if state2.response.next().await.is_some() { - diff!("State differs for {}.", c); - found_diff = true; + + if state2.next().await.is_some() { + warn!("State for {c} not present in block 1."); } } + bar.finish_and_clear(); - Ok(found_diff) + + Ok(()) } async fn compare_passive_delegators( @@ -539,38 +418,29 @@ async fn compare_passive_delegators( client2: &mut v2::Client, block1: BlockHash, block2: BlockHash, - pv1: ProtocolVersion, - pv2: ProtocolVersion, -) -> anyhow::Result { - eprintln!("Checking passive delegators."); - let mut passive1 = if pv1 >= ProtocolVersion::P4 { - client1 - .get_passive_delegators(block1) - .await? - .response - .try_collect::>() - .await? - } else { - Vec::new() - }; - let mut passive2 = if pv2 >= ProtocolVersion::P4 { - client2 - .get_passive_delegators(block2) - .await? - .response - .try_collect::>() - .await? - } else { - Vec::new() - }; +) -> anyhow::Result<()> { + info!("Checking passive delegators."); + + let mut passive1 = client1 + .get_passive_delegators(block1) + .await? + .response + .try_collect::>() + .await?; + + let mut passive2 = client2 + .get_passive_delegators(block2) + .await? + .response + .try_collect::>() + .await?; + passive1.sort_unstable_by_key(|x| x.account); passive2.sort_unstable_by_key(|x| x.account); - if passive1 != passive2 { - diff!("Passive delegators differ."); - Ok(true) - } else { - Ok(false) - } + + compare!(passive1, passive2, "Passive delegators"); + + Ok(()) } async fn compare_active_bakers( @@ -578,27 +448,17 @@ async fn compare_active_bakers( client2: &mut v2::Client, block1: BlockHash, block2: BlockHash, - pv1: ProtocolVersion, - pv2: ProtocolVersion, -) -> anyhow::Result { - eprintln!("Checking active bakers."); - let mut found_diff = false; +) -> anyhow::Result<()> { + info!("Checking active bakers."); + let (ei1, ei2) = futures::try_join!( client1.get_election_info(block1), client2.get_election_info(block2) )?; - if pv1 < ProtocolVersion::P6 - && pv2 < ProtocolVersion::P6 - && ei1.response.election_difficulty != ei2.response.election_difficulty - { - diff!("Election difficulty differs."); - found_diff = true; - } - if ei1.response.bakers != ei2.response.bakers { - diff!("Bakers differ."); - found_diff = true; - } - Ok(found_diff) + + compare!(ei1.response, ei2.response, "Election info"); + + Ok(()) } async fn compare_baker_pools( @@ -606,51 +466,83 @@ async fn compare_baker_pools( client2: &mut v2::Client, block1: BlockHash, block2: BlockHash, - pv1: ProtocolVersion, - pv2: ProtocolVersion, -) -> anyhow::Result { - eprintln!("Checking baker pools."); +) -> anyhow::Result<()> { + info!("Checking baker pools."); + let mut pools1 = client1 .get_baker_list(block1) .await? .response .try_collect::>() .await?; + let mut pools2 = client2 .get_baker_list(block2) .await? .response .try_collect::>() .await?; + pools1.sort_unstable(); pools2.sort_unstable(); - let mut found_diff = compare_iters("Pool", block1, block2, pools1.iter(), pools2.iter()); - if pv1 >= ProtocolVersion::P4 && pv2 >= ProtocolVersion::P4 { - for pool in pools1 { - let (d1, d2) = futures::try_join!( - client1.get_pool_delegators(block1, pool), - client2.get_pool_delegators(block2, pool) - )?; - let mut ds1 = d1.response.try_collect::>().await?; - let mut ds2 = d2.response.try_collect::>().await?; - ds1.sort_unstable_by_key(|x| x.account); - ds2.sort_unstable_by_key(|x| x.account); - if ds1 != ds2 { - diff!("Delegators for pool {} differ.", pool); - found_diff = true; + + compare_iters("Pool", block1, block2, pools1.iter(), pools2.iter()); + + for pool in pools1 { + let (d1, d2) = futures::join!( + client1.get_pool_delegators(block1, pool), + client2.get_pool_delegators(block2, pool) + ); + + let (d1, d2) = match (d1, d2) { + (Ok(d1), Ok(d2)) => (d1.response, d2.response), + (Ok(_), Err(e)) => { + warn!("Failed to get delegators for pool {pool} in block 2: {e}"); + continue; + } + // The pool should definitely appear in the first block, we got the list of pools from + // that block. + (Err(e), Ok(_)) => { + return Err(e).with_context(|| format!("Failed to get delegators for pool {pool}")) } + (Err(e1), Err(e2)) => { + return Err(e2) + .context(e1) + .with_context(|| format!("Failed to get delegators for pool {pool}")) + } + }; + + let mut ds1 = d1.try_collect::>().await?; + let mut ds2 = d2.try_collect::>().await?; - let (p1, p2) = futures::try_join!( - client1.get_pool_info(block1, pool), - client2.get_pool_info(block2, pool) - )?; - if p1.response != p2.response { - diff!("Pool {} differs.", pool); - found_diff = true; + ds1.sort_unstable_by_key(|x| x.account); + ds2.sort_unstable_by_key(|x| x.account); + + compare!(ds1, ds2, "Delegators for pool {pool}"); + + let (p1, p2) = futures::join!( + client1.get_pool_info(block1, pool), + client2.get_pool_info(block2, pool) + ); + + let (p1, p2) = match (p1, p2) { + (Ok(p1), Ok(p2)) => (p1.response, p2.response), + (Ok(_), Err(e)) => { + warn!("Failed to get pool {pool} in block 2: {e}"); + continue; } - } - } else { - warn!("Not comparing baker pools since one of the protocol versions is before P4.") + // The pool should definitely appear in the first block, we got the list of pools from + // that block. + (Err(e), Ok(_)) => return Err(e).with_context(|| format!("Failed to get pool {pool}")), + (Err(e1), Err(e2)) => { + return Err(e2) + .context(e1) + .with_context(|| format!("Failed to get pool {pool}")) + } + }; + + compare!(p1, p2, "Pool {pool}"); } - Ok(found_diff) + + Ok(()) }