diff --git a/.github/workflows/release_linux.yml b/.github/workflows/release_linux.yml index 553f9bad72..69ab106879 100644 --- a/.github/workflows/release_linux.yml +++ b/.github/workflows/release_linux.yml @@ -183,6 +183,7 @@ jobs: env: VERSION: ${{ steps.get_version.outputs.VERSION }} run: | + source packaging/images.env ARCH=${{ matrix.arch }} docker run --rm --platform linux/$ARCH -v "$PWD":/work -w /work $FEDORA_IMAGE \ packaging/checks/smoke-rpm.sh dist/Mintlayer_Node_linux_${VERSION}_${ARCH}.rpm mintlayer-node node @@ -213,10 +214,10 @@ jobs: # x86_64 leg: arch-matched container (native strip, ldd-based # dependencies, help2man man pages), so the package installs natively. - # aarch64 leg: the same amd64-only image installs the foreign-arch - # package with IgnoreArch and runs the binaries through the qemu binfmt - # handlers registered above — the same emulation the deb/rpm legs use - # for their arm64 smoke tests. + # aarch64 leg: Docker Hub publishes no arm64 Arch image, so + # smoke-arch.sh assembles an Arch Linux ARM chroot and installs/runs the + # package there through the qemu binfmt handlers registered above — the + # same emulation the deb/rpm legs use for their arm64 smoke tests. - name: Smoke test Arch packages env: VERSION: ${{ steps.get_version.outputs.VERSION }} diff --git a/.gitignore b/.gitignore index ff2c0a517f..dc03e16b58 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,4 @@ target-debian/ /installer-gui.nsi /Mintlayer_Node_win_*_Setup.exe /Mintlayer_Node_GUI_win_*_Setup.exe +.opencode/ diff --git a/CHANGELOG.md b/CHANGELOG.md index ef62f43ff1..492d2a264a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,13 @@ The format is loosely based on [Keep a Changelog](https://keepachangelog.com/en/ ## [Unreleased] +## [1.4.1] - 2026-09-25 + +### Added + - Node: + - Added PoS seal indexing infrastructure: duplicate-proof evidence storage for + block seals, active by default, with a chainstate configuration option to disable it. + ### Fixed - Wallet: - Fixed an issue where `standalone-add-watch-only-address`, `standalone-add-private-key-from-hex` and diff --git a/Cargo.lock b/Cargo.lock index dbef7d51b2..280f6a0dc0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,7 +20,7 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" [[package]] name = "accounting" -version = "1.4.0" +version = "1.4.1" dependencies = [ "common", "parity-scale-codec", @@ -227,7 +227,7 @@ checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" [[package]] name = "api-blockchain-scanner-daemon" -version = "1.4.0" +version = "1.4.1" dependencies = [ "api-blockchain-scanner-lib", "api-server-common", @@ -247,7 +247,7 @@ dependencies = [ [[package]] name = "api-blockchain-scanner-lib" -version = "1.4.0" +version = "1.4.1" dependencies = [ "api-server-common", "async-trait", @@ -277,7 +277,7 @@ dependencies = [ [[package]] name = "api-server-backend-test-suite" -version = "1.4.0" +version = "1.4.1" dependencies = [ "api-server-common", "async-trait", @@ -300,7 +300,7 @@ dependencies = [ [[package]] name = "api-server-common" -version = "1.4.0" +version = "1.4.1" dependencies = [ "async-trait", "bb8-postgres", @@ -328,7 +328,7 @@ dependencies = [ [[package]] name = "api-server-stack-test-suite" -version = "1.4.0" +version = "1.4.1" dependencies = [ "api-blockchain-scanner-lib", "api-server-backend-test-suite", @@ -360,7 +360,7 @@ dependencies = [ [[package]] name = "api-web-server" -version = "1.4.0" +version = "1.4.1" dependencies = [ "api-server-common", "async-trait", @@ -1003,7 +1003,7 @@ dependencies = [ [[package]] name = "blockprod" -version = "1.4.0" +version = "1.4.1" dependencies = [ "async-trait", "chainstate", @@ -1145,7 +1145,7 @@ dependencies = [ [[package]] name = "build_utils" -version = "1.4.0" +version = "1.4.1" dependencies = [ "substrate-build-script-utils", ] @@ -1364,7 +1364,7 @@ dependencies = [ [[package]] name = "chainstate" -version = "1.4.0" +version = "1.4.1" dependencies = [ "accounting", "async-trait", @@ -1410,7 +1410,7 @@ dependencies = [ [[package]] name = "chainstate-db-dumper" -version = "1.4.0" +version = "1.4.1" dependencies = [ "anyhow", "chainstate", @@ -1435,7 +1435,7 @@ dependencies = [ [[package]] name = "chainstate-launcher" -version = "1.4.0" +version = "1.4.1" dependencies = [ "chainstate", "chainstate-storage", @@ -1451,7 +1451,7 @@ dependencies = [ [[package]] name = "chainstate-storage" -version = "1.4.0" +version = "1.4.1" dependencies = [ "accounting", "chainstate-types", @@ -1478,7 +1478,7 @@ dependencies = [ [[package]] name = "chainstate-test-framework" -version = "1.4.0" +version = "1.4.1" dependencies = [ "chainstate", "chainstate-storage", @@ -1508,7 +1508,7 @@ dependencies = [ [[package]] name = "chainstate-test-suite" -version = "1.4.0" +version = "1.4.1" dependencies = [ "accounting", "chainstate", @@ -1541,7 +1541,7 @@ dependencies = [ [[package]] name = "chainstate-types" -version = "1.4.0" +version = "1.4.1" dependencies = [ "common", "crypto", @@ -1755,7 +1755,7 @@ dependencies = [ [[package]] name = "common" -version = "1.4.0" +version = "1.4.1" dependencies = [ "anyhow", "bech32 0.11.1", @@ -1815,7 +1815,7 @@ dependencies = [ [[package]] name = "consensus" -version = "1.4.0" +version = "1.4.1" dependencies = [ "chainstate-types", "common", @@ -1902,7 +1902,7 @@ dependencies = [ [[package]] name = "constraints-value-accumulator" -version = "1.4.0" +version = "1.4.1" dependencies = [ "accounting", "common", @@ -2168,7 +2168,7 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto" -version = "1.4.0" +version = "1.4.1" dependencies = [ "argon2", "bip39", @@ -2681,7 +2681,7 @@ checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" [[package]] name = "dns-server" -version = "1.4.0" +version = "1.4.1" dependencies = [ "anyhow", "async-trait", @@ -4975,7 +4975,7 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "log_error" -version = "1.4.0" +version = "1.4.1" dependencies = [ "derive_more 1.0.0", "logging", @@ -4988,7 +4988,7 @@ dependencies = [ [[package]] name = "logging" -version = "1.4.0" +version = "1.4.1" dependencies = [ "console-subscriber", "log", @@ -5139,7 +5139,7 @@ dependencies = [ [[package]] name = "mempool" -version = "1.4.0" +version = "1.4.1" dependencies = [ "accounting", "anyhow", @@ -5187,7 +5187,7 @@ dependencies = [ [[package]] name = "mempool-types" -version = "1.4.0" +version = "1.4.1" dependencies = [ "p2p-types", "rpc-description", @@ -5256,7 +5256,7 @@ dependencies = [ [[package]] name = "mintlayer-core" -version = "1.4.0" +version = "1.4.1" dependencies = [ "chainstate", "chainstate-storage", @@ -5307,7 +5307,7 @@ dependencies = [ [[package]] name = "mintlayer-test" -version = "1.4.0" +version = "1.4.1" dependencies = [ "clap", "ctor", @@ -5324,7 +5324,7 @@ dependencies = [ [[package]] name = "mintscript" -version = "1.4.0" +version = "1.4.1" dependencies = [ "common", "crypto", @@ -5386,7 +5386,7 @@ dependencies = [ [[package]] name = "mocks" -version = "1.4.0" +version = "1.4.1" dependencies = [ "async-trait", "chainstate", @@ -5483,7 +5483,7 @@ dependencies = [ [[package]] name = "networking" -version = "1.4.0" +version = "1.4.1" dependencies = [ "async-trait", "bytes", @@ -5521,7 +5521,7 @@ dependencies = [ [[package]] name = "node-comm" -version = "1.4.0" +version = "1.4.1" dependencies = [ "anyhow", "async-trait", @@ -5554,7 +5554,7 @@ dependencies = [ [[package]] name = "node-daemon" -version = "1.4.0" +version = "1.4.1" dependencies = [ "anyhow", "assert_cmd", @@ -5569,7 +5569,7 @@ dependencies = [ [[package]] name = "node-gui" -version = "1.4.0" +version = "1.4.1" dependencies = [ "anyhow", "chainstate", @@ -5604,7 +5604,7 @@ dependencies = [ [[package]] name = "node-gui-backend" -version = "1.4.0" +version = "1.4.1" dependencies = [ "anyhow", "chainstate", @@ -5639,7 +5639,7 @@ dependencies = [ [[package]] name = "node-lib" -version = "1.4.0" +version = "1.4.1" dependencies = [ "anyhow", "blockprod", @@ -6260,7 +6260,7 @@ dependencies = [ [[package]] name = "orders-accounting" -version = "1.4.0" +version = "1.4.1" dependencies = [ "accounting", "common", @@ -6311,7 +6311,7 @@ dependencies = [ [[package]] name = "p2p" -version = "1.4.0" +version = "1.4.1" dependencies = [ "async-trait", "bytes", @@ -6368,7 +6368,7 @@ dependencies = [ [[package]] name = "p2p-backend-test-suite" -version = "1.4.0" +version = "1.4.1" dependencies = [ "chainstate", "common", @@ -6388,7 +6388,7 @@ dependencies = [ [[package]] name = "p2p-test-utils" -version = "1.4.0" +version = "1.4.1" dependencies = [ "chainstate", "chainstate-storage", @@ -6408,7 +6408,7 @@ dependencies = [ [[package]] name = "p2p-types" -version = "1.4.0" +version = "1.4.1" dependencies = [ "common", "networking", @@ -6747,7 +6747,7 @@ checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" [[package]] name = "pos-accounting" -version = "1.4.0" +version = "1.4.1" dependencies = [ "accounting", "common", @@ -7166,7 +7166,7 @@ dependencies = [ [[package]] name = "randomness" -version = "1.4.0" +version = "1.4.1" dependencies = [ "rand 0.10.1", "rand 0.8.6", @@ -7499,7 +7499,7 @@ checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" [[package]] name = "rpc" -version = "1.4.0" +version = "1.4.1" dependencies = [ "anyhow", "async-trait", @@ -7529,7 +7529,7 @@ dependencies = [ [[package]] name = "rpc-description" -version = "1.4.0" +version = "1.4.1" dependencies = [ "rpc-description-macro", "serde_json", @@ -7537,7 +7537,7 @@ dependencies = [ [[package]] name = "rpc-description-macro" -version = "1.4.0" +version = "1.4.1" dependencies = [ "proc-macro2", "quote", @@ -7546,7 +7546,7 @@ dependencies = [ [[package]] name = "rpc-types" -version = "1.4.0" +version = "1.4.1" dependencies = [ "hex", "rpc-description", @@ -7891,7 +7891,7 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "script" -version = "1.4.0" +version = "1.4.1" dependencies = [ "crypto", "flate2", @@ -8193,7 +8193,7 @@ dependencies = [ [[package]] name = "serialization" -version = "1.4.0" +version = "1.4.1" dependencies = [ "hex", "rpc-description", @@ -8206,7 +8206,7 @@ dependencies = [ [[package]] name = "serialization-core" -version = "1.4.0" +version = "1.4.1" dependencies = [ "arraytools", "hex-literal", @@ -8219,7 +8219,7 @@ dependencies = [ [[package]] name = "serialization-tagged" -version = "1.4.0" +version = "1.4.1" dependencies = [ "parity-scale-codec", "proptest", @@ -8231,7 +8231,7 @@ dependencies = [ [[package]] name = "serialization-tagged-derive" -version = "1.4.0" +version = "1.4.1" dependencies = [ "itertools 0.14.0", "proc-macro2", @@ -8602,7 +8602,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "storage" -version = "1.4.0" +version = "1.4.1" dependencies = [ "common", "rstest", @@ -8615,7 +8615,7 @@ dependencies = [ [[package]] name = "storage-backend-test-suite" -version = "1.4.0" +version = "1.4.1" dependencies = [ "libtest-mimic", "logging", @@ -8630,7 +8630,7 @@ dependencies = [ [[package]] name = "storage-core" -version = "1.4.0" +version = "1.4.1" dependencies = [ "common", "itertools 0.14.0", @@ -8643,7 +8643,7 @@ dependencies = [ [[package]] name = "storage-failing" -version = "1.4.0" +version = "1.4.1" dependencies = [ "enumflags2", "storage", @@ -8655,7 +8655,7 @@ dependencies = [ [[package]] name = "storage-inmemory" -version = "1.4.0" +version = "1.4.1" dependencies = [ "storage-backend-test-suite", "storage-core", @@ -8664,7 +8664,7 @@ dependencies = [ [[package]] name = "storage-lmdb" -version = "1.4.0" +version = "1.4.1" dependencies = [ "lmdb-mintlayer", "logging", @@ -8678,7 +8678,7 @@ dependencies = [ [[package]] name = "storage-sqlite" -version = "1.4.0" +version = "1.4.1" dependencies = [ "hex", "logging", @@ -8781,7 +8781,7 @@ checksum = "b285e7d183a32732fdc119f3d81b7915790191fad602b7c709ef247073c77a2e" [[package]] name = "subsystem" -version = "1.4.0" +version = "1.4.1" dependencies = [ "async-trait", "cfg-if", @@ -8937,7 +8937,7 @@ checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" [[package]] name = "test-rpc-functions" -version = "1.4.0" +version = "1.4.1" dependencies = [ "async-trait", "chainstate", @@ -8959,7 +8959,7 @@ dependencies = [ [[package]] name = "test-utils" -version = "1.4.0" +version = "1.4.1" dependencies = [ "common", "crypto", @@ -9141,7 +9141,7 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokens-accounting" -version = "1.4.0" +version = "1.4.1" dependencies = [ "accounting", "chainstate-types", @@ -9600,7 +9600,7 @@ checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" [[package]] name = "tx-verifier" -version = "1.4.0" +version = "1.4.1" dependencies = [ "accounting", "chainstate-storage", @@ -9629,14 +9629,14 @@ dependencies = [ [[package]] name = "typename" -version = "1.4.0" +version = "1.4.1" dependencies = [ "typename-derive", ] [[package]] name = "typename-derive" -version = "1.4.0" +version = "1.4.1" dependencies = [ "itertools 0.14.0", "quote", @@ -9796,7 +9796,7 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "utils" -version = "1.4.0" +version = "1.4.1" dependencies = [ "anyhow", "clap", @@ -9831,7 +9831,7 @@ dependencies = [ [[package]] name = "utils-networking" -version = "1.4.0" +version = "1.4.1" dependencies = [ "addr", "itertools 0.14.0", @@ -9846,7 +9846,7 @@ dependencies = [ [[package]] name = "utxo" -version = "1.4.0" +version = "1.4.1" dependencies = [ "chainstate-types", "common", @@ -9920,7 +9920,7 @@ dependencies = [ [[package]] name = "wallet" -version = "1.4.0" +version = "1.4.1" dependencies = [ "anyhow", "async-trait", @@ -9973,7 +9973,7 @@ dependencies = [ [[package]] name = "wallet-address-generator" -version = "1.4.0" +version = "1.4.1" dependencies = [ "clap", "common", @@ -9989,7 +9989,7 @@ dependencies = [ [[package]] name = "wallet-address-generator-lib" -version = "1.4.0" +version = "1.4.1" dependencies = [ "build_utils", "clap", @@ -10004,7 +10004,7 @@ dependencies = [ [[package]] name = "wallet-cli" -version = "1.4.0" +version = "1.4.1" dependencies = [ "clap", "ctor", @@ -10015,7 +10015,7 @@ dependencies = [ [[package]] name = "wallet-cli-commands" -version = "1.4.0" +version = "1.4.1" dependencies = [ "async-trait", "bigdecimal", @@ -10072,7 +10072,7 @@ dependencies = [ [[package]] name = "wallet-cli-lib" -version = "1.4.0" +version = "1.4.1" dependencies = [ "async-trait", "blockprod", @@ -10122,7 +10122,7 @@ dependencies = [ [[package]] name = "wallet-controller" -version = "1.4.0" +version = "1.4.1" dependencies = [ "anyhow", "async-trait", @@ -10164,7 +10164,7 @@ dependencies = [ [[package]] name = "wallet-rpc-client" -version = "1.4.0" +version = "1.4.1" dependencies = [ "async-trait", "base64 0.22.1", @@ -10196,7 +10196,7 @@ dependencies = [ [[package]] name = "wallet-rpc-daemon" -version = "1.4.0" +version = "1.4.1" dependencies = [ "clap", "common", @@ -10213,7 +10213,7 @@ dependencies = [ [[package]] name = "wallet-rpc-lib" -version = "1.4.0" +version = "1.4.1" dependencies = [ "anyhow", "async-trait", @@ -10256,7 +10256,7 @@ dependencies = [ [[package]] name = "wallet-storage" -version = "1.4.0" +version = "1.4.1" dependencies = [ "bip39", "common", @@ -10275,7 +10275,7 @@ dependencies = [ [[package]] name = "wallet-test-node" -version = "1.4.0" +version = "1.4.1" dependencies = [ "blockprod", "chainstate", @@ -10296,7 +10296,7 @@ dependencies = [ [[package]] name = "wallet-types" -version = "1.4.0" +version = "1.4.1" dependencies = [ "bip39", "common", @@ -10433,7 +10433,7 @@ dependencies = [ [[package]] name = "wasm-doc-gen" -version = "1.4.0" +version = "1.4.1" dependencies = [ "anyhow", "clap", @@ -10479,7 +10479,7 @@ dependencies = [ [[package]] name = "wasm-wrappers" -version = "1.4.0" +version = "1.4.1" dependencies = [ "bip39", "common", diff --git a/Cargo.toml b/Cargo.toml index db8c5fa950..7e023cd5ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ homepage = "https://mintlayer.org" repository = "https://github.com/mintlayer/mintlayer-core" readme = "README.md" license = "MIT" -version = "1.4.0" +version = "1.4.1" edition = "2024" [workspace] @@ -128,7 +128,7 @@ utxo = { path = "utxo" } [workspace.package] edition = "2024" rust-version = "1.92" -version = "1.4.0" +version = "1.4.1" license = "MIT" [workspace.dependencies] diff --git a/a.out b/a.out new file mode 100644 index 0000000000..d138dc9019 Binary files /dev/null and b/a.out differ diff --git a/api-server/api-server-common/src/storage/impls/in_memory/mod.rs b/api-server/api-server-common/src/storage/impls/in_memory/mod.rs index 9d59e68b36..cef86b2e74 100644 --- a/api-server/api-server-common/src/storage/impls/in_memory/mod.rs +++ b/api-server/api-server-common/src/storage/impls/in_memory/mod.rs @@ -53,6 +53,13 @@ impl PartialOrd for TokenTransactionOrderedByTxId { } } +/// An offset that doesn't fit a usize is past the end of any in-memory result set. +/// Skipping by usize::MAX yields an empty page instead of wrapping to a small number +/// (which would return the first page) on targets with a narrower pointer width. +fn offset_to_usize(offset: u64) -> usize { + usize::try_from(offset).unwrap_or(usize::MAX) +} + impl Ord for TokenTransactionOrderedByTxId { fn cmp(&self, other: &Self) -> Ordering { self.0.tx_id.cmp(&other.0.tx_id) @@ -198,7 +205,7 @@ impl ApiServerInMemoryStorage { &self, token_id: TokenId, len: u32, - tx_global_index: u64, + offset: u64, ) -> Result, ApiServerStorageError> { Ok(self .token_transactions_table @@ -212,7 +219,7 @@ impl ApiServerInMemoryStorage { txs.sort_by_key(|tx| std::cmp::Reverse(tx.tx_global_index)); txs }) - .flat_map(|tx| (tx.tx_global_index < tx_global_index).then_some(tx)) + .skip(offset_to_usize(offset)) .cloned() .take(len as usize) .collect() @@ -265,7 +272,7 @@ impl ApiServerInMemoryStorage { }, ) }) - .skip(offset as usize) + .skip(offset_to_usize(offset)) .take(len as usize) .collect()) } @@ -457,7 +464,7 @@ impl ApiServerInMemoryStorage { offset: u64, ) -> Result, ApiServerStorageError> { let len = len as usize; - let offset = offset as usize; + let offset = offset_to_usize(offset); if offset >= self.orders_table.len() { return Ok(vec![]); @@ -490,7 +497,7 @@ impl ApiServerInMemoryStorage { offset: u64, ) -> Result, ApiServerStorageError> { let len = len as usize; - let offset = offset as usize; + let offset = offset_to_usize(offset); let mut order_data: Vec<_> = self .orders_table @@ -524,7 +531,7 @@ impl ApiServerInMemoryStorage { offset: u64, ) -> Result, ApiServerStorageError> { let len = len as usize; - let offset = offset as usize; + let offset = offset_to_usize(offset); let mut pool_data: Vec<_> = self .pool_data_table .iter() @@ -555,7 +562,7 @@ impl ApiServerInMemoryStorage { offset: u64, ) -> Result, ApiServerStorageError> { let len = len as usize; - let offset = offset as usize; + let offset = offset_to_usize(offset); let mut pool_data: Vec<_> = self .pool_data_table .iter() @@ -750,7 +757,7 @@ impl ApiServerInMemoryStorage { .fungible_token_data .keys() .chain(self.nft_token_issuances.keys()) - .skip(offset as usize) + .skip(offset_to_usize(offset)) .take(len as usize) .copied() .collect()) @@ -781,7 +788,7 @@ impl ApiServerInMemoryStorage { String::from_utf8_lossy(value_ticker).to_ascii_lowercase(); (lowercased_value_ticker.contains(&lowercased_ticker)).then_some(key) })) - .skip(offset as usize) + .skip(offset_to_usize(offset)) .take(len as usize) .copied() .collect()) diff --git a/api-server/api-server-common/src/storage/impls/in_memory/transactional/read.rs b/api-server/api-server-common/src/storage/impls/in_memory/transactional/read.rs index 546f7e1951..7aeee421c7 100644 --- a/api-server/api-server-common/src/storage/impls/in_memory/transactional/read.rs +++ b/api-server/api-server-common/src/storage/impls/in_memory/transactional/read.rs @@ -72,9 +72,9 @@ impl ApiServerStorageRead for ApiServerInMemoryStorageTransactionalRo<'_> { &self, token_id: TokenId, len: u32, - tx_global_index: u64, + offset: u64, ) -> Result, ApiServerStorageError> { - self.transaction.get_token_transactions(token_id, len, tx_global_index) + self.transaction.get_token_transactions(token_id, len, offset) } async fn get_block( diff --git a/api-server/api-server-common/src/storage/impls/in_memory/transactional/write.rs b/api-server/api-server-common/src/storage/impls/in_memory/transactional/write.rs index 5039ae013d..f13ff885ff 100644 --- a/api-server/api-server-common/src/storage/impls/in_memory/transactional/write.rs +++ b/api-server/api-server-common/src/storage/impls/in_memory/transactional/write.rs @@ -357,9 +357,9 @@ impl ApiServerStorageRead for ApiServerInMemoryStorageTransactionalRw<'_> { &self, token_id: TokenId, len: u32, - tx_global_index: u64, + offset: u64, ) -> Result, ApiServerStorageError> { - self.transaction.get_token_transactions(token_id, len, tx_global_index) + self.transaction.get_token_transactions(token_id, len, offset) } async fn get_latest_blocktimestamps( diff --git a/api-server/api-server-common/src/storage/impls/postgres/queries.rs b/api-server/api-server-common/src/storage/impls/postgres/queries.rs index ee6e3b8d5e..4b27a78db2 100644 --- a/api-server/api-server-common/src/storage/impls/postgres/queries.rs +++ b/api-server/api-server-common/src/storage/impls/postgres/queries.rs @@ -97,6 +97,13 @@ impl<'a, 'b> QueryFromConnection<'a, 'b> { .map_err(|_| ApiServerStorageError::TxGlobalIndexTooHigh(tx_global_index)) } + /// An offset that doesn't fit a bigint is past the end of any result set. + /// Postgres rejects a negative OFFSET, so callers map overflow to an empty + /// page instead of wrapping. + fn offset_to_bigint(offset: u64) -> Option { + i64::try_from(offset).ok() + } + pub async fn is_initialized(&mut self) -> Result { let query_str = Self::get_table_exists_query("misc_data"); let row_count = self @@ -506,9 +513,11 @@ impl<'a, 'b> QueryFromConnection<'a, 'b> { &self, token_id: TokenId, len: u32, - tx_global_index: u64, + offset: u64, ) -> Result, ApiServerStorageError> { - let tx_global_index = Self::tx_global_index_to_postgres_friendly(tx_global_index)?; + let Some(offset) = Self::offset_to_bigint(offset) else { + return Ok(Vec::new()); + }; let len = len as i64; let rows = self .tx @@ -516,11 +525,12 @@ impl<'a, 'b> QueryFromConnection<'a, 'b> { r#" SELECT tx_global_index, transaction_id FROM ml.token_transactions - WHERE token_id = $1 AND tx_global_index < $2 + WHERE token_id = $1 ORDER BY tx_global_index DESC + OFFSET $2 LIMIT $3; "#, - &[&token_id.encode(), &tx_global_index, &len], + &[&token_id.encode(), &offset, &len], ) .await .map_err(|e| ApiServerStorageError::LowLevelStorageError(e.to_string()))?; @@ -1716,7 +1726,9 @@ impl<'a, 'b> QueryFromConnection<'a, 'b> { chain_config: &ChainConfig, ) -> Result, ApiServerStorageError> { let len = len as i64; - let offset = offset as i64; + let Some(offset) = Self::offset_to_bigint(offset) else { + return Ok(Vec::new()); + }; self.tx .query( r#" @@ -1760,7 +1772,9 @@ impl<'a, 'b> QueryFromConnection<'a, 'b> { chain_config: &ChainConfig, ) -> Result, ApiServerStorageError> { let len = len as i64; - let offset = offset as i64; + let Some(offset) = Self::offset_to_bigint(offset) else { + return Ok(Vec::new()); + }; self.tx .query( r#" @@ -1931,7 +1945,9 @@ impl<'a, 'b> QueryFromConnection<'a, 'b> { offset: u64, ) -> Result, ApiServerStorageError> { let len = len as i64; - let offset = offset as i64; + let Some(offset) = Self::offset_to_bigint(offset) else { + return Ok(Vec::new()); + }; let rows = self .tx .query( @@ -2538,9 +2554,7 @@ impl<'a, 'b> QueryFromConnection<'a, 'b> { offset: u64, ) -> Result, ApiServerStorageError> { let len = len as i64; - // An offset too big for a bigint is past the end of any result set. Postgres rejects - // a negative OFFSET, so return an empty page rather than let the cast wrap. - let Ok(offset) = i64::try_from(offset) else { + let Some(offset) = Self::offset_to_bigint(offset) else { return Ok(Vec::new()); }; self.tx @@ -2576,9 +2590,7 @@ impl<'a, 'b> QueryFromConnection<'a, 'b> { ticker: &str, ) -> Result, ApiServerStorageError> { let len = len as i64; - // An offset too big for a bigint is past the end of any result set. Postgres rejects - // a negative OFFSET, so return an empty page rather than let the cast wrap. - let Ok(offset) = i64::try_from(offset) else { + let Some(offset) = Self::offset_to_bigint(offset) else { return Ok(Vec::new()); }; let escaped_ticker = escape_for_like(ticker); @@ -3006,7 +3018,9 @@ impl<'a, 'b> QueryFromConnection<'a, 'b> { chain_config: &ChainConfig, ) -> Result, ApiServerStorageError> { let len = len as i64; - let offset = offset as i64; + let Some(offset) = Self::offset_to_bigint(offset) else { + return Ok(Vec::new()); + }; self.tx .query( r#" @@ -3039,7 +3053,9 @@ impl<'a, 'b> QueryFromConnection<'a, 'b> { chain_config: &ChainConfig, ) -> Result, ApiServerStorageError> { let len = len as i64; - let offset = offset as i64; + let Some(offset) = Self::offset_to_bigint(offset) else { + return Ok(Vec::new()); + }; self.tx .query( r#" diff --git a/api-server/api-server-common/src/storage/impls/postgres/transactional/read.rs b/api-server/api-server-common/src/storage/impls/postgres/transactional/read.rs index 6fa3514681..74cabe1026 100644 --- a/api-server/api-server-common/src/storage/impls/postgres/transactional/read.rs +++ b/api-server/api-server-common/src/storage/impls/postgres/transactional/read.rs @@ -99,10 +99,10 @@ impl ApiServerStorageRead for ApiServerPostgresTransactionalRo<'_> { &self, token_id: TokenId, len: u32, - tx_global_index: u64, + offset: u64, ) -> Result, ApiServerStorageError> { let conn = QueryFromConnection::new(self.connection.as_ref().expect(CONN_ERR)); - let res = conn.get_token_transactions(token_id, len, tx_global_index).await?; + let res = conn.get_token_transactions(token_id, len, offset).await?; Ok(res) } diff --git a/api-server/api-server-common/src/storage/impls/postgres/transactional/write.rs b/api-server/api-server-common/src/storage/impls/postgres/transactional/write.rs index c9a53f3c28..9ff20b4224 100644 --- a/api-server/api-server-common/src/storage/impls/postgres/transactional/write.rs +++ b/api-server/api-server-common/src/storage/impls/postgres/transactional/write.rs @@ -500,10 +500,10 @@ impl ApiServerStorageRead for ApiServerPostgresTransactionalRw<'_> { &self, token_id: TokenId, len: u32, - tx_global_index: u64, + offset: u64, ) -> Result, ApiServerStorageError> { let conn = QueryFromConnection::new(self.connection.as_ref().expect(CONN_ERR)); - let res = conn.get_token_transactions(token_id, len, tx_global_index).await?; + let res = conn.get_token_transactions(token_id, len, offset).await?; Ok(res) } diff --git a/api-server/api-server-common/src/storage/storage_api/mod.rs b/api-server/api-server-common/src/storage/storage_api/mod.rs index 05863da479..d99ea5237c 100644 --- a/api-server/api-server-common/src/storage/storage_api/mod.rs +++ b/api-server/api-server-common/src/storage/storage_api/mod.rs @@ -619,13 +619,15 @@ pub trait ApiServerStorageRead: Sync { ) -> Result>, ApiServerStorageError>; /// Returns a page of transaction IDs that reference this `token_id`, limited to `len` entries - /// and with a `tx_global_index` older than the specified value. - /// The `tx_global_index` is not continuous for a specific `token_id`. + /// and starting `offset` entries into the full, newest-first list for the token. An offset + /// greater than or equal to the number of transactions for the token yields an empty page. + /// As with the other offset-paginated queries, the cost of a request grows linearly with the + /// offset. async fn get_token_transactions( &self, token_id: TokenId, len: u32, - tx_global_index: u64, + offset: u64, ) -> Result, ApiServerStorageError>; async fn get_best_block(&self) -> Result; diff --git a/api-server/scanner-lib/src/sync/tests/mod.rs b/api-server/scanner-lib/src/sync/tests/mod.rs index 25aabce4c5..cb711c5c02 100644 --- a/api-server/scanner-lib/src/sync/tests/mod.rs +++ b/api-server/scanner-lib/src/sync/tests/mod.rs @@ -1361,7 +1361,7 @@ async fn token_transactions_storage_check(#[case] seed: Seed) { // Check count: Issue(1) + Mint(1) = 2 let db_tx = local_state.storage().transaction_ro().await.unwrap(); let txs = db_tx - .get_token_transactions(token_id, 100, u64::MAX) + .get_token_transactions(token_id, 100, 0) .await .unwrap() .into_iter() @@ -1453,7 +1453,7 @@ async fn token_transactions_storage_check(#[case] seed: Seed) { // Verify Storage: 2 previous + 4 new = 6 transactions let db_tx = local_state.storage().transaction_ro().await.unwrap(); let txs = db_tx - .get_token_transactions(token_id, 100, u64::MAX) + .get_token_transactions(token_id, 100, 0) .await .unwrap() .into_iter() @@ -1494,7 +1494,7 @@ async fn token_transactions_storage_check(#[case] seed: Seed) { token_txs.insert(tx_spend_id); let db_tx = local_state.storage().transaction_ro().await.unwrap(); let txs = db_tx - .get_token_transactions(token_id, 100, u64::MAX) + .get_token_transactions(token_id, 100, 0) .await .unwrap() .into_iter() @@ -1549,7 +1549,7 @@ async fn token_transactions_storage_check(#[case] seed: Seed) { // Verify Storage: Order creation involves the token (in 'Give'), so it should be indexed. let db_tx = local_state.storage().transaction_ro().await.unwrap(); let txs = db_tx - .get_token_transactions(token_id, 100, u64::MAX) + .get_token_transactions(token_id, 100, 0) .await .unwrap() .into_iter() @@ -1599,7 +1599,7 @@ async fn token_transactions_storage_check(#[case] seed: Seed) { // Verify Storage: Fill Order should be indexed for the token let db_tx = local_state.storage().transaction_ro().await.unwrap(); let txs = db_tx - .get_token_transactions(token_id, 100, u64::MAX) + .get_token_transactions(token_id, 100, 0) .await .unwrap() .into_iter() @@ -1641,7 +1641,7 @@ async fn token_transactions_storage_check(#[case] seed: Seed) { // Verify Storage: Conclude Order should be indexed for the token let db_tx = local_state.storage().transaction_ro().await.unwrap(); let txs = db_tx - .get_token_transactions(token_id, 100, u64::MAX) + .get_token_transactions(token_id, 100, 0) .await .unwrap() .into_iter() @@ -1683,7 +1683,7 @@ async fn token_transactions_storage_check(#[case] seed: Seed) { // Verify Storage: Conclude Order should be indexed for the token let db_tx = local_state.storage().transaction_ro().await.unwrap(); let txs = db_tx - .get_token_transactions(token_id, 100, u64::MAX) + .get_token_transactions(token_id, 100, 0) .await .unwrap() .into_iter() diff --git a/api-server/storage-test-suite/src/basic.rs b/api-server/storage-test-suite/src/basic.rs index 7395508a23..68f85159f0 100644 --- a/api-server/storage-test-suite/src/basic.rs +++ b/api-server/storage-test-suite/src/basic.rs @@ -981,28 +981,26 @@ where } let len = 5; - let global_idx = 10; - let token_txs = - db_tx.get_token_transactions(random_token_id, len, global_idx).await.unwrap(); + let offset = 0; + let token_txs = db_tx.get_token_transactions(random_token_id, len, offset).await.unwrap(); let expected_txs: Vec<_> = token_transactions .iter() .rev() - .filter(|tx| tx.tx_global_index < global_idx) + .skip(offset as usize) .take(len as usize) .cloned() .collect(); assert_eq!(token_txs, expected_txs); let len = 5; - let global_idx = 5; - let token_txs = - db_tx.get_token_transactions(random_token_id, len, global_idx).await.unwrap(); + let offset = 5; + let token_txs = db_tx.get_token_transactions(random_token_id, len, offset).await.unwrap(); let expected_txs: Vec<_> = token_transactions .iter() .rev() - .filter(|tx| tx.tx_global_index < global_idx) + .skip(offset as usize) .take(len as usize) .cloned() .collect(); @@ -1033,14 +1031,13 @@ where } let len = 5; - let global_idx = 200; - let token_txs = - db_tx.get_token_transactions(random_token_id, len, global_idx).await.unwrap(); + let offset = 0; + let token_txs = db_tx.get_token_transactions(random_token_id, len, offset).await.unwrap(); let expected_txs: Vec<_> = updated_token_transactions .iter() .rev() - .filter(|tx| tx.tx_global_index < global_idx) + .skip(offset as usize) .take(len as usize) .cloned() .collect(); @@ -1048,14 +1045,13 @@ where assert_eq!(token_txs, expected_txs); let len = 5; - let global_idx = 105; - let token_txs = - db_tx.get_token_transactions(random_token_id, len, global_idx).await.unwrap(); + let offset = 5; + let token_txs = db_tx.get_token_transactions(random_token_id, len, offset).await.unwrap(); let expected_txs: Vec<_> = updated_token_transactions .iter() .rev() - .filter(|tx| tx.tx_global_index < global_idx) + .skip(offset as usize) .take(len as usize) .cloned() .collect(); @@ -1082,20 +1078,20 @@ where } let len = rng.random_range(0..5); - let global_idx = rng.random_range(5..=10); - let token_txs = - db_tx.get_token_transactions(random_token_id, len, global_idx).await.unwrap(); - eprintln!("getting len: {len} < idx {global_idx}"); + let offset = rng.random_range(0..=5); + let token_txs = db_tx.get_token_transactions(random_token_id, len, offset).await.unwrap(); + eprintln!("getting len: {len}, offset {offset}"); let expected_token_txs: Vec<_> = token_transactions .iter() .rev() - .filter_map(|(idx, tx_id, _)| { + .map(|(idx, tx_id, _)| { let idx = *idx; - ((idx) < global_idx).then_some(TokenTransaction { + TokenTransaction { tx_global_index: idx, tx_id: *tx_id, - }) + } }) + .skip(offset as usize) .take(len as usize) .collect(); @@ -2224,6 +2220,85 @@ fn random_order( (order_id, order) } +/// The offset passed to `get_token_transactions` skips over the newest entries of the token's +/// transaction list, matching the skip-N semantics of the offset parameter the HTTP API passes: +/// offset 0 starts at the newest transaction, and an offset at or past the end of the list +/// yields an empty page. +pub async fn token_transactions_offset_paging( + storage_maker: Arc, + seed_maker: Box Seed + Send>, +) -> Result<(), Failed> +where + S: ApiServerStorage, + Fut: Future + Send + 'static, + F: Fn() -> Fut, +{ + let mut rng = make_seedable_rng(seed_maker()); + let token_id = TokenId::random_using(&mut rng); + let mut storage = storage_maker().await; + { + let mut tx = storage.transaction_rw().await.unwrap(); + let chain_config = create_unit_test_config(); + tx.reinitialize_storage(&chain_config).await.unwrap(); + tx.commit().await.unwrap(); + } + + // 10 transactions for the token, with continuous tx_global_index values 0..10. + let token_transactions: Vec<_> = (0..10) + .map(|idx| { + let random_tx_id = Id::::random_using(&mut rng); + TokenTransaction { + tx_global_index: idx, + tx_id: random_tx_id, + } + }) + .collect(); + + let mut tx = storage.transaction_rw().await.unwrap(); + for txn in &token_transactions { + tx.set_token_transaction_at_height( + token_id, + txn.tx_id, + BlockHeight::new(txn.tx_global_index), + txn.tx_global_index, + ) + .await + .unwrap(); + } + + let newest_first: Vec<_> = token_transactions.iter().rev().cloned().collect(); + + // Default paging (offset 0) returns the newest transactions first. + assert_eq!( + tx.get_token_transactions(token_id, 10, 0).await.unwrap(), + newest_first + ); + + // The page length is respected. + assert_eq!( + tx.get_token_transactions(token_id, 2, 0).await.unwrap(), + newest_first[..2] + ); + + // The offset skips over the newest entries. + assert_eq!( + tx.get_token_transactions(token_id, 4, 3).await.unwrap(), + &newest_first[3..7] + ); + + // An offset at or past the end of the list yields an empty page, however large. + assert!(tx.get_token_transactions(token_id, 10, 10).await.unwrap().is_empty()); + assert!(tx.get_token_transactions(token_id, 10, 12345).await.unwrap().is_empty()); + assert!(tx.get_token_transactions(token_id, 10, u64::MAX).await.unwrap().is_empty()); + // An offset that fits a 64-bit usize but not a 32-bit one must not wrap to a small value + // (which would return the first page) on narrow-pointer targets. + assert!(tx.get_token_transactions(token_id, 10, 1 << 35).await.unwrap().is_empty()); + + tx.commit().await.unwrap(); + + Ok(()) +} + pub fn build_tests Fut + Send + Sync + 'static>( storage_maker: Arc, ) -> impl Iterator @@ -2234,6 +2309,7 @@ where vec![ make_test!(initialization, storage_maker.clone()), make_test!(set_get, storage_maker.clone()), + make_test!(token_transactions_offset_paging, storage_maker.clone()), make_test!(stream_events_append_and_read, storage_maker), ] .into_iter() diff --git a/build-tools/win/nsi/common.nsh b/build-tools/win/nsi/common.nsh index caffd5614b..000308e07c 100644 --- a/build-tools/win/nsi/common.nsh +++ b/build-tools/win/nsi/common.nsh @@ -3,6 +3,22 @@ ; the template directory to makensis via /I so the bare name resolves. ; Pure ASCII only (the generator writes the rendered script as ASCII). +; ${RunningX64} for the MintlayerRequireX64 macro below. +!include "x64.nsh" + +; Refuse to run on 32-bit Windows and switch registry operations to the +; 64-bit view, so HKLM\SOFTWARE entries (uninstall keys, PATH backup) land in +; the native hive instead of the WOW6432Node redirect. Must be inserted from +; both .onInit (installer) and un.onInit (uninstaller): the uninstaller is a +; separate process and does not inherit the installer's registry view. +!macro MintlayerRequireX64 + ${IfNot} ${RunningX64} + MessageBox MB_OK|MB_ICONSTOP "${APPNAME} requires a 64-bit version of Windows." + Abort + ${EndIf} + SetRegView 64 +!macroend + ; Silently remove a previously installed version of the product whose ; uninstall registry entries live under ${UNINSTKEY}. The stored ; UninstallString includes quotes, which must not be re-quoted, so they are diff --git a/build-tools/win/nsi/gui.nsi.in b/build-tools/win/nsi/gui.nsi.in index 4a28ca0c14..9468fb5208 100644 --- a/build-tools/win/nsi/gui.nsi.in +++ b/build-tools/win/nsi/gui.nsi.in @@ -2,6 +2,9 @@ ; Rendered from gui.nsi.in by build-tools/win/create-nsis-installers.ps1 - ; do not use this file directly. Requires NSIS 3 (Unicode). Unicode true +; 64-bit only: .onInit/un.onInit abort on 32-bit Windows and switch the +; registry view to 64-bit, keeping HKLM\SOFTWARE writes out of the WOW6432Node +; redirect (see MintlayerRequireX64 in common.nsh). RequestExecutionLevel admin SetCompressor /SOLID lzma @@ -38,9 +41,15 @@ OutFile "Mintlayer_Node_GUI_win_${VERSION}_Setup.exe" ; Silently remove a previously installed version so upgrades do not leave ; stale files behind. Function .onInit + !insertmacro MintlayerRequireX64 !insertmacro MintlayerRemovePreviousInstall FunctionEnd +; The uninstaller is a separate process; give it the 64-bit registry view too. +Function un.onInit + !insertmacro MintlayerRequireX64 +FunctionEnd + ; The install is machine-wide (admin); make the shortcuts available to all ; users ($DESKTOP/$SMPROGRAMS resolve to the common profile directories). Section "Install" diff --git a/build-tools/win/nsi/node.nsi.in b/build-tools/win/nsi/node.nsi.in index e95b0e0ac9..b719d899b2 100644 --- a/build-tools/win/nsi/node.nsi.in +++ b/build-tools/win/nsi/node.nsi.in @@ -2,6 +2,9 @@ ; Rendered from node.nsi.in by build-tools/win/create-nsis-installers.ps1 - ; do not use this file directly. Requires NSIS 3 (Unicode). Unicode true +; 64-bit only: .onInit/un.onInit abort on 32-bit Windows and switch the +; registry view to 64-bit, keeping HKLM\SOFTWARE writes out of the WOW6432Node +; redirect (see MintlayerRequireX64 in common.nsh). RequestExecutionLevel admin SetCompressor /SOLID lzma @@ -54,9 +57,15 @@ ${UnStrLoc} ; Silently remove a previously installed version so upgrades behave like ; fresh installs (the old uninstaller cleans PATH/service state). Function .onInit + !insertmacro MintlayerRequireX64 !insertmacro MintlayerRemovePreviousInstall FunctionEnd +; The uninstaller is a separate process; give it the 64-bit registry view too. +Function un.onInit + !insertmacro MintlayerRequireX64 +FunctionEnd + ; --------------------------------------------------------------------------- ; Appends $INSTDIR to the machine PATH (idempotent); writes the registry ; directly and notifies running applications so new shells pick the change up. diff --git a/common/src/chain/config/checkpoints_data/testnet.rs b/common/src/chain/config/checkpoints_data/testnet.rs index 0d678d2111..9c70326227 100644 --- a/common/src/chain/config/checkpoints_data/testnet.rs +++ b/common/src/chain/config/checkpoints_data/testnet.rs @@ -1417,4 +1417,113 @@ pub const CHECKPOINTS_DATA: &[(u64, &str)] = &[ (763000, "6D29F9126BEC5B81BF1C9510149A2CE0692FA285F05254E703880CC82DCA6268"), (763500, "1F47C2EBC0A5FC44F1D868EDA6C3B45FE1C6AE36BB55C22235112FDA9A475BCF"), (764000, "E265BC8300181C341E054145CD1ED5A8DA01F861B6670745486C20209BCFA208"), + (764500, "C06C89F6D9ADCC2947CC7400D2811F347BAA69290D84D1290686F907A43E5DC2"), + (765000, "EC4A065CC7DA808F008CBE2FC5EDD6E53AD31B24C3A670DEFFF0065DF282C9AE"), + (765500, "D5EB5D5ED254A8E7BBFF622CCE3541FD715F27D02610D2BE8FE1E47A7220D75C"), + (766000, "155D71F838F875543AA13EBC1A813D3A2EC8F8970B133919C66762BD5E19F7BF"), + (766500, "0C82EF218D03360322153ADCE8F61EA258709B87BCA9AE5C21EFFA9DFA35CA79"), + (767000, "94DAEDED078F315E42A2B09BD88E0DFB4103986F5C76C8AEEA33D2193C5D2561"), + (767500, "5E25D6958DB31CB111A27E67D73D347C4E588C892D8D668FD73D6AEEB61F93C5"), + (768000, "1E491BAFAD374136FD7045789C21F64B8C82021E357ABBC390D5864471EEBBB2"), + (768500, "F6EE81712637FBA033C8C5E1319E60E2AFA00C3085558BC85614A210414A90FF"), + (769000, "EE2C5F7ECBC9245D06A2FDBF609B527384933D72A714F015691005B5DCEFB56F"), + (769500, "66C368D20B5C6AD3DC650D1546845C9F982C01CCB8956C2C316DBBCDCFEAAA89"), + (770000, "129F6109FCCF34C7589B119B380BE0082FE16B4A03879F00DD0CA6F8FB56E528"), + (770500, "AA3A5B01F579EE5B412B8228B28CF0D0E52C6F77BD36DE61FAAC833306077468"), + (771000, "6117435C1F4D172447AD471C140EE13068268219EA563B258E39A47BF686044B"), + (771500, "5C27E436724B1226F7B99FB2906549C718293CF758C2BBE11848255F5579BE23"), + (772000, "86308FB0F927889DB0F0A53E7041A1FDC03EB70B13A6E9820E870BE9F556590A"), + (772500, "61F1091E68B160438885DE537F26B3EDD2D6DA843173AFF28AA04761EA535332"), + (773000, "A8B6CDBE8094382CA3919B1589AA1A1B32763F0C6038B7157F8C08E0ABFAC692"), + (773500, "D91C7621301B44F62709EF43C10A22FF2D85B29396174E926589212CA6085534"), + (774000, "4D72329E9E79934EEA114F7E353DE202B1398411609E619B5CD82C7C3C621A47"), + (774500, "F1E6F184BCDD193BA91A02E87AB65ABE01451A83935BEE80E8C5561F5EC28280"), + (775000, "E97CCC6AEF3B959E4540A455D44AD4DA336260E60E3E1EDA87B3E6E543535CFA"), + (775500, "B2BE44DFB66444F666297A9E3F1BB76CA7C333B76567C3AFBF161BA22636516C"), + (776000, "487DF31EBC555AD305255B667CF4CD2610BCB934AD4B5E43B0EB23AA915B0E42"), + (776500, "4C98453952482C36943F6D6E253D79EA4A0C07FD58AF988D1C76D5212CE34ECC"), + (777000, "4454F8176C91B45A67E31FF2D49047B8D1A5098A90E64C3CA2431BD1499134B4"), + (777500, "68D370F2A0CBDF4E4B067855211DFEC128B495B324E9253DAEE651EDBF07BB91"), + (778000, "4DA53A2048B4F13890C669DAF962580165BF7C1E38147E183A4CFDC3765E25F7"), + (778500, "54718E5DF4A8930724B3FBFBFE62B85C423429DCD09C151489673602200E364C"), + (779000, "222A50E645284908EDE4AFBAE649D4B6623FF8BF67FBA7A20A4944C7A3013F13"), + (779500, "C54F476623A56E315FE7E092A66F924A3F09AACCDCC0B242DFE200684FE52899"), + (780000, "583C58FABB0B38238FC0E23B3982DFBBA130A2020F75F8E3D97880BA41D53C79"), + (780500, "44358E4A5B33D6EA5C1D0E9D2A40B52751F952234A9F9038D936E2FA24E85534"), + (781000, "3B9C31D5D8E31B45CAA06AB26F300F4DBAA2D22B5BF21E62BEF76BEF432F4817"), + (781500, "FA6BC4BC08997E39AA19D7A467E95D4DD8B9465E799488671782B682941E7894"), + (782000, "5B77C93D914F16DB92CABD90417CA0947C021E4FA3F13CF69C6EABD2E2DAF8C2"), + (782500, "9FDEED38239C7ADD62C17765F103B3D22A4A42E78385A47C13EB7A1669206E5A"), + (783000, "29FFDE698D9F05F5E23D2E94C0F9886A48D64A4675907F0966D525DADB945926"), + (783500, "1637910ADE9B4FE22E1C94CD3E7F5E50B131A610E00012BDE3303887C191903A"), + (784000, "A68D25D59CA6351656E4607D73DEB663CDD001E57B57164628858A5BCB8466F9"), + (784500, "34E32D752207332A7F86DA80D92D6FBADB44F022D98CF8C2AE5B8E6CCB11E774"), + (785000, "35ED5413DAEDC850564BEC623923507A34EED230DFC329D192CF6DCA325E1AF0"), + (785500, "D3E95FC61DBD63E592FCC054D96F5C636AFD76F0B1C3E4E96B402941C7164A61"), + (786000, "107687776D97AEB451E29189F73467F21986D62350EAB979E57DA8AAC3ED4AEF"), + (786500, "A8195ED02179973439F429EEB980C1A2C3D1CA103AD1D467145FD43E6F2E1E18"), + (787000, "ACE92D00D0BD9E8C849F223C6E67D0AF196B9C43CB3071CCBCB554FFBEB89A36"), + (787500, "799028848FCA67DB6AD712A4D449583458C61D60155AE9B7FDC53B5920FD6D4F"), + (788000, "6730042047F70C57A9B9B7A8DB78BCAC92DB0342D0870C62FD5F547DD6D67B8B"), + (788500, "1AEAE643E0C9A0E52DE2C60C806142910B08237FB0B0693763DB81BA6DC2DA29"), + (789000, "EF27B4FA164EEC1A94BDD949B0734686B8B41E899EBADD34672072389B4F2E71"), + (789500, "97571A70B06C6F7C53D2DCCC4A7B418461E8F1AD92CDA2C1BD3328FF8EA93CEA"), + (790000, "6A2D1FAB7C7EB6F69C894CC357215B2F66AE75C2DEB0360D6D2F85C0BEA4C4EC"), + (790500, "1563C67973A0BA7A2CEC0E853F241B5051EB1FF52D562D18C981B0CC0FC3C678"), + (791000, "5724362BE438D2F7C97B2961F46CF6E60C82CBCF5B3E92A457BB3733D98C9458"), + (791500, "99A74BE8F881E1564283DC4B92D60CDCC7AEDE47D81500076E764F82639239A7"), + (792000, "B04ECBA8DF439163074E73D5C6311EC89A5FBDECFDCAC46544EAED9EB1C84850"), + (792500, "2713F9613C487BCCBB0D41C396F8C4FD1D423743D607D77E241C05ED2BFBEA90"), + (793000, "E95CE47BCEE3DB408FB9CEECE55C84C0A8FF436EAA116858AD8BCCD85B5B14AE"), + (793500, "4019B3E6B1C9148FE8234A6476DE78105E259E98DB0AE8DBE108BD3774967B65"), + (794000, "568701C54571D2353506DBEF32DAA91CB0E89F05F7BDB376462EE21ABCEB5428"), + (794500, "3C2FBFF8F945BE838B7C2C4C2199E1A166BD180EB8A67633C71DE71ED05040BE"), + (795000, "917FCEC2733F96D80BD8F31729084FF46F030918B10340A647360EC2369A973C"), + (795500, "2ABF40F679A0DFEED123F8C31076D118C0671111828453FAFC15D3BD2F8A3FB3"), + (796000, "1AD7B09B626613DB6CEC12D20665A56C8014B06E9DA149E98D5F89B22FE8B835"), + (796500, "000052390AD62861B9242925EF351FB0D39A5143D2A689BA2AD040A12F396DED"), + (797000, "AB6FC2467A3ADFA7085B44ED4C3E88A68AC866C682F5AA06856274F41F8A1432"), + (797500, "87665347D7DC8D0A5AEDCECE5C442D800D99C459CAED96AE7042CECE84249D09"), + (798000, "2EC99179F21C4E7F36482A544503926AFE4BE0AA25B233AE7ACC32334F5A2592"), + (798500, "380C91247E54268D0992BBDB4E39F324B5A3795EA5B1E37E219A3154B794B1C8"), + (799000, "D51B16CFD40F5F5867CA8FDB4B83CAFF1B5FC76803FD5CECF74F73246D0DF442"), + (799500, "BA267B83E1D679428D74A5DE2B0C1DA7A01E9A76D3485D9107392A12AB12F437"), + (800000, "0778E119E6C06AEDA1B1D65CA3970160D729A943B461E556D3F7ED63A42E23EE"), + (800500, "367342B65B613122B90F186337DD52FE4128A8AE1C761C5DED4C86E736E5F874"), + (801000, "2E232B650D512A52F12E8A5D2656F0B7DB1FB52DEFDDDE2007667C58336676C6"), + (801500, "2806103311335615C358FFC72047AC26F187C2A2DEDA1CC5BDB864E138FC6973"), + (802000, "BB42010EFA33D68578D6B7D75ACA4F00A5AFDF514450F0E1391A462BE0FAEF79"), + (802500, "8957AB2727C5ECC8492C19E28723530E4DF30A9D3B932205D5B5219F113C8812"), + (803000, "0C73D66225CA235DB9A99C86A3FA800D4848DEB30BA6ED85F4DFB233D29C5FAF"), + (803500, "5A589ED0523BC6A690BC04C5834ED5F69939539BE6AA55712F9F964B84684C44"), + (804000, "3F39800AC338EB2226026026A40F578C0B67248181DDA364DD758BB04404E43E"), + (804500, "3EAAFEC994442F29C74A36D2C280CE1ED7AE455E8B1D7B1429BF5C664420F4AE"), + (805000, "6D3CB20BB30D3B85DBABFF44DB7A0B3A0AD3E170C18F1AA8A5C4CC3BFC7A236A"), + (805500, "16B4796E969B512B5EDF9590AFE5D6AFD356D30751C15CB30F14917A58ACD1B1"), + (806000, "EB7B66E6980C85784A57206FD3A7B97699282C793FC00B9757F0AD7778647A98"), + (806500, "9E70E4700A6B5EE7FCB7339D1AB5E5A859F9BCCB25789BA28A88F26A9EB25848"), + (807000, "541F72222125DE81FC55AA52B60025FBC7537CA35620F5DA70D9BBEDE0184500"), + (807500, "16871BC3AB4385218F976C84E21B00F9752225F7BEDEE660B03A52AEE806DB61"), + (808000, "E4D1C69A8B7A0A54526E32CD98B9F0FBFC76C6CEA8E32262F3F0E0A6CA1E1CE5"), + (808500, "91F34D2FBCA84E6410B6C85BF01C46F0B63D60C8F024C6DE4B238776125FC637"), + (809000, "827C00F2CA4E7498C4A2ACFECEC1BFB86B22C55ADA0D6F76CF60B2BF26330B93"), + (809500, "C570717AF360B7BE560C878F78E9DB914A794086D7C3BE08505A14E895FD9E27"), + (810000, "08FF628410B79339CBCA4E9D9ADC1B2D0A9A35A74C87EEF9AC17FD6BFB93B7A3"), + (810500, "00C0AC589A26C7D3A972146BD69914AE452AE3178375D3BC6FF3461A55C7BB04"), + (811000, "F1486DBCC0F22E85069C5A2B1064ECDF8E2245332989BBB8D896DC9739F733CF"), + (811500, "E94682221F1C446D637B0F2DA953159576B063C5E92C29EEB83AEE90B4E598DB"), + (812000, "B341BC659B22F23D596A6DC9F0AB94E3251DD79FB8C356ABDA08C12796177C42"), + (812500, "F9C190227D4089D5A1687A4FA7B73A8103FAFF38877C132778B16E9C47749E7C"), + (813000, "232EA974132E5A9C0D189AA0E205B05F8E93314C8CD956E69506E9FEEE946DEB"), + (813500, "587A25A7ACFA5323BEFE56128B9188F57D8D481002BD6920F1DE48B4C2F83890"), + (814000, "9433E0C86EE1171DBA4190F085E55783201CF59BEA914AA92414F9EA8455A439"), + (814500, "76DC25872F49A350DE65621C611FBD5F3E545791EC2130F5E3C024098CD814B0"), + (815000, "F35F36AE6E7732D67EAF97AEDF571A83630CC586C4E4814512790853F84AB5FC"), + (815500, "2B96E22C996290208C497480700DB0AA03419C2AEC8915631AE4A39F10951438"), + (816000, "5E82393262331057150A9C25035735824817A47617CF08A766A8F12DCEFE80B3"), + (816500, "7B3E887ED47946824B41BA976F674671198E4E6F5FCF01175E6C2B37F89FF2DF"), + (817000, "2318F13FFE92375D0ACFF490D2581B3B5980639DE399572B806B6C2C2E01D23F"), + (817500, "41A8BCFFE3FB0CE535CAF82535D4C35CA3BB1736D2332195BF8DE9F6C6FC2BDF"), + (818000, "1E28C864FC80211C7394E674C2A9EE2FA76E0C0ED8C28EB77581023E0DEC94FB"), + (818500, "9E045BBC3ABC38039A41EB59587633632B1FD4779040F9ED94C36E5A2F9D45F7"), ]; diff --git a/node-daemon/docs/RPC.md b/node-daemon/docs/RPC.md index e59611bc9c..b261a731cd 100644 --- a/node-daemon/docs/RPC.md +++ b/node-daemon/docs/RPC.md @@ -1,6 +1,6 @@ # RPC documentation for Mintlayer node -Version `1.4.0`. +Version `1.4.1`. ## Module `node` diff --git a/node-daemon/docs/RPC_DEV.md b/node-daemon/docs/RPC_DEV.md index fd8e3413bc..0a289cb699 100644 --- a/node-daemon/docs/RPC_DEV.md +++ b/node-daemon/docs/RPC_DEV.md @@ -1,6 +1,6 @@ # RPC documentation for Mintlayer node developer functions -Version `1.4.0`. +Version `1.4.1`. These functions are used for testing and only enabled in regtest. diff --git a/packaging/checks/smoke-arch.sh b/packaging/checks/smoke-arch.sh index 665a8be582..33dd242f5b 100755 --- a/packaging/checks/smoke-arch.sh +++ b/packaging/checks/smoke-arch.sh @@ -4,83 +4,171 @@ # Usage: smoke-arch.sh node|gui [pkg-arch] # # The optional fourth argument is the architecture the package was built for -# (defaults to the container architecture). For a foreign-architecture package -# (the cross-target aarch64 leg reuses the amd64-only Arch image), pacman's -# architecture check is bypassed via IgnoreArch and the binaries are executed -# through the host's qemu binfmt handlers — the same emulation the deb/rpm -# legs use for their arm64 smoke tests. +# (defaults to the container architecture). Docker Hub publishes no arm64 +# Arch image (library/archlinux and archlinux/archlinux are amd64-only), so a +# foreign-architecture package (the cross-target aarch64 leg reuses the +# amd64-only image) is smoke-tested in a chroot of the Arch Linux ARM rootfs: +# the package and its dependencies are installed by the chroot's pacman and +# its binaries execute through the host's qemu binfmt handlers — the same +# emulation the deb/rpm legs use for their arm64 smoke tests. set -euo pipefail -# Shared binary list (NODE_BINARIES) -. "$(cd "$(dirname "${BASH_SOURCE[0]}")/../common" && pwd)/lib.sh" +CHROOT_DIR=/alarm +# Primary + fallback download locations for the Arch Linux ARM rootfs; the +# env var overrides for local testing (e.g. a pre-downloaded file:// copy). +alarm_rootfs_urls() { + if [ -n "${ALARM_ROOTFS_URL:-}" ]; then + printf '%s\n' "$ALARM_ROOTFS_URL" + else + printf '%s\n' \ + https://fl.us.mirror.archlinuxarm.org/os/ArchLinuxARM-aarch64-latest.tar.gz \ + https://mirror.archlinuxarm.org/os/ArchLinuxARM-aarch64-latest.tar.gz + fi +} + +# Shared binary list (NODE_BINARIES). Inside the chroot the repo tree is not +# visible; the foreign-arch branch copies lib.sh in next to the script. +if [ "${1:-}" = "--chroot" ]; then + . /smoke-lib.sh +else + . "$(cd "$(dirname "${BASH_SOURCE[0]}")/../common" && pwd)/lib.sh" +fi + +# Inside the ARM chroot pacman must not drop to its download sandbox user: +# the (emulated) setuid switch fails under the container's seccomp profile. +pacman_cmd() { + if [ -n "${SMOKE_CHROOT:-}" ]; then + pacman --disable-sandbox "$@" + else + pacman "$@" + fi +} + +main() { + # Sync the databases so the package dependencies resolve from the repos. + pacman_cmd -Sy --noconfirm >/dev/null + pacman_cmd -U --noconfirm "$PKG_FILE" >/dev/null + + echo "== pacman query ==" + pacman_cmd -Qi "$PKG_NAME" + + if [ "$KIND" = node ]; then + echo "== system user ==" + id mintlayer + + echo "== binaries ==" + for bin in "${NODE_BINARIES[@]}"; do + test -x "/usr/bin/mintlayer-$bin" + "/usr/bin/mintlayer-$bin" --help >/dev/null 2>&1 + echo " ok: mintlayer-$bin --help" + done + + echo "== systemd units ==" + shopt -s nullglob + units=(/usr/lib/systemd/system/mintlayer-*.service) + shopt -u nullglob + [ ${#units[@]} -gt 0 ] || { echo "ERROR: no mintlayer units installed" >&2; exit 1; } + for unit in "${units[@]}"; do + systemd-analyze verify "$unit" + echo " ok: $(basename "$unit")" + done + test -f /usr/lib/systemd/system-preset/90-mintlayer.preset + grep -q "enable mintlayer-node@mainnet.service" \ + /usr/lib/systemd/system-preset/90-mintlayer.preset + test -f /usr/lib/sysusers.d/mintlayer.conf + test -f /usr/lib/udev/rules.d/51-mintlayer.rules + test -f /etc/mintlayer/mainnet/node.env + test -f /etc/mintlayer/testnet/node.env + echo " ok: preset/sysusers/udev/conffiles present" + + echo "== man pages ==" + pacman_cmd -Ql "$PKG_NAME" | grep -q "usr/share/man/man1/mintlayer-.*\.1\.gz$" + echo " ok" + else + echo "== gui files ==" + test -x /usr/bin/mintlayer-node-gui + "/usr/bin/mintlayer-node-gui" --help >/dev/null 2>&1 + echo " ok: mintlayer-node-gui --help" + + pacman_cmd -S --noconfirm --needed desktop-file-utils >/dev/null + desktop-file-validate /usr/share/applications/mintlayer-node-gui.desktop + + echo "== icons ==" + for size in 64x64 128x128 256x256 512x512; do + test -f "/usr/share/icons/hicolor/${size}/apps/mintlayer-node-gui.png" + echo " ok: hicolor ${size}" + done + + echo "== man pages ==" + pacman_cmd -Ql "$PKG_NAME" | grep -q "usr/share/man/man1/mintlayer-node-gui\.1\.gz$" + echo " ok" + fi + + echo "smoke test passed for $PKG_NAME" +} + +# Re-entry mode: the foreign-arch branch below invokes this script inside the +# ARM chroot with `--chroot node|gui`. +if [ "${1:-}" = "--chroot" ]; then + shift + SMOKE_CHROOT=1 + PKG_FILE="$1" + PKG_NAME="$2" + KIND="$3" + main + exit 0 +fi PKG_FILE="$(readlink -f "$1")" PKG_NAME="$2" KIND="$3" PKG_ARCH="${4:-$(uname -m)}" -if [ "$PKG_ARCH" != "$(uname -m)" ]; then - echo "foreign-architecture package ($PKG_ARCH on $(uname -m)): enabling IgnoreArch" - sed -i "/^\[options\]$/a IgnoreArch = $PKG_ARCH" /etc/pacman.conf +if [ "$PKG_ARCH" = "$(uname -m)" ]; then + main + exit 0 fi -# Sync the databases so the package dependencies resolve from the repos. -pacman -Sy --noconfirm >/dev/null -pacman -U --noconfirm "$PKG_FILE" >/dev/null - -echo "== pacman query ==" -pacman -Qi "$PKG_NAME" - -if [ "$KIND" = node ]; then - echo "== system user ==" - id mintlayer - - echo "== binaries ==" - for bin in "${NODE_BINARIES[@]}"; do - test -x "/usr/bin/mintlayer-$bin" - "/usr/bin/mintlayer-$bin" --help >/dev/null 2>&1 - echo " ok: mintlayer-$bin --help" - done - - echo "== systemd units ==" - shopt -s nullglob - units=(/usr/lib/systemd/system/mintlayer-*.service) - shopt -u nullglob - [ ${#units[@]} -gt 0 ] || { echo "ERROR: no mintlayer units installed" >&2; exit 1; } - for unit in "${units[@]}"; do - systemd-analyze verify "$unit" - echo " ok: $(basename "$unit")" - done - test -f /usr/lib/systemd/system-preset/90-mintlayer.preset - grep -q "enable mintlayer-node@mainnet.service" \ - /usr/lib/systemd/system-preset/90-mintlayer.preset - test -f /usr/lib/sysusers.d/mintlayer.conf - test -f /usr/lib/udev/rules.d/51-mintlayer.rules - test -f /etc/mintlayer/mainnet/node.env - test -f /etc/mintlayer/testnet/node.env - echo " ok: preset/sysusers/udev/conffiles present" - - echo "== man pages ==" - pacman -Ql "$PKG_NAME" | grep -q "usr/share/man/man1/mintlayer-.*\.1\.gz$" - echo " ok" -else - echo "== gui files ==" - test -x /usr/bin/mintlayer-node-gui - /usr/bin/mintlayer-node-gui --help >/dev/null 2>&1 - echo " ok: mintlayer-node-gui --help" - - pacman -S --noconfirm --needed desktop-file-utils >/dev/null - desktop-file-validate /usr/share/applications/mintlayer-node-gui.desktop - - echo "== icons ==" - for size in 64x64 128x128 256x256 512x512; do - test -f "/usr/share/icons/hicolor/${size}/apps/mintlayer-node-gui.png" - echo " ok: hicolor ${size}" - done - - echo "== man pages ==" - pacman -Ql "$PKG_NAME" | grep -q "usr/share/man/man1/mintlayer-node-gui\.1\.gz$" - echo " ok" -fi +echo "foreign-architecture package ($PKG_ARCH on $(uname -m)): smoke-testing in an Arch Linux ARM chroot" + +# Assemble a native $PKG_ARCH userland. Requires qemu binfmt handlers for the +# architecture to be registered on the runner's kernel (release_linux.yml +# sets them up via docker/setup-qemu-action; the F-flag keeps them working +# inside the chroot). +mkdir -p "$CHROOT_DIR" +rootfs_tar=/tmp/alarm.tar.gz +rc=1 +for url in "$(alarm_rootfs_urls)"; do + echo "downloading ARM rootfs from $url" + if curl --proto '=https' --tlsv1.2 -sSfL -o "$rootfs_tar" "$url"; then rc=0; break; fi +done +[ "$rc" -eq 0 ] || { echo "ERROR: could not download the Arch Linux ARM rootfs" >&2; exit 1; } +# The rootfs ships device nodes we cannot recreate inside an unprivileged +# container; skip them. Remaining tar warnings are non-fatal. +tar -xzf "$rootfs_tar" -C "$CHROOT_DIR" --exclude='./dev/*' 2>/dev/null || true + +# chroot plumbing the rootfs does not ship: DNS resolution and a mount table +# (pacman reads /etc/mtab for its free-space check; /proc is not mounted here, +# so a static copy of the container's table is used). +rm -f "$CHROOT_DIR/etc/resolv.conf" +cp -L /etc/resolv.conf "$CHROOT_DIR/etc/resolv.conf" +rm -f "$CHROOT_DIR/etc/mtab" +cp /proc/self/mounts "$CHROOT_DIR/etc/mtab" + +# Initialize the chroot's pacman keyring from the container: gpg inside the +# chroot would run under emulation, and pacman-key's --populate needs +# process substitution (/dev/fd), which requires a mounted /proc the chroot +# does not have. The host container's pacman-key can act on the chroot's +# keyring via --gpgdir once the archlinuxarm keyring is visible to it. +cp "$CHROOT_DIR/usr/share/pacman/keyrings/archlinuxarm.gpg" /usr/share/pacman/keyrings/ +pacman-key --gpgdir "$CHROOT_DIR/etc/pacman.d/gnupg" --init >/dev/null +pacman-key --gpgdir "$CHROOT_DIR/etc/pacman.d/gnupg" --populate archlinuxarm >/dev/null + +# /work is not visible inside the chroot: copy the script, its lib.sh and the +# package in. +cp "$PKG_FILE" "$CHROOT_DIR/$(basename "$PKG_FILE")" +cp "$0" "$CHROOT_DIR/smoke-arch.sh" +cp "$(cd "$(dirname "$0")/../common" && pwd)/lib.sh" "$CHROOT_DIR/smoke-lib.sh" -echo "smoke test passed for $PKG_NAME" +chroot "$CHROOT_DIR" /smoke-arch.sh --chroot "/$(basename "$PKG_FILE")" "$PKG_NAME" "$KIND" +rm -f "$CHROOT_DIR/smoke-arch.sh" "$CHROOT_DIR/smoke-lib.sh" "$CHROOT_DIR/$(basename "$PKG_FILE")" diff --git a/packaging/test-local.sh b/packaging/test-local.sh index b81651d040..b0128c8623 100755 --- a/packaging/test-local.sh +++ b/packaging/test-local.sh @@ -250,7 +250,7 @@ for arch in "${ARCHES[@]}"; do mintlayer-node-gui gui # The aarch64 leg relies on the qemu binfmt handlers (already required - # for the deb/rpm arm64 smoke tests) plus smoke-arch.sh's IgnoreArch + # for the deb/rpm arm64 smoke tests) plus smoke-arch.sh's foreign-arch # handling, mirroring the arm64 leg of release_linux.yml. run_step "smoke pkg node ($arch)" \ docker run --rm -v "$REPO_ROOT:/work" -w /work "$ARCH_IMAGE" \ diff --git a/wallet/wallet-rpc-daemon/docs/RPC.md b/wallet/wallet-rpc-daemon/docs/RPC.md index 0a90028b13..0cbba9daa9 100644 --- a/wallet/wallet-rpc-daemon/docs/RPC.md +++ b/wallet/wallet-rpc-daemon/docs/RPC.md @@ -1,6 +1,6 @@ # RPC documentation for Mintlayer node wallet -Version `1.4.0`. +Version `1.4.1`. ## Module `WalletRpc`