From 2b93e32475e52e92761a8be18e9dff8c20aa964d Mon Sep 17 00:00:00 2001 From: Philip DiSarro Date: Wed, 7 Oct 2026 05:50:16 -0500 Subject: [PATCH 1/6] Integrate per-output Receiving in Dijkstra node --- CHANGELOG.md | 14 + bench/locli/CHANGELOG.md | 4 + bench/locli/locli.cabal | 4 +- .../plutus-scripts-bench.cabal | 10 +- bench/tx-generator/CHANGELOG.md | 4 + bench/tx-generator/tx-generator.cabal | 8 +- cabal.project | 104 +++++- .../20261006_000000_cip160_integration.yml | 5 + .../cardano-node-chairman.cabal | 6 +- cardano-node/ChangeLog.md | 11 + cardano-node/cardano-node.cabal | 23 +- .../Cardano/Node/Configuration/LedgerDB.hs | 1 + .../src/Cardano/Node/Configuration/POM.hs | 6 +- cardano-node/src/Cardano/Node/Orphans.hs | 2 +- .../src/Cardano/Node/Protocol/Dijkstra.hs | 16 +- cardano-node/src/Cardano/Node/Run.hs | 1 - .../src/Cardano/Node/Tracing/Consistency.hs | 9 +- .../src/Cardano/Node/Tracing/Documentation.hs | 16 + .../src/Cardano/Node/Tracing/Era/Shelley.hs | 119 +++++-- .../src/Cardano/Node/Tracing/Render.hs | 20 +- .../src/Cardano/Node/Tracing/Tracers.hs | 12 + .../Cardano/Node/Tracing/Tracers/ChainDB.hs | 76 ++++- .../Cardano/Node/Tracing/Tracers/Consensus.hs | 259 +++++++++++++-- .../src/Cardano/Node/Tracing/Tracers/Rpc.hs | 16 + .../Cardano/Node/Tracing/Tracers/Startup.hs | 7 - .../test/Test/Cardano/Config/Dijkstra.hs | 30 ++ .../test/Test/Cardano/Tracing/Receiving.hs | 278 ++++++++++++++++ cardano-node/test/cardano-node-test.hs | 11 +- .../20261006_000000_cip160_integration.yml | 5 + cardano-submit-api/cardano-submit-api.cabal | 10 +- .../20261006_000000_cip160_integration.yml | 5 + cardano-testnet/cardano-testnet.cabal | 21 +- cardano-testnet/src/Testnet/Defaults.hs | 5 +- .../Cardano/Testnet/Test/Golden/Config.hs | 45 +++ .../cardano-testnet-golden.hs | 3 +- .../files/golden/version_cmd.cli | 6 +- .../Cardano/Testnet/Test/Cli/Receiving.hs | 298 ++++++++++++++++++ .../Cardano/Testnet/Test/Rpc/SearchUtxos.hs | 9 +- .../Cardano/Testnet/Test/Rpc/Transaction.hs | 11 +- .../cardano-testnet-test.hs | 4 +- .../files/plutus/v4/README.md | 18 ++ .../plutus/v4/receiving-even-datum.plutus | 5 + cardano-tracer/CHANGELOG.md | 4 +- cardano-tracer/cardano-tracer.cabal | 6 +- flake.lock | 6 +- .../genesis/zero/genesis/dijkstra.json | 128 +++++++- trace-forward/CHANGELOG.md | 4 + trace-forward/trace-forward.cabal | 4 +- 48 files changed, 1520 insertions(+), 149 deletions(-) create mode 100644 cardano-node-chairman/.changes/20261006_000000_cip160_integration.yml create mode 100644 cardano-node/test/Test/Cardano/Config/Dijkstra.hs create mode 100644 cardano-node/test/Test/Cardano/Tracing/Receiving.hs create mode 100644 cardano-submit-api/.changes/20261006_000000_cip160_integration.yml create mode 100644 cardano-testnet/.changes/20261006_000000_cip160_integration.yml create mode 100644 cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Cli/Receiving.hs create mode 100644 cardano-testnet/test/cardano-testnet-test/files/plutus/v4/README.md create mode 100644 cardano-testnet/test/cardano-testnet-test/files/plutus/v4/receiving-even-datum.plutus diff --git a/CHANGELOG.md b/CHANGELOG.md index de050002e96..5bfc6560583 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ ## Changelog +### Unreleased Dijkstra integration (node 11.1.1.1 proposal) + +* Use complete API defaults for fallback Dijkstra genesis, including the V4 cost model. +* Render the Receiving script purpose in node traces. +* Align normal-project benchmark dependency bounds with API 11.9, CLI 11.3, + network/diffusion 1.3 and Plutus 1.71 (locli 2.4.0.1, tx-generator 2.18.0.1 + and plutus-scripts-bench 1.0.5.1 proposals). +* Refresh the package-index dates and verified CHaP Nix input to include the + published DMQ 0.7.2.0 consensus-5.1 metadata revision. +* Align Dijkstra testnet configuration with genesis protocol major 12, the local + protected-address activation proposal. Enable `ExperimentalHardForksEnabled` + only for requested Dijkstra testnets; earlier-era configurations remain unchanged. + Upstream agreement remains separate. + Changelogs for components can be found as follows: - [cardano-testnet](https://github.com/IntersectMBO/cardano-node/blob/master/cardano-testnet/CHANGELOG.md) diff --git a/bench/locli/CHANGELOG.md b/bench/locli/CHANGELOG.md index ab26857c4a8..6cdf5bd627a 100644 --- a/bench/locli/CHANGELOG.md +++ b/bench/locli/CHANGELOG.md @@ -1,3 +1,7 @@ +# Unreleased 2.4.0.1 + +- Update the explicit network API bound to 1.3 for the Dijkstra feature dependency graph. Compilation and runtime validation remain pending. + # Revision history for locli ## 2.4 -- Jul 2026 diff --git a/bench/locli/locli.cabal b/bench/locli/locli.cabal index 0dc2ca15045..743b8c6d7a1 100644 --- a/bench/locli/locli.cabal +++ b/bench/locli/locli.cabal @@ -1,7 +1,7 @@ cabal-version: 3.0 name: locli -version: 2.4 +version: 2.4.0.1 synopsis: Cardano log analysis CLI description: Cardano log analysis CLI. category: Cardano, @@ -128,7 +128,7 @@ library , hashable , optparse-applicative , ouroboros-consensus:ouroboros-consensus - , ouroboros-network:api ^>= 1.2 + , ouroboros-network:api ^>= 1.3 , sop-core , split , sqlite-easy >= 1.1.0.1 diff --git a/bench/plutus-scripts-bench/plutus-scripts-bench.cabal b/bench/plutus-scripts-bench/plutus-scripts-bench.cabal index 9d81a5b16a0..ec3fde90cb2 100644 --- a/bench/plutus-scripts-bench/plutus-scripts-bench.cabal +++ b/bench/plutus-scripts-bench/plutus-scripts-bench.cabal @@ -1,6 +1,6 @@ cabal-version: 3.0 name: plutus-scripts-bench -version: 1.0.5.0 +version: 1.0.5.1 synopsis: Plutus scripts used for benchmarking description: Plutus scripts used for benchmarking. category: Cardano, @@ -86,10 +86,10 @@ library -- IOG dependencies -------------------------- build-depends: - , cardano-api ^>=11.7 - , plutus-ledger-api ^>=1.65 - , plutus-tx ^>=1.65 - , plutus-tx-plugin ^>=1.65 + , cardano-api ^>=11.9 + , plutus-ledger-api ^>=1.71 + , plutus-tx ^>=1.71 + , plutus-tx-plugin ^>=1.71 ------------------------ -- Non-IOG dependencies diff --git a/bench/tx-generator/CHANGELOG.md b/bench/tx-generator/CHANGELOG.md index 26fa79322fd..af0e07e6ffd 100644 --- a/bench/tx-generator/CHANGELOG.md +++ b/bench/tx-generator/CHANGELOG.md @@ -1,3 +1,7 @@ +# Unreleased 2.18.0.1 + +- Update API, CLI and diffusion bounds for the Dijkstra Receiving source proposals. Compilation and runtime validation remain pending. + # ChangeLog ## 2.18 -- Sep 2026 diff --git a/bench/tx-generator/tx-generator.cabal b/bench/tx-generator/tx-generator.cabal index 875a0c799e3..f14a2975046 100644 --- a/bench/tx-generator/tx-generator.cabal +++ b/bench/tx-generator/tx-generator.cabal @@ -1,7 +1,7 @@ cabal-version: 3.0 name: tx-generator -version: 2.18 +version: 2.18.0.1 synopsis: A transaction workload generator for Cardano clusters description: A transaction workload generator for Cardano clusters. category: Cardano, @@ -116,13 +116,13 @@ library , attoparsec-aeson , base16-bytestring , bytestring - , cardano-api ^>= 11.7 + , cardano-api ^>= 11.9 , cardano-binary - , cardano-cli ^>= 11.2.2 + , cardano-cli ^>= 11.3 , cardano-crypto-class , cardano-crypto-wrapper , cardano-data - , cardano-diffusion ^>= 1.1.1 + , cardano-diffusion ^>= 1.3 , cardano-git-rev ^>= 0.2.2 , cardano-ledger-alonzo , cardano-ledger-api diff --git a/cabal.project b/cabal.project index 9259d05bdd4..eddeb4c9a05 100644 --- a/cabal.project +++ b/cabal.project @@ -13,10 +13,14 @@ repository cardano-haskell-packages -- See CONTRIBUTING for information about these, including some Nix commands -- you need to run if you change them index-state: - , hackage.haskell.org 2026-08-14T13:38:07Z - , cardano-haskell-packages 2026-09-22T20:39:47Z + , hackage.haskell.org 2026-09-02T14:05:07Z + , cardano-haskell-packages 2026-10-06T16:46:26Z constraints: + -- Both time providers export the same C clock symbol in static musl builds. + -- DNS 4.2.0.1 uses the modern provider required by TLS. + , hourglass < 0 + , dns ^>= 4.2.0.1 -- haskell.nix patch does not work for 1.6.8 -- grapesy currently depends on crypton-x509-system (>=1.6 && <1.7) , any.crypton-x509-system < 1.6.8 || >= 1.7 @@ -166,3 +170,99 @@ if impl(ghc >= 9.14) -- Do NOT add more source-repository-package stanzas here unless they are strictly -- temporary! Please read the section in CONTRIBUTING about updating dependencies. + +-- Temporary public source prerequisites for the coordinated CIP-160 proposals. +-- Proposed package versions are unreleased; remove after official package delivery. +-- Remove these overrides after the reviewed package versions reach CHaP. +source-repository-package + type: git + location: https://github.com/colll78/cardano-ledger.git + tag: 4ab1c0df3373b962c641efb4935aca0e7e68967b + --sha256: sha256-+EearRC1REi8n7pRvsR6AmTC/lyLYkxRdvkwUV1Ilk0= + subdir: + eras/allegra/impl + eras/alonzo/impl + eras/babbage/impl + eras/byron/crypto + eras/byron/ledger/impl + eras/conway/impl + eras/dijkstra/impl + eras/mary/impl + eras/shelley/impl + libs/cardano-data + libs/cardano-ledger-api + libs/cardano-ledger-binary + libs/cardano-ledger-core + libs/cardano-protocol + libs/cardano-protocol-tpraos + libs/non-integral + libs/small-steps + libs/vector-map + +source-repository-package + type: git + location: https://github.com/colll78/plutus.git + tag: dcbb7e3232c3322557410fe341ec84f3cd78dc04 + --sha256: sha256-6n+edByIhXRu9+ZfeMhIKLlfkcj/7aRJ1mrH29mn4nE= + subdir: plutus-ledger-api + +-- Exact public source prerequisites; proposed versions are unreleased. + +source-repository-package + type: git + location: https://github.com/colll78/cardano-addresses.git + tag: 83cf033c671d8a03d95de52c1451195f331d538e + --sha256: sha256-L0Bt4ynhocsSopD2xsY+JqajASjSAggrBP46Szbzygg= + +source-repository-package + type: git + location: https://github.com/colll78/cardano-keys.git + tag: b63987bd43d99b82b91d29020ceab089fb61b123 + --sha256: sha256-7oRxoZjCv/RhnnRWQ8LvI4sknlEGqy81j8uFxuJzOc0= + subdir: cardano-keys + +source-repository-package + type: git + location: https://github.com/colll78/cardano-config.git + tag: e7a6f979964d2f613671832af001d06f99a67a86 + --sha256: sha256-D+PbL2GcvEwCpazxy8cYncl8EUjwuR4H5Wj2HWO3UX0= + +source-repository-package + type: git + location: https://github.com/colll78/ouroboros-consensus.git + tag: 0c91555c0c0dfeef6cb0ac6facd42b471630f5b0 + --sha256: sha256-19A5YUoKnfUAc/KBPg3s25HaI+zwMGiIk4Vng3NJXH4= + + +-- Published feature sources retain the complete normal component graph. +source-repository-package + type: git + location: https://github.com/colll78/cardano-cli.git + tag: 7bc80d20076082ae5ebe17ac03bb47bc41c9ec48 + --sha256: sha256-Tk0kqzvbntxMrjvVghv6IOzE+nDhU8rKPSoVXH175Xg= + subdir: cardano-cli + +source-repository-package + type: git + location: https://github.com/colll78/cardano-api.git + tag: a3966fb8dca37e796929ae870f0bc8edea521986 + --sha256: sha256-zhmSimpXLCW+NvoZWMX1HNnEpPOSSsngsXzSwbpx9pE= + subdir: + cardano-api + cardano-api-gen + cardano-rpc + cardano-wasm + +source-repository-package + type: git + location: https://github.com/colll78/ekg-forward.git + tag: 420e68445d1f853274724ccd18c1bd2222843f3a + --sha256: sha256-OWGyRyqprIoQi+zzCw5aHk9/bA778dDaVn9pJfspLhE= + +-- Temporary upstream DNS prerequisite: https://github.com/kazu-yamamoto/dns/pull/177 +-- Share the modern time provider required by TLS; remove after upstream delivery. +source-repository-package + type: git + location: https://github.com/colll78/dns.git + tag: a3ef6cdcdb5173d88c2e2a3201f528dbe0df1c0c + --sha256: sha256-fOWp3BFVkD4dJvxVtskdOyPg5uN9GUEL7NEj9Z4pBm8= diff --git a/cardano-node-chairman/.changes/20261006_000000_cip160_integration.yml b/cardano-node-chairman/.changes/20261006_000000_cip160_integration.yml new file mode 100644 index 00000000000..2a1a56f9470 --- /dev/null +++ b/cardano-node-chairman/.changes/20261006_000000_cip160_integration.yml @@ -0,0 +1,5 @@ +pr: 0 +kind: + - maintenance +description: | + Update crypto-class and protocol dependency bounds for the selected Receiving-aware node integration. diff --git a/cardano-node-chairman/cardano-node-chairman.cabal b/cardano-node-chairman/cardano-node-chairman.cabal index 86723446f1c..754da477df2 100644 --- a/cardano-node-chairman/cardano-node-chairman.cabal +++ b/cardano-node-chairman/cardano-node-chairman.cabal @@ -1,7 +1,7 @@ cabal-version: 3.0 name: cardano-node-chairman -version: 11.1.1 +version: 11.1.1.1 synopsis: The cardano full node description: The cardano full node. category: Cardano, @@ -67,7 +67,7 @@ test-suite chairman-tests build-depends: , cardano-api , cardano-testnet - , cardano-crypto-class ^>=2.5 + , cardano-crypto-class ^>=2.6 , data-default-class , filepath , hedgehog @@ -86,5 +86,5 @@ test-suite chairman-tests ghc-options: -threaded -rtsopts "-with-rtsopts=-N -T" build-tool-depends: cardano-node:cardano-node - , cardano-cli:cardano-cli ^>= 11.2.2 + , cardano-cli:cardano-cli ^>= 11.3 , cardano-node-chairman:cardano-node-chairman diff --git a/cardano-node/ChangeLog.md b/cardano-node/ChangeLog.md index 2af27890aed..3d087b7f2d2 100644 --- a/cardano-node/ChangeLog.md +++ b/cardano-node/ChangeLog.md @@ -2,6 +2,17 @@ ## Next version +- Update ledger/consensus and credential-envelope adapters for the selected + dependencies, preserving the LSM cache default and credential envelope type. + Render current account/withdrawal/pool and Peras failures, snapshot-policy + configuration and mempool-capacity changes. Expose ReadMempool/WatchMempool + RPC span namespaces, documentation and request counters. Use the supported + randomized responder salt API and display the supported client protocol version. + +- Render Receiving purposes by original output index. Missing-redeemer logs + preserve every purpose sharing a script hash: singleton values keep their + existing JSON shape, while multiple values form an array in source order. + - Fix `BlockFetch.Decision` trace namespace drift: documentation and the configuration consistency check now use the runtime `TraceDecisionEvent` type, so the documented message namespaces are diff --git a/cardano-node/cardano-node.cabal b/cardano-node/cardano-node.cabal index 46bfdae65da..8f2e9df8fd4 100644 --- a/cardano-node/cardano-node.cabal +++ b/cardano-node/cardano-node.cabal @@ -1,7 +1,7 @@ cabal-version: 3.8 name: cardano-node -version: 11.1.1 +version: 11.1.1.1 synopsis: The cardano full node description: The cardano full node. category: Cardano, @@ -133,9 +133,9 @@ library , async , base16-bytestring , bytestring - , cardano-api ^>= 11.7 + , cardano-api ^>= 11.9 , cardano-data - , cardano-crypto-class ^>=2.5 + , cardano-crypto-class ^>=2.6 , cardano-crypto-wrapper , cardano-git-rev ^>=0.2.2 , cardano-ledger-alonzo @@ -149,7 +149,7 @@ library , cardano-ledger-dijkstra , cardano-ledger-shelley , cardano-prelude - , cardano-protocol ^>= 0.1 + , cardano-protocol ^>= 0.3 , cardano-protocol-tpraos >= 1.4 , cardano-slotting >= 0.2 , cardano-rpc ^>= 11.4 @@ -167,7 +167,7 @@ library , hashable , hostname , io-classes:{io-classes,strict-stm,si-timers} ^>= 1.8 || ^>= 1.9 - , kes-agent ^>=1.3 + , kes-agent ^>=1.4 , microlens , mmap , network-mux @@ -176,9 +176,9 @@ library , network-mux >= 0.8 , nothunks , optparse-applicative - , ouroboros-consensus:{ouroboros-consensus, lsm, cardano, diffusion, protocol} ^>= 4.2.0.1 - , ouroboros-network:{api, ouroboros-network, orphan-instances, framework, protocols, tracing} ^>= 1.2 - , cardano-diffusion:{api, cardano-diffusion, tracing, orphan-instances} ^>=1.1.1 + , ouroboros-consensus:{ouroboros-consensus, lsm, cardano, diffusion, protocol} ^>= 5.1 + , ouroboros-network:{api, ouroboros-network, orphan-instances, framework, protocols, tracing} ^>= 1.3 + , cardano-diffusion:{api, cardano-diffusion, tracing, orphan-instances} ^>=1.3 , prettyprinter , prettyprinter-ansi-terminal , psqueues @@ -236,6 +236,9 @@ test-suite cardano-node-test , cardano-crypto-class , cardano-crypto-wrapper , cardano-api + , cardano-ledger-alonzo + , cardano-ledger-core + , cardano-ledger-dijkstra , cardano-rpc , cardano-diffusion:{api, cardano-diffusion, orphan-instances} , cardano-node @@ -258,12 +261,14 @@ test-suite cardano-node-test , vector , yaml - other-modules: Test.Cardano.Config.Mainnet + other-modules: Test.Cardano.Config.Dijkstra + Test.Cardano.Config.Mainnet Test.Cardano.Node.FilePermissions Test.Cardano.Node.Gen Test.Cardano.Node.Json Test.Cardano.Node.POM Test.Cardano.Node.TopLevel Test.Cardano.Tracing.NewTracing.Consistency + Test.Cardano.Tracing.Receiving ghc-options: -threaded -rtsopts "-with-rtsopts=-N -T" diff --git a/cardano-node/src/Cardano/Node/Configuration/LedgerDB.hs b/cardano-node/src/Cardano/Node/Configuration/LedgerDB.hs index 04ce0bebb0e..876d1e1c765 100644 --- a/cardano-node/src/Cardano/Node/Configuration/LedgerDB.hs +++ b/cardano-node/src/Cardano/Node/Configuration/LedgerDB.hs @@ -86,3 +86,4 @@ selectorToArgs (V2LSM fp fpExport) fastStoragePath = (fromMaybe "lsm" fp) fpExport fastStoragePath + LSM.DiskCacheAll diff --git a/cardano-node/src/Cardano/Node/Configuration/POM.hs b/cardano-node/src/Cardano/Node/Configuration/POM.hs index 3b6de2bea52..8206bdedabd 100644 --- a/cardano-node/src/Cardano/Node/Configuration/POM.hs +++ b/cardano-node/src/Cardano/Node/Configuration/POM.hs @@ -53,6 +53,7 @@ import Ouroboros.Consensus.Storage.LedgerDB.Snapshots (NumOfDiskSnapsh mithrilSnapshotPolicyArgs) import Ouroboros.Network.Diffusion.Configuration as Configuration import qualified Ouroboros.Network.Diffusion.Configuration as Ouroboros +import Ouroboros.Network.Hashable (mkSaltIO) import qualified Ouroboros.Network.Mux as Mux import Ouroboros.Network.OrphanInstances () import qualified Ouroboros.Network.PeerSelection.Governor as PeerSelection @@ -74,7 +75,6 @@ import Data.Yaml (decodeFileThrow) import GHC.Generics (Generic) import Options.Applicative import System.FilePath (takeDirectory, ()) -import System.Random (randomIO) import Generic.Data (gmappend) import Generic.Data.Orphans () @@ -213,7 +213,7 @@ data NodeConfiguration -- -- * `NoResponderCoreAffinity` corresponds to `Ouroboros.Network.Mux.noBindForkPolicy` -- * `ResponderCoreAffinity` corresponds to `Ouroboros.Network.Mux.responderForkPolicy` --- with a `randomIO` generated salt and `getNumCapabilities`. +-- with a `mkSaltIO` generated salt and `getNumCapabilities`. -- data ResponderCoreAffinityPolicy = NoResponderCoreAffinity | ResponderCoreAffinity deriving (Eq, Show, Generic, FromJSON) @@ -221,7 +221,7 @@ data ResponderCoreAffinityPolicy = NoResponderCoreAffinity | ResponderCoreAffini getForkPolicy :: Hashable peerAddr => ResponderCoreAffinityPolicy -> IO (Mux.ForkPolicy peerAddr) getForkPolicy = \case NoResponderCoreAffinity -> pure Mux.noBindForkPolicy - ResponderCoreAffinity -> Mux.responderForkPolicy <$> randomIO <*> getNumCapabilities + ResponderCoreAffinity -> Mux.responderForkPolicy <$> mkSaltIO <*> getNumCapabilities data PartialNodeConfiguration = PartialNodeConfiguration diff --git a/cardano-node/src/Cardano/Node/Orphans.hs b/cardano-node/src/Cardano/Node/Orphans.hs index fdbde6766be..4bf362369d4 100644 --- a/cardano-node/src/Cardano/Node/Orphans.hs +++ b/cardano-node/src/Cardano/Node/Orphans.hs @@ -80,6 +80,6 @@ instance HasTypeProxy (PraosCredentialsSource StandardCrypto) where -- TODO(11.0): consider moving to `cardano-api` instance HasTextEnvelope (PraosCredentialsSource StandardCrypto) where - textEnvelopeType _ = + textEnvelopeTypes _ = pure $ "PraosCredentialsSource_" <> fromString (Crypto.algorithmNameKES (Proxy @(KES StandardCrypto))) diff --git a/cardano-node/src/Cardano/Node/Protocol/Dijkstra.hs b/cardano-node/src/Cardano/Node/Protocol/Dijkstra.hs index 39c146ed50c..1cc85097aac 100644 --- a/cardano-node/src/Cardano/Node/Protocol/Dijkstra.hs +++ b/cardano-node/src/Cardano/Node/Protocol/Dijkstra.hs @@ -12,18 +12,15 @@ module Cardano.Node.Protocol.Dijkstra import Cardano.Api import qualified Cardano.Crypto.Hash.Class as Crypto -import Cardano.Ledger.BaseTypes import qualified Cardano.Ledger.Binary as L -import Cardano.Ledger.Dijkstra.Genesis (DijkstraGenesis (..)) +import Cardano.Ledger.Dijkstra.Genesis (DijkstraGenesis) import qualified Cardano.Ledger.Dijkstra.Genesis as Dijkstra -import Cardano.Ledger.Dijkstra.PParams import Cardano.Node.Orphans () import Cardano.Node.Protocol.Shelley (GenesisReadError, readGenesisAny) import Cardano.Node.Types import qualified Data.ByteString.Lazy as LB -import Data.Maybe (fromMaybe) readGenesisMaybe :: Maybe GenesisFile -> Maybe GenesisHash @@ -36,14 +33,9 @@ readGenesisMaybe Nothing _ = do return (dijkstraGenesis, genesisHash) emptyDijkstraGenesis :: DijkstraGenesis -emptyDijkstraGenesis = - let upgradePParamsDef = UpgradeDijkstraPParams - { udppMaxRefScriptSizePerBlock = 1048576 - , udppMaxRefScriptSizePerTx = 204800 - , udppRefScriptCostStride = unsafeNonZero 25600 - , udppRefScriptCostMultiplier = fromMaybe (error "impossible") $ boundRational 1.2 - } - in DijkstraGenesis { dgUpgradePParams = upgradePParamsDef } +-- Share the complete API defaults, including the V4 cost model and every +-- Dijkstra integration field. +emptyDijkstraGenesis = dijkstraGenesisDefaults readGenesis :: GenesisFile diff --git a/cardano-node/src/Cardano/Node/Run.hs b/cardano-node/src/Cardano/Node/Run.hs index 25af1760d04..33177e7c848 100644 --- a/cardano-node/src/Cardano/Node/Run.hs +++ b/cardano-node/src/Cardano/Node/Run.hs @@ -750,7 +750,6 @@ updateLedgerPeerSnapshot startupTracer NodeConfiguration { ncConsensusMode } net | otherwise -> oops $ "NetworkMagic " <> showT networkMagic <> " doesn't match " <> "peer snapshot NetworkMagic " <> showT magic - LedgerPeerSnapshotV2 {} -> oops "Unsupported legacy peer snapshot version." case afterSlot of Always -> do traceL $ LedgerPeerSnapshotLoaded fileSlot diff --git a/cardano-node/src/Cardano/Node/Tracing/Consistency.hs b/cardano-node/src/Cardano/Node/Tracing/Consistency.hs index 7929bf76cb9..c1ef55ba0dd 100644 --- a/cardano-node/src/Cardano/Node/Tracing/Consistency.hs +++ b/cardano-node/src/Cardano/Node/Tracing/Consistency.hs @@ -65,7 +65,8 @@ import Ouroboros.Consensus.MiniProtocol.ObjectDiffusion.PerasCert (Per import Ouroboros.Consensus.MiniProtocol.ObjectDiffusion.PerasVote (PerasVoteDiffusion, TracePerasVoteDiffusionInbound, TracePerasVoteDiffusionOutbound) import Ouroboros.Consensus.Node.GSM -import Ouroboros.Consensus.Node.Tracers (TraceForgeEvent) +import Ouroboros.Consensus.Node.Tracers (TraceForgeEvent, TracePerasCertInclusionEvent, + TracePerasVoteForgingEvent) import qualified Ouroboros.Consensus.Protocol.Ledger.HotKey as HotKey import Ouroboros.Consensus.Protocol.Praos.AgentClient (KESAgentClientTrace) import qualified Ouroboros.Consensus.Storage.ChainDB as ChainDB @@ -216,6 +217,10 @@ getAllNamespaces = (TraceTxLogic remotePeer (GenTxId blk) (GenTx blk))]) txCountersNS = map (nsGetTuple . nsReplacePrefix ["txCounters", "Remote"]) (allNamespaces :: [Namespace TxSubmissionCounters]) + perasCertInclusionNS = map (nsGetTuple . nsReplacePrefix ["Peras", "Cert", "Inclusion"]) + (allNamespaces :: [Namespace (TracePerasCertInclusionEvent blk)]) + perasVoteForgingNS = map (nsGetTuple . nsReplacePrefix ["Peras", "Vote", "Forging"]) + (allNamespaces :: [Namespace (TracePerasVoteForgingEvent blk)]) perasCertInboundNS = map (nsGetTuple . nsReplacePrefix ["Peras", "Cert", "Inbound"]) (allNamespaces :: [Namespace (BlockFetch.TraceLabelPeer remotePeer (TracePerasCertDiffusionInbound blk))]) @@ -475,6 +480,8 @@ getAllNamespaces = <> txLogicNS <> txCountersNS -- Peras + <> perasCertInclusionNS + <> perasVoteForgingNS <> perasCertInboundNS <> perasCertOutboundNS <> perasVoteInboundNS diff --git a/cardano-node/src/Cardano/Node/Tracing/Documentation.hs b/cardano-node/src/Cardano/Node/Tracing/Documentation.hs index c6198bd361d..a3a94f0b8ff 100644 --- a/cardano-node/src/Cardano/Node/Tracing/Documentation.hs +++ b/cardano-node/src/Cardano/Node/Tracing/Documentation.hs @@ -470,6 +470,20 @@ docTracersFirstPhase condConfigFileName = do txCountersTrDoc <- documentTracer (txCountersTr :: Logging.Trace IO TxSubmissionCounters) + perasCertInclusionTr <- mkCardanoTracer + trBase trForward mbTrEKG + ["Peras", "Cert", "Inclusion"] + configureTracers configReflection trConfig [perasCertInclusionTr] + perasCertInclusionTrDoc <- documentTracer (perasCertInclusionTr :: + Logging.Trace IO (Consensus.TracePerasCertInclusionEvent blk)) + + perasVoteForgingTr <- mkCardanoTracer + trBase trForward mbTrEKG + ["Peras", "Vote", "Forging"] + configureTracers configReflection trConfig [perasVoteForgingTr] + perasVoteForgingTrDoc <- documentTracer (perasVoteForgingTr :: + Logging.Trace IO (Consensus.TracePerasVoteForgingEvent blk)) + perasCertInboundTr <- mkCardanoTracer trBase trForward mbTrEKG ["Peras", "Cert", "Inbound"] @@ -835,6 +849,8 @@ docTracersFirstPhase condConfigFileName = do <> txLogicTrDoc <> txCountersTrDoc -- Peras + <> perasCertInclusionTrDoc + <> perasVoteForgingTrDoc <> perasCertInboundTrDoc <> perasCertOutboundTrDoc <> perasVoteInboundTrDoc diff --git a/cardano-node/src/Cardano/Node/Tracing/Era/Shelley.hs b/cardano-node/src/Cardano/Node/Tracing/Era/Shelley.hs index 16311d6cf64..b7ad9a4efbc 100644 --- a/cardano-node/src/Cardano/Node/Tracing/Era/Shelley.hs +++ b/cardano-node/src/Cardano/Node/Tracing/Era/Shelley.hs @@ -174,8 +174,8 @@ instance LogFormatting (Conway.ConwayDelegPredFailure era) where , "amount" .= coin , "error" .= String "Incorrect deposit amount" ] - Conway.StakeKeyRegisteredDELEG credential -> - [ "kind" .= String "StakeKeyRegisteredDELEG" + Conway.DelegAccountAlreadyRegistered (AccountAlreadyRegistered credential) -> + [ "kind" .= String "DelegAccountAlreadyRegistered" , "credential" .= String (textShow credential) , "error" .= String "Stake key already registered" ] @@ -532,6 +532,10 @@ instance , "network" .= network , "addrs" .= addrs ] + forMachine _dtal (UnsupportedOutputAddresses outputIndexes) = + mconcat [ "kind" .= String "UnsupportedOutputAddresses" + , "outputIndexes" .= NonEmptySet.toSet outputIndexes + ] forMachine _dtal (WrongNetworkWithdrawal network addrs) = mconcat [ "kind" .= String "WrongNetworkWithdrawal" , "network" .= network @@ -573,6 +577,10 @@ instance , "network" .= network , "addrs" .= addrs ] + forMachine _dtal (Allegra.UnsupportedOutputAddresses outputIndexes) = + mconcat [ "kind" .= String "UnsupportedOutputAddresses" + , "outputIndexes" .= NonEmptySet.toSet outputIndexes + ] forMachine _dtal (Allegra.WrongNetworkWithdrawal network addrs) = mconcat [ "kind" .= String "WrongNetworkWithdrawal" , "network" .= network @@ -643,8 +651,8 @@ instance forMachine dtal (DelegFailure f) = forMachine dtal f instance LogFormatting (ShelleyDelegPredFailure era) where - forMachine _dtal (StakeKeyAlreadyRegisteredDELEG alreadyRegistered) = - mconcat [ "kind" .= String "StakeKeyAlreadyRegisteredDELEG" + forMachine _dtal (DelegAccountAlreadyRegistered (AccountAlreadyRegistered alreadyRegistered)) = + mconcat [ "kind" .= String "DelegAccountAlreadyRegistered" , "credential" .= String (textShow alreadyRegistered) , "error" .= String "Staking credential already registered" ] @@ -768,6 +776,27 @@ instance LogFormatting (ShelleyPoolPredFailure era) where ] +instance LogFormatting (Dijkstra.DijkstraPoolPredFailure era) where + forMachine dtal = \case + Dijkstra.StakePoolNotRegisteredOnKeyPOOL poolId -> + forMachine dtal (StakePoolNotRegisteredOnKeyPOOL poolId :: ShelleyPoolPredFailure era) + Dijkstra.StakePoolRetirementWrongEpochPOOL current intended -> + forMachine dtal (StakePoolRetirementWrongEpochPOOL current intended :: ShelleyPoolPredFailure era) + Dijkstra.StakePoolCostTooLowPOOL mismatch -> + forMachine dtal (StakePoolCostTooLowPOOL mismatch :: ShelleyPoolPredFailure era) + Dijkstra.WrongNetworkPOOL mismatch poolId -> + forMachine dtal (WrongNetworkPOOL mismatch poolId :: ShelleyPoolPredFailure era) + Dijkstra.PoolMedataHashTooBig poolId hashSize -> + forMachine dtal (PoolMedataHashTooBig poolId hashSize :: ShelleyPoolPredFailure era) + Dijkstra.VRFKeyHashAlreadyRegistered poolId vrfKeyHash -> + forMachine dtal (VRFKeyHashAlreadyRegistered poolId vrfKeyHash :: ShelleyPoolPredFailure era) + Dijkstra.BlsKeyInvalidProofOfPossession poolId blsKey -> + mconcat [ "kind" .= String "BlsKeyInvalidProofOfPossession" + , "poolId" .= String (textShow poolId) + , "blsKey" .= String (textShow blsKey) + ] + + instance ( Ledger.Crypto crypto ) => LogFormatting (PrtclPredicateFailure crypto) where @@ -926,6 +955,10 @@ instance , "network" .= network , "addrs" .= addrs ] + forMachine _dtal (Alonzo.UnsupportedOutputAddresses outputIndexes) = + mconcat [ "kind" .= String "UnsupportedOutputAddresses" + , "outputIndexes" .= NonEmptySet.toSet outputIndexes + ] forMachine _dtal (Alonzo.WrongNetworkWithdrawal network addrs) = mconcat [ "kind" .= String "WrongNetworkWithdrawal" , "network" .= network @@ -1209,10 +1242,6 @@ instance ( Consensus.ShelleyBasedEra era , LogFormatting (PredicateFailure (Ledger.EraRule "CERT" era)) ) => LogFormatting (Conway.ConwayCertsPredFailure era) where - forMachine _ (Conway.WithdrawalsNotInRewardsCERTS rs) = - mconcat [ "kind" .= String "WithdrawalsNotInRewardsCERTS" - , "rewardAccounts" .= unWithdrawals rs - ] forMachine dtal (Conway.CertFailure certFailure) = forMachine dtal certFailure @@ -1247,32 +1276,62 @@ instance ( LogFormatting (PredicateFailure (Ledger.EraRule "CERTS" ledgerera)) ) => LogFormatting (Dijkstra.EntitiesPredFailure ledgerera) where forMachine v (Dijkstra.CertsFailure f) = forMachine v f - forMachine _ (Dijkstra.MissingAccountsInWithdrawals withdrawals) = - mconcat [ "kind" .= String "MissingAccountsInWithdrawals" + forMachine _ (Dijkstra.WithdrawalAccountsMissing withdrawals) = + mconcat [ "kind" .= String "WithdrawalAccountsMissing" , "withdrawals" .= unWithdrawals withdrawals ] - forMachine _ (Dijkstra.IncompleteWithdrawals incompleteWithdrawals) = - mconcat [ "kind" .= String "IncompleteWithdrawals" + forMachine _ (Dijkstra.WithdrawalAmountsInexactInLegacyMode incompleteWithdrawals) = + mconcat [ "kind" .= String "WithdrawalAmountsInexactInLegacyMode" , "withdrawals" .= String (textShow incompleteWithdrawals) ] - forMachine _ (Dijkstra.ExceededBalancesInWithdrawals accounts) = - mconcat [ "kind" .= String "ExceededBalancesInWithdrawals" + forMachine _ (Dijkstra.WithdrawalAmountsExceedingOriginalBalance accounts) = + mconcat [ "kind" .= String "WithdrawalAmountsExceedingOriginalBalance" , "accounts" .= String (textShow accounts) ] - forMachine _ (Dijkstra.MissingAccountsInDirectDeposits directDeposits) = - mconcat [ "kind" .= String "MissingAccountsInDirectDeposits" + forMachine _ (Dijkstra.DirectDepositAccountsMissing directDeposits) = + mconcat [ "kind" .= String "DirectDepositAccountsMissing" , "directDeposits" .= String (textShow directDeposits) ] - forMachine _ (Dijkstra.WrongNetworkInWithdrawals network accounts) = - mconcat [ "kind" .= String "WrongNetworkInWithdrawals" + forMachine _ (Dijkstra.WithdrawalAddressesWithWrongNetwork network accounts) = + mconcat [ "kind" .= String "WithdrawalAddressesWithWrongNetwork" + , "network" .= network + , "accounts" .= NonEmptySet.toSet accounts + ] + forMachine _ (Dijkstra.DirectDepositAddressesWithWrongNetwork network accounts) = + mconcat [ "kind" .= String "DirectDepositAddressesWithWrongNetwork" + , "network" .= network + , "accounts" .= NonEmptySet.toSet accounts + ] + forMachine _ (Dijkstra.WithdrawalAccountsMissingFromOriginal withdrawals) = + mconcat [ "kind" .= String "WithdrawalAccountsMissingFromOriginal" + , "withdrawals" .= unWithdrawals withdrawals + ] + forMachine _ (Dijkstra.WrongNetworkInAccountBalanceIntervals network accounts) = + mconcat [ "kind" .= String "WrongNetworkInAccountBalanceIntervals" , "network" .= network , "accounts" .= NonEmptySet.toSet accounts ] - forMachine _ (Dijkstra.WrongNetworkInDirectDeposits network accounts) = - mconcat [ "kind" .= String "WrongNetworkInDirectDeposits" + forMachine _ (Dijkstra.WrongNetworkInStartingAccountBalanceIntervals network accounts) = + mconcat [ "kind" .= String "WrongNetworkInStartingAccountBalanceIntervals" , "network" .= network , "accounts" .= NonEmptySet.toSet accounts ] + forMachine _ (Dijkstra.MissingAccountsInAccountBalanceIntervals accounts) = + mconcat [ "kind" .= String "MissingAccountsInAccountBalanceIntervals" + , "accounts" .= String (textShow accounts) + ] + forMachine _ (Dijkstra.BalancesOutsideAccountBalanceIntervals accounts) = + mconcat [ "kind" .= String "BalancesOutsideAccountBalanceIntervals" + , "accounts" .= String (textShow accounts) + ] + forMachine _ (Dijkstra.MissingAccountsInStartingAccountBalanceIntervals accounts) = + mconcat [ "kind" .= String "MissingAccountsInStartingAccountBalanceIntervals" + , "accounts" .= String (textShow accounts) + ] + forMachine _ (Dijkstra.BalancesOutsideStartingAccountBalanceIntervals accounts) = + mconcat [ "kind" .= String "BalancesOutsideStartingAccountBalanceIntervals" + , "accounts" .= String (textShow accounts) + ] instance ( Consensus.ShelleyBasedEra ledgerera @@ -1451,11 +1510,23 @@ instance , "produced" .= mismatchExpected , "error" .= renderValueNotConservedErr mismatchSupplied mismatchExpected ] + Dijkstra.ValueNotConservedInLegacyMode Mismatch {mismatchSupplied, mismatchExpected} -> + mconcat [ "kind" .= String "ValueNotConservedInLegacyMode" + , "consumed" .= mismatchSupplied + , "produced" .= mismatchExpected + , "error" .= renderValueNotConservedErr mismatchSupplied mismatchExpected + ] Dijkstra.WrongNetwork network addrs -> mconcat [ "kind" .= String "WrongNetwork" , "network" .= network , "addrs" .= NonEmptySet.toSet addrs ] + Dijkstra.UnsupportedOutputAddresses outputIndexes -> + mconcat [ "kind" .= String "UnsupportedOutputAddresses" + , "outputIndexes" .= NonEmptySet.toSet outputIndexes + ] + Dijkstra.ProtectedCollateralReturn -> + mconcat [ "kind" .= String "ProtectedCollateralReturn" ] Dijkstra.OutputBootAddrAttrsTooBig badOutputs -> mconcat [ "kind" .= String "OutputBootAddrAttrsTooBig" , "outputs" .= badOutputs @@ -1517,10 +1588,6 @@ instance mconcat [ "kind" .= String "PtrPresentInCollateralReturn" , "output" .= output ] - Dijkstra.WithdrawalsExceedAccountBalance accounts -> - mconcat [ "kind" .= String "WithdrawalsExceedAccountBalance" - , "accounts" .= String (textShow accounts) - ] instance ( Api.ShelleyLedgerEra era ~ ledgerera @@ -1777,6 +1844,10 @@ instance , "network" .= network , "addrs" .= addrs ] + Conway.UnsupportedOutputAddresses outputIndexes -> + mconcat [ "kind" .= String "UnsupportedOutputAddresses" + , "outputIndexes" .= NonEmptySet.toSet outputIndexes + ] Conway.WrongNetworkWithdrawal network addrs -> mconcat [ "kind" .= String "WrongNetworkWithdrawal" , "network" .= network diff --git a/cardano-node/src/Cardano/Node/Tracing/Render.hs b/cardano-node/src/Cardano/Node/Tracing/Render.hs index 0462c48032e..7495259419e 100644 --- a/cardano-node/src/Cardano/Node/Tracing/Render.hs +++ b/cardano-node/src/Cardano/Node/Tracing/Render.hs @@ -60,9 +60,9 @@ import qualified Data.Aeson.Key as Aeson import qualified Data.Aeson.Types as Aeson import qualified Data.ByteString.Base16 as B16 import Data.List.NonEmpty (NonEmpty) -import qualified Data.List.NonEmpty as NonEmpty import Data.Map.NonEmpty (NonEmptyMap) import qualified Data.Map.NonEmpty as NonEmptyMap +import qualified Data.Map.Strict as Map import Data.Proxy (Proxy (..)) import Data.Text (Text) import qualified Data.Text as Text @@ -194,13 +194,17 @@ renderMissingRedeemers :: forall era. () => Api.ShelleyBasedEra era -> NonEmpty (PlutusPurpose AsItem (Api.ShelleyLedgerEra era), Ledger.ScriptHash) -> Aeson.Value -renderMissingRedeemers sbe scripts = Aeson.object $ NonEmpty.toList $ NonEmpty.map renderTuple scripts +-- Preserve every purpose for a shared script hash. Singleton values keep the +-- existing JSON shape; multiple values form an array in source order. +renderMissingRedeemers sbe scripts = + Aeson.object $ map renderTuple $ Map.toList $ foldr addPurpose Map.empty scripts where - renderTuple :: () - => (PlutusPurpose AsItem (Api.ShelleyLedgerEra era), Ledger.ScriptHash) - -> Aeson.Pair - renderTuple (scriptPurpose, sHash) = - Aeson.fromText (renderScriptHash sHash) .= renderScriptPurpose sbe scriptPurpose + addPurpose (scriptPurpose, sHash) = + Map.insertWith (++) (renderScriptHash sHash) [renderScriptPurpose sbe scriptPurpose] + renderTuple (sHash, purposes) = + Aeson.fromText sHash .= case purposes of + [purpose] -> purpose + _ -> Aeson.toJSON purposes renderIncompleteWithdrawals :: forall payload. Show payload => NonEmptyMap Ledger.AccountAddress (Mismatch RelEQ payload) @@ -281,3 +285,5 @@ renderDijkstraPlutusPurpose = \case Aeson.object ["proposing" .= Aeson.toJSON proposal] DijkstraGuarding sHash -> Aeson.object ["guarding" .= Aeson.toJSON sHash] + DijkstraReceiving (AsItem outputIndex) -> + Aeson.object ["receiving" .= Aeson.toJSON outputIndex] diff --git a/cardano-node/src/Cardano/Node/Tracing/Tracers.hs b/cardano-node/src/Cardano/Node/Tracing/Tracers.hs index 4c127580a28..1840a24ecfd 100644 --- a/cardano-node/src/Cardano/Node/Tracing/Tracers.hs +++ b/cardano-node/src/Cardano/Node/Tracing/Tracers.hs @@ -352,6 +352,16 @@ mkConsensusTracers configReflection trBase trForward mbTrEKG _trDataPoint trConf ["txCounters", "Remote"] configureTracers configReflection trConfig [txCountersTracer] + !perasCertInclusionTr <- mkCardanoTracer + trBase trForward mbTrEKG + ["Peras", "Cert", "Inclusion"] + configureTracers configReflection trConfig [perasCertInclusionTr] + + !perasVoteForgingTr <- mkCardanoTracer + trBase trForward mbTrEKG + ["Peras", "Vote", "Forging"] + configureTracers configReflection trConfig [perasVoteForgingTr] + !txPerasCertIn <- mkCardanoTracer trBase trForward mbTrEKG ["Peras", "Cert", "Inbound"] @@ -424,6 +434,8 @@ mkConsensusTracers configReflection trBase trForward mbTrEKG _trDataPoint trConf traceWith txLogicTracer , Consensus.txCountersTracer = mkTracer $ traceWith txCountersTracer + , Consensus.perasCertInclusionTracer = mkTracer $ traceWith perasCertInclusionTr + , Consensus.perasVoteForgingTracer = mkTracer $ traceWith perasVoteForgingTr , Consensus.perasCertDiffusionInboundTracer = mkTracer $ traceWith txPerasCertIn , Consensus.perasCertDiffusionOutboundTracer = mkTracer $ traceWith txPerasCertOut , Consensus.perasVoteDiffusionInboundTracer = mkTracer $ traceWith txPerasVoteIn diff --git a/cardano-node/src/Cardano/Node/Tracing/Tracers/ChainDB.hs b/cardano-node/src/Cardano/Node/Tracing/Tracers/ChainDB.hs index a87ac6781b7..460a126d9c6 100644 --- a/cardano-node/src/Cardano/Node/Tracing/Tracers/ChainDB.hs +++ b/cardano-node/src/Cardano/Node/Tracing/Tracers/ChainDB.hs @@ -14,6 +14,7 @@ module Cardano.Node.Tracing.Tracers.ChainDB , fragmentChainDensity ) where +import Cardano.Ledger.BaseTypes (unNonZero) import Cardano.Logging import Cardano.Node.Tracing.Era.Byron () import Cardano.Node.Tracing.Era.Shelley () @@ -21,6 +22,7 @@ import Cardano.Node.Tracing.Formatting () import Cardano.Node.Tracing.Render import Cardano.Node.Tracing.Tracers.HasIssuer import Cardano.Prelude (maximumDef) +import Cardano.Slotting.Time (getSlotLength) import Ouroboros.Consensus.Block import Ouroboros.Consensus.HardFork.Combinator.Abstract.CanHardFork import Ouroboros.Consensus.HardFork.Combinator.Abstract.SingleEraBlock @@ -34,7 +36,7 @@ import Ouroboros.Consensus.Ledger.Inspect (InspectLedger, LedgerEvent import Ouroboros.Consensus.Ledger.SupportsProtocol (LedgerSupportsProtocol) import Ouroboros.Consensus.Peras.SelectView import Ouroboros.Consensus.Protocol.Abstract (Comparing (..), ReasonForSwitch, - SelectViewReasonForSwitch (..), TiebreakerView, ValidationErr) + SelectViewReasonForSwitch (..), TiebreakerView, ValidationErr, maxRollbacks) import qualified Ouroboros.Consensus.Protocol.PBFT as PBFT import Ouroboros.Consensus.Protocol.Praos.Common import qualified Ouroboros.Consensus.Storage.ChainDB as ChainDB @@ -101,6 +103,8 @@ instance ( LogFormatting (Header blk) , InspectLedger blk , HasIssuer blk , LogFormatting (ReasonForSwitch (TiebreakerView (BlockProtocol blk))) + , Show (PerasError blk) + , Show (PerasCert blk) ) => LogFormatting (ChainDB.TraceEvent blk) where forHuman ChainDB.TraceLastShutdownUnclean = "ChainDB is not clean. Validating all immutable chunks" @@ -533,6 +537,7 @@ instance ( LogFormatting (Header blk) , LedgerSupportsProtocol blk , InspectLedger blk , HasIssuer blk + , Show (PerasError blk) ) => LogFormatting (ChainDB.TraceAddBlockEvent blk) where forHuman (ChainDB.IgnoreBlockOlderThanImmTip pt) = "Ignoring block older than ImmTip: " <> renderRealPointAsPhrase pt @@ -1201,7 +1206,7 @@ instance MetaTrace (ChainDB.TraceGCEvent blk) where -- -- TraceInitChainSelEvent -- -------------------------------------------------------------------------------- -instance (ConvertRawHash blk, ConvertRawHash (Header blk), LedgerSupportsProtocol blk) +instance (ConvertRawHash blk, ConvertRawHash (Header blk), LedgerSupportsProtocol blk, Show (PerasError blk)) => LogFormatting (ChainDB.TraceInitChainSelEvent blk) where forHuman (ChainDB.InitChainSelValidation v) = forHuman v forHuman ChainDB.InitialChainSelected{} = @@ -1284,7 +1289,9 @@ instance MetaTrace (ChainDB.TraceInitChainSelEvent blk) where instance ( LedgerSupportsProtocol blk , ConvertRawHash (Header blk) , ConvertRawHash blk - , LogFormatting (RealPoint blk)) + , LogFormatting (RealPoint blk) + , Show (PerasError blk) + ) => LogFormatting (ChainDB.TraceValidationEvent blk) where forHuman (ChainDB.InvalidBlock err pt) = "Invalid block " <> renderRealPointAsPhrase pt <> ": " <> showT err @@ -1682,7 +1689,7 @@ instance MetaTrace (PerasVoteDB.TraceEvent blk) where documentFor (Namespace _ ["GarbageCollected"]) = Just "GarbageCollected" documentFor _ = Nothing -instance StandardHash blk => LogFormatting (PerasVoteDB.TraceEvent blk) where +instance Show (PerasCert blk) => LogFormatting (PerasVoteDB.TraceEvent blk) where forHuman (PerasVoteDB.AddVote voteId _vote result) = "Peras vote " <> Text.pack (show voteId) <> ": " <> Text.pack (show result) forHuman (PerasVoteDB.GarbageCollected slotNo) = @@ -1805,6 +1812,8 @@ instance MetaTrace (LedgerDB.TraceEvent blk) where instance ( StandardHash blk , ConvertRawHash blk) => LogFormatting (LedgerDB.TraceSnapshotEvent blk) where + forHuman (LedgerDB.ConfiguredSnapshotPolicy info) = + "Configured snapshot policy: " <> showT info <> "; mismatches: " <> showT (LedgerDB.snapshotPolicyMismatches info) forHuman (LedgerDB.SnapshotRequestDelayed _snapshotRequestTime delayBeforeSnapshotting slots) = Text.unwords [ "Scheduling to take ledger state snapshots at slots " , showT (NonEmpty.toList slots) @@ -1850,6 +1859,30 @@ instance ( StandardHash blk " Snapshot was created for a different backend. Convert it with `snapshot-converter`." _ -> "" + forMachine _dtals (LedgerDB.ConfiguredSnapshotPolicy info) = + mconcat $ + [ "kind" .= String "ConfiguredSnapshotPolicy" + , "mismatches" .= toJSON (map show (LedgerDB.snapshotPolicyMismatches info)) + , "securityParam" .= unNonZero (maxRollbacks (LedgerDB.spiSecurityParam info)) + , "slotLengthSeconds" .= snapshotPolicySeconds (getSlotLength (LedgerDB.spiSlotLengthAtTip info)) + , "numOfDiskSnapshots" + .= LedgerDB.getNumOfDiskSnapshots (LedgerDB.spaNum (LedgerDB.spiArgs info)) + ] + <> case ( LedgerDB.spaFrequency (LedgerDB.spiArgs info) + , LedgerDB.spiIntervalSlots info + ) of + (LedgerDB.SnapshotFrequency fargs, Just intervalSlots) -> + [ "intervalSlots" .= unNonZero intervalSlots + , "intervalSeconds" + .= toJSON (snapshotPolicySeconds <$> LedgerDB.snapshotPolicyIntervalTime info) + , "offsetSlots" .= unSlotNo (LedgerDB.sfaOffset fargs) + , "rateLimitSeconds" .= snapshotPolicySeconds (LedgerDB.sfaRateLimit fargs) + , "writeDelayMinSeconds" + .= snapshotPolicySeconds (LedgerDB.minimumDelay (LedgerDB.sfaDelaySnapshotRange fargs)) + , "writeDelayMaxSeconds" + .= snapshotPolicySeconds (LedgerDB.maximumDelay (LedgerDB.sfaDelaySnapshotRange fargs)) + ] + _ -> ["snapshotsDisabled" .= True] forMachine _dtals (LedgerDB.SnapshotRequestDelayed snapshotRequestTime delayBeforeSnapshotting slots) = mconcat [ "kind" .= String "SnapshotRequestDelayed" , "requestTime" .= show snapshotRequestTime @@ -1874,12 +1907,17 @@ instance ( StandardHash blk , "failure" .= show failure ] instance MetaTrace (LedgerDB.TraceSnapshotEvent blk) where + namespaceFor (LedgerDB.ConfiguredSnapshotPolicy info) + | null (LedgerDB.snapshotPolicyMismatches info) = Namespace [] ["ConfiguredSnapshotPolicy"] + | otherwise = Namespace [] ["ImplausibleSnapshotPolicy"] namespaceFor LedgerDB.SnapshotRequestDelayed {} = Namespace [] ["SnapshotRequestDelayed"] namespaceFor LedgerDB.SnapshotRequestCompleted {} = Namespace [] ["SnapshotRequestCompleted"] namespaceFor LedgerDB.TookSnapshot {} = Namespace [] ["TookSnapshot"] namespaceFor LedgerDB.DeletedSnapshot {} = Namespace [] ["DeletedSnapshot"] namespaceFor LedgerDB.InvalidSnapshot {} = Namespace [] ["InvalidSnapshot"] + severityFor (Namespace _ ["ConfiguredSnapshotPolicy"]) _ = Just Info + severityFor (Namespace _ ["ImplausibleSnapshotPolicy"]) _ = Just Warning severityFor (Namespace _ ["SnapshotRequestDelayed"]) _ = Just Debug severityFor (Namespace _ ["SnapshotRequestCompleted"]) _ = Just Debug severityFor (Namespace _ ["TookSnapshot"]) _ = Just Info @@ -1887,6 +1925,10 @@ instance MetaTrace (LedgerDB.TraceSnapshotEvent blk) where severityFor (Namespace _ ["InvalidSnapshot"]) _ = Just Error severityFor _ _ = Nothing + documentFor (Namespace _ ["ConfiguredSnapshotPolicy"]) = Just + "The configured snapshot interval, slot length, write delays and rate limit when opening the ledger database." + documentFor (Namespace _ ["ImplausibleSnapshotPolicy"]) = Just + "The configured write delay or rate limit is at least as long as the snapshot interval at the current slot length." documentFor (Namespace _ ["TookSnapshot"]) = Just $ mconcat [ "A snapshot is being written to disk. Two events will be traced, one" , " for when the node starts taking the snapshot and another one for" @@ -1906,13 +1948,18 @@ instance MetaTrace (LedgerDB.TraceSnapshotEvent blk) where documentFor _ = Nothing allNamespaces = - [ Namespace [] ["TookSnapshot"] + [ Namespace [] ["ConfiguredSnapshotPolicy"] + , Namespace [] ["ImplausibleSnapshotPolicy"] + , Namespace [] ["TookSnapshot"] , Namespace [] ["DeletedSnapshot"] , Namespace [] ["InvalidSnapshot"] , Namespace [] ["SnapshotRequestDelayed"] , Namespace [] ["SnapshotRequestCompleted"] ] +snapshotPolicySeconds :: Real a => a -> Double +snapshotPolicySeconds = realToFrac + -------------------------------------------------------------------------------- -- LedgerDB TraceReplayEvent -------------------------------------------------------------------------------- @@ -2885,16 +2932,33 @@ instance ( StandardHash blk instance ( LogFormatting (LedgerError blk) - , LogFormatting (HeaderError blk)) + , LogFormatting (HeaderError blk) + , Show (PerasError blk)) => LogFormatting (ExtValidationError blk) where forMachine dtal (ExtValidationErrorLedger err) = forMachine dtal err forMachine dtal (ExtValidationErrorHeader err) = forMachine dtal err + forMachine _dtal (ExtValidationErrorPerasEpochContextResolver err) = + mconcat [ "kind" .= String "ExtValidationErrorPerasEpochContextResolver" + , "error" .= String (showT err) + ] + forMachine _dtal (ExtValidationErrorPerasCertInBlock err) = + mconcat [ "kind" .= String "ExtValidationErrorPerasCertInBlock" + , "error" .= String (showT err) + ] + forHuman (ExtValidationErrorLedger err) = forHuman err forHuman (ExtValidationErrorHeader err) = forHuman err + forHuman (ExtValidationErrorPerasEpochContextResolver err) = + "Could not resolve the Peras epoch context: " <> showT err + forHuman (ExtValidationErrorPerasCertInBlock err) = + "Invalid Peras certificate in block: " <> showT err + asMetrics (ExtValidationErrorLedger err) = asMetrics err asMetrics (ExtValidationErrorHeader err) = asMetrics err + asMetrics ExtValidationErrorPerasEpochContextResolver{} = [] + asMetrics ExtValidationErrorPerasCertInBlock{} = [] instance (Show (PBFT.PBftVerKeyHash c)) => LogFormatting (PBFT.PBftValidationErr c) where diff --git a/cardano-node/src/Cardano/Node/Tracing/Tracers/Consensus.hs b/cardano-node/src/Cardano/Node/Tracing/Tracers/Consensus.hs index 5f80cd8db99..ee8a2399665 100644 --- a/cardano-node/src/Cardano/Node/Tracing/Tracers/Consensus.hs +++ b/cardano-node/src/Cardano/Node/Tracing/Tracers/Consensus.hs @@ -44,7 +44,8 @@ import Ouroboros.Consensus.Genesis.Governor (DensityBounds (..), GDDDe import Ouroboros.Consensus.Ledger.Extended (ExtValidationError) import Ouroboros.Consensus.Ledger.Inspect (LedgerEvent (..), LedgerUpdate, LedgerWarning) import Ouroboros.Consensus.Ledger.SupportsMempool (ApplyTxErr, ByteSize32 (..), GenTxId, - HasTxId, LedgerSupportsMempool, txForgetValidated, txId) + HasTxId, LedgerSupportsMempool, TxMeasurePhase1Metrics (..), + TxMeasurePhase2Metrics (..), txForgetValidated, txId) import Ouroboros.Consensus.Ledger.SupportsProtocol import Ouroboros.Consensus.Mempool (MempoolRejectionDetails (..), MempoolSize (..), TraceEventMempool (..), jsonMempoolRejectionDetails) @@ -60,10 +61,6 @@ import Ouroboros.Consensus.MiniProtocol.ObjectDiffusion.Inbound (NumOb TraceObjectDiffusionInbound (..)) import Ouroboros.Consensus.MiniProtocol.ObjectDiffusion.Outbound (TraceObjectDiffusionOutbound (..)) -import Ouroboros.Consensus.MiniProtocol.ObjectDiffusion.PerasCert - (TracePerasCertDiffusionInbound, TracePerasCertDiffusionOutbound) -import Ouroboros.Consensus.MiniProtocol.ObjectDiffusion.PerasVote - (TracePerasVoteDiffusionInbound, TracePerasVoteDiffusionOutbound) import Ouroboros.Consensus.Node.GSM import Ouroboros.Consensus.Node.Run (SerialiseNodeToNodeConstraints, estimateBlockSize) import Ouroboros.Consensus.Node.Tracers @@ -1097,7 +1094,7 @@ instance .= map ( \(tx, err) -> Aeson.object $ - [ "tx" .= forMachine dtal (txForgetValidated tx) + [ "tx" .= forMachine dtal tx ] <> [ "err" .= forMachine dtal err | dtal >= DDetailed @@ -1110,7 +1107,7 @@ instance mconcat [ "kind" .= String "TraceMempoolManuallyRemovedTxs" , "txsRemoved" .= map (String . renderTxIdForDetails dtal) (toList txs0) - , "txsInvalidated" .= map (forMachine dtal . txForgetValidated) txs1 + , "txsInvalidated" .= map (forMachine dtal) txs1 , "mempoolSize" .= forMachine dtal mpSz ] forMachine dtal (TraceMempoolSyncNotNeeded t) = @@ -1134,6 +1131,12 @@ instance [ "kind" .= String "TraceMempoolTipMovedBetweenSTMBlocks" ] + forMachine _dtal (TraceMempoolCapacityChanged capBefore capAfter) = + mconcat [ "kind" .= String "TraceMempoolCapacityChanged" + , "capacityBefore" .= jsonTxMeasure capBefore + , "capacityAfter" .= jsonTxMeasure capAfter + ] + asMetrics (TraceMempoolAddedTx _tx _mpSzBefore mpSz) = [ IntM "txsInMempool" (fromIntegral $ msNumTxs mpSz) , IntM "mempoolBytes" (fromIntegral . unByteSize32 . msNumBytes $ mpSz) @@ -1166,6 +1169,15 @@ instance asMetrics TraceMempoolAttemptingAdd {} = [] asMetrics TraceMempoolTipMovedBetweenSTMBlocks {} = [] + asMetrics TraceMempoolCapacityChanged {} = [] + +jsonTxMeasure :: (TxMeasurePhase1Metrics m, TxMeasurePhase2Metrics m) => m -> Aeson.Value +jsonTxMeasure measure = Aeson.object + [ "txSizeBytes" .= unByteSize32 (txMeasureMetricTxSizeBytes measure) + , "exUnitsMemory" .= txMeasureMetricExUnitsMemory measure + , "exUnitsSteps" .= txMeasureMetricExUnitsSteps measure + , "refScriptsSizeBytes" .= unByteSize32 (txMeasureMetricRefScriptsSizeBytes measure) + ] instance LogFormatting MempoolSize where forMachine _dtal MempoolSize{msNumTxs, msNumBytes} = @@ -1184,6 +1196,7 @@ instance MetaTrace (TraceEventMempool blk) where namespaceFor TraceMempoolSyncNotNeeded {} = Namespace [] ["SyncNotNeeded"] namespaceFor TraceMempoolAttemptingAdd {} = Namespace [] ["AttemptAdd"] namespaceFor TraceMempoolTipMovedBetweenSTMBlocks {} = Namespace [] ["TipMovedBetweenSTMBlocks"] + namespaceFor TraceMempoolCapacityChanged {} = Namespace [] ["CapacityChanged"] severityFor (Namespace _ ["AddedTx"]) _ = Just Info @@ -1194,6 +1207,7 @@ instance MetaTrace (TraceEventMempool blk) where severityFor (Namespace _ ["SyncNotNeeded"]) _ = Just Debug severityFor (Namespace _ ["AttemptAdd"]) _ = Just Debug severityFor (Namespace [] ["TipMovedBetweenSTMBlocks"]) _ = Just Debug + severityFor (Namespace _ ["CapacityChanged"]) _ = Just Debug severityFor _ _ = Nothing metricsDocFor (Namespace _ ["AddedTx"]) = @@ -1241,6 +1255,8 @@ instance MetaTrace (TraceEventMempool blk) where "Mempool is about to try to validate and add a transaction." documentFor (Namespace _ ["TipMovedBetweenSTMBlocks"]) = Just "LedgerDB moved to an alternative fork between two reads during re-sync." + documentFor (Namespace _ ["CapacityChanged"]) = Just + "The mempool capacity has changed when re-syncing the mempool to the latest tip" documentFor _ = Nothing allNamespaces = @@ -1252,6 +1268,7 @@ instance MetaTrace (TraceEventMempool blk) where , Namespace [] ["SyncNotNeeded"] , Namespace [] ["AttemptAdd"] , Namespace [] ["TipMovedBetweenSTMBlocks"] + , Namespace [] ["CapacityChanged"] ] -------------------------------------------------------------------------------- @@ -1272,7 +1289,9 @@ instance ( tx ~ GenTx blk , Show (TxId (GenTx blk)) , LogFormatting (CannotForge blk) , LogFormatting (ExtValidationError blk) - , LogFormatting (ForgeStateUpdateError blk)) + , LogFormatting (ForgeStateUpdateError blk) + , Show (PerasError blk) + ) => LogFormatting (TraceForgeEvent blk) where forMachine _dtal (TraceStartLeadershipCheck slotNo) = mconcat @@ -2379,22 +2398,22 @@ allNamespacesObjectDiffusionInbound = , Namespace [] ["TraceObjectDiffusionInboundCannotRequestMoreObjects"] ] -instance LogFormatting (TracePerasCertDiffusionInbound blk) where +instance LogFormatting (TraceObjectDiffusionInbound PerasRoundNo object) where forMachine _ = forMachineObjectDiffusionInbound asMetrics = asMetricsObjectDiffusionInbound perasCertMetricsPrefix -instance MetaTrace (TracePerasCertDiffusionInbound blk) where +instance MetaTrace (TraceObjectDiffusionInbound PerasRoundNo object) where namespaceFor = namespaceForObjectDiffusionInbound severityFor ns _ = severityForObjectDiffusionInbound ns documentFor _ = Nothing metricsDocFor = metricsDocForObjectDiffusionInbound perasCertMetricsPrefix allNamespaces = allNamespacesObjectDiffusionInbound -instance LogFormatting (TracePerasVoteDiffusionInbound blk) where +instance LogFormatting (TraceObjectDiffusionInbound PerasVoteId object) where forMachine _ = forMachineObjectDiffusionInbound asMetrics = asMetricsObjectDiffusionInbound perasVoteMetricsPrefix -instance MetaTrace (TracePerasVoteDiffusionInbound blk) where +instance MetaTrace (TraceObjectDiffusionInbound PerasVoteId object) where namespaceFor = namespaceForObjectDiffusionInbound severityFor ns _ = severityForObjectDiffusionInbound ns documentFor _ = Nothing @@ -2485,26 +2504,230 @@ allNamespacesObjectDiffusionOutbound = , Namespace [] ["TraceObjectDiffusionOutboundTerminated"] ] -instance Show (PerasCert blk) - => LogFormatting (TracePerasCertDiffusionOutbound blk) where +instance Show object + => LogFormatting (TraceObjectDiffusionOutbound PerasRoundNo object) where forMachine _ = forMachineObjectDiffusionOutbound asMetrics = asMetricsObjectDiffusionOutbound perasCertMetricsPrefix -instance MetaTrace (TracePerasCertDiffusionOutbound blk) where +instance MetaTrace (TraceObjectDiffusionOutbound PerasRoundNo object) where namespaceFor = namespaceForObjectDiffusionOutbound severityFor ns _ = severityForObjectDiffusionOutbound ns documentFor _ = Nothing metricsDocFor = metricsDocForObjectDiffusionOutbound perasCertMetricsPrefix allNamespaces = allNamespacesObjectDiffusionOutbound -instance Show (PerasVote blk) - => LogFormatting (TracePerasVoteDiffusionOutbound blk) where +instance Show object + => LogFormatting (TraceObjectDiffusionOutbound PerasVoteId object) where forMachine _ = forMachineObjectDiffusionOutbound asMetrics = asMetricsObjectDiffusionOutbound perasVoteMetricsPrefix -instance MetaTrace (TracePerasVoteDiffusionOutbound blk) where +instance MetaTrace (TraceObjectDiffusionOutbound PerasVoteId object) where namespaceFor = namespaceForObjectDiffusionOutbound severityFor ns _ = severityForObjectDiffusionOutbound ns documentFor _ = Nothing metricsDocFor = metricsDocForObjectDiffusionOutbound perasVoteMetricsPrefix allNamespaces = allNamespacesObjectDiffusionOutbound + +-------------------------------------------------------------------------------- +-- Peras cert inclusion Tracer +-------------------------------------------------------------------------------- + +instance + Show (PerasCert blk) => + LogFormatting (TracePerasCertInclusionEvent blk) + where + forMachine _dtal = \case + TracePerasCertInclusionNoCertToInclude slotNo -> + mconcat + [ "kind" .= String "TracePerasCertInclusionNoCertToInclude" + , "slot" .= unSlotNo slotNo + ] + TracePerasCertInclusionRulesDecision slotNo roundNo decision -> + mconcat + [ "kind" .= String "TracePerasCertInclusionRulesDecision" + , "slot" .= unSlotNo slotNo + , "round" .= unPerasRoundNo roundNo + , "decision" .= String (showT decision) + ] + + forHuman = \case + TracePerasCertInclusionNoCertToInclude slotNo -> + "No Peras certificate available to include in slot " + <> showT (unSlotNo slotNo) + TracePerasCertInclusionRulesDecision slotNo roundNo decision -> + "Peras certificate inclusion rules decided " + <> showT decision + <> " in slot " + <> showT (unSlotNo slotNo) + <> ", round " + <> showT (unPerasRoundNo roundNo) + +instance MetaTrace (TracePerasCertInclusionEvent blk) where + namespaceFor TracePerasCertInclusionNoCertToInclude{} = + Namespace [] ["NoCertToInclude"] + namespaceFor TracePerasCertInclusionRulesDecision{} = + Namespace [] ["RulesDecision"] + + severityFor (Namespace _ ["NoCertToInclude"]) _ = Just Debug + severityFor (Namespace _ ["RulesDecision"]) _ = Just Info + severityFor _ _ = Nothing + + documentFor (Namespace _ ["NoCertToInclude"]) = + Just + "There is no Peras certificate available to include in the block being forged." + documentFor (Namespace _ ["RulesDecision"]) = + Just + "The decision taken by the Peras certificate inclusion rules, i.e. whether a\ + \ certificate is to be included in the block being forged, and why." + documentFor _ = Nothing + + allNamespaces = + [ Namespace [] ["NoCertToInclude"] + , Namespace [] ["RulesDecision"] + ] + +-------------------------------------------------------------------------------- +-- Peras vote forging Tracer +-------------------------------------------------------------------------------- + +instance + ( StandardHash blk + , Show (PerasVote blk) + , Show (PerasCert blk) + ) => + LogFormatting (TracePerasVoteForgingEvent blk) + where + forMachine _dtal = \case + TracePerasVotingNoVoteAfterFirstSlotInRound roundNo slotInRound -> + mconcat + [ "kind" .= String "TracePerasVotingNoVoteAfterFirstSlotInRound" + , "round" .= unPerasRoundNo roundNo + , "slotInRound" .= slotInRound + ] + TracePerasVotingNotAVoterInRound roundNo -> + mconcat + [ "kind" .= String "TracePerasVotingNotAVoterInRound" + , "round" .= unPerasRoundNo roundNo + ] + TracePerasVotingRulesDecision roundNo decision -> + mconcat + [ "kind" .= String "TracePerasVotingRulesDecision" + , "round" .= unPerasRoundNo roundNo + , "decision" .= String (showT decision) + ] + TracePerasVotingForgedVote roundNo vote -> + mconcat + [ "kind" .= String "TracePerasVotingForgedVote" + , "round" .= unPerasRoundNo roundNo + , "vote" .= String (showT vote) + ] + TracePerasVotingAddVoteResult roundNo result -> + mconcat + [ "kind" .= String "TracePerasVotingAddVoteResult" + , "round" .= unPerasRoundNo roundNo + , "result" .= String (showT result) + ] + TracePerasVotingAddCertChainSelOutcome roundNo outcome -> + mconcat + [ "kind" .= String "TracePerasVotingAddCertChainSelOutcome" + , "round" .= unPerasRoundNo roundNo + , "outcome" .= String (showT outcome) + ] + TracePerasVotingCantReadEnv err -> + mconcat + [ "kind" .= String "TracePerasVotingCantReadEnv" + , "error" .= String (Text.pack err) + ] + + forHuman = \case + TracePerasVotingNoVoteAfterFirstSlotInRound roundNo slotInRound -> + "Not voting in Peras round " + <> showT (unPerasRoundNo roundNo) + <> ": past the first slot of the round (slot " + <> showT slotInRound + <> ")" + TracePerasVotingNotAVoterInRound roundNo -> + "Not a voter in Peras round " <> showT (unPerasRoundNo roundNo) + TracePerasVotingRulesDecision roundNo decision -> + "Peras voting rules decided " + <> showT decision + <> " in round " + <> showT (unPerasRoundNo roundNo) + TracePerasVotingForgedVote roundNo vote -> + "Forged Peras vote in round " + <> showT (unPerasRoundNo roundNo) + <> ": " + <> showT vote + TracePerasVotingAddVoteResult roundNo result -> + "Adding the Peras vote of round " + <> showT (unPerasRoundNo roundNo) + <> " to the vote DB: " + <> showT result + TracePerasVotingAddCertChainSelOutcome roundNo outcome -> + "Adding the Peras certificate of round " + <> showT (unPerasRoundNo roundNo) + <> " to the cert DB: " + <> showT outcome + TracePerasVotingCantReadEnv err -> + "Could not read the Peras voting environment: " <> Text.pack err + +instance MetaTrace (TracePerasVoteForgingEvent blk) where + namespaceFor TracePerasVotingNoVoteAfterFirstSlotInRound{} = + Namespace [] ["NoVoteAfterFirstSlotInRound"] + namespaceFor TracePerasVotingNotAVoterInRound{} = + Namespace [] ["NotAVoterInRound"] + namespaceFor TracePerasVotingRulesDecision{} = + Namespace [] ["RulesDecision"] + namespaceFor TracePerasVotingForgedVote{} = + Namespace [] ["ForgedVote"] + namespaceFor TracePerasVotingAddVoteResult{} = + Namespace [] ["AddVoteResult"] + namespaceFor TracePerasVotingAddCertChainSelOutcome{} = + Namespace [] ["AddCertChainSelOutcome"] + namespaceFor TracePerasVotingCantReadEnv{} = + Namespace [] ["CantReadEnv"] + + severityFor (Namespace _ ["NoVoteAfterFirstSlotInRound"]) _ = Just Debug + severityFor (Namespace _ ["NotAVoterInRound"]) _ = Just Debug + severityFor (Namespace _ ["RulesDecision"]) _ = Just Info + severityFor (Namespace _ ["ForgedVote"]) _ = Just Info + severityFor (Namespace _ ["AddVoteResult"]) _ = Just Info + severityFor (Namespace _ ["AddCertChainSelOutcome"]) _ = Just Info + severityFor (Namespace _ ["CantReadEnv"]) _ = Just Error + severityFor _ _ = Nothing + + documentFor (Namespace _ ["NoVoteAfterFirstSlotInRound"]) = + Just + "Votes are only cast in the first slot of a Peras round, and this slot is not it." + documentFor (Namespace _ ["NotAVoterInRound"]) = + Just + "This node was not elected to the voting committee of the current Peras round." + documentFor (Namespace _ ["RulesDecision"]) = + Just + "The decision taken by the Peras voting rules, i.e. whether a vote is to be\ + \ cast in the current round, and why." + documentFor (Namespace _ ["ForgedVote"]) = + Just + "A Peras vote was forged for the current round." + documentFor (Namespace _ ["AddVoteResult"]) = + Just + "The result of adding the freshly forged vote to the Peras vote DB, which is\ + \ where it may complete a quorum and yield a new certificate." + documentFor (Namespace _ ["AddCertChainSelOutcome"]) = + Just + "The outcome of handing a certificate generated by the freshly forged vote to\ + \ chain selection." + documentFor (Namespace _ ["CantReadEnv"]) = + Just + "The Peras voting environment could not be read." + documentFor _ = Nothing + + allNamespaces = + [ Namespace [] ["NoVoteAfterFirstSlotInRound"] + , Namespace [] ["NotAVoterInRound"] + , Namespace [] ["RulesDecision"] + , Namespace [] ["ForgedVote"] + , Namespace [] ["AddVoteResult"] + , Namespace [] ["AddCertChainSelOutcome"] + , Namespace [] ["CantReadEnv"] + ] diff --git a/cardano-node/src/Cardano/Node/Tracing/Tracers/Rpc.hs b/cardano-node/src/Cardano/Node/Tracing/Tracers/Rpc.hs index 518192b7f84..197ab378a54 100644 --- a/cardano-node/src/Cardano/Node/Tracing/Tracers/Rpc.hs +++ b/cardano-node/src/Cardano/Node/Tracing/Tracers/Rpc.hs @@ -57,6 +57,8 @@ instance LogFormatting TraceRpc where TraceRpcSubmitSpan s -> [spanToObject s] TraceRpcEvalTxDecodingError _ -> [] TraceRpcEvalTxSpan s -> [spanToObject s] + TraceRpcReadMempoolSpan s -> [spanToObject s] + TraceRpcWatchMempoolSpan s -> [spanToObject s] TraceRpcSync syncTrace -> ["kind" .= String "SyncService"] <> case syncTrace of @@ -86,6 +88,8 @@ instance LogFormatting TraceRpc where TraceRpcQuery (TraceRpcQueryReadEraSummarySpan (SpanBegin _)) -> [CounterM "rpc.request.QueryService.ReadEraSummary" Nothing] TraceRpcSubmit (TraceRpcSubmitSpan (SpanBegin _)) -> [CounterM "rpc.request.SubmitService.SubmitTx" Nothing] TraceRpcSubmit (TraceRpcEvalTxSpan (SpanBegin _)) -> [CounterM "rpc.request.SubmitService.EvalTx" Nothing] + TraceRpcSubmit (TraceRpcReadMempoolSpan (SpanBegin _)) -> [CounterM "rpc.request.SubmitService.ReadMempool" Nothing] + TraceRpcSubmit (TraceRpcWatchMempoolSpan (SpanBegin _)) -> [CounterM "rpc.request.SubmitService.WatchMempool" Nothing] TraceRpcSync (TraceRpcFetchBlockSpan (SpanBegin _)) -> [CounterM "rpc.request.SyncService.FetchBlock" Nothing] TraceRpcSync (TraceRpcReadTipSpan (SpanBegin _)) -> [CounterM "rpc.request.SyncService.ReadTip" Nothing] TraceRpcSync (TraceRpcFollowTipSpan (SpanBegin _)) -> [CounterM "rpc.request.SyncService.FollowTip" Nothing] @@ -113,6 +117,8 @@ instance MetaTrace TraceRpc where TraceRpcSubmitSpan _ -> ["SubmitTx", "Span"] TraceRpcEvalTxDecodingError _ -> ["EvalTxDecodingError"] TraceRpcEvalTxSpan _ -> ["EvalTx", "Span"] + TraceRpcReadMempoolSpan _ -> ["ReadMempool", "Span"] + TraceRpcWatchMempoolSpan _ -> ["WatchMempool", "Span"] TraceRpcSync syncTrace -> "SyncService" : case syncTrace of @@ -137,6 +143,8 @@ instance MetaTrace TraceRpc where ["QueryService", "ReadEraSummary", "Span"] -> Just Debug ["SubmitService", "SubmitTx", "Span"] -> Just Debug ["SubmitService", "EvalTx", "Span"] -> Just Debug + ["SubmitService", "ReadMempool", "Span"] -> Just Debug + ["SubmitService", "WatchMempool", "Span"] -> Just Debug ["SubmitService", "N2cConnectionError"] -> Just Warning -- this is a more serious error, this shouldn't happen ["SubmitService", "TxDecodingError"] -> Just Debug -- request error ["SubmitService", "TxValidationError"] -> Just Debug -- request error @@ -160,6 +168,8 @@ instance MetaTrace TraceRpc where ["QueryService", "ReadEraSummary", "Span"] -> Just "Span for the ReadEraSummary UTXORPC method." ["SubmitService", "SubmitTx", "Span"] -> Just "Span for the SubmitTx UTXORPC method." ["SubmitService", "EvalTx", "Span"] -> Just "Span for the EvalTx UTXORPC method." + ["SubmitService", "ReadMempool", "Span"] -> Just "Span for the ReadMempool UTXORPC method." + ["SubmitService", "WatchMempool", "Span"] -> Just "Span for the WatchMempool UTXORPC method." ["SubmitService", "N2cConnectionError"] -> Just "Node connection error. This should not happen, as this means that there is an issue in cardano-rpc configuration." @@ -190,6 +200,10 @@ instance MetaTrace TraceRpc where [("rpc.request.SubmitService.SubmitTx", "Span for the SubmitTx UTXORPC method.")] ["SubmitService", "EvalTx", "Span"] -> [("rpc.request.SubmitService.EvalTx", "Span for the EvalTx UTXORPC method.")] + ["SubmitService", "ReadMempool", "Span"] -> + [("rpc.request.SubmitService.ReadMempool", "Span for the ReadMempool UTXORPC method.")] + ["SubmitService", "WatchMempool", "Span"] -> + [("rpc.request.SubmitService.WatchMempool", "Span for the WatchMempool UTXORPC method.")] ["SyncService", "FetchBlock", "Span"] -> [("rpc.request.SyncService.FetchBlock", "Span for the FetchBlock SyncService method.")] ["SyncService", "ReadTip", "Span"] -> @@ -207,6 +221,8 @@ instance MetaTrace TraceRpc where , ["QueryService", "SearchUtxos", "Span"] , ["SubmitService", "SubmitTx", "Span"] , ["SubmitService", "EvalTx", "Span"] + , ["SubmitService", "ReadMempool", "Span"] + , ["SubmitService", "WatchMempool", "Span"] , ["SubmitService", "N2cConnectionError"] , ["SubmitService", "TxDecodingError"] , ["SubmitService", "TxValidationError"] diff --git a/cardano-node/src/Cardano/Node/Tracing/Tracers/Startup.hs b/cardano-node/src/Cardano/Node/Tracing/Tracers/Startup.hs index 37a362ec3ad..5eeee5adc66 100644 --- a/cardano-node/src/Cardano/Node/Tracing/Tracers/Startup.hs +++ b/cardano-node/src/Cardano/Node/Tracing/Tracers/Startup.hs @@ -549,13 +549,6 @@ instance MetaTrace (StartupTrace blk) where nodeToClientVersionToInt :: NodeToClientVersion -> Int nodeToClientVersionToInt = \case - NodeToClientV_16 -> 16 - NodeToClientV_17 -> 17 - NodeToClientV_18 -> 18 - NodeToClientV_19 -> 19 - NodeToClientV_20 -> 20 - NodeToClientV_21 -> 21 - NodeToClientV_22 -> 22 NodeToClientV_23 -> 23 nodeToNodeVersionToInt :: NodeToNodeVersion -> Int diff --git a/cardano-node/test/Test/Cardano/Config/Dijkstra.hs b/cardano-node/test/Test/Cardano/Config/Dijkstra.hs new file mode 100644 index 00000000000..4b657b9fd7a --- /dev/null +++ b/cardano-node/test/Test/Cardano/Config/Dijkstra.hs @@ -0,0 +1,30 @@ +{-# LANGUAGE OverloadedStrings #-} + +module Test.Cardano.Config.Dijkstra (tests) where + +import Cardano.Api (dijkstraGenesisDefaults) + +import Cardano.Node.Protocol.Dijkstra (emptyDijkstraGenesis, readGenesisMaybe) + +import Control.Monad.Trans.Except (runExceptT) +import qualified Data.Aeson as Aeson + +import Hedgehog (Property, (===)) +import qualified Hedgehog as H +import Hedgehog.Extras.Test.Base (propertyOnce) + +-- The fallback must contain every integration field and the V4 cost model, +-- rather than an obsolete partial UpgradeDijkstraPParams record. +hprop_completeFallbackGenesis :: Property +hprop_completeFallbackGenesis = propertyOnce $ do + emptyDijkstraGenesis === dijkstraGenesisDefaults + Aeson.eitherDecode (Aeson.encode emptyDijkstraGenesis) === Right emptyDijkstraGenesis + result <- H.evalIO $ runExceptT $ readGenesisMaybe Nothing Nothing + case result of + Left err -> H.annotateShow err >> H.failure + Right (genesis, _) -> genesis === dijkstraGenesisDefaults + +tests :: IO Bool +tests = H.checkSequential $ H.Group "Test.Config.Dijkstra" + [ ("complete fallback genesis", hprop_completeFallbackGenesis) + ] diff --git a/cardano-node/test/Test/Cardano/Tracing/Receiving.hs b/cardano-node/test/Test/Cardano/Tracing/Receiving.hs new file mode 100644 index 00000000000..b042eb78ad8 --- /dev/null +++ b/cardano-node/test/Test/Cardano/Tracing/Receiving.hs @@ -0,0 +1,278 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TypeApplications #-} + +module Test.Cardano.Tracing.Receiving (tests) where + +import Cardano.Api (ByronEra, DijkstraEra, NetworkMagic (..), + ShelleyBasedEra (ShelleyBasedEraDijkstra), textShow) +import qualified Cardano.Api.Consensus as Api +import Cardano.Api.Serialise.SerialiseUsing (UsingRawBytesHex (..)) + +import qualified Cardano.Crypto.Hash.Class as Crypto +import Cardano.Ledger.Alonzo.Scripts (AsItem (..)) +import Cardano.Ledger.BaseTypes (Mismatch (..), unsafeNonZero) +import Cardano.Ledger.Coin (Coin (..)) +import qualified Cardano.Ledger.Dijkstra as Ledger +import qualified Cardano.Ledger.Dijkstra.Rules as Dijkstra +import Cardano.Ledger.Dijkstra.Scripts (DijkstraPlutusPurpose (..)) +import Cardano.Ledger.Hashes (ScriptHash (..)) +import Cardano.Ledger.Keys (KeyHash (..)) +import Cardano.Logging (DetailLevel (..), LogFormatting (..), MetaTrace (..), Metric (..), + Namespace (..), SeverityS (..)) +import Cardano.Node.Startup (StartupTrace (..)) +import Cardano.Node.Tracing.Consistency (getAllNamespaces) +import Cardano.Node.Tracing.Era.Shelley () +import Cardano.Node.Tracing.Render (renderMissingRedeemers, renderScriptHash) +import Cardano.Node.Tracing.Tracers.ChainDB () +import Cardano.Node.Tracing.Tracers.Consensus () +import Cardano.Node.Tracing.Tracers.Rpc () +import Cardano.Node.Tracing.Tracers.Startup (ppStartupInfoTrace) +import Cardano.Rpc.Server (TraceRpc (..), TraceRpcSubmit (..), TraceSpanEvent (..)) +import Cardano.Slotting.Time (mkSlotLength) +import Ouroboros.Consensus.Block (VoteWeight (..)) +import Ouroboros.Consensus.Ledger.Extended (ExtValidationError (..)) +import Ouroboros.Consensus.Ledger.SupportsMempool (ByteSize32 (..), IgnoringOverflow (..), + TrivialTxMeasurePhase2 (..), TxMeasure (..)) +import Ouroboros.Consensus.Mempool (TraceEventMempool (..)) +import Ouroboros.Consensus.Node.NetworkProtocolVersion + (SupportedNetworkProtocolVersion (..)) +import Ouroboros.Consensus.Node.Tracers (TracePerasCertInclusionEvent (..), + TracePerasVoteForgingEvent (..)) +import Ouroboros.Consensus.Peras.Context (PerasEpochContextNotFoundForRound (..)) +import qualified Ouroboros.Consensus.Peras.Error.V1 as Peras +import qualified Ouroboros.Consensus.Storage.LedgerDB.Snapshots as LedgerDB +import qualified Ouroboros.Network.Block as Network +import Ouroboros.Network.PeerSelection.LedgerPeers.Type (BigLedgerPeers, + LedgerPeerSnapshot (..), RawBlockHash (..)) + +import Control.Monad (forM_) +import qualified Data.Aeson as Aeson +import qualified Data.Aeson.Key as Aeson +import qualified Data.Aeson.KeyMap as KeyMap +import qualified Data.ByteString as BS +import qualified Data.ByteString.Short as SBS +import Data.List.NonEmpty (NonEmpty (..)) +import Data.Proxy (Proxy (..)) +import qualified Data.Text as Text +import Data.Word (Word32) + +import Hedgehog (Property, (===)) +import qualified Hedgehog as H +import Hedgehog.Extras.Test.Base (propertyOnce) + +-- Repeated hashes must not overwrite distinct body-local Receiving indexes. +-- Preserve the old singleton JSON value while extending duplicates to an array. +hprop_missingReceivingRedeemers :: Property +hprop_missingReceivingRedeemers = propertyOnce $ do + hash <- H.evalMaybe $ Crypto.hashFromBytes (BS.replicate 28 1) + let scriptHash = ScriptHash hash + second = (DijkstraReceiving (AsItem 2), scriptHash) + third = (DijkstraReceiving (AsItem 3), scriptHash) + outputIndex index = Aeson.object ["receiving" Aeson..= (index :: Word32)] + keyed value = Aeson.object [Aeson.fromText (renderScriptHash scriptHash) Aeson..= value] + renderMissingRedeemers ShelleyBasedEraDijkstra (second :| []) === keyed (outputIndex 2) + renderMissingRedeemers ShelleyBasedEraDijkstra (second :| [third]) + === keyed (Aeson.toJSON [outputIndex 2, outputIndex 3]) + renderMissingRedeemers ShelleyBasedEraDijkstra (third :| [second]) + === keyed (Aeson.toJSON [outputIndex 3, outputIndex 2]) + +-- A new RPC span must be selectable and documented, and count each request once. +hprop_mempoolRpcSpans :: Property +hprop_mempoolRpcSpans = propertyOnce $ + forM_ [("ReadMempool", TraceRpcReadMempoolSpan), ("WatchMempool", TraceRpcWatchMempoolSpan)] $ \(methodName, makeSpan) -> do + let spanId = UsingRawBytesHex 1 + begin = TraceRpcSubmit $ makeSpan $ SpanBegin spanId + end = TraceRpcSubmit $ makeSpan $ SpanEnd spanId + namespace = Namespace [] ["SubmitService", methodName, "Span"] + metricName = "rpc.request.SubmitService." <> methodName + description = "Span for the " <> methodName <> " UTXORPC method." + namespaceFor begin === namespace + namespaceFor end === namespace + H.assert $ namespace `elem` allNamespaces @TraceRpc + severityFor @TraceRpc namespace Nothing === Just Debug + documentFor @TraceRpc namespace === Just description + metricsDocFor @TraceRpc namespace === [(metricName, description)] + asMetrics begin === [CounterM metricName Nothing] + asMetrics end === [] + forM_ [(begin, "begin"), (end, "end")] $ \(event, spanKind) -> do + let rendered = forMachine DNormal event + KeyMap.lookup "kind" rendered === Just (Aeson.String "SubmitService") + KeyMap.lookup "span" rendered === Just (Aeson.String spanKind) + KeyMap.lookup "spanId" rendered === Just (Aeson.toJSON spanId) + +hprop_snapshotPolicyTrace :: Property +hprop_snapshotPolicyTrace = propertyOnce $ do + let policy = LedgerDB.snapshotPolicyInfo (Api.SecurityParam $ unsafeNonZero 2160) + LedgerDB.defaultSnapshotPolicyArgs (mkSlotLength 1) + shortPolicy = policy {LedgerDB.spiSlotLengthAtTip = mkSlotLength 0.001} + disabledPolicy = policy + { LedgerDB.spiArgs = LedgerDB.defaultSnapshotPolicyArgs {LedgerDB.spaFrequency = LedgerDB.DisableSnapshots} + , LedgerDB.spiIntervalSlots = Nothing + } + event info = LedgerDB.ConfiguredSnapshotPolicy info :: LedgerDB.TraceSnapshotEvent (Api.CardanoBlock Api.StandardCrypto) + normal = event policy + short = event shortPolicy + disabled = event disabledPolicy + rendered = forMachine DNormal normal + expectedFields = + [ ("kind", Aeson.String "ConfiguredSnapshotPolicy") + , ("securityParam", Aeson.toJSON (2160 :: Integer)) + , ("slotLengthSeconds", Aeson.toJSON (1 :: Double)) + , ("numOfDiskSnapshots", Aeson.toJSON (2 :: Integer)) + , ("intervalSlots", Aeson.toJSON (86400 :: Integer)) + , ("intervalSeconds", Aeson.toJSON (86400 :: Double)) + , ("offsetSlots", Aeson.toJSON (0 :: Integer)) + , ("rateLimitSeconds", Aeson.toJSON (600 :: Double)) + , ("writeDelayMinSeconds", Aeson.toJSON (300 :: Double)) + , ("writeDelayMaxSeconds", Aeson.toJSON (21600 :: Double)) + , ("mismatches", Aeson.toJSON ([] :: [Text.Text])) + ] + forM_ expectedFields $ \(key, value) -> KeyMap.lookup key rendered === Just value + namespaceFor normal === Namespace [] ["ConfiguredSnapshotPolicy"] + namespaceFor short === Namespace [] ["ImplausibleSnapshotPolicy"] + severityFor (namespaceFor normal) Nothing === Just Info + severityFor (namespaceFor short) Nothing === Just Warning + LedgerDB.snapshotPolicyMismatches shortPolicy + === [LedgerDB.WriteDelayExceedsInterval, LedgerDB.RateLimitExceedsInterval] + KeyMap.lookup "mismatches" (forMachine DNormal short) + === Just (Aeson.toJSON (["WriteDelayExceedsInterval", "RateLimitExceedsInterval"] :: [Text.Text])) + KeyMap.lookup "snapshotsDisabled" (forMachine DNormal disabled) === Just (Aeson.Bool True) + KeyMap.lookup "intervalSlots" (forMachine DNormal disabled) === Nothing + forM_ [normal, short, disabled] $ \trace -> do + H.assert $ namespaceFor trace `elem` allNamespaces + H.assert $ maybe False (not . Text.null) $ documentFor $ namespaceFor trace + H.assert $ not $ Text.null $ forHuman trace + +hprop_perasValidationErrors :: Property +hprop_perasValidationErrors = propertyOnce $ do + let contextError = PerasEpochContextNotFoundForRound 5 "context unavailable" + certificateError = Peras.PerasQuorumNotReachedError (VoteWeight 7) + :: Peras.PerasError (Api.ConsensusBlockForEra DijkstraEra) + contextEvent = ExtValidationErrorPerasEpochContextResolver contextError + :: ExtValidationError (Api.ConsensusBlockForEra DijkstraEra) + certificateEvent = ExtValidationErrorPerasCertInBlock certificateError + :: ExtValidationError (Api.ConsensusBlockForEra DijkstraEra) + forM_ [(contextEvent, "ExtValidationErrorPerasEpochContextResolver", textShow contextError), + (certificateEvent, "ExtValidationErrorPerasCertInBlock", textShow certificateError)] $ \(event, kind, message) -> do + let rendered = forMachine DNormal event + KeyMap.lookup "kind" rendered === Just (Aeson.String kind) + KeyMap.lookup "error" rendered === Just (Aeson.String message) + H.assert $ message `Text.isInfixOf` forHuman event + asMetrics event === [] + +hprop_mempoolCapacityTrace :: Property +hprop_mempoolCapacityTrace = propertyOnce $ do + let before = TxMeasure (IgnoringOverflow $ ByteSize32 1024) TrivialTxMeasurePhase2 + :: TxMeasure (Api.ConsensusBlockForEra ByronEra) + after = TxMeasure (IgnoringOverflow $ ByteSize32 2048) TrivialTxMeasurePhase2 + :: TxMeasure (Api.ConsensusBlockForEra ByronEra) + event = TraceMempoolCapacityChanged before after + capacity bytes = Aeson.object + [ "txSizeBytes" Aeson..= (bytes :: Integer) + , "exUnitsMemory" Aeson..= (0 :: Integer) + , "exUnitsSteps" Aeson..= (0 :: Integer) + , "refScriptsSizeBytes" Aeson..= (0 :: Integer) + ] + rendered = forMachine DNormal event + KeyMap.lookup "kind" rendered === Just (Aeson.String "TraceMempoolCapacityChanged") + KeyMap.lookup "capacityBefore" rendered === Just (capacity 1024) + KeyMap.lookup "capacityAfter" rendered === Just (capacity 2048) + namespaceFor event === Namespace [] ["CapacityChanged"] + H.assert $ namespaceFor event `elem` allNamespaces + severityFor (namespaceFor event) Nothing === Just Debug + H.assert $ maybe False (not . Text.null) $ documentFor $ namespaceFor event + asMetrics event === [] + +hprop_dijkstraPoolFailures :: Property +hprop_dijkstraPoolFailures = propertyOnce $ do + hash <- H.evalMaybe $ Crypto.hashFromBytes (BS.replicate 28 2) + let poolId = KeyHash hash + missing = Dijkstra.StakePoolNotRegisteredOnKeyPOOL poolId + :: Dijkstra.DijkstraPoolPredFailure Ledger.DijkstraEra + lowCost = Dijkstra.StakePoolCostTooLowPOOL (Mismatch (Coin 2) (Coin 3)) + :: Dijkstra.DijkstraPoolPredFailure Ledger.DijkstraEra + missingRendered = forMachine DNormal missing + costRendered = forMachine DNormal lowCost + KeyMap.lookup "kind" missingRendered === Just (Aeson.String "StakePoolNotRegisteredOnKeyPOOL") + KeyMap.lookup "unregisteredKeyHash" missingRendered === Just (Aeson.String $ textShow hash) + KeyMap.lookup "kind" costRendered === Just (Aeson.String "StakePoolCostTooLowPOOL") + KeyMap.lookup "certificateCost" costRendered === Just (Aeson.String $ textShow $ Coin 2) + KeyMap.lookup "protocolParCost" costRendered === Just (Aeson.String $ textShow $ Coin 3) + +hprop_startupProtocolVersions :: Property +hprop_startupProtocolVersions = propertyOnce $ do + let block = Proxy :: Proxy (Api.CardanoBlock Api.StandardCrypto) + event = StartupInfo [] Nothing + (supportedNodeToNodeVersions block) (supportedNodeToClientVersions block) + :: StartupTrace (Api.CardanoBlock Api.StandardCrypto) + rendered = ppStartupInfoTrace event + H.assert $ "node-to-client versions: [23]" `Text.isInfixOf` rendered + +hprop_perasForgingTraces :: Property +hprop_perasForgingTraces = propertyOnce $ do + let certificateEvent = TracePerasCertInclusionNoCertToInclude 17 + :: TracePerasCertInclusionEvent (Api.CardanoBlock Api.StandardCrypto) + certificateNamespace = Namespace [] ["NoCertToInclude"] + inventory = [outer <> inner | (outer, inner) <- getAllNamespaces] + voteEvents = + [ (TracePerasVotingNoVoteAfterFirstSlotInRound 3 2, "NoVoteAfterFirstSlotInRound", Debug) + , (TracePerasVotingNotAVoterInRound 5, "NotAVoterInRound", Debug) + , (TracePerasVotingCantReadEnv "environment unavailable", "CantReadEnv", Error) + ] :: [(TracePerasVoteForgingEvent (Api.CardanoBlock Api.StandardCrypto), Text.Text, SeverityS)] + KeyMap.lookup "slot" (forMachine DNormal certificateEvent) === Just (Aeson.toJSON (17 :: Integer)) + namespaceFor certificateEvent === certificateNamespace + severityFor @(TracePerasCertInclusionEvent (Api.CardanoBlock Api.StandardCrypto)) certificateNamespace Nothing === Just Debug + H.assert $ maybe False (not . Text.null) $ documentFor @(TracePerasCertInclusionEvent (Api.CardanoBlock Api.StandardCrypto)) certificateNamespace + H.assert $ ["Peras", "Cert", "Inclusion", "NoCertToInclude"] `elem` inventory + asMetrics certificateEvent === [] + forM_ voteEvents $ \(event, suffix, severity) -> do + let namespace = Namespace [] [suffix] + rendered = forMachine DNormal event + namespaceFor event === namespace + severityFor @(TracePerasVoteForgingEvent (Api.CardanoBlock Api.StandardCrypto)) namespace Nothing === Just severity + H.assert $ namespace `elem` allNamespaces @(TracePerasVoteForgingEvent (Api.CardanoBlock Api.StandardCrypto)) + H.assert $ maybe False (not . Text.null) $ documentFor @(TracePerasVoteForgingEvent (Api.CardanoBlock Api.StandardCrypto)) namespace + H.assert $ ["Peras", "Vote", "Forging", suffix] `elem` inventory + H.assert $ not $ Text.null $ forHuman event + asMetrics event === [] + case event of + TracePerasVotingNoVoteAfterFirstSlotInRound _ _ -> do + KeyMap.lookup "round" rendered === Just (Aeson.toJSON (3 :: Integer)) + KeyMap.lookup "slotInRound" rendered === Just (Aeson.toJSON (2 :: Integer)) + TracePerasVotingNotAVoterInRound _ -> + KeyMap.lookup "round" rendered === Just (Aeson.toJSON (5 :: Integer)) + TracePerasVotingCantReadEnv _ -> + KeyMap.lookup "error" rendered === Just (Aeson.String "environment unavailable") + _ -> H.failure + +hprop_peerSnapshotKinds :: Property +hprop_peerSnapshotKinds = propertyOnce $ do + let point = Network.BlockPoint 17 (RawBlockHash $ SBS.toShort $ BS.replicate 32 3) + big = LedgerBigPeerSnapshotV23 point (NetworkMagic 42) [] + allPeers = LedgerAllPeerSnapshotV23 point (NetworkMagic 42) [] + decodeBig bytes = Aeson.eitherDecode bytes :: Either String (LedgerPeerSnapshot BigLedgerPeers) + legacy = case Aeson.toJSON big of + Aeson.Object fields -> Aeson.Object $ KeyMap.insert "NodeToClientVersion" (Aeson.toJSON (22 :: Int)) fields + value -> value + -- The node file reader uses this typed decoder. The all-peers constructor + -- cannot inhabit its BigLedgerPeers result, and legacy versions fail here. + decodeBig (Aeson.encode big) === Right big + case decodeBig (Aeson.encode legacy) of + Left err -> H.assert $ "unsupported version 22" `Text.isInfixOf` Text.pack err + Right _ -> H.failure + case decodeBig (Aeson.encode allPeers) of + Left err -> H.assert $ "bigLedgerPools" `Text.isInfixOf` Text.pack err + Right _ -> H.failure + +tests :: IO Bool +tests = H.checkSequential $ H.Group "Test.Tracing.Receiving" + [ ("missing same-hash Receiving redeemers", hprop_missingReceivingRedeemers) + , ("mempool RPC span metadata", hprop_mempoolRpcSpans) + , ("snapshot policy payload and metadata", hprop_snapshotPolicyTrace) + , ("Peras validation error payloads", hprop_perasValidationErrors) + , ("mempool capacity payload and metadata", hprop_mempoolCapacityTrace) + , ("Dijkstra pool failure payloads", hprop_dijkstraPoolFailures) + , ("startup supported protocol versions", hprop_startupProtocolVersions) + , ("Peras forging payloads and registration", hprop_perasForgingTraces) + , ("peer snapshot version and kind rejection", hprop_peerSnapshotKinds) + ] diff --git a/cardano-node/test/cardano-node-test.hs b/cardano-node/test/cardano-node-test.hs index 0706e005edb..eb9f0e7c46c 100644 --- a/cardano-node/test/cardano-node-test.hs +++ b/cardano-node/test/cardano-node-test.hs @@ -4,9 +4,11 @@ #define UNIX #endif -import Hedgehog.Main (defaultMain) +import qualified Cardano.Crypto.Init as Crypto + import System.IO (BufferMode (LineBuffering), hSetBuffering, hSetEncoding, stdout, utf8) +import qualified Test.Cardano.Config.Dijkstra import qualified Test.Cardano.Config.Mainnet #ifdef UNIX import qualified Test.Cardano.Node.FilePermissions @@ -15,8 +17,9 @@ import qualified Test.Cardano.Node.Json import qualified Test.Cardano.Node.POM import qualified Test.Cardano.Node.TopLevel import qualified Test.Cardano.Tracing.NewTracing.Consistency +import qualified Test.Cardano.Tracing.Receiving -import qualified Cardano.Crypto.Init as Crypto +import Hedgehog.Main (defaultMain) main :: IO () main = do @@ -31,9 +34,11 @@ main = do [ Test.Cardano.Node.FilePermissions.tests ] <> #endif - [ Test.Cardano.Config.Mainnet.tests + [ Test.Cardano.Config.Dijkstra.tests + , Test.Cardano.Config.Mainnet.tests , Test.Cardano.Node.Json.tests , Test.Cardano.Node.POM.tests , Test.Cardano.Node.TopLevel.tests , Test.Cardano.Tracing.NewTracing.Consistency.tests + , Test.Cardano.Tracing.Receiving.tests ] diff --git a/cardano-submit-api/.changes/20261006_000000_cip160_integration.yml b/cardano-submit-api/.changes/20261006_000000_cip160_integration.yml new file mode 100644 index 00000000000..e4501c907fe --- /dev/null +++ b/cardano-submit-api/.changes/20261006_000000_cip160_integration.yml @@ -0,0 +1,5 @@ +pr: 0 +kind: + - maintenance +description: | + Update explicit API and CLI dependency bounds for the selected Receiving-aware node integration. diff --git a/cardano-submit-api/cardano-submit-api.cabal b/cardano-submit-api/cardano-submit-api.cabal index a8eae11ea5f..c3435554e46 100644 --- a/cardano-submit-api/cardano-submit-api.cabal +++ b/cardano-submit-api/cardano-submit-api.cabal @@ -1,7 +1,7 @@ cabal-version: 3.0 name: cardano-submit-api -version: 11.1.1 +version: 11.1.1.1 synopsis: A web server that allows transactions to be POSTed to the cardano chain description: A web server that allows transactions to be POSTed to the cardano chain. homepage: https://github.com/intersectmbo/cardano-node @@ -39,10 +39,10 @@ library , aeson , async , bytestring - , cardano-api ^>= 11.7 + , cardano-api ^>= 11.9 , cardano-binary - , cardano-cli ^>= 11.2.2 - , cardano-crypto-class ^>=2.5 + , cardano-cli ^>= 11.3 + , cardano-crypto-class ^>=2.6 , containers , ekg-core , http-media @@ -99,4 +99,4 @@ test-suite unit main-is: test.hs hs-source-dirs: test build-depends: base - , cardano-crypto-class ^>=2.5 + , cardano-crypto-class ^>=2.6 diff --git a/cardano-testnet/.changes/20261006_000000_cip160_integration.yml b/cardano-testnet/.changes/20261006_000000_cip160_integration.yml new file mode 100644 index 00000000000..292c8e84ecd --- /dev/null +++ b/cardano-testnet/.changes/20261006_000000_cip160_integration.yml @@ -0,0 +1,5 @@ +pr: 0 +kind: + - bugfix +description: | + Align Dijkstra testnet block protocol major with genesis major 12 and the local protected-address proposal. Enable ExperimentalHardForksEnabled only for requested Dijkstra testnets, preserving earlier-era configurations. Add real protected key/native/V4 creation, query, same-database relay restart and spending coverage. Update dependency bounds for the selected Receiving-aware API, CLI, consensus and network packages. diff --git a/cardano-testnet/cardano-testnet.cabal b/cardano-testnet/cardano-testnet.cabal index 4d72894ef66..d5e5befcad0 100644 --- a/cardano-testnet/cardano-testnet.cabal +++ b/cardano-testnet/cardano-testnet.cabal @@ -1,7 +1,7 @@ cabal-version: 3.0 name: cardano-testnet -version: 11.1.1 +version: 11.1.1.1 synopsis: The cardano full node description: The cardano full node. copyright: 2021-2023 Input Output Global Inc (IOG), 2023-2026 Intersect. @@ -14,6 +14,8 @@ license-files: LICENSE NOTICE build-type: Simple extra-doc-files: CHANGELOG.md +extra-source-files: test/cardano-testnet-test/files/plutus/v4/README.md + test/cardano-testnet-test/files/plutus/v4/receiving-even-datum.plutus common project-config default-language: Haskell2010 @@ -41,9 +43,9 @@ library , annotated-exception , ansi-terminal , bytestring - , cardano-api ^>= 11.7 - , cardano-cli:{cardano-cli, cardano-cli-test-lib} ^>= 11.2.2 - , cardano-crypto-class ^>=2.5 + , cardano-api ^>= 11.9 + , cardano-cli:{cardano-cli, cardano-cli-test-lib} ^>= 11.3 + , cardano-crypto-class ^>=2.6 , cardano-crypto-wrapper , cardano-git-rev ^>= 0.2.2 , cardano-ledger-alonzo @@ -57,7 +59,7 @@ library , cardano-ledger-dijkstra , cardano-ledger-shelley , cardano-node - , cardano-diffusion:ping ^>= 1.1.1 + , cardano-diffusion:ping ^>= 1.3 , cardano-prelude , cardano-tracer , cardano-rpc @@ -83,8 +85,8 @@ library , network , optparse-applicative-fork , parsec - , ouroboros-network:{api, framework, ouroboros-network} ^>= 1.2 - , cardano-diffusion:{api, cardano-diffusion} ^>= 1.1.1 + , ouroboros-network:{api, framework, ouroboros-network} ^>= 1.3 + , cardano-diffusion:{api, cardano-diffusion} ^>= 1.3 , prettyprinter , process , resourcet @@ -155,7 +157,7 @@ executable cardano-testnet main-is: cardano-testnet.hs - build-depends: cardano-crypto-class ^>=2.5 + build-depends: cardano-crypto-class ^>=2.6 , cardano-cli , cardano-testnet , optparse-applicative-fork @@ -181,6 +183,7 @@ test-suite cardano-testnet-golden , bytestring , cardano-api , cardano-crypto-class + , cardano-node , cardano-testnet , exceptions , filepath @@ -215,6 +218,7 @@ test-suite cardano-testnet-test Cardano.Testnet.Test.Cli.LeadershipSchedule Cardano.Testnet.Test.Cli.Query Cardano.Testnet.Test.Cli.QuerySlotNumber + Cardano.Testnet.Test.Cli.Receiving Cardano.Testnet.Test.Cli.Plutus.Scripts Cardano.Testnet.Test.Cli.Plutus.CostCalculation Cardano.Testnet.Test.Cli.Plutus.BuildRaw @@ -277,6 +281,7 @@ test-suite cardano-testnet-test , cardano-ledger-binary , cardano-ledger-conway , cardano-ledger-core + , cardano-node , cardano-ledger-shelley , cardano-prelude , cardano-rpc diff --git a/cardano-testnet/src/Testnet/Defaults.hs b/cardano-testnet/src/Testnet/Defaults.hs index 5d40eb75810..75d25663120 100644 --- a/cardano-testnet/src/Testnet/Defaults.hs +++ b/cardano-testnet/src/Testnet/Defaults.hs @@ -240,7 +240,7 @@ defaultYamlHardforkViaConfig sbe = ShelleyBasedEraAlonzo -> ("LastKnownBlockVersion-Major", Aeson.Number 5) ShelleyBasedEraBabbage -> ("LastKnownBlockVersion-Major", Aeson.Number 8) ShelleyBasedEraConway -> ("LastKnownBlockVersion-Major", Aeson.Number 9) - ShelleyBasedEraDijkstra -> ("LastKnownBlockVersion-Major", Aeson.Number 10) + ShelleyBasedEraDijkstra -> ("LastKnownBlockVersion-Major", Aeson.Number 12) , ("LastKnownBlockVersion-Minor", Aeson.Number 0) , ("LastKnownBlockVersion-Alt", Aeson.Number 0) ] @@ -286,7 +286,8 @@ defaultYamlHardforkViaConfig sbe = , ("TestConwayHardForkAtEpoch", Aeson.Number 0) ] ShelleyBasedEraDijkstra -> - [ ("TestShelleyHardForkAtEpoch", Aeson.Number 0) + [ ("ExperimentalHardForksEnabled", Aeson.Bool True) + , ("TestShelleyHardForkAtEpoch", Aeson.Number 0) , ("TestAllegraHardForkAtEpoch", Aeson.Number 0) , ("TestMaryHardForkAtEpoch", Aeson.Number 0) , ("TestAlonzoHardForkAtEpoch", Aeson.Number 0) diff --git a/cardano-testnet/test/cardano-testnet-golden/Cardano/Testnet/Test/Golden/Config.hs b/cardano-testnet/test/cardano-testnet-golden/Cardano/Testnet/Test/Golden/Config.hs index 3e7de33f14c..171dfdd7921 100644 --- a/cardano-testnet/test/cardano-testnet-golden/Cardano/Testnet/Test/Golden/Config.hs +++ b/cardano-testnet/test/cardano-testnet-golden/Cardano/Testnet/Test/Golden/Config.hs @@ -1,15 +1,27 @@ {-# LANGUAGE GADTs #-} +{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} module Cardano.Testnet.Test.Golden.Config ( goldenDefaultConfigYaml + , dijkstraProtocolConfig ) where +import Cardano.Api (EpochNo (..), + ShelleyBasedEra (ShelleyBasedEraConway, ShelleyBasedEraDijkstra)) + +import Cardano.Node.Configuration.POM (PartialNodeConfiguration (..)) +import Cardano.Node.Types (NodeHardForkProtocolConfiguration (..), + NodeProtocolConfiguration (..)) + import Prelude import Data.Aeson import Data.Aeson.Encode.Pretty (encodePretty) +import qualified Data.Aeson.KeyMap as KeyMap import qualified Data.ByteString.Lazy as LB +import Data.Maybe (isJust) +import Data.Monoid (Last (..)) import qualified Data.Text as Text import qualified Data.Text.Encoding as Text import System.FilePath (()) @@ -36,3 +48,36 @@ createConfigYamlString :: String createConfigYamlString = let configBs = LB.toStrict $ encodePretty $ Object $ defaultYamlHardforkViaConfig defaultEra in Text.unpack $ Text.decodeUtf8 configBs + +-- The hardfork trigger and advertised block version must match Dijkstra genesis +-- and the protected-address activation version. +dijkstraProtocolConfig :: Property +dijkstraProtocolConfig = propertyOnce $ do + let config = defaultYamlHardforkViaConfig ShelleyBasedEraDijkstra + KeyMap.lookup "LastKnownBlockVersion-Major" config === Just (Number 12) + KeyMap.lookup "TestDijkstraHardForkAtEpoch" config === Just (Number 0) + KeyMap.lookup "ExperimentalHardForksEnabled" config === Just (Bool True) + (dijkstraGenesis, hardfork) <- parseProtocol config + assert $ isJust dijkstraGenesis + npcExperimentalHardForksEnabled hardfork === True + npcTestDijkstraHardForkAtEpoch hardfork === Just (EpochNo 0) + -- Removing the opt-in makes the real node parser discard the Dijkstra + -- trigger and genesis, even though the epoch field remains present. + (disabledGenesis, disabledHardfork) <- parseProtocol $ KeyMap.delete "ExperimentalHardForksEnabled" config + disabledGenesis === Nothing + npcExperimentalHardForksEnabled disabledHardfork === False + npcTestDijkstraHardForkAtEpoch disabledHardfork === Nothing + let conwayConfig = defaultYamlHardforkViaConfig ShelleyBasedEraConway + KeyMap.lookup "ExperimentalHardForksEnabled" conwayConfig === Nothing + (conwayGenesis, conwayHardfork) <- parseProtocol conwayConfig + conwayGenesis === Nothing + npcExperimentalHardForksEnabled conwayHardfork === False + npcTestConwayHardForkAtEpoch conwayHardfork === Just (EpochNo 0) + where + parseProtocol config = do + parsed <- evalEither $ case fromJSON (Object config) of + Error err -> Left err + Success result -> Right (result :: PartialNodeConfiguration) + case getLast (pncProtocolConfig parsed) of + Just (NodeProtocolConfigurationCardano _ _ _ _ genesis hardfork _) -> pure (genesis, hardfork) + Nothing -> failure diff --git a/cardano-testnet/test/cardano-testnet-golden/cardano-testnet-golden.hs b/cardano-testnet/test/cardano-testnet-golden/cardano-testnet-golden.hs index 34f78d90659..d2aa2f1df4a 100644 --- a/cardano-testnet/test/cardano-testnet-golden/cardano-testnet-golden.hs +++ b/cardano-testnet/test/cardano-testnet-golden/cardano-testnet-golden.hs @@ -24,7 +24,8 @@ import qualified Test.Tasty.Ingredients as T tests :: IO TestTree tests = pure $ T.testGroup "Golden tests" - [ H.testPropertyNamed "golden_DefaultConfig" (fromString "golden_DefaultConfig") Cardano.Testnet.Test.Golden.Config.goldenDefaultConfigYaml + [ H.testPropertyNamed "dijkstra_ProtocolConfig" (fromString "dijkstra_ProtocolConfig") Cardano.Testnet.Test.Golden.Config.dijkstraProtocolConfig + , H.testPropertyNamed "golden_DefaultConfig" (fromString "golden_DefaultConfig") Cardano.Testnet.Test.Golden.Config.goldenDefaultConfigYaml , H.testPropertyNamed "golden_HelpAll" (fromString "golden_HelpAll") Cardano.Testnet.Test.Golden.Help.golden_HelpAll , H.testPropertyNamed "golden_HelpCmds" (fromString "golden_HelpCmds") Cardano.Testnet.Test.Golden.Help.golden_HelpCmds , ignoreOnWindows "golden_HelpReadme" Cardano.Testnet.Test.Golden.Help.golden_HelpReadme diff --git a/cardano-testnet/test/cardano-testnet-golden/files/golden/version_cmd.cli b/cardano-testnet/test/cardano-testnet-golden/files/golden/version_cmd.cli index 82f77d9fe9c..ab210957c9a 100644 --- a/cardano-testnet/test/cardano-testnet-golden/files/golden/version_cmd.cli +++ b/cardano-testnet/test/cardano-testnet-golden/files/golden/version_cmd.cli @@ -1,3 +1,3 @@ -built against cardano-api 11.7.0.0 -built against cardano-rpc 11.4.0.0 -built against cardano-cli 11.2.3.1 +built against cardano-api 11.9.0.0 +built against cardano-rpc 11.4.1.0 +built against cardano-cli 11.3.0.0 diff --git a/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Cli/Receiving.hs b/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Cli/Receiving.hs new file mode 100644 index 00000000000..4f5efefd43f --- /dev/null +++ b/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Cli/Receiving.hs @@ -0,0 +1,298 @@ +{-# LANGUAGE GADTs #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} + +module Cardano.Testnet.Test.Cli.Receiving (hprop_receiving_lifecycle) where + +import Cardano.Api +import Cardano.Api.Experimental (Some (Some)) +import Cardano.Api.Ledger (EpochInterval (..)) +import qualified Cardano.Api.Ledger as L + +import qualified Cardano.Ledger.Core as Ledger +import Cardano.Node.Testnet.Paths (defaultNamedNodeDataDir, defaultNodeTopologyFile) +import Cardano.Testnet + +import Prelude + +import Control.Monad (void) +import qualified Data.Aeson as Aeson +import qualified Data.Aeson.KeyMap as KeyMap +import Data.Default.Class (Default (def)) +import Data.Foldable (toList) +import Data.List (isInfixOf, sort) +import qualified Data.List.NonEmpty as NEL +import qualified Data.Map.Strict as Map +import qualified Data.Text as Text +import Lens.Micro ((^.)) +import System.Directory (makeAbsolute) +import System.Exit (ExitCode (..)) +import System.FilePath (()) +import System.Process (interruptProcessGroupOf) + +import Testnet.Components.Query (TestnetWaitPeriod (..), findAllUtxos, + findLargestUtxoForPaymentKey, getEpochStateView, retryUntilJustM) +import Testnet.Process.Cli.Transaction (failToSubmitTx, retrieveTransactionId, signTx, + submitTx) +import Testnet.Process.Run (execCli', execCliStdoutToJson, mkExecConfig) +import Testnet.Property.Util (integrationRetryWorkspace) +import Testnet.Runtime (startNode) +import Testnet.Types + +import Hedgehog (Property, (===)) +import qualified Hedgehog as H +import qualified Hedgehog.Extras.Test.Base as H +import qualified Hedgehog.Extras.Test.File as H +import qualified Hedgehog.Extras.Test.Process as H +import qualified Hedgehog.Extras.Test.TestWatchdog as H + +-- | Two admitted transactions exercise Receiving and later Spending through the +-- real node, with a rejected recipient-signature variant of the same first body. +-- Run with: DISABLE_RETRIES=1 cabal test cardano-testnet-test +-- --test-options '-p "/Dijkstra Receiving lifecycle/"' +hprop_receiving_lifecycle :: Property +hprop_receiving_lifecycle = integrationRetryWorkspace 2 "dijkstra-receiving" $ \base -> H.runWithDefaultWatchdog_ $ do + conf@Conf{tempAbsPath} <- mkConf base + let tempPath = unTmpAbsPath tempAbsPath + sbe = ShelleyBasedEraDijkstra + cEra = AnyCardanoEra DijkstraEra + creationOptions = def{creationEra = AnyShelleyBasedEra sbe} + fixture <- H.evalIO $ makeAbsolute "test/cardano-testnet-test/files/plutus/v4/receiving-even-datum.plutus" + work <- H.createDirectoryIfMissing $ tempPath "work" + runtime@TestnetRuntime + { configurationFile + , testnetMagic + , testnetNodes + , wallets = sender : collateral : recipient : _ + } <- createAndRunTestnet creationOptions def conf + let node = NEL.head testnetNodes + recipientAddress = Text.unpack (paymentKeyInfoAddr recipient) + senderAddress = Text.unpack (paymentKeyInfoAddr sender) + execConfig <- mkExecConfig (makeTmpBaseAbsPath (TmpAbsolutePath tempPath)) (nodeSprocket node) testnetMagic + let queryNodeUTxO queryExecConfig queryName = do + let queryFile = work queryName <> "-utxo.json" + void $ execCli' queryExecConfig + [ "dijkstra", "query", "utxo", "--whole-utxo", "--cardano-mode" + , "--out-file", queryFile + ] + queried :: UTxO DijkstraEra <- H.readJsonFileOk queryFile + pure queried + epochStateView <- getEpochStateView configurationFile (nodeSocketPath node) + fundingInput <- findLargestUtxoForPaymentKey epochStateView sbe sender + collateralInput <- findLargestUtxoForPaymentKey epochStateView sbe collateral + recipientHash <- Text.unpack . Text.strip . Text.pack <$> execCli' execConfig + [ "dijkstra", "address", "key-hash" + , "--payment-verification-key-file", verificationKeyFp (paymentKeyInfoPair recipient) + ] + let nativeFile = work "recipient-native.json" + H.writeFile nativeFile $ "{\"type\":\"sig\",\"keyHash\":\"" <> recipientHash <> "\"}" + nativeAddress <- Text.unpack . Text.strip . Text.pack <$> execCli' execConfig + [ "dijkstra", "address", "build", "--payment-script-file", nativeFile + , "--testnet-magic", show testnetMagic + ] + plutusAddress <- Text.unpack . Text.strip . Text.pack <$> execCli' execConfig + [ "dijkstra", "address", "build", "--payment-script-file", fixture + , "--testnet-magic", show testnetMagic + ] + [keyProtected, nativeProtected, plutusProtected] <- mapM + (\address -> Text.unpack . Text.strip . Text.pack <$> execCli' execConfig + ["dijkstra", "address", "protect", "--address", address]) + [recipientAddress, nativeAddress, plutusAddress] + nativeHash <- Text.unpack . Text.strip . Text.pack <$> execCli' execConfig + ["dijkstra", "transaction", "policyid", "--script-file", nativeFile] + plutusHash <- Text.unpack . Text.strip . Text.pack <$> execCli' execConfig + ["dijkstra", "transaction", "policyid", "--script-file", fixture] + void $ H.note $ "Native Receiving script hash: " <> nativeHash + void $ H.note $ "V4 Receiving script hash: " <> plutusHash + let creationBody = File (work "creation.txbody") + creationOutputs = + [ "--tx-out", keyProtected <> "+5000000" + , "--tx-out", nativeProtected <> "+6000000" + , "--tx-out", plutusProtected <> "+7000000", "--tx-out-inline-datum-value", "2" + , "--tx-out", plutusProtected <> "+8000000", "--tx-out-inline-datum-value", "4" + ] + void $ execCli' execConfig $ + [ "dijkstra", "transaction", "build" + , "--tx-in", Text.unpack (renderTxIn fundingInput) + , "--tx-in-collateral", Text.unpack (renderTxIn collateralInput) + , "--change-address", senderAddress + ] <> creationOutputs <> + [ "--receiving-output-index", "1", "--receiving-script-file", nativeFile + , "--receiving-output-index", "2", "--receiving-script-file", fixture + , "--receiving-redeemer-value", "0" + , "--receiving-output-index", "3", "--receiving-script-file", fixture + , "--receiving-redeemer-value", "0" + , "--out-file", unFile creationBody + ] + creationEnvelope <- H.readJsonFileOk (unFile creationBody) + unsignedCreation :: Tx DijkstraEra <- H.evalEither $ deserialiseFromTextEnvelope creationEnvelope + let creationId = txIdFor unsignedCreation + expectedCreated = outputsByInput unsignedCreation + protectedCreated = Map.filter isProtectedOutput expectedCreated + expectedSpecs = + [ (Text.pack keyProtected, Nothing) + , (Text.pack nativeProtected, Nothing) + , (Text.pack plutusProtected, Just 2) + , (Text.pack plutusProtected, Just 4) + ] + sort (map outputSpec (Map.elems protectedCreated)) === sort expectedSpecs + length protectedCreated === 4 + -- The native witness is shared authorization. Each protected V4 output + -- has its own original body index, redeemer and execution budget. + creationView :: Aeson.Value <- execCliStdoutToJson execConfig + ["debug", "transaction", "view", "--tx-body-file", unFile creationBody, "--output-json"] + case creationView of + Aeson.Object fields -> case KeyMap.lookup "redeemers" fields of + Just (Aeson.Array redeemers) -> do + length redeemers === 2 + let pointers = + [ KeyMap.lookup "redeemer pointer" redeemerFields + | Aeson.Object redeemerFields <- toList redeemers + ] + receivingPointer :: Int -> Aeson.Value + receivingPointer index = Aeson.object + [ "kind" Aeson..= ("DijkstraReceiving" :: Text.Text) + , "value" Aeson..= Aeson.object ["index" Aeson..= index] + ] + pointers === map (Just . receivingPointer) [2, 3] + _ -> H.failure + _ -> H.failure + before@(UTxO beforeMap) <- findAllUtxos epochStateView sbe + H.assert $ Map.member fundingInput beforeMap && Map.member collateralInput beforeMap + H.assert $ all (\input -> not (Map.member input beforeMap)) (Map.keys expectedCreated) + nodeBefore@(UTxO nodeBeforeMap) <- queryNodeUTxO execConfig "before-rejected-creation" + H.assert $ Map.member fundingInput nodeBeforeMap && Map.member collateralInput nodeBeforeMap + H.assert $ all (\input -> not (Map.member input nodeBeforeMap)) (Map.keys expectedCreated) + missingRecipient <- signTx execConfig cEra work "missing-recipient" creationBody + [Some (paymentKeyInfoPair sender), Some (paymentKeyInfoPair collateral)] + retrieveTransactionId execConfig missingRecipient >>= (=== creationId) + failToSubmitTx execConfig cEra missingRecipient "MissingVKeyWitnessesUTXOW" + after <- findAllUtxos epochStateView sbe + after === before + nodeAfter <- queryNodeUTxO execConfig "after-rejected-creation" + nodeAfter === nodeBefore + signedCreation <- signTx execConfig cEra work "creation" creationBody + [ Some (paymentKeyInfoPair sender) + , Some (paymentKeyInfoPair collateral) + , Some (paymentKeyInfoPair recipient) + ] + retrieveTransactionId execConfig signedCreation >>= (=== creationId) + submitTx execConfig cEra signedCreation + confirmedCreation@(UTxO confirmedMap) <- retryUntilJustM epochStateView (WaitForEpochs (EpochInterval 2)) $ do + actual@(UTxO actualMap) <- findAllUtxos epochStateView sbe + pure $ if Map.restrictKeys actualMap (Map.keysSet expectedCreated) == expectedCreated then Just actual else Nothing + H.assert $ not (Map.member fundingInput confirmedMap) + Map.lookup collateralInput confirmedMap === Map.lookup collateralInput beforeMap + -- Independently replay the chain through the API fold from genesis. Exact + -- TxIns, complete values, protected addresses and datums must match the body. + Map.restrictKeys confirmedMap (Map.keysSet expectedCreated) === expectedCreated + sort (map outputSpec (Map.elems (Map.restrictKeys confirmedMap (Map.keysSet protectedCreated)))) === sort expectedSpecs + H.noteShow_ confirmedCreation + -- Query the live node via Local State Query as separate evidence from replay. + UTxO nodeCreatedMap <- retryUntilJustM epochStateView (WaitForEpochs (EpochInterval 2)) $ do + queried@(UTxO queriedMap) <- queryNodeUTxO execConfig "confirmed-creation" + pure $ if Map.restrictKeys queriedMap (Map.keysSet expectedCreated) == expectedCreated then Just queried else Nothing + Map.restrictKeys nodeCreatedMap (Map.keysSet expectedCreated) === expectedCreated + sort (map outputSpec (Map.elems (Map.restrictKeys nodeCreatedMap (Map.keysSet protectedCreated)))) === sort expectedSpecs + H.assert $ not (Map.member fundingInput nodeCreatedMap) + Map.lookup collateralInput nodeCreatedMap === Map.lookup collateralInput nodeBeforeMap + -- Keep the producer and its API replay running while one relay is stopped. + -- A fresh socket/log name preserves its original logs; its database and + -- topology paths, TCP port, configuration and genesis files stay the same. + relay : _ <- pure (relayNodes runtime) + relayExecConfig <- mkExecConfig (makeTmpBaseAbsPath tempAbsPath) (nodeSprocket relay) testnetMagic + relayBefore@(UTxO relayBeforeMap) <- retryUntilJustM epochStateView (WaitForEpochs (EpochInterval 2)) $ do + queried@(UTxO queriedMap) <- queryNodeUTxO relayExecConfig "relay-before-restart" + pure $ if Map.restrictKeys queriedMap (Map.keysSet expectedCreated) == expectedCreated then Just queried else Nothing + Map.restrictKeys relayBeforeMap (Map.keysSet protectedCreated) === protectedCreated + H.evalIO $ interruptProcessGroupOf (nodeProcessHandle relay) + shutdown <- H.waitSecondsForProcess 5 (nodeProcessHandle relay) + case shutdown of + Right (ExitFailure _) -> pure () + other -> H.annotateShow other >> H.failure + oldRelayLogs <- H.readFile (nodeStdout relay) + H.assert $ "\"kind\":\"TraceOpenEvent.ClosedDB\"" `isInfixOf` oldRelayLogs + restarted <- H.evalEither =<< runExceptT (retryOnAddressInUseError $ + startNode tempAbsPath (nodeName relay <> "-restored") (nodeIpv4 relay) (nodePort relay) testnetMagic Nothing + [ "run" + , "--config", unFile configurationFile + , "--topology", tempPath defaultNodeTopologyFile (nodeName relay) + , "--database-path", tempPath defaultNamedNodeDataDir (nodeName relay) "db" + ]) + restartedExecConfig <- mkExecConfig (makeTmpBaseAbsPath tempAbsPath) (nodeSprocket restarted) testnetMagic + restartedView <- getEpochStateView configurationFile (nodeSocketPath restarted) + relayRestored <- retryUntilJustM restartedView (WaitForEpochs (EpochInterval 2)) $ do + queried@(UTxO queriedMap) <- queryNodeUTxO restartedExecConfig "relay-after-restart" + pure $ if Map.restrictKeys queriedMap (Map.keysSet expectedCreated) == expectedCreated then Just queried else Nothing + relayRestored === relayBefore + restoredReplay <- retryUntilJustM restartedView (WaitForEpochs (EpochInterval 2)) $ do + actual@(UTxO actualMap) <- findAllUtxos restartedView sbe + pure $ if Map.restrictKeys actualMap (Map.keysSet expectedCreated) == expectedCreated then Just actual else Nothing + restoredReplay === relayBefore + -- This is same-database restart/replay evidence. Snapshot restoration requires + -- matching snapshot creation/replay events and is not inferred from restart. + let keyInputs = Map.keys (Map.filter ((== Text.pack keyProtected) . fst . outputSpec) protectedCreated) + nativeInputs = Map.keys (Map.filter ((== Text.pack nativeProtected) . fst . outputSpec) protectedCreated) + plutusInputs = Map.keys (Map.filter ((== Text.pack plutusProtected) . fst . outputSpec) protectedCreated) + spendingBody = File (work "spending.txbody") + length keyInputs === 1 + length nativeInputs === 1 + length plutusInputs === 2 + void $ execCli' restartedExecConfig $ + [ "dijkstra", "transaction", "build" + , "--change-address", recipientAddress + , "--tx-in-collateral", Text.unpack (renderTxIn collateralInput) + , "--tx-out", senderAddress <> "+5000000" + ] <> + concatMap (\input -> ["--tx-in", Text.unpack (renderTxIn input)]) keyInputs <> + concatMap (\input -> ["--tx-in", Text.unpack (renderTxIn input), "--tx-in-script-file", nativeFile]) nativeInputs <> + concatMap (\input -> + [ "--tx-in", Text.unpack (renderTxIn input), "--tx-in-script-file", fixture + , "--tx-in-inline-datum-present", "--tx-in-redeemer-value", "0" + ]) plutusInputs <> + ["--out-file", unFile spendingBody] + spendingEnvelope <- H.readJsonFileOk (unFile spendingBody) + unsignedSpending :: Tx DijkstraEra <- H.evalEither $ deserialiseFromTextEnvelope spendingEnvelope + let expectedSpent = outputsByInput unsignedSpending + H.assert $ not (any isProtectedOutput (Map.elems expectedSpent)) + signedSpending <- signTx restartedExecConfig cEra work "spending" spendingBody + [Some (paymentKeyInfoPair collateral), Some (paymentKeyInfoPair recipient)] + submitTx restartedExecConfig cEra signedSpending + UTxO spentMap <- retryUntilJustM epochStateView (WaitForEpochs (EpochInterval 2)) $ do + actual@(UTxO actualMap) <- findAllUtxos epochStateView sbe + pure $ if all (\input -> not (Map.member input actualMap)) (Map.keys protectedCreated) + && Map.restrictKeys actualMap (Map.keysSet expectedSpent) == expectedSpent + then Just actual else Nothing + Map.restrictKeys spentMap (Map.keysSet expectedSpent) === expectedSpent + H.assert $ all (\input -> not (Map.member input spentMap)) (Map.keys protectedCreated) + Map.lookup collateralInput spentMap === Map.lookup collateralInput beforeMap + UTxO nodeSpentMap <- retryUntilJustM epochStateView (WaitForEpochs (EpochInterval 2)) $ do + queried@(UTxO queriedMap) <- queryNodeUTxO restartedExecConfig "confirmed-spending" + pure $ if all (\input -> not (Map.member input queriedMap)) (Map.keys protectedCreated) + && Map.restrictKeys queriedMap (Map.keysSet expectedSpent) == expectedSpent + then Just queried else Nothing + Map.restrictKeys nodeSpentMap (Map.keysSet expectedSpent) === expectedSpent + H.assert $ all (\input -> not (Map.member input nodeSpentMap)) (Map.keys protectedCreated) + Map.lookup collateralInput nodeSpentMap === Map.lookup collateralInput nodeBeforeMap + +outputsByInput :: Tx DijkstraEra -> Map.Map TxIn (TxOut CtxUTxO DijkstraEra) +outputsByInput tx@(ShelleyTx sbe ledgerTx) = Map.fromList + [ (TxIn (txIdFor tx) (TxIx index), fromShelleyTxOut sbe txOut) + | (index, txOut) <- zip [0 ..] (toList (ledgerTx ^. L.bodyTxL . L.outputsTxBodyL)) + ] + +txIdFor :: Tx DijkstraEra -> TxId +txIdFor (ShelleyTx _ ledgerTx) = fromShelleyTxId (Ledger.txIdTxBody (ledgerTx ^. L.bodyTxL)) + +isProtectedOutput :: TxOut CtxUTxO DijkstraEra -> Bool +isProtectedOutput (TxOut (AddressInEra addressType address) _ _ _) = case addressType of + ShelleyAddressInEra{} -> isProtectedShelleyAddress address + ByronAddressInAnyEra -> False + +outputSpec :: TxOut CtxUTxO DijkstraEra -> (Text.Text, Maybe Integer) +outputSpec (TxOut address _ datum _) = (serialiseAddress address, case datum of + TxOutDatumInline _ value -> case getScriptData value of + ScriptDataNumber number -> Just number + _ -> Nothing + _ -> Nothing) diff --git a/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/SearchUtxos.hs b/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/SearchUtxos.hs index 9e86010b6bc..462c6c528c8 100644 --- a/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/SearchUtxos.hs +++ b/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/SearchUtxos.hs @@ -18,10 +18,9 @@ import qualified Cardano.Api.Experimental.Tx as Exp import qualified Cardano.Api.Ledger as L import qualified Cardano.Rpc.Client as Rpc -import qualified Cardano.Rpc.Proto.Api.UtxoRpc.Query as U5c hiding (cardano) +import qualified Cardano.Rpc.Proto.Api.UtxoRpc.Query as U5c import qualified Cardano.Rpc.Proto.Api.UtxoRpc.Query as UtxoRpc -import qualified Cardano.Rpc.Proto.Api.UtxoRpc.Submit as U5c -import qualified Cardano.Rpc.Proto.Api.UtxoRpc.Submit as UtxoRpc +import qualified Cardano.Rpc.Proto.Api.UtxoRpc.Submit as Submit import Cardano.Rpc.Server.Internal.UtxoRpc.Predicate (exactAddressPredicate, serialisePaymentCredential) import Cardano.Rpc.Server.Internal.UtxoRpc.Type @@ -123,8 +122,8 @@ hprop_rpc_search_utxos = integrationRetryWorkspace 2 "rpc-search-utxos" $ \tempA liftBaseOp (Rpc.withConnection def rpcServer) $ \conn -> do _submitResponse <- H.noteShowM . H.evalIO $ - Rpc.nonStreaming conn (Rpc.rpc @(Rpc.Protobuf UtxoRpc.SubmitService "submitTx")) $ - def & U5c.tx .~ (def & U5c.raw .~ serialiseToRawBytes (Exp.SignedTx signedLedgerTx)) + Rpc.nonStreaming conn (Rpc.rpc @(Rpc.Protobuf Submit.SubmitService "submitTx")) $ + def & Submit.tx .~ (def & Submit.raw .~ serialiseToRawBytes (Exp.SignedTx signedLedgerTx)) ------------------------------------------- -- Wait for UTxOs to appear at address1 diff --git a/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/Transaction.hs b/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/Transaction.hs index 58f13064fc3..e39d65a5333 100644 --- a/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/Transaction.hs +++ b/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/Transaction.hs @@ -17,10 +17,9 @@ import qualified Cardano.Api.Experimental.Tx as Exp import qualified Cardano.Api.Ledger as L import qualified Cardano.Rpc.Client as Rpc -import qualified Cardano.Rpc.Proto.Api.UtxoRpc.Query as U5c hiding (cardano) +import qualified Cardano.Rpc.Proto.Api.UtxoRpc.Query as U5c import qualified Cardano.Rpc.Proto.Api.UtxoRpc.Query as UtxoRpc -import qualified Cardano.Rpc.Proto.Api.UtxoRpc.Submit as U5c -import qualified Cardano.Rpc.Proto.Api.UtxoRpc.Submit as UtxoRpc +import qualified Cardano.Rpc.Proto.Api.UtxoRpc.Submit as Submit import Cardano.Rpc.Server.Internal.UtxoRpc.Predicate (exactAddressPredicate) import Cardano.Rpc.Server.Internal.UtxoRpc.Type import Cardano.Testnet @@ -116,10 +115,10 @@ hprop_rpc_transaction = integrationRetryWorkspace 2 "rpc-tx" $ \tempAbsBasePath' liftBaseOp (Rpc.withConnection def rpcServer) $ \conn -> do submitResponse <- H.noteShowM . H.evalIO $ - Rpc.nonStreaming conn (Rpc.rpc @(Rpc.Protobuf UtxoRpc.SubmitService "submitTx")) $ - def & U5c.tx .~ (def & U5c.raw .~ serialiseToRawBytes (Exp.SignedTx signedLedgerTx)) + Rpc.nonStreaming conn (Rpc.rpc @(Rpc.Protobuf Submit.SubmitService "submitTx")) $ + def & Submit.tx .~ (def & Submit.raw .~ serialiseToRawBytes (Exp.SignedTx signedLedgerTx)) - submittedTxId <- H.leftFail . deserialiseFromRawBytes AsTxId $ submitResponse ^. U5c.ref + submittedTxId <- H.leftFail . deserialiseFromRawBytes AsTxId $ submitResponse ^. Submit.ref H.note_ "Ensure that submitTx returns the same transaction ID as the locally computed signed transaction ID" txId' === submittedTxId diff --git a/cardano-testnet/test/cardano-testnet-test/cardano-testnet-test.hs b/cardano-testnet/test/cardano-testnet-test/cardano-testnet-test.hs index 955728500db..b9576a29cd7 100644 --- a/cardano-testnet/test/cardano-testnet-test/cardano-testnet-test.hs +++ b/cardano-testnet/test/cardano-testnet-test/cardano-testnet-test.hs @@ -13,6 +13,7 @@ import qualified Cardano.Testnet.Test.Cli.Plutus.MultiAssetReturnCollateral import qualified Cardano.Testnet.Test.Cli.Plutus.Scripts import qualified Cardano.Testnet.Test.Cli.Query import qualified Cardano.Testnet.Test.Cli.QuerySlotNumber +import qualified Cardano.Testnet.Test.Cli.Receiving import qualified Cardano.Testnet.Test.Cli.Scripts.Simple.CostCalculation import qualified Cardano.Testnet.Test.Cli.Scripts.Simple.Mint import qualified Cardano.Testnet.Test.Cli.StakeSnapshot @@ -118,7 +119,8 @@ tests = do , T.testGroup "API" [ignoreOnWindows "actual datum for transaction datum hashes in ref inputs" Cardano.Testnet.Test.Api.TxReferenceInputDatum.hprop_tx_refin_datum] , T.testGroup "CLI" - [ ignoreOnWindows "Shutdown" Cardano.Testnet.Test.Node.Shutdown.hprop_shutdown + [ ignoreOnWindows "Dijkstra Receiving lifecycle" Cardano.Testnet.Test.Cli.Receiving.hprop_receiving_lifecycle + , ignoreOnWindows "Shutdown" Cardano.Testnet.Test.Node.Shutdown.hprop_shutdown -- ShutdownOnSigint fails on Mac with -- "Log file: /private/tmp/tmp.JqcjW7sLKS/kes-period-info-2-test-30c2d0d8eb042a37/logs/test-spo.stdout.log had no logs indicating the relevant node has minted blocks." , ignoreOnMacAndWindows "Shutdown On Sigint" Cardano.Testnet.Test.Node.Shutdown.hprop_shutdownOnSigint diff --git a/cardano-testnet/test/cardano-testnet-test/files/plutus/v4/README.md b/cardano-testnet/test/cardano-testnet-test/files/plutus/v4/README.md new file mode 100644 index 00000000000..481a5b8109b --- /dev/null +++ b/cardano-testnet/test/cardano-testnet-test/files/plutus/v4/README.md @@ -0,0 +1,18 @@ +# CIP-160 per-output Receiving/Spending fixture + +This envelope is byte-identical to the genuine ledger preprocessor output for +`receivingEvenDatum`, regenerated against the per-output V4 context source at +Plutus proposal commit `dcbb7e3`. Receiving checks its specific authorized +output's inline integer datum is even; Spending checks the consumed inline +datum. Each protected Plutus output has a separate Receiving index and budget. + +Text-envelope SHA256: df7f93cfaa4fc11032aed99d00215d43479eec5922a5765c696e09cc8240ae44. +Serialized script: 208 bytes; SHA256 dbce2a96a7a81771a27ea29635b0c05d4f7c45fee8714e63a2ab83b1cd436fbc. +Plutus V4 script hash: 28d467557081773a051b5f83982574abfccceb079bc8012f192505e2. + +The preprocessor completed two byte-identical generation passes; its receipt +compares all 41 legacy V1-V3 fixtures unchanged. The registered real-node test uses distinct indexes 2/3 for the two V4 outputs. +Its creation/rejection, exact Local State Query and API replay, same-database relay +restart and subsequent spending checks passed with the current 369-parameter +V4 genesis model. This validates the local Dijkstra lifecycle; it does not +establish scheduled activation, snapshot restoration, rollback or Leios support. diff --git a/cardano-testnet/test/cardano-testnet-test/files/plutus/v4/receiving-even-datum.plutus b/cardano-testnet/test/cardano-testnet-test/files/plutus/v4/receiving-even-datum.plutus new file mode 100644 index 00000000000..fc140d88da9 --- /dev/null +++ b/cardano-testnet/test/cardano-testnet-test/files/plutus/v4/receiving-even-datum.plutus @@ -0,0 +1,5 @@ +{ + "type": "PlutusScriptV4", + "description": "Compiler-generated per-output CIP-160 V4 fixture: receivingEvenDatum", + "cborHex": "58ce0102009800aba4aba1ab9cabd8488888c8ca64cdc3a401d20052200264cdc3a400520052200252866400a44002914c9804000ca00260120029400923004001a5019900291000a4465266e1d20049001910012942600860132003220010dd5499803240086eb0c02000644004264c6600a921035054350049a930a6600200329375400522801246500237560052328011bac00294004dd68012465002375c00549900748cdc3a400066e180052004a501baa993758003229001914800c8a00200d0048014dd718050008488880081" +} diff --git a/cardano-tracer/CHANGELOG.md b/cardano-tracer/CHANGELOG.md index 44e893a9112..b16bd3aa308 100644 --- a/cardano-tracer/CHANGELOG.md +++ b/cardano-tracer/CHANGELOG.md @@ -1,6 +1,8 @@ # ChangeLog -## NEXT +## NEXT (0.5.0.1 proposal) + +* Update explicit network and diffusion dependency bounds for the selected node integration. ## 0.5.0 (June 2026) * Timeseries query endpoint: `POST /timeseries/query` now expects an diff --git a/cardano-tracer/cardano-tracer.cabal b/cardano-tracer/cardano-tracer.cabal index 50cd5f2f83c..9113c5c2fee 100644 --- a/cardano-tracer/cardano-tracer.cabal +++ b/cardano-tracer/cardano-tracer.cabal @@ -1,7 +1,7 @@ cabal-version: 3.0 name: cardano-tracer -version: 0.5.0 +version: 0.5.0.1 synopsis: A service for logging and monitoring over Cardano nodes description: A service for logging and monitoring over Cardano nodes. category: Cardano, @@ -112,7 +112,7 @@ library , bimap , blaze-html , bytestring - , cardano-diffusion ^>= 1.1.1 + , cardano-diffusion ^>= 1.3 , cborg ^>= 0.2.4 , containers , contra-tracer @@ -127,7 +127,7 @@ library , network , network-mux >= 0.8 , optparse-applicative - , ouroboros-network:{api, framework} ^>= 1.2 + , ouroboros-network:{api, framework} ^>= 1.3 , signal , slugify , smtp-mail ^>= 0.5 diff --git a/flake.lock b/flake.lock index 9d6b015e9b5..4d3267c0d23 100644 --- a/flake.lock +++ b/flake.lock @@ -3,11 +3,11 @@ "CHaP": { "flake": false, "locked": { - "lastModified": 1790112025, - "narHash": "sha256-kyrp5BFBkDaQjWz3XIirM7oGEfDoYyDgyLiex4xIr4M=", + "lastModified": 1791320560, + "narHash": "sha256-IbzWjnP7ETXz0V0QG5pc8TVLp1d3A0oZHd+u6bfz1Do=", "owner": "intersectmbo", "repo": "cardano-haskell-packages", - "rev": "cca2e2920ae9040b8721c6ca7297a243f6399271", + "rev": "f6abe8aa50daaa4bf66a81ea903aab06a31686ea", "type": "github" }, "original": { diff --git a/nix/workbench/genesis/zero/genesis/dijkstra.json b/nix/workbench/genesis/zero/genesis/dijkstra.json index 95f4234f509..cc6ec8c40ed 100644 --- a/nix/workbench/genesis/zero/genesis/dijkstra.json +++ b/nix/workbench/genesis/zero/genesis/dijkstra.json @@ -255,6 +255,124 @@ 0, 0, 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, 0 ], "leiosAnnouncementPeriodLength": 0, @@ -268,5 +386,13 @@ "memory": 0, "steps": 0 }, - "maxRefScriptSizePerEndorserBlock": 0 + "maxRefScriptSizePerEndorserBlock": 0, + "perasMinCandidateBlockAge": 0, + "perasHealingFactor": 1, + "perasCertBoost": 0, + "perasTargetCommitteeSize": 0, + "perasBootstrapRound": null, + "perasQuorumThresholdSafetyMargin": 0, + "refInputsCostPerMultiAssetPolicy": 0, + "refInputsCostPerDatumByte": 0 } diff --git a/trace-forward/CHANGELOG.md b/trace-forward/CHANGELOG.md index c027eb6cc6c..15b2b354a8c 100644 --- a/trace-forward/CHANGELOG.md +++ b/trace-forward/CHANGELOG.md @@ -1,5 +1,9 @@ # ChangeLog +## Unreleased (2.5.0.1 proposal) + +* Update the network API/protocol dependency bounds to the selected 1.3 integration. + ## 2.4.1 - Mar 2026 * Increase robustness of evaluating trace objects to be forwarded diff --git a/trace-forward/trace-forward.cabal b/trace-forward/trace-forward.cabal index b63ba188cdb..b180bd9f923 100644 --- a/trace-forward/trace-forward.cabal +++ b/trace-forward/trace-forward.cabal @@ -1,7 +1,7 @@ cabal-version: 3.0 name: trace-forward -version: 2.5.0 +version: 2.5.0.1 synopsis: The forwarding protocols library for cardano node. description: The library providing typed protocols for forwarding different information from the cardano node to an external application. @@ -69,7 +69,7 @@ library , io-classes , network , network-mux - , ouroboros-network:{api, framework} ^>= 1.2 + , ouroboros-network:{api, framework} ^>= 1.3 , ekg-core , ekg-forward >= 1.2 , singletons ^>= 3.0 From ace70fcb68355defa44acfdd345f10bce83e015f Mon Sep 17 00:00:00 2001 From: Philip DiSarro Date: Wed, 7 Oct 2026 05:51:32 -0500 Subject: [PATCH 2/6] Link node integration changelogs to PR 6727 --- .../.changes/20261006_000000_cip160_integration.yml | 2 +- .../.changes/20261006_000000_cip160_integration.yml | 2 +- cardano-testnet/.changes/20261006_000000_cip160_integration.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cardano-node-chairman/.changes/20261006_000000_cip160_integration.yml b/cardano-node-chairman/.changes/20261006_000000_cip160_integration.yml index 2a1a56f9470..c3917a91542 100644 --- a/cardano-node-chairman/.changes/20261006_000000_cip160_integration.yml +++ b/cardano-node-chairman/.changes/20261006_000000_cip160_integration.yml @@ -1,4 +1,4 @@ -pr: 0 +pr: 6727 kind: - maintenance description: | diff --git a/cardano-submit-api/.changes/20261006_000000_cip160_integration.yml b/cardano-submit-api/.changes/20261006_000000_cip160_integration.yml index e4501c907fe..dc5abf2482c 100644 --- a/cardano-submit-api/.changes/20261006_000000_cip160_integration.yml +++ b/cardano-submit-api/.changes/20261006_000000_cip160_integration.yml @@ -1,4 +1,4 @@ -pr: 0 +pr: 6727 kind: - maintenance description: | diff --git a/cardano-testnet/.changes/20261006_000000_cip160_integration.yml b/cardano-testnet/.changes/20261006_000000_cip160_integration.yml index 292c8e84ecd..ce5e8021d3a 100644 --- a/cardano-testnet/.changes/20261006_000000_cip160_integration.yml +++ b/cardano-testnet/.changes/20261006_000000_cip160_integration.yml @@ -1,4 +1,4 @@ -pr: 0 +pr: 6727 kind: - bugfix description: | From e12e1ab3faff2ccf3dcc1615df62cc7e055b8533 Mon Sep 17 00:00:00 2001 From: Philip DiSarro Date: Wed, 7 Oct 2026 07:12:11 -0500 Subject: [PATCH 3/6] Fix generator era dispatch and platform dependency compatibility --- bench/locli/CHANGELOG.md | 2 +- bench/tx-generator/CHANGELOG.md | 2 +- .../tx-generator/src/Cardano/Benchmarking/Script/Core.hs | 4 ++-- cabal.project | 8 ++++---- flake.nix | 1 + nix/haskell.nix | 9 +++++++++ 6 files changed, 18 insertions(+), 8 deletions(-) diff --git a/bench/locli/CHANGELOG.md b/bench/locli/CHANGELOG.md index 6cdf5bd627a..5bee55ef3d6 100644 --- a/bench/locli/CHANGELOG.md +++ b/bench/locli/CHANGELOG.md @@ -1,6 +1,6 @@ # Unreleased 2.4.0.1 -- Update the explicit network API bound to 1.3 for the Dijkstra feature dependency graph. Compilation and runtime validation remain pending. +- Update the explicit network API bound to 1.3 for the Dijkstra feature dependency graph. # Revision history for locli diff --git a/bench/tx-generator/CHANGELOG.md b/bench/tx-generator/CHANGELOG.md index af0e07e6ffd..bd3b215d598 100644 --- a/bench/tx-generator/CHANGELOG.md +++ b/bench/tx-generator/CHANGELOG.md @@ -1,6 +1,6 @@ # Unreleased 2.18.0.1 -- Update API, CLI and diffusion bounds for the Dijkstra Receiving source proposals. Compilation and runtime validation remain pending. +- Update API, CLI and diffusion bounds for the Dijkstra Receiving source proposals. Use the current public era-dispatch API for node era queries. # ChangeLog diff --git a/bench/tx-generator/src/Cardano/Benchmarking/Script/Core.hs b/bench/tx-generator/src/Cardano/Benchmarking/Script/Core.hs index f20e9c5725b..dc15f071e4a 100644 --- a/bench/tx-generator/src/Cardano/Benchmarking/Script/Core.hs +++ b/bench/tx-generator/src/Cardano/Benchmarking/Script/Core.hs @@ -63,7 +63,7 @@ import qualified Data.ByteString as BS (hPut) import Data.ByteString.Lazy.Char8 as BSL (writeFile) import Data.Ratio ((%)) import qualified Data.Text as Text (unpack) -import System.IO (IOMode(..), withFile) +import System.IO (IOMode (..), withFile) import Streaming import qualified Streaming.Prelude as Streaming @@ -172,7 +172,7 @@ queryEra = do AnyCardanoEra era <- mapExceptT liftIO . modifyError (Env.TxGenError . TxGenError . show) $ queryNodeLocalState localNodeConnectInfo (SpecificPoint $ chainTipToChainPoint chainTip) QueryCurrentEra - caseByronOrShelleyBasedEra + inEonForEra (liftTxGenError $ TxGenError "queryEra Byron not supported") (return . AnyShelleyBasedEra) era diff --git a/cabal.project b/cabal.project index eddeb4c9a05..4f2831199ce 100644 --- a/cabal.project +++ b/cabal.project @@ -238,8 +238,8 @@ source-repository-package source-repository-package type: git location: https://github.com/colll78/cardano-cli.git - tag: 7bc80d20076082ae5ebe17ac03bb47bc41c9ec48 - --sha256: sha256-Tk0kqzvbntxMrjvVghv6IOzE+nDhU8rKPSoVXH175Xg= + tag: 24713d80e2dcf43fee20305a894ab1b7b069ce48 + --sha256: sha256-hGpi99zHh7IsKmb9PajbHqvYEmu+KIwCx/BQ5sdMad8= subdir: cardano-cli source-repository-package @@ -264,5 +264,5 @@ source-repository-package source-repository-package type: git location: https://github.com/colll78/dns.git - tag: a3ef6cdcdb5173d88c2e2a3201f528dbe0df1c0c - --sha256: sha256-fOWp3BFVkD4dJvxVtskdOyPg5uN9GUEL7NEj9Z4pBm8= + tag: 7bd4304298b38db3da0e72dec2b311f4cd785657 + --sha256: sha256-HRBHCYqk4Y63M81ry+qs7o6qtottda1RddK84lTSb/E= diff --git a/flake.nix b/flake.nix index a07c098cc97..998e05eb9a1 100644 --- a/flake.nix +++ b/flake.nix @@ -554,6 +554,7 @@ (import ./nix/haskell.nix { inherit (final) haskell-nix; inherit CHaP incl windowsCompilerNixName; + haskellNixSource = haskellNix; macOS-security = macOS-security (final.pkgs); # buildPlatform, not hostPlatform: herald is a developer tool that # runs on the machine, so cross shells (e.g. windows) must not try diff --git a/nix/haskell.nix b/nix/haskell.nix index d58f4f1ecbb..6e2153ab1ce 100644 --- a/nix/haskell.nix +++ b/nix/haskell.nix @@ -2,6 +2,7 @@ # Builds Haskell packages with Haskell.nix ############################################################################ { haskell-nix +, haskellNixSource , incl , CHaP , macOS-security @@ -160,6 +161,14 @@ let packages.plutus-ledger-api.components.library.doHaddock = false; }) ({ lib, pkgs, ...}: lib.mkIf (pkgs.stdenv.hostPlatform.isWindows) { + # The locked haskell.nix overlay omits the .patch suffix for >= 1.7. + # Replace that defective list while retaining both versioned patches. + packages.crypton-x509-system.patches = lib.mkForce [ + ({ version }: + if builtins.compareVersions version "1.7" < 0 + then haskellNixSource + "/overlays/patches/crypton-x509-system-1.6.patch" + else haskellNixSource + "/overlays/patches/crypton-x509-system.patch") + ]; packages.basement.configureFlags = [ "--hsc2hs-option=--cflag=-Wno-int-conversion" ]; # This fix seems fairly fishy; but somehow it's required to make this work :confused_parrot: packages.unix-compat.postPatch = '' From e6a3b4bf3b0790517331738a47efd6e4174bf071 Mon Sep 17 00:00:00 2001 From: Philip DiSarro Date: Wed, 7 Oct 2026 07:40:22 -0500 Subject: [PATCH 4/6] Fix RPC completion, genesis hashes and tracer test synchronization --- .../Cardano/Testnet/Test/Rpc/FollowTip.hs | 71 +++--- cardano-tracer/cardano-tracer.cabal | 1 + .../Test/ForwardingStressTest/Script.hs | 204 +++++++++++------- .../test/cardano-tracer-test-ext.hs | 10 +- nix/nixos/tests/cardano-node-dbtools.nix | 15 ++ .../Forward/Protocol/TraceObject/Tests.hs | 27 ++- trace-forward/trace-forward.cabal | 1 + 7 files changed, 224 insertions(+), 105 deletions(-) diff --git a/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/FollowTip.hs b/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/FollowTip.hs index 54d7e7207a1..1ee9a9034db 100644 --- a/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/FollowTip.hs +++ b/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/FollowTip.hs @@ -25,11 +25,12 @@ import Cardano.Testnet import Prelude -import Control.Exception (try) -import Control.Monad (replicateM, void) +import Control.Exception (Exception, catch, throwIO, try) +import Control.Monad (replicateM, void, (>=>)) import qualified Data.ByteString as BS import Data.Default.Class import Data.List.NonEmpty (NonEmpty ((:|))) +import Data.Typeable (Typeable) import GHC.Stack (HasCallStack, callStack) import Lens.Micro import Network.GRPC.Spec (GrpcError (..), GrpcException (..), NextElem (..)) @@ -40,6 +41,17 @@ import Testnet.Types import qualified Hedgehog as H import qualified Hedgehog.Extras as H +-- A bounded reader deliberately stops an ongoing FollowTip call. Carry the +-- completed result through exception cleanup so grapesy does not mistake a +-- normal callback return for an unhandled client cancellation. Only this +-- private marker is caught; receive failures remain failures. +newtype FollowTipReadComplete a = FollowTipReadComplete a + +instance Show (FollowTipReadComplete a) where + show _ = "FollowTipReadComplete" + +instance Typeable a => Exception (FollowTipReadComplete a) + -- | Run with: -- @TASTY_PATTERN='/RPC FollowTip/' cabal test cardano-testnet-test@ hprop_rpc_follow_tip :: H.Property @@ -60,15 +72,23 @@ hprop_rpc_follow_tip = integrationRetryWorkspace 2 "rpc-follow-tip" $ \tempAbsBa rpcSocket <- H.note . unFile $ nodeRpcSocketPath node0 let rpcServer = Rpc.ServerUnix rpcSocket - -- Open a fresh FollowTip stream intersecting at the given references, - -- read exactly the requested number of messages, then let the - -- connection close: the stream is never consumed beyond what the - -- caller asked for. - followTipN :: Int -> [Rpc.Proto U5c.BlockRef] -> H.Integration [NextElem (Rpc.Proto U5c.FollowTipResponse)] - followTipN messageCount intersectRefs = + -- Finish a bounded read by propagating its result through withRPC's + -- exception cleanup, then catch only our completion marker while the + -- connection is still open. An actual gRPC error is never caught here. + followTipBounded + :: forall a. Typeable a + => [Rpc.Proto U5c.BlockRef] + -> (IO (NextElem (Rpc.Proto U5c.FollowTipResponse)) -> IO a) + -> H.Integration a + followTipBounded intersectRefs consume = H.evalIO . Rpc.withConnection def rpcServer $ \conn -> - Rpc.serverStreaming conn (Rpc.rpc @(Rpc.Protobuf U5c.SyncService "followTip")) (def & U5c.intersect .~ intersectRefs) $ - \recv -> replicateM messageCount recv + catch + (Rpc.serverStreaming conn (Rpc.rpc @(Rpc.Protobuf U5c.SyncService "followTip")) (def & U5c.intersect .~ intersectRefs) $ + consume >=> throwIO . FollowTipReadComplete) + (\(FollowTipReadComplete result :: FollowTipReadComplete a) -> pure result) + + followTipN :: Int -> [Rpc.Proto U5c.BlockRef] -> H.Integration [NextElem (Rpc.Proto U5c.FollowTipResponse)] + followTipN messageCount intersectRefs = followTipBounded intersectRefs (replicateM messageCount) -- Open a stream expecting it to fail before any message is delivered, -- mirroring FetchBlock's @fetchBlockExpectingError@ pattern. @@ -108,22 +128,21 @@ hprop_rpc_follow_tip = integrationRetryWorkspace 2 "rpc-follow-tip" $ \tempAbsBa -> Int -> H.Integration (Maybe (Rpc.Proto U5c.Tx)) followTipUntilTx intersectRefs txHash maxApplyMessages = - H.evalIO . Rpc.withConnection def rpcServer $ \conn -> - Rpc.serverStreaming conn (Rpc.rpc @(Rpc.Protobuf U5c.SyncService "followTip")) (def & U5c.intersect .~ intersectRefs) $ \recv -> - let scanApplies remaining - | remaining <= 0 = pure Nothing - | otherwise = do - next <- recv - case next of - NoNextElem -> pure Nothing - NextElem message -> - case message ^. U5c.maybe'apply of - Nothing -> scanApplies remaining - Just block -> - case filter (\t -> t ^. U5c.hash == txHash) (block ^. U5c.cardano . U5c.body . U5c.tx) of - (tx : _) -> pure (Just tx) - [] -> scanApplies (remaining - 1) - in scanApplies maxApplyMessages + followTipBounded intersectRefs $ \recv -> + let scanApplies remaining + | remaining <= 0 = pure Nothing + | otherwise = do + next <- recv + case next of + NoNextElem -> pure Nothing + NextElem message -> + case message ^. U5c.maybe'apply of + Nothing -> scanApplies remaining + Just block -> + case filter (\t -> t ^. U5c.hash == txHash) (block ^. U5c.cardano . U5c.body . U5c.tx) of + (tx : _) -> pure (Just tx) + [] -> scanApplies (remaining - 1) + in scanApplies maxApplyMessages -- Origin is a BlockRef with an empty hash: clients append it as an -- infallible catch-all requesting full-history sync. diff --git a/cardano-tracer/cardano-tracer.cabal b/cardano-tracer/cardano-tracer.cabal index 9113c5c2fee..219163774ea 100644 --- a/cardano-tracer/cardano-tracer.cabal +++ b/cardano-tracer/cardano-tracer.cabal @@ -363,6 +363,7 @@ test-suite cardano-tracer-test-ext , ouroboros-network:{api, framework} , process , QuickCheck + , stm <2.5.2 || >=2.5.3 , tasty , tasty-quickcheck , text diff --git a/cardano-tracer/test/Cardano/Tracer/Test/ForwardingStressTest/Script.hs b/cardano-tracer/test/Cardano/Tracer/Test/ForwardingStressTest/Script.hs index cecad3ff9e3..e964da3d4b6 100644 --- a/cardano-tracer/test/Cardano/Tracer/Test/ForwardingStressTest/Script.hs +++ b/cardano-tracer/test/Cardano/Tracer/Test/ForwardingStressTest/Script.hs @@ -26,14 +26,18 @@ import Control.Concurrent.Async (forConcurrently_) import Control.Monad (when) import Data.Aeson import qualified Data.Aeson.KeyMap as KeyMap +import qualified Data.ByteString.Char8 as StrictBS import Data.ByteString.Lazy.Char8 (ByteString, pack) -import Data.Either (partitionEithers) +import Data.Either (partitionEithers, rights) import Data.IORef import Data.List (nub) import Data.Map.Strict (fromList) import Data.Vector (Vector) import qualified Data.Vector as Vector import System.FilePath.Glob +import System.IO (IOMode (ReadMode), SeekMode (AbsoluteSeek), hFileSize, hSeek, + withBinaryFile) +import System.Timeout (timeout) import Test.QuickCheck @@ -49,7 +53,7 @@ simpleTestConfig = emptyTraceConfig { ] } --- | Run scripts in three threads in parallel. +-- | Run scripts using the configured number of concurrent producers. -- The duration of the test is given by time in seconds runScriptForwarding :: TestSetup Identity @@ -64,80 +68,128 @@ runScriptForwarding TestSetup{..} msgCountersRef msgsRef tracerGetter = do Nothing -> scale (* 500) arbitrary Just numMsg -> Script <$> Utils.sizedVectorOf numMsg arbitrary forAll generator \(scripts :: Vector Script) -> ioProperty do - tr <- tracerGetter - confState <- emptyConfigReflection - configureTracers confState simpleTestConfig [tr] - - let scripts' = Vector.map (\(Script sc) -> Script (Utils.vectorSort sc)) scripts - - scripts'' = Vector.map (\(ind, Script sc) -> Script (withMessageIds (unI tsThreads) ind sc)) (Vector.indexed scripts') - - scripts''' = Vector.map (\(Script sc) -> Script - $ Vector.map (withTimeFactor (unI tsTime)) sc) scripts'' - - let messages :: Vector Message - messages = Vector.concatMap scriptMessages scripts''' - - threadDelay 0_500_000 --wait 0,5 seconds - forConcurrently_ scripts''' do - playIt tr 0.0 - - let numMsg = sum (fmap (\ (Script sc) -> length sc) scripts''') - if numMsg > 0 then do - -- TODO multiple files - let logfileGlobPattern = unI tsWorkDir <> "/logs/*sock_*/node-*.json" - logs <- glob logfileGlobPattern - logFile <- case logs of - [] -> fail $ "No files match the logfile glob pattern: " <> logfileGlobPattern - _:_:_ -> fail $ "More than one file matches the logfile glob pattern: " <> logfileGlobPattern - x:_ -> pure x - - contents <- readFile logFile - - let prs :: ByteString -> Either String Message - prs "" = Left "empty line" - prs str = - case decode @Object str of - Nothing -> Left "no decode" - Just a -> - case KeyMap.lookup "data" a of - Nothing -> Left "no data" - Just deita -> - case fromJSON @Message deita of - Data.Aeson.Error str' -> Left str' - Data.Aeson.Success a' -> Right a' - - let lineLength = length (lines contents) - - totalNumMsg :: [Int] - <- atomicModifyIORef msgCountersRef \case - [] -> let newLen = [numMsg] in (newLen, newLen) - len:lens -> let newlen = (len + numMsg) : len:lens in (newlen, newlen) - - let parsedLines :: [Either String Message] - parsedLines = map (prs . pack) (lines contents) - - failures :: [String] - parsedMessages :: [Message] - (failures, parsedMessages) = partitionEithers parsedLines - - case nub failures of - [] -> pure () - ["empty line"] -> pure () - _ -> error ".." - - totalMsgs :: Vector Message <- atomicModifyIORef msgsRef (\ac -> - let nc = ac <> messages - in (nc, nc)) - - pure $ conjoin - [ counterexample ("Number of messages (" ++ show (head totalNumMsg) ++ ") does not match log file " ++ logFile ++ " length: " ++ show (lineLength - 1)) do - head totalNumMsg === (lineLength - 1) - , counterexample "Messages do not match the Messages do not match." do - Utils.vectorSort (Vector.fromList parsedMessages) === Utils.vectorSort totalMsgs - ] - else - pure (property True) + let requestedSeconds = unI tsTime + maxTimeoutSeconds = fromIntegral (maxBound :: Int) / 1_000_000 + when (isNaN requestedSeconds || isInfinite requestedSeconds + || requestedSeconds > maxTimeoutSeconds - 50) $ + fail "The configured script duration must fit a finite timeout" + let deadlineSeconds = max 60 (requestedSeconds + 50) + deadlineMicros = ceiling (deadlineSeconds * 1_000_000) + priorCounts <- readIORef msgCountersRef + let barrier = Message2 (negate (length priorCounts + 1)) "end-to-end-barrier" + expectedCount = sum (fmap scriptLength scripts) + 1 + progress <- newIORef Nothing + -- Bound admission and the consumer acknowledgment together. A stalled + -- consumer must fail, rather than leave a producer blocked forever. + result <- timeout deadlineMicros do + tr <- tracerGetter + confState <- emptyConfigReflection + configureTracers confState simpleTestConfig [tr] + + let scripts' = Vector.map (\(Script sc) -> Script (Utils.vectorSort sc)) scripts + + scripts'' = Vector.map (\(ind, Script sc) -> Script (withMessageIds (unI tsThreads) ind sc)) (Vector.indexed scripts') + + scripts''' = Vector.map (\(Script sc) -> Script + $ Vector.map (withTimeFactor (unI tsTime)) sc) scripts'' + + let messages :: Vector Message + messages = Vector.snoc (Vector.concatMap scriptMessages scripts''') barrier + + threadDelay 0_500_000 --wait 0,5 seconds + forConcurrently_ scripts''' do + playIt tr 0.0 + + traceWith tr barrier + + let numMsg = sum (fmap (\ (Script sc) -> length sc) scripts''') + 1 + -- TODO multiple files + let logfileGlobPattern = unI tsWorkDir <> "/logs/*sock_*/node-*.json" + let prs :: ByteString -> Either String Message + prs "" = Left "empty line" + prs str = + case decode @Object str of + Nothing -> Left "no decode" + Just a -> + case KeyMap.lookup "data" a of + Nothing -> Left "no data" + Just deita -> + case fromJSON @Message deita of + Data.Aeson.Error str' -> Left str' + Data.Aeson.Success a' -> Right a' + (logFile, contents) <- awaitBarrier logfileGlobPattern barrier prs progress + + let lineLength = length (lines contents) + + totalNumMsg :: [Int] + <- atomicModifyIORef msgCountersRef \case + [] -> let newLen = [numMsg] in (newLen, newLen) + len:lens -> let newlen = (len + numMsg) : len:lens in (newlen, newlen) + + let parsedLines :: [Either String Message] + parsedLines = map (prs . pack) (lines contents) + + failures :: [String] + parsedMessages :: [Message] + (failures, parsedMessages) = partitionEithers parsedLines + + case nub failures of + [] -> pure () + ["empty line"] -> pure () + _ -> error ".." + + totalMsgs :: Vector Message <- atomicModifyIORef msgsRef (\ac -> + let nc = ac <> messages + in (nc, nc)) + + pure $ conjoin + [ counterexample ("Number of messages (" ++ show (head totalNumMsg) ++ ") does not match log file " ++ logFile ++ " length: " ++ show (lineLength - 1)) do + head totalNumMsg === (lineLength - 1) + , counterexample "Messages do not match the Messages do not match." do + Utils.vectorSort (Vector.fromList parsedMessages) === Utils.vectorSort totalMsgs + ] + case result of + Just prop -> pure prop + Nothing -> do + observed <- readIORef progress + pure $ counterexample + ("Timed out after " ++ show deadlineSeconds + ++ " seconds forwarding/draining " ++ show expectedCount + ++ " messages including barrier " ++ show barrier + ++ "; last observed logfile and byte size: " ++ show observed) + (property False) + +-- The barrier is admitted after all five producers finish. The single FIFO +-- acceptor writes and flushes a batch before requesting another, so observing +-- this final record acknowledges every earlier record without a blind sleep. +awaitBarrier :: FilePath + -> Message + -> (ByteString -> Either String Message) + -> IORef (Maybe (FilePath, Integer)) + -> IO (FilePath, String) +awaitBarrier pattern' barrier parseMessage progress = go + where + go = do + logs <- glob pattern' + case logs of + [] -> wait + [logFile] -> do + -- Only the final small barrier record is needed while observing; read + -- the complete cumulative log once after it arrives for strict checks. + (size, contents) <- withBinaryFile logFile ReadMode $ \handle -> do + size <- hFileSize handle + let bytes = min size 4096 + hSeek handle AbsoluteSeek (size - bytes) + contents <- StrictBS.hGet handle (fromIntegral bytes) + pure (size, contents) + writeIORef progress (Just (logFile, size)) + if barrier `elem` rights (map (parseMessage . pack) (lines (StrictBS.unpack contents))) + then do + complete <- StrictBS.readFile logFile + pure (logFile, StrictBS.unpack complete) + else wait + _ -> fail $ "More than one file matches the logfile glob pattern: " ++ pattern' + wait = threadDelay 20_000 >> go playIt :: Trace IO Message -> Double -> Script -> IO () playIt tr d (Script script) = diff --git a/cardano-tracer/test/cardano-tracer-test-ext.hs b/cardano-tracer/test/cardano-tracer-test-ext.hs index cc93ef8f956..4e22bc0f88f 100644 --- a/cardano-tracer/test/cardano-tracer-test-ext.hs +++ b/cardano-tracer/test/cardano-tracer-test-ext.hs @@ -12,6 +12,8 @@ import Cardano.Tracer.Test.Utils import Ouroboros.Network.Magic (NetworkMagic (..)) import Control.Concurrent (threadDelay) +import qualified Control.Concurrent.STM as STM +import qualified Control.Concurrent.STM.TBQueue as TBQueue import Control.Exception import Control.Monad.Extra import Data.Functor ((<&>)) @@ -31,7 +33,7 @@ import Test.Tasty import Test.Tasty.QuickCheck import Trace.Forward.Forwarding (InitForwardingConfig (..), initForwarding) -import Trace.Forward.Utils.TraceObject (writeToSink) +import Trace.Forward.Utils.ForwardSink (ForwardSink (..)) main :: IO () main = do @@ -148,4 +150,8 @@ getExternalTracerState TestSetup{..} ref = do , initOnForwardInterruption = Nothing , initOnQueueOverflow = Nothing } - pure (externalTracerHdl, forwardTracer (writeToSink forwardSink)) + -- This lossless transport test admits each object atomically and waits + -- for capacity. Production writeToSink intentionally drops a full queue; + -- its overload policy is covered by the separate trace-forward test. + pure (externalTracerHdl, forwardTracer $ \obj -> + STM.atomically $ TBQueue.writeTBQueue (forwardQueue forwardSink) obj) diff --git a/nix/nixos/tests/cardano-node-dbtools.nix b/nix/nixos/tests/cardano-node-dbtools.nix index 6df3ba2e276..7fe4bee6729 100644 --- a/nix/nixos/tests/cardano-node-dbtools.nix +++ b/nix/nixos/tests/cardano-node-dbtools.nix @@ -41,6 +41,7 @@ in { }; testScript = '' + import json import re countRegex = r'Counted (\d+) blocks\.' @@ -54,6 +55,20 @@ in { # Epoch length ideally should also be divisible by 10k = 500 slots print(machine.succeed("cardano-testnet create-env --epoch-length 1000 --output ${testDir}")) + # create-env omits hashes so its genesis files remain editable. The database + # tools require hashes, so pin the generated files before consuming them. + config_path = "${testDir}/configuration.yaml" + config = json.loads(machine.succeed(f"cat {config_path}")) + for era in ["Byron", "Shelley", "Alonzo", "Conway", "Dijkstra"]: + genesis_path = "${testDir}/" + config[f"{era}GenesisFile"] + command = ( + f"cardano-cli byron genesis print-genesis-hash --genesis-json {genesis_path}" + if era == "Byron" + else f"cardano-cli hash genesis-file --genesis {genesis_path}" + ) + config[f"{era}GenesisHash"] = machine.succeed(command).strip() + machine.succeed(f"cat > {config_path} <<'GENESIS_CONFIG'\n{json.dumps(config)}\nGENESIS_CONFIG") + print(machine.succeed("echo Synthesize one epoch")) print(machine.succeed("db-synthesizer \ --config ${testDir}/configuration.yaml \ diff --git a/trace-forward/test/Test/Trace/Forward/Protocol/TraceObject/Tests.hs b/trace-forward/test/Test/Trace/Forward/Protocol/TraceObject/Tests.hs index fb36ddb3d2b..21a8fdce9b8 100644 --- a/trace-forward/test/Test/Trace/Forward/Protocol/TraceObject/Tests.hs +++ b/trace-forward/test/Test/Trace/Forward/Protocol/TraceObject/Tests.hs @@ -10,6 +10,8 @@ import Ouroboros.Network.Channel import Ouroboros.Network.Driver.Simple (runConnectedPeers) import qualified Codec.Serialise as CBOR +import qualified Control.Concurrent.STM as STM +import qualified Control.Concurrent.STM.TBQueue as TBQueue import Control.Monad.Class.MonadAsync import Control.Monad.Class.MonadST import Control.Monad.Class.MonadSTM @@ -17,6 +19,7 @@ import Control.Monad.Class.MonadThrow import Control.Monad.IOSim (runSimOrThrow) import Control.Monad.ST (runST) import Control.Tracer (nullTracer) +import Data.IORef (modifyIORef', newIORef, readIORef) import Network.TypedProtocol.Codec import Network.TypedProtocol.Codec.Properties import Network.TypedProtocol.Proofs @@ -29,14 +32,18 @@ import Test.Trace.Forward.Protocol.TraceObject.Direct import Test.Trace.Forward.Protocol.TraceObject.Examples import Test.Trace.Forward.Protocol.TraceObject.Item +import Trace.Forward.Configuration.TraceObject (ForwarderConfiguration (..)) import Trace.Forward.Protocol.TraceObject.Acceptor import Trace.Forward.Protocol.TraceObject.Codec import Trace.Forward.Protocol.TraceObject.Forwarder import Trace.Forward.Protocol.TraceObject.Type +import Trace.Forward.Utils.ForwardSink (ForwardSink (..)) +import Trace.Forward.Utils.TraceObject (initForwardSink, writeToSink) tests :: TestTree tests = testGroup "Trace.Forward.Protocol.TraceObject" - [ testProperty "codec" prop_codec_TraceObjectForward + [ testProperty "full queue reports dropped prefix and retains new item" (once prop_overflow_TraceObjectForward) + , testProperty "codec" prop_codec_TraceObjectForward , testProperty "codec 2-splits" prop_codec_splits2_TraceObjectForward , testProperty "codec 3-splits" (withNumTests 33 prop_codec_splits3_TraceObjectForward) , testProperty "direct" prop_direct_TraceObjectForward @@ -148,3 +155,21 @@ prop_channel_IO_TraceObjectForward f (NonNegative n) = withNumTests :: Testable prop => Int -> prop -> Property withNumTests = withMaxSuccess #endif + +-- Exercise the unchanged production overload policy without a consumer. +-- Filling the same capacity as the stress test must flush exactly that prefix. +prop_overflow_TraceObjectForward :: Property +prop_overflow_TraceObjectForward = ioProperty $ do + droppedRef <- newIORef [] + sink <- initForwardSink + ForwarderConfiguration { forwarderTracer = nullTracer, queueSize = 768 } + (\items -> modifyIORef' droppedRef (items :)) + mapM_ (writeToSink sink) ([0 .. 768] :: [Int]) + dropped <- readIORef droppedRef + retained <- STM.atomically $ TBQueue.flushTBQueue (forwardQueue sink) + pure $ conjoin + [ counterexample "Overflow callback must report exactly the full old queue" + (dropped === [[0 .. 767]]) + , counterexample "The new object must remain queued after the drop" + (retained === [768]) + ] diff --git a/trace-forward/trace-forward.cabal b/trace-forward/trace-forward.cabal index b180bd9f923..86980bcaae1 100644 --- a/trace-forward/trace-forward.cabal +++ b/trace-forward/trace-forward.cabal @@ -108,6 +108,7 @@ test-suite test , trace-forward , QuickCheck , serialise + , stm <2.5.2 || >=2.5.3 , tasty , tasty-quickcheck , typed-protocols:{typed-protocols, codec-properties} From 14ee5ef475cf74f9d665cf0c93c9d12a181a6bc6 Mon Sep 17 00:00:00 2001 From: Philip DiSarro Date: Wed, 7 Oct 2026 11:22:53 -0500 Subject: [PATCH 5/6] Adapt testnet epoch-state JSON to current ledger schema --- cabal.project | 21 ++++++++++--------- .../20261006_000000_cip160_integration.yml | 1 + cardano-testnet/src/Testnet/Runtime.hs | 15 ++++++------- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/cabal.project b/cabal.project index 4f2831199ce..69f9d095a01 100644 --- a/cabal.project +++ b/cabal.project @@ -177,8 +177,8 @@ if impl(ghc >= 9.14) source-repository-package type: git location: https://github.com/colll78/cardano-ledger.git - tag: 4ab1c0df3373b962c641efb4935aca0e7e68967b - --sha256: sha256-+EearRC1REi8n7pRvsR6AmTC/lyLYkxRdvkwUV1Ilk0= + tag: 4b06e253c2fee5436d57cf56669c4e8a9358717b + --sha256: sha256-PzoJjGuFq23OHvnDDwXnsCkxfywF41typfb7oCKefNw= subdir: eras/allegra/impl eras/alonzo/impl @@ -189,6 +189,7 @@ source-repository-package eras/dijkstra/impl eras/mary/impl eras/shelley/impl + eras/shelley/test-suite libs/cardano-data libs/cardano-ledger-api libs/cardano-ledger-binary @@ -230,23 +231,23 @@ source-repository-package source-repository-package type: git location: https://github.com/colll78/ouroboros-consensus.git - tag: 0c91555c0c0dfeef6cb0ac6facd42b471630f5b0 - --sha256: sha256-19A5YUoKnfUAc/KBPg3s25HaI+zwMGiIk4Vng3NJXH4= + tag: bb55e647b1c1142948562c3702fe96ab2ee160f4 + --sha256: sha256-pqHyVyZfmTmU0UCgHZKjrpGvipR/3Mre7JohH8dilXk= -- Published feature sources retain the complete normal component graph. source-repository-package type: git location: https://github.com/colll78/cardano-cli.git - tag: 24713d80e2dcf43fee20305a894ab1b7b069ce48 - --sha256: sha256-hGpi99zHh7IsKmb9PajbHqvYEmu+KIwCx/BQ5sdMad8= + tag: 9fa3747627cfadeb4fae15d1e09adee70ad63bea + --sha256: sha256-hQUYoPCkLRO6Ym34Wk5SWuZd/ZzUys6ZPt4mrWnVhoQ= subdir: cardano-cli source-repository-package type: git location: https://github.com/colll78/cardano-api.git - tag: a3966fb8dca37e796929ae870f0bc8edea521986 - --sha256: sha256-zhmSimpXLCW+NvoZWMX1HNnEpPOSSsngsXzSwbpx9pE= + tag: 70415a82d7fbbc786aa646199c0cafe4063194fe + --sha256: sha256-+F+j2njiPyKY1suO11uAL/fkxpB8/INDt/ifUzxBCvA= subdir: cardano-api cardano-api-gen @@ -264,5 +265,5 @@ source-repository-package source-repository-package type: git location: https://github.com/colll78/dns.git - tag: 7bd4304298b38db3da0e72dec2b311f4cd785657 - --sha256: sha256-HRBHCYqk4Y63M81ry+qs7o6qtottda1RddK84lTSb/E= + tag: df28303f5d07058388e40b3b9fa59509a11abdbb + --sha256: sha256-2w9tP3bY7jOo+oyI4ozhyH/co+E1VEUpBqwKHpOY57Y= diff --git a/cardano-testnet/.changes/20261006_000000_cip160_integration.yml b/cardano-testnet/.changes/20261006_000000_cip160_integration.yml index ce5e8021d3a..29ff5735ffb 100644 --- a/cardano-testnet/.changes/20261006_000000_cip160_integration.yml +++ b/cardano-testnet/.changes/20261006_000000_cip160_integration.yml @@ -3,3 +3,4 @@ kind: - bugfix description: | Align Dijkstra testnet block protocol major with genesis major 12 and the local protected-address proposal. Enable ExperimentalHardForksEnabled only for requested Dijkstra testnets, preserving earlier-era configurations. Add real protected key/native/V4 creation, query, same-database relay restart and spending coverage. Update dependency bounds for the selected Receiving-aware API, CLI, consensus and network packages. + Update epoch-state logging for the upstream six-field NewEpochState, preserving outer JSON keys and reading currentStakeDistribution from the current set snapshot. Nested state JSON and CBOR follow the upstream ledger schema. diff --git a/cardano-testnet/src/Testnet/Runtime.hs b/cardano-testnet/src/Testnet/Runtime.hs index 90c9f60d96c..13445d9a663 100644 --- a/cardano-testnet/src/Testnet/Runtime.hs +++ b/cardano-testnet/src/Testnet/Runtime.hs @@ -47,6 +47,7 @@ import qualified Data.List as List import qualified Data.List.NonEmpty as NEL import GHC.Stack import qualified GHC.Stack as GHC +import Lens.Micro ((^.)) import Network.Socket (HostAddress, PortNumber) import Prettyprinter (unAnnotate) import qualified System.Directory as IO @@ -551,14 +552,14 @@ calculateEpochStateDiff current next = else ppDiff diffResult instance (L.EraTxOut ledgerera, L.EraGov ledgerera, L.EraCertState ledgerera, L.EraStake ledgerera) => ToJSON (L.NewEpochState ledgerera) where - toJSON (L.NewEpochState nesEL nesBprev nesBCur nesEs nesRu nesPd _stashedAvvm) = + toJSON newEpochState = object - [ "currentEpoch" .= nesEL - , "priorBlocks" .= nesBprev - , "currentEpochBlocks" .= nesBCur - , "currentEpochState" .= nesEs - , "rewardUpdate" .= nesRu - , "currentStakeDistribution" .= nesPd + [ "currentEpoch" .= L.nesEL newEpochState + , "priorBlocks" .= L.nesBprev newEpochState + , "currentEpochBlocks" .= L.nesBcur newEpochState + , "currentEpochState" .= L.nesEs newEpochState + , "rewardUpdate" .= L.nesRu newEpochState + , "currentStakeDistribution" .= (newEpochState ^. L.nesStakePoolDistrG) ] From a220b269af580500b2163200a96e45251852a0e9 Mon Sep 17 00:00:00 2001 From: Philip DiSarro Date: Wed, 7 Oct 2026 14:57:20 -0500 Subject: [PATCH 6/6] changelog: reference the migrated fork pull request --- .../.changes/20261006_000000_cip160_integration.yml | 2 +- .../.changes/20261006_000000_cip160_integration.yml | 2 +- cardano-testnet/.changes/20261006_000000_cip160_integration.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cardano-node-chairman/.changes/20261006_000000_cip160_integration.yml b/cardano-node-chairman/.changes/20261006_000000_cip160_integration.yml index c3917a91542..f10e6a705c8 100644 --- a/cardano-node-chairman/.changes/20261006_000000_cip160_integration.yml +++ b/cardano-node-chairman/.changes/20261006_000000_cip160_integration.yml @@ -1,4 +1,4 @@ -pr: 6727 +pr: 1 kind: - maintenance description: | diff --git a/cardano-submit-api/.changes/20261006_000000_cip160_integration.yml b/cardano-submit-api/.changes/20261006_000000_cip160_integration.yml index dc5abf2482c..255a8fcc0e0 100644 --- a/cardano-submit-api/.changes/20261006_000000_cip160_integration.yml +++ b/cardano-submit-api/.changes/20261006_000000_cip160_integration.yml @@ -1,4 +1,4 @@ -pr: 6727 +pr: 1 kind: - maintenance description: | diff --git a/cardano-testnet/.changes/20261006_000000_cip160_integration.yml b/cardano-testnet/.changes/20261006_000000_cip160_integration.yml index 29ff5735ffb..4644aa4437e 100644 --- a/cardano-testnet/.changes/20261006_000000_cip160_integration.yml +++ b/cardano-testnet/.changes/20261006_000000_cip160_integration.yml @@ -1,4 +1,4 @@ -pr: 6727 +pr: 1 kind: - bugfix description: |