Repository navigation
v0.1.4-alpha
Pre-releasev0.1.4-alpha
Two targeted operator-reliability fixes on top of v0.1.3-alpha: downstream Antelope tooling compatibility for eosio-bootstrapped chains, and a sync-catchup CPU stall that could permanently wedge a node on slower hardware. No protocol or consensus changes.
Upgrade Notice
Recommended for all v0.1.3-alpha operators. Required if you are:
- Running Anvo Core on an eosio-bootstrapped chain (e.g., Libre testnet) and shipping SHiP data to downstream Antelope tooling (Hyperion state-history consumer, abieos, EOSIO SDKs).
- Syncing a fresh node from genesis on hardware that could hit producer-recorded CPU budgets under OC tier-up timing (modest VMs, contested hosts, the test-001 reproducer class).
Downstream tooling compatibility on eosio chains (#105, PR #106)
Anvo Core emitted core_net::abi/* unconditionally on the SHiP session-initial ABI and bundled system-contract ABI. Downstream Antelope tooling hard-codes the eosio::abi/ prefix in its version allowlist (abieos/src/abieos.hpp:469-473) and disconnected with unsupported abi version. Libre testnet operators worked around this by pointing Hyperion at a parallel Spring/Leap node that still emitted eosio::abi/* — technical debt every legacy chain adopting Anvo Core would otherwise need to carry.
SHiP session handshake (the Hyperion-breaking path). The state history plugin ABI was a compile-time extern const char* constant in libraries/state_history/abi.cpp:4 with a hardcoded "version": "core_net::abi/1.1". Every SHiP WebSocket session (session.hpp:139) wrote this constant to the client on connection open. Fix: a new core_net::state_history::session_wire_abi() function (abi.hpp / abi.cpp) returns a std::string_view that selects between two cached variants — the original core_net::abi/1.1 template and a lazily-constructed eosio::abi/1.1 version — based on the runtime config::system_account_name(). The session handshake writes from the returned view instead of the raw constant. Both variants are materialized once on first access and held as function-local statics.
Bundled system-contract ABI. core_net_contract_abi() in libraries/chain/system_contract_abi.cpp:24 assigned "core_net::abi/1.0" when the caller passed an empty version. Fix: the assignment now queries config::system_account_name() and selects "eosio::abi/1.0" for eosio-prefixed chains, "core_net::abi/1.0" otherwise.
Heritage signal. Both fixes key off config::system_account_name(), which returns the process-global system account name ("eosio"_n for legacy chains, "core"_n or other for fresh chains). This is set once at controller startup from genesis_state::system_account_prefix → global_property_object::system_account_prefix → config::set_system_accounts() (controller.cpp:2011,2042,2574). No new configuration — the same field already controls system account naming, reserved-prefix enforcement, and system contract bootstrapping.
ABI ingest continues to accept both prefixes unchanged (abi_serializer.cpp:133-136). After upgrading on a legacy chain, the Spring/Leap intermediary node can be retired.
Sync catchup no longer stalls on subjective CPU (#104, PR #107)
During P2P sync catchup, blocks received from peers were applied with block_status::complete, which keeps subjective per-account CPU validation active through validate_account_cpu_usage() (transaction_context.cpp:583). The gate is !control.skip_trx_checks(), which calls light_validation_allowed() (controller.cpp:5083). For complete status in validation_mode::FULL with no trusted-producers, neither the consider_skipping_on_replay branch (requires irreversible or validated) nor the consider_skipping_on_validate branch (requires LIGHT mode or trusted-producer) fires — subjective checks remain active.
On hardware slower than the original producer — or on any hardware during an unfortunate OC tier-up window mid-transaction — local execution could exceed the historical account's per-transaction CPU budget and throw tx_cpu_usage_exceeded. Combined with block_status_monitor_ (net_plugin.cpp:759), which closes the connection after 13 consecutive 2ms rejection windows, the repeated failure closed and reopened the peer connection against the same failing block forever. The block_status_monitor_ stall loop is permanent: restart doesn't help because the chain state replays the same failing block.
Reproducer. test-001 (Libre testnet, single peer, plaintext): permanent stall at block 64,298,514. The failing transaction (stake.libre::updatevp in block 64,288,958) billed locally at 11,122 µs during an OC tier-up interruption, against an account CPU limit of 47 µs — the producer's recorded elapsed for the same action was 1,736 µs. An encrypted run of the same binary, config, and peer completed cleanly to 234.8M blocks because the ~20% slower block arrival shifted the OC tier-up window out of the failing transaction's execution path.
Fix: deep-sync status promotion. In maybe_apply_blocks() (controller.cpp:4574), the block status selection was:
bsp->is_valid() ? validated : complete
The fix adds a third branch. net_plugin's sync_manager reports the highest peer-observed fork_db_root_num (network LIB) to the controller via a new set_best_known_peer_lib_num() setter — a monotonic std::atomic<uint32_t> with a CAS update loop that ignores non-increasing values, so a dropped or lying peer cannot rewind the high-water mark. maybe_apply_blocks() reads the atomic once per batch and promotes complete → validated when the block being applied is more than deep_sync_lib_margin_blocks (1000 blocks, ~8.3 minutes of chain time) behind the peer LIB:
bsp->is_valid() ? validated
: deep_sync ? validated
: complete
With validated status, light_validation_allowed()'s existing consider_skipping_on_replay branch fires and skip_trx_checks() returns true — matching the semantics of on-disk block-log replay. force_all_checks=true still overrides via the existing !conf.force_all_checks gate, preserving the forensic-replay escape hatch.
Peer-LIB wiring. net_plugin's four sync_known_fork_db_root_num assignment sites (handshake update, connection-close recalculation, failure-reset, start_sync target) are funneled through a new set_sync_known_fork_db_root_num() helper that both assigns the local field and forwards the value to controller::set_best_known_peer_lib_num().
What is preserved. Consensus-critical validation (block structure, merkle root, QC signatures under force_all_checks, protocol features) is unchanged. Only the subjective / wall-clock-dependent checks (per-account CPU budget, authorization) are bypassed — the same checks that are already skipped on replay from the on-disk block log.
Why v0.1.3-alpha didn't catch these
#105: The test suite exercises abi_serializer acceptance of both prefixes but never asserts on the emitted prefix. All unit tests use core_net_contract_abi() with the default ("core_net") config, and no SHiP integration test inspects the version field in the session-initial ABI JSON. The emission discrepancy is only visible when connecting a downstream tool that validates the version prefix — which happens in production (Hyperion on Libre testnet), not in the unit test environment.
#104: The subjective CPU validation path only triggers when local wall-clock execution exceeds the producer-recorded limit. The test suite runs on uniform hardware where local execution matches producer-recorded timings, so validate_account_cpu_usage() never fires. The OC tier-up timing race that causes the local/producer CPU divergence requires specific hardware contention conditions (modest VM, scheduling pressure) or a very large chain history where OC compilation of a contract occurs mid-transaction on a block with tight CPU margins. The permanent-stall behavior of block_status_monitor_ is visible only on live sync from genesis against real chain data, not in the test cluster's short-lived chains.
Added tests
#105:
unittests/system_accounts_tests.cpp: abi_version_prefix_follows_heritage— boots a legacy (eosio) tester and a fresh (core) tester; assertscore_net_contract_abi()emits"eosio::abi/1.0"vs"core_net::abi/1.0"andsession_wire_abi()emits"eosio::abi/1.1"vs"core_net::abi/1.1"respectively.
#104:
unittests/chain_tests.cpp: best_known_peer_lib_num_monotonic— exercises the controller setter across increasing, decreasing, equal, zero, andUINT32_MAXvalues; asserts monotonic (non-decreasing) behavior.
Verification
#105:
unit_test --run_test=system_accounts_tests— 13 tests, 0 failures (includes the new heritage test).unit_test --run_test=abi_tests— 49 tests, 0 failures (no regression in ABI parsing/serialization).unit_test --run_test=test_state_history— 43 tests, 0 failures (SHiP serialization unaffected).- Full CI (x86_64 + ARM64 builds, parallelizable + non-parallelizable test suites) — all green.
#104:
unit_test --run_test=chain_tests— 12 tests, 0 failures.unit_test --run_test=forked_tests— 8 tests, 0 failures (no regression in fork-switch block application).unit_test --run_test=restart_chain_tests— 8 tests, 0 failures.- Full CI (x86_64 + ARM64 builds, parallelizable + non-parallelizable test suites) — all green.
- Live verification of the test-001 reproducer (Libre testnet single-peer plaintext sync past block 64M) pending post-release deployment.
Install
Debian/Ubuntu packages
# x86_64
wget https://github.com/AnvoIO/core/releases/download/v0.1.4-alpha/anvo-core_0.1.4-alpha-ubuntu24.04_amd64.deb
sudo apt install ./anvo-core_0.1.4-alpha-ubuntu24.04_amd64.deb
# ARM64
wget https://github.com/AnvoIO/core/releases/download/v0.1.4-alpha/anvo-core_0.1.4-alpha-ubuntu24.04_arm64.deb
sudo apt install ./anvo-core_0.1.4-alpha-ubuntu24.04_arm64.debTarball
# x86_64
wget https://github.com/AnvoIO/core/releases/download/v0.1.4-alpha/anvo-core-0.1.4-alpha-ubuntu24.04-x86_64.tar.zst
# ARM64
wget https://github.com/AnvoIO/core/releases/download/v0.1.4-alpha/anvo-core-0.1.4-alpha-ubuntu24.04-aarch64.tar.zstCloses
- #104 — sync: subjective CPU checks fire on peer-received blocks that are already network-finalized
- #105 — SHiP + system-contract ABI emit
core_net::abi/*on eosio-bootstrapped chains, breaking downstream Antelope tooling
Full Changelog: v0.1.3-alpha...v0.1.4-alpha
What's Changed
- chain/ship: emit ABI version prefix matching chain heritage (fixes #105) by @rwcii in #106
- chain/net: bypass subjective CPU on deeply finalized blocks during sync (fixes #104) by @rwcii in #107
- Bump version to v0.1.4-alpha by @rwcii in #108
Full Changelog: v0.1.3-alpha...v0.1.4-alpha