├── .buildkite ├── pipeline.yml ├── stack-cabal-sync.nix └── stack-cabal-sync.sh ├── .gitattributes ├── .gitignore ├── .stack-to-nix.cache ├── Dockerfile ├── Readme.md ├── bors.toml ├── cabal.project ├── cardano-explorer-db ├── CHANGELOG.md ├── LICENSE ├── Setup.hs ├── app │ ├── Explorer │ │ ├── App.hs │ │ └── App │ │ │ └── DB │ │ │ ├── UtxoSet.hs │ │ │ └── Validation.hs │ └── cardano-explorer-db-tool.hs ├── cardano-explorer-db.cabal ├── shell.nix ├── src │ └── Explorer │ │ ├── DB.hs │ │ └── DB │ │ ├── Delete.hs │ │ ├── Error.hs │ │ ├── Insert.hs │ │ ├── Migration.hs │ │ ├── Migration │ │ ├── Haskell.hs │ │ └── Version.hs │ │ ├── PGConfig.hs │ │ ├── Query.hs │ │ ├── Run.hs │ │ ├── Schema.hs │ │ └── Types.hs └── test │ ├── LICENSE │ ├── Test │ ├── IO │ │ └── Explorer │ │ │ └── DB │ │ │ ├── Insert.hs │ │ │ ├── Migration.hs │ │ │ ├── Rollback.hs │ │ │ ├── TotalSupply.hs │ │ │ └── Util.hs │ └── Property │ │ ├── Explorer │ │ └── DB │ │ │ ├── Migration.hs │ │ │ └── Types.hs │ │ └── Upstream.hs │ ├── cardano-explorer-db-test.cabal │ ├── test-db.hs │ └── test.hs ├── cardano-explorer-node ├── CHANGELOG.md ├── LICENSE ├── Setup.hs ├── app │ └── cardano-explorer-node.hs ├── cardano-explorer-node.cabal ├── shell.nix ├── src │ └── Explorer │ │ ├── Node.hs │ │ └── Node │ │ ├── Config.hs │ │ ├── Database.hs │ │ ├── Error.hs │ │ ├── Genesis.hs │ │ ├── Metrics.hs │ │ ├── Plugin.hs │ │ ├── Plugin │ │ ├── Default.hs │ │ ├── Default │ │ │ ├── Insert.hs │ │ │ └── Rollback.hs │ │ └── Epoch.hs │ │ ├── Tracing │ │ └── ToObjectOrphans.hs │ │ └── Util.hs └── test │ └── test.hs ├── cardano-explorer-webapi ├── CHANGELOG.md ├── LICENSE ├── Setup.hs ├── app │ ├── Explorer │ │ └── Web │ │ │ ├── Validate.hs │ │ │ └── Validate │ │ │ ├── Address.hs │ │ │ ├── BlocksTxs.hs │ │ │ ├── ErrorHandling.hs │ │ │ ├── GenesisAddress.hs │ │ │ └── Random.hs │ ├── cardano-explorer-webapi.hs │ ├── cardano-webapi-compare.hs │ └── cardano-webapi-validate.hs ├── cardano-explorer-webapi.cabal ├── shell.nix ├── src │ └── Explorer │ │ ├── Web.hs │ │ └── Web │ │ ├── Api.hs │ │ ├── Api │ │ ├── HttpBridge.hs │ │ ├── HttpBridge │ │ │ └── AddressBalance.hs │ │ ├── Legacy.hs │ │ └── Legacy │ │ │ ├── AddressSummary.hs │ │ │ ├── BlockAddress.hs │ │ │ ├── BlockPagesTotal.hs │ │ │ ├── BlocksPages.hs │ │ │ ├── BlocksTxs.hs │ │ │ ├── EpochPage.hs │ │ │ ├── EpochSlot.hs │ │ │ ├── GenesisAddress.hs │ │ │ ├── GenesisPages.hs │ │ │ ├── GenesisSummary.hs │ │ │ ├── RedeemSummary.hs │ │ │ ├── StatsTxs.hs │ │ │ ├── TxLast.hs │ │ │ ├── TxsSummary.hs │ │ │ ├── Types.hs │ │ │ └── Util.hs │ │ ├── ClientTypes.hs │ │ ├── Error.hs │ │ ├── Query.hs │ │ └── Server.hs ├── swagger.yaml └── test │ ├── Test │ └── Explorer │ │ └── Web │ │ └── Property │ │ └── Types.hs │ └── test.hs ├── cardano-extended-db-node ├── CHANGELOG.md ├── LICENSE ├── Setup.hs ├── app │ └── cardano-extended-db-node.hs ├── cardano-extended-db-node.cabal └── src │ └── Explorer │ └── Plugin │ └── Extended.hs ├── cardano-tx-submit ├── CHANGELOG.md ├── LICENSE ├── Readme.md ├── app │ └── cardano-tx-submit-webapi.hs ├── cardano-tx-submit.cabal ├── src │ └── Cardano │ │ ├── TxSubmit.hs │ │ └── TxSubmit │ │ ├── Config.hs │ │ ├── ErrorRender.hs │ │ ├── Metrics.hs │ │ ├── Node.hs │ │ ├── Tracing │ │ └── ToObjectOrphans.hs │ │ ├── Tx.hs │ │ ├── Types.hs │ │ ├── Util.hs │ │ └── Web.hs ├── test │ └── test.hs └── testing-tx-submit.md ├── config ├── explorer-mainnet-config.yaml ├── explorer-testnet-config.yaml ├── pgpass └── tx-submit-mainnet-config.yaml ├── default.nix ├── doc ├── building-running.md ├── docker.md ├── plugin-system.md ├── schema-management.md ├── tx-submit.md ├── validation.md └── webapi-atomic-requests.md ├── docker ├── default.nix ├── deroot.cpp ├── nix.conf ├── nixpkgs.patch └── runit.patch ├── docs ├── Makefile ├── default.nix └── iohk-skeleton.tex ├── lib.nix ├── monitoring └── explorer-dashboard.json ├── niv-shell.nix ├── nix ├── .stack.nix │ ├── cardano-binary-test.nix │ ├── cardano-binary.nix │ ├── cardano-crypto-class.nix │ ├── cardano-crypto-test.nix │ ├── cardano-crypto-wrapper.nix │ ├── cardano-crypto.nix │ ├── cardano-explorer-db-test.nix │ ├── cardano-explorer-db.nix │ ├── cardano-explorer-node.nix │ ├── cardano-explorer-webapi.nix │ ├── cardano-ledger-test.nix │ ├── cardano-ledger.nix │ ├── cardano-prelude-test.nix │ ├── cardano-prelude.nix │ ├── cardano-shell.nix │ ├── cardano-sl-x509.nix │ ├── cardano-slotting.nix │ ├── cardano-tx-submit.nix │ ├── cborg.nix │ ├── contra-tracer.nix │ ├── cs-blockchain.nix │ ├── cs-ledger.nix │ ├── default.nix │ ├── goblins.nix │ ├── io-sim-classes.nix │ ├── io-sim.nix │ ├── iohk-monitoring.nix │ ├── lobemo-backend-aggregation.nix │ ├── lobemo-backend-editor.nix │ ├── lobemo-backend-ekg.nix │ ├── lobemo-backend-monitoring.nix │ ├── lobemo-scribe-systemd.nix │ ├── network-mux.nix │ ├── ouroboros-consensus.nix │ ├── ouroboros-network-framework.nix │ ├── ouroboros-network.nix │ ├── small-steps.nix │ ├── tracer-transformers.nix │ ├── typed-protocols-cbor.nix │ ├── typed-protocols-examples.nix │ └── typed-protocols.nix ├── cardano-graphql │ ├── default.nix │ ├── packages.nix │ └── pkgs.nix ├── check-lint-fuzz.nix ├── hackage-nix.json ├── nixos │ ├── cardano-explorer-everything.nix │ ├── cardano-explorer-frontend.nix │ ├── cardano-explorer-webapi.nix │ ├── cardano-exporter-service.nix │ ├── cardano-graphql-service.nix │ ├── cardano-tx-submitter.nix │ ├── default.nix │ ├── graphql-engine-service.nix │ ├── graphql-engine │ │ ├── ci-info.nix │ │ ├── default.nix │ │ ├── graphql-engine.nix │ │ ├── graphql-parser.nix │ │ ├── pg-client.nix │ │ └── test.nix │ ├── module-list.nix │ └── tests │ │ ├── chairmans-cluster.nix │ │ └── default.nix ├── pkgs.nix ├── regenerate.sh ├── scripts.nix ├── sources.json ├── sources.nix ├── stack-shell.nix └── util.nix ├── release.nix ├── schema ├── byron-schema.sql ├── migration-1-0000-20190730.sql ├── migration-1-0001-20190730.sql ├── migration-1-0002-20190912.sql ├── migration-1-0003-20200211.sql ├── migration-2-0001-20190902.sql ├── migration-2-0002-20190918.sql ├── migration-2-0003-20191017.sql ├── migration-2-0004-20191022.sql ├── migration-2-0005-20191028.sql ├── migration-2-0006-20191030.sql ├── migration-2-0007-20191031.sql ├── migration-2-0008-20191112.sql ├── migration-2-0009-20191119.sql ├── migration-2-0010-20191126.sql ├── migration-2-0011-20200128.sql └── migration-3-0001-20190816.sql ├── scripts ├── gen-tx-submit-config.sh └── postgresql-setup.sh ├── shell.nix ├── stack.yaml └── usage.nix /.buildkite/pipeline.yml: -------------------------------------------------------------------------------- 1 | # The Buildkite pipeline definition. 2 | # See https://buildkite.com/docs/pipelines for documentation. 3 | 4 | steps: 5 | - label: Check Hydra evaluation of release.nix 6 | commands: 7 | - "nix-build -A iohkLib.check-hydra -o check-hydra.sh" 8 | - "./check-hydra.sh" 9 | agents: 10 | system: x86_64-linux 11 | 12 | # This will ensure that the generated Nix code is kept up to date. 13 | - label: Check auto-generated Nix 14 | commands: 15 | - "nix-build -A iohkLib.check-nix-tools -o check-nix-tools.sh" 16 | - "./check-nix-tools.sh" 17 | agents: 18 | system: x86_64-linux 19 | 20 | # TODO: Remove this and define your own build steps. 21 | - label: Lint the fuzz 22 | commands: 23 | - "nix-build -A checks.lint-fuzz -o check-lint-fuzz.sh" 24 | - "./check-lint-fuzz.sh" 25 | agents: 26 | system: x86_64-linux 27 | 28 | # Imperative build steps 29 | #- label: Stack build 30 | #commands: 31 | #- "nix-build .buildkite/default.nix -o stack-rebuild" 32 | #- "./stack-rebuild" 33 | #agents: 34 | #system: x86_64-linux 35 | #timeout_in_minutes: 60 36 | 37 | - label: 'stack-cabal-sync' 38 | command: 'nix-shell --run .buildkite/stack-cabal-sync.sh .buildkite/stack-cabal-sync.nix' 39 | agents: 40 | system: x86_64-linux 41 | -------------------------------------------------------------------------------- /.buildkite/stack-cabal-sync.nix: -------------------------------------------------------------------------------- 1 | { system ? builtins.currentSystem }: 2 | 3 | let 4 | self = import ../. { inherit system; }; 5 | pkgs = self.pkgs; 6 | repo-tool-src = pkgs.fetchFromGitHub { 7 | owner = "input-output-hk"; 8 | repo = "cardano-repo-tool"; 9 | rev = "94087feff428ebc3f2c2ae8bbb2c958b22ac51dd"; 10 | sha256 = "09gydz3ic1rjljpajbs6zgdr27a3gsp43c034724zks6karybav8"; 11 | }; 12 | cardano-repo-tool = import repo-tool-src { inherit system; }; 13 | in pkgs.runCommand "stack-cabal-sync-shell" { 14 | buildInputs = [ cardano-repo-tool.cardano-repo-tool ]; 15 | shellHook = '' 16 | for EXE in cardano-repo-tool; do 17 | source <($EXE --bash-completion-script `type -p $EXE`) 18 | done 19 | ''; 20 | } "" 21 | -------------------------------------------------------------------------------- /.buildkite/stack-cabal-sync.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -eu 3 | 4 | # This script checks that the `stack.yaml` and `cabal.project` files have 5 | # consistent git hashes for the packages they depend on. We use 6 | # `cardano-repo-tool`'s `update-cabal-project` command which modifies 7 | # `cabal.project` to be consistent with `stack.yaml`s versions. If the 8 | # diff is non-empty, we know they're out of sync. 9 | 10 | # Check that functions are defined. 11 | HELP_TEXT="cardano-repo-tool not found." 12 | type cardano-repo-tool > /dev/null 2>&1 || { echo "${HELP_TEXT}"; exit 1; } 13 | HELP_TEXT="git not found." 14 | type git > /dev/null 2>&1 || { echo "${HELP_TEXT}"; exit 1; } 15 | 16 | # Update `cabal.project` from the `stack.yaml` file. 17 | cardano-repo-tool update-cabal-project 18 | 19 | git diff cabal.project | tee stack-cabal.patch 20 | 21 | if test "$(wc -l < stack-cabal.patch)" -gt 0 ; then 22 | buildkite-agent artifact upload stack-cabal.patch --job "$BUILDKITE_JOB_ID" 23 | exit 1 24 | fi 25 | 26 | exit 0 27 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # GitHub Linguist annotations. 2 | # Hide nix/.stack.nix/*.nix 3 | # That is stuff that is generated by nix-tools stack-to-nix 4 | 5 | nix/.stack.nix/*.nix linguist-generated=true merge=union 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist-newstyle/ 2 | .stack-work/ 3 | dist 4 | *.swp 5 | log-dir 6 | result* 7 | launch_* 8 | cabal.project.local 9 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | ARG environment=all 4 | ENV ENVIRONMENT=mainnet 5 | 6 | RUN apt-get update 7 | RUN apt-get install -y sudo bzip2 curl git xz-utils 8 | 9 | RUN useradd -ms /bin/bash cardano && mkdir /nix /etc/nix && chown cardano /nix 10 | 11 | USER cardano 12 | ENV USER cardano 13 | 14 | RUN curl https://nixos.org/nix/install | sh 15 | ENV PATH /home/cardano/.nix-profile/bin/:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin 16 | 17 | COPY docker/nix.conf /etc/nix/nix.conf 18 | 19 | COPY . /home/cardano/cardano-explorer 20 | 21 | USER root 22 | RUN chown cardano -R /home/cardano 23 | 24 | WORKDIR /home/cardano/cardano-explorer 25 | USER cardano 26 | 27 | RUN nix-build docker -A dockerFileSetup -o initial-setup 28 | 29 | USER root 30 | RUN ./initial-setup && rm initial-setup 31 | 32 | RUN set -e ; if [ ${environment} = all ]; then \ 33 | for env in mainnet testnet staging; do \ 34 | nix build -f docker configFiles -o /etc/cardano-${env} --arg forDockerFile true --argstr environment ${env}; \ 35 | done; \ 36 | ln -sv /etc/cardano-mainnet /etc/cardano-cfg; \ 37 | else \ 38 | nix-build -Q docker -A configFiles -o /etc/cardano-cfg --arg forDockerFile true --argstr environment ${environment}; \ 39 | fi 40 | 41 | RUN ln -sv /etc/cardano-cfg/etc/runit /etc/runit && \ 42 | ln -sv /etc/cardano-cfg/etc/service /etc/service && \ 43 | ln -sv /usr/bin/sudo /bin/sudo && \ 44 | rm /etc/pam.d/sudo /etc/pam.d/other && \ 45 | ln -sv /etc/cardano-cfg/etc/pam.d/sudo /etc/pam.d/sudo && \ 46 | ln -sv /nix/var/nix/profiles/per-user/cardano/profile/bin/deroot /bin/deroot 47 | 48 | RUN nix-env -iA dockerFileBinaries -f docker -I nixpkgs=docker/nixpkgs --profile /nix/var/nix/profiles/per-user/cardano/profile 49 | 50 | RUN cat /etc/sudoers | grep -v secure_path > /etc/sudoers.tmp && mv /etc/sudoers.tmp /etc/sudoers && chmod 440 /etc/sudoers 51 | 52 | # explorer api 53 | EXPOSE 8100 54 | # monitoring interface 55 | EXPOSE 80 56 | 57 | ENTRYPOINT [ "/nix/var/nix/profiles/per-user/cardano/profile/bin/runit" ] 58 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Disclaimer 2 | 3 | :warning: Since 2020-02-13, development of these components has been split and moved to different locations :warning:. 4 | 5 | - If you are an **existing API user** using the **current** Byron explorer API or transaction submission API, have a look at: 6 | 7 | https://github.com/input-output-hk/cardano-rest 8 | 9 | :warning: Note however that this project will no longer undergo feature updates and will not be updated for Shelley. 10 | 11 | - If you are a **new API user** looking for long-term solutions which will also work with Shelley, have a look at: 12 | 13 | https://github.com/input-output-hk/cardano-graphql 14 | 15 | - If you are a **curious developer** keen on knowing the internals and understanding what happens behind the scene, you can dive into: 16 | 17 | https://github.com/input-output-hk/cardano-db-sync 18 | 19 | # Cardano Explorer 20 | 21 | The new cardano-explorer consists of a set of components: 22 | 23 | * `cardano-explorer-db` which defines common data types and functions that are shared by the 24 | following two components. In particular, it defines the database schema. 25 | * `cardano-explorer-node` which acts as a Cardano node, following the chain and inserting 26 | data from the chain into a PostgreSQL database. 27 | * `cardano-explorer-webapi` which serves data from the PostgreSQL database via HTTP. 28 | * `cardano-tx-submit-webapi` allows submission of pre-formed transmissions via a HTTP POST 29 | operation. 30 | 31 | 32 | ## Architecture 33 | 34 | The explorer is written in a highly modular fashion to allow it to be as flexible as possible. 35 | 36 | The `cardano-explorer-node` connects to a locally running `cardano-node` (ie one connected to other 37 | nodes in the Cardano network over the internet with TCP/IP) using a Unix domain socket, retrieves 38 | blocks and stores parts of each block in a local PostgreSQL database. The database does not store 39 | things like cryptographic signatures but does store enough information to follow the chain of 40 | blocks and look at the transactions within blocks. 41 | 42 | The PostgreSQL database is designed to be accessed in a read-only fashion from other applications. 43 | The database schema is highly normalised which helps prevent data inconsistencies (specifically 44 | with the use of foreign keys from one table to another). More user friendly database queries can be 45 | implemented using [Postgres Views][PostgresView] to implement joins between tables. 46 | 47 | The `cardano-explorer-webapi` is a client than serves data from the PostgreSQL database as JSON via a 48 | HTTP REST API. 49 | 50 | 51 | ## Further Reading 52 | 53 | * [BuildingRunning][BuildingRunning]: Building and running the explorer node and webapi. 54 | * [SchemaManagement][Schema Management]: How the database schema is managed and modified. 55 | * [Validation][Validation]: Explanation of validation done by the explorer and assumptions made. 56 | 57 | [BuildingRunning]: doc/building-running.md 58 | [PostgresView]: https://www.postgresql.org/docs/current/sql-createview.html 59 | [Schema Management]: doc/schema-management.md 60 | [Validation]: doc/validation.md 61 | -------------------------------------------------------------------------------- /bors.toml: -------------------------------------------------------------------------------- 1 | status = [ 2 | "buildkite/cardano-explorer", 3 | "ci/hydra:Cardano:cardano-explorer:required", 4 | ] 5 | timeout_sec = 7200 6 | required_approvals = 1 7 | block_labels = [ "WIP", "DO NOT MERGE" ] 8 | delete_merged_branches = true 9 | -------------------------------------------------------------------------------- /cardano-explorer-db/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Revision history for cardano-explorer-db 2 | 3 | ## 1.3.0 -- January 2020 4 | 5 | * Update dependencies to latest versions. 6 | 7 | ## 1.2.2 -- January 2020 8 | 9 | * Update dependencies to latest versions. 10 | * Allow building with latest version of persistent library. 11 | 12 | ## 1.2.1 -- January 2020 13 | 14 | * Update dependencies to latest versions. 15 | 16 | ## 1.2.0 -- December 2019 17 | 18 | * Update dependencies to latest versions. 19 | 20 | ## 1.1.0 -- December 2019 21 | 22 | * Added SQL views for benefit of GraphQL frontend (#172) 23 | * Added extra DB indexes for improved query performance (#175). 24 | * Adjusted column names for consistency (#176). 25 | * Run all transactions at an isolation level of Serializable (#189, #133) 26 | 27 | ## 1.0.0 -- November 2019 28 | 29 | * Release to support first release of cardano-explorer-node and 30 | cardano-explorer. 31 | * PostgreSQL data access for explorer-style applications. 32 | * Support for Byron chain data in the DB schema. 33 | -------------------------------------------------------------------------------- /cardano-explorer-db/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /cardano-explorer-db/app/Explorer/App.hs: -------------------------------------------------------------------------------- 1 | module Explorer.App 2 | ( module X 3 | ) where 4 | 5 | import Explorer.App.DB.UtxoSet as X 6 | import Explorer.App.DB.Validation as X 7 | -------------------------------------------------------------------------------- /cardano-explorer-db/app/Explorer/App/DB/Validation.hs: -------------------------------------------------------------------------------- 1 | module Explorer.App.DB.Validation 2 | ( runValidation 3 | ) where 4 | 5 | import Control.Monad (replicateM_) 6 | 7 | import Data.Word (Word64) 8 | 9 | import Explorer.DB 10 | 11 | import System.Console.ANSI (setSGRCode) 12 | import System.Console.ANSI.Types (Color (..), ColorIntensity (..), ConsoleLayer (..), SGR (..)) 13 | import System.IO (hFlush, stdout) 14 | import System.Random (randomRIO) 15 | 16 | runValidation :: Word -> IO () 17 | runValidation count = 18 | validateTotalSupplyDecreasing count 19 | 20 | 21 | data TestParams = TestParams 22 | { testFirstBlockNo :: Word64 23 | , testSecondBlockNo :: Word64 24 | , genesisSupply :: Ada 25 | } 26 | 27 | genTestParameters :: IO TestParams 28 | genTestParameters = do 29 | mlatest <- runDbNoLogging queryLatestBlockNo 30 | case mlatest of 31 | Nothing -> error "Explorer.App.DB.Validation: Empty database" 32 | Just latest -> do 33 | block1 <- randomRIO (1, latest - 1) 34 | TestParams block1 35 | <$> randomRIO (block1, latest) 36 | <*> runDbNoLogging queryGenesisSupply 37 | 38 | 39 | -- | Validate that the total supply is decreasing. 40 | -- This is only true for the Byron error where transaction fees are burnt. 41 | validateTotalSupplyDecreasing :: Word -> IO () 42 | validateTotalSupplyDecreasing count = 43 | replicateM_ (fromIntegral count) $ do 44 | test <- genTestParameters 45 | 46 | putStrF $ "Total supply plus fees at block " ++ show (testFirstBlockNo test) 47 | ++ " is same as genesis supply: " 48 | (fee1, supply1) <- runDbNoLogging $ do 49 | (,) <$> queryFeesUpToBlockNo (testFirstBlockNo test) 50 | <*> fmap2 utxoSetSum queryUtxoAtBlockNo (testFirstBlockNo test) 51 | if genesisSupply test == supply1 + fee1 52 | then putStrLn $ greenText "ok" 53 | else error $ redText (show (genesisSupply test) ++ " /= " ++ show (supply1 + fee1)) 54 | 55 | putStrF $ "Validate total supply decreasing from block " ++ show (testFirstBlockNo test) 56 | ++ " to block " ++ show (testSecondBlockNo test) ++ ": " 57 | 58 | supply2 <- runDbNoLogging $ fmap2 utxoSetSum queryUtxoAtBlockNo (testSecondBlockNo test) 59 | if supply1 >= supply2 60 | then putStrLn $ greenText "ok" 61 | else error $ redText (show supply1 ++ " < " ++ show supply2) 62 | 63 | -- ----------------------------------------------------------------------------- 64 | 65 | codeGreen :: String 66 | codeGreen = setSGRCode [SetColor Foreground Vivid Green] 67 | 68 | codeRed :: String 69 | codeRed = setSGRCode [SetColor Foreground Vivid Red] 70 | 71 | codeReset :: String 72 | codeReset = setSGRCode [Reset] 73 | 74 | fmap2 :: (Functor f, Functor g) => (a -> b) -> f (g a) -> f (g b) 75 | fmap2 = fmap . fmap 76 | 77 | greenText :: String -> String 78 | greenText s = codeGreen ++ s ++ codeReset 79 | 80 | redText :: String -> String 81 | redText s = codeRed ++ s ++ codeReset 82 | 83 | putStrF :: String -> IO () 84 | putStrF s = putStr s >> hFlush stdout 85 | 86 | utxoSetSum :: [(TxOut, a)] -> Ada 87 | utxoSetSum xs = 88 | word64ToAda . sum $ map (txOutValue . fst) xs 89 | -------------------------------------------------------------------------------- /cardano-explorer-db/shell.nix: -------------------------------------------------------------------------------- 1 | let 2 | self = import ../. {}; 3 | in self.haskellPackages.shellFor { 4 | name = "cardano-explorer-db"; 5 | packages = ps: [ ps.cardano-explorer-db ]; 6 | buildInputs = with self.pkgs.haskellPackages; [ hlint stylish-haskell ghcid ]; 7 | } 8 | -------------------------------------------------------------------------------- /cardano-explorer-db/src/Explorer/DB.hs: -------------------------------------------------------------------------------- 1 | module Explorer.DB 2 | ( module X 3 | 4 | -- Data types from Explorer.DB.Schema: 5 | , Block (..) 6 | , Tx (..) 7 | , TxIn (..) 8 | , TxOut (..) 9 | ) where 10 | 11 | import Explorer.DB.Delete as X 12 | import Explorer.DB.Error as X 13 | import Explorer.DB.Insert as X 14 | import Explorer.DB.Migration as X 15 | import Explorer.DB.Migration.Version as X 16 | import Explorer.DB.PGConfig as X 17 | import Explorer.DB.Query as X 18 | import Explorer.DB.Run as X 19 | import Explorer.DB.Schema as X 20 | import Explorer.DB.Types as X 21 | -------------------------------------------------------------------------------- /cardano-explorer-db/src/Explorer/DB/Delete.hs: -------------------------------------------------------------------------------- 1 | module Explorer.DB.Delete 2 | ( deleteCascadeBlock 3 | , deleteCascadeBlockNo 4 | ) where 5 | 6 | 7 | import Control.Monad.IO.Class (MonadIO) 8 | import Control.Monad.Trans.Reader (ReaderT) 9 | 10 | import Data.Word (Word64) 11 | 12 | import Database.Persist.Sql (SqlBackend, (==.), deleteCascade, selectList) 13 | import Database.Persist.Types (entityKey) 14 | 15 | import Explorer.DB.Schema 16 | 17 | 18 | -- | Delete a block if it exists. Returns 'True' if it did exist and has been 19 | -- deleted and 'False' if it did not exist. 20 | deleteCascadeBlock :: MonadIO m => Block -> ReaderT SqlBackend m Bool 21 | deleteCascadeBlock block = do 22 | keys <- selectList [ BlockHash ==. blockHash block ] [] 23 | mapM_ (deleteCascade . entityKey) keys 24 | pure $ not (null keys) 25 | 26 | -- | Delete a block if it exists. Returns 'True' if it did exist and has been 27 | -- deleted and 'False' if it did not exist. 28 | deleteCascadeBlockNo :: MonadIO m => Word64 -> ReaderT SqlBackend m Bool 29 | deleteCascadeBlockNo blkNo = do 30 | keys <- selectList [ BlockBlockNo ==. Just blkNo ] [] 31 | mapM_ (deleteCascade . entityKey) keys 32 | pure $ not (null keys) 33 | -------------------------------------------------------------------------------- /cardano-explorer-db/src/Explorer/DB/Error.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | module Explorer.DB.Error 4 | ( LookupFail (..) 5 | , renderLookupFail 6 | ) where 7 | 8 | 9 | import Data.ByteString.Char8 (ByteString) 10 | import qualified Data.ByteString.Base16 as Base16 11 | import qualified Data.Text as Text 12 | import Data.Text (Text) 13 | import qualified Data.Text.Encoding as Text 14 | import Data.Word (Word16, Word64) 15 | 16 | 17 | data LookupFail 18 | = DbLookupBlockHash !ByteString 19 | | DbLookupBlockId !Word64 20 | | DbLookupMessage !Text 21 | | DbLookupTxHash !ByteString 22 | | DbLookupTxOutPair !ByteString !Word16 23 | | DbMetaEmpty 24 | | DbMetaMultipleRows 25 | 26 | renderLookupFail :: LookupFail -> Text 27 | renderLookupFail lf = 28 | case lf of 29 | DbLookupBlockHash h -> "block hash " <> base16encode h 30 | DbLookupBlockId blkid -> "block id " <> textShow blkid 31 | DbLookupMessage txt -> txt 32 | DbLookupTxHash h -> "tx hash " <> base16encode h 33 | DbLookupTxOutPair h i -> 34 | Text.concat [ "tx out pair (", base16encode h, ", ", textShow i, ")" ] 35 | DbMetaEmpty -> "Meta table is empty" 36 | DbMetaMultipleRows -> "Multiple rows in Meta table which should only contain one" 37 | 38 | base16encode :: ByteString -> Text 39 | base16encode = Text.decodeUtf8 . Base16.encode 40 | 41 | textShow :: Show a => a -> Text 42 | textShow = Text.pack . show 43 | -------------------------------------------------------------------------------- /cardano-explorer-db/src/Explorer/DB/Insert.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleContexts #-} 2 | {-# LANGUAGE TypeFamilies #-} 3 | 4 | module Explorer.DB.Insert 5 | ( insertBlock 6 | , insertEpoch 7 | , insertMeta 8 | , insertSlotLeader 9 | , insertTx 10 | , insertTxIn 11 | , insertTxOut 12 | 13 | -- Export mainly for testing. 14 | , insertByReturnKey 15 | ) where 16 | 17 | 18 | import Control.Monad.IO.Class (MonadIO) 19 | import Control.Monad.Trans.Reader (ReaderT) 20 | 21 | import Database.Persist.Class (AtLeastOneUniqueKey, Key, PersistEntityBackend, 22 | getByValue, insert) 23 | import Database.Persist.Sql (SqlBackend) 24 | import Database.Persist.Types (entityKey) 25 | 26 | import Explorer.DB.Schema 27 | 28 | 29 | insertBlock :: MonadIO m => Block -> ReaderT SqlBackend m BlockId 30 | insertBlock = insertByReturnKey 31 | 32 | insertEpoch :: MonadIO m => Epoch -> ReaderT SqlBackend m EpochId 33 | insertEpoch = insertByReturnKey 34 | 35 | insertMeta :: MonadIO m => Meta -> ReaderT SqlBackend m MetaId 36 | insertMeta = insertByReturnKey 37 | 38 | insertSlotLeader :: MonadIO m => SlotLeader -> ReaderT SqlBackend m SlotLeaderId 39 | insertSlotLeader = insertByReturnKey 40 | 41 | insertTx :: MonadIO m => Tx -> ReaderT SqlBackend m TxId 42 | insertTx = insertByReturnKey 43 | 44 | insertTxIn :: MonadIO m => TxIn -> ReaderT SqlBackend m TxInId 45 | insertTxIn = insertByReturnKey 46 | 47 | insertTxOut :: MonadIO m => TxOut -> ReaderT SqlBackend m TxOutId 48 | insertTxOut = insertByReturnKey 49 | 50 | -- ----------------------------------------------------------------------------- 51 | 52 | -- | Insert a record (with a Unique constraint), and return 'Right key' if the 53 | -- record is inserted and 'Left key' if the record already exists in the DB. 54 | insertByReturnKey 55 | :: ( AtLeastOneUniqueKey record 56 | , MonadIO m 57 | , PersistEntityBackend record ~ SqlBackend 58 | ) 59 | => record -> ReaderT SqlBackend m (Key record) 60 | insertByReturnKey value = do 61 | res <- getByValue value 62 | case res of 63 | Nothing -> insert value 64 | Just r -> pure $ entityKey r 65 | -------------------------------------------------------------------------------- /cardano-explorer-db/src/Explorer/DB/Migration/Haskell.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE ConstraintKinds #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | 4 | module Explorer.DB.Migration.Haskell 5 | ( runHaskellMigration 6 | ) where 7 | 8 | import Control.Exception (SomeException, handle) 9 | import Control.Monad.Logger (MonadLogger) 10 | import Control.Monad.Trans.Reader (ReaderT) 11 | 12 | import Data.Map.Strict (Map) 13 | import qualified Data.Map.Strict as Map 14 | 15 | import Database.Persist.Sql (SqlBackend) 16 | 17 | import Explorer.DB.Migration.Version 18 | import Explorer.DB.Run 19 | 20 | import System.Exit (exitFailure) 21 | import System.IO (Handle, hClose, hFlush, hPutStrLn, stdout) 22 | 23 | -- | Run a migration written in Haskell (eg one that cannot easily be done in SQL). 24 | -- The Haskell migration is paired with an SQL migration and uses the same MigrationVersion 25 | -- numbering system. For example when 'migration-2-0008-20190731.sql' is applied this 26 | -- function will be called and if a Haskell migration with that version number exists 27 | -- in the 'migrationMap' it will be run. 28 | -- 29 | -- An example of how this may be used is: 30 | -- 1. 'migration-2-0008-20190731.sql' adds a new NULL-able column. 31 | -- 2. Haskell migration 'MigrationVersion 2 8 20190731' populates new column from data already 32 | -- in the database. 33 | -- 3. 'migration-2-0009-20190731.sql' makes the new column NOT NULL. 34 | 35 | runHaskellMigration :: Handle -> MigrationVersion -> IO () 36 | runHaskellMigration logHandle mversion = 37 | case Map.lookup mversion migrationMap of 38 | Nothing -> pure () 39 | Just action -> do 40 | hPutStrLn logHandle $ "Running : migration-" ++ renderMigrationVersion mversion ++ ".hs" 41 | putStr $ " migration-" ++ renderMigrationVersion mversion ++ ".hs ... " 42 | hFlush stdout 43 | handle handler $ runDbHandleLogger logHandle action 44 | putStrLn "ok" 45 | where 46 | handler :: SomeException -> IO a 47 | handler e = do 48 | putStrLn $ "runHaskellMigration: " ++ show e 49 | hPutStrLn logHandle $ "runHaskellMigration: " ++ show e 50 | hClose logHandle 51 | exitFailure 52 | 53 | -------------------------------------------------------------------------------- 54 | 55 | migrationMap :: MonadLogger m => Map MigrationVersion (ReaderT SqlBackend m ()) 56 | migrationMap = 57 | Map.fromList 58 | [ ( MigrationVersion 2 1 20190731, migration0001 ) 59 | ] 60 | 61 | -------------------------------------------------------------------------------- 62 | 63 | migration0001 :: MonadLogger m => ReaderT SqlBackend m () 64 | migration0001 = 65 | -- Place holder. 66 | pure () 67 | 68 | -------------------------------------------------------------------------------- 69 | 70 | -------------------------------------------------------------------------------- /cardano-explorer-db/src/Explorer/DB/Migration/Version.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | module Explorer.DB.Migration.Version 4 | ( MigrationVersion (..) 5 | , parseMigrationVersionFromFile 6 | , nextMigrationVersion 7 | , renderMigrationVersion 8 | , renderMigrationVersionFile 9 | ) where 10 | 11 | import qualified Data.List as List 12 | import qualified Data.List.Extra as List 13 | import qualified Data.Time.Calendar as Time 14 | import qualified Data.Time.Clock as Time 15 | 16 | import Text.Printf (printf) 17 | 18 | 19 | data MigrationVersion = MigrationVersion 20 | { mvStage :: Int 21 | , mvVersion :: Int 22 | , mvDate :: Int 23 | } deriving (Eq, Ord, Show) 24 | 25 | 26 | parseMigrationVersionFromFile :: String -> Maybe MigrationVersion 27 | parseMigrationVersionFromFile str = 28 | case List.splitOn "-" (List.takeWhile (/= '.') str) of 29 | [_, stage, ver, date] -> 30 | case (readMaybe stage, readMaybe ver, readMaybe date) of 31 | (Just s, Just v, Just d) -> Just $ MigrationVersion s v d 32 | _ -> Nothing 33 | _ -> Nothing 34 | 35 | nextMigrationVersion :: MigrationVersion -> IO MigrationVersion 36 | nextMigrationVersion (MigrationVersion _stage ver _date) = do 37 | -- We can ignore the provided 'stage' and 'date' fields, but we do bump the version number. 38 | -- All new versions have 'stage == 2' because the stage 2 migrations are the Presistent 39 | -- generated ones. For the date we use today's date. 40 | (y, m, d) <- Time.toGregorian . Time.utctDay <$> Time.getCurrentTime 41 | pure $ MigrationVersion 2 (ver + 1) (fromIntegral y * 10000 + m * 100 + d) 42 | 43 | renderMigrationVersion :: MigrationVersion -> String 44 | renderMigrationVersion mv = 45 | List.intercalate "-" 46 | [ printf "%d" (mvStage mv) 47 | , printf "%04d" (mvVersion mv) 48 | , show (mvDate mv) 49 | ] 50 | 51 | renderMigrationVersionFile :: MigrationVersion -> String 52 | renderMigrationVersionFile mv = 53 | List.concat 54 | [ "migration-" 55 | , renderMigrationVersion mv 56 | , ".sql" 57 | ] 58 | 59 | -- ----------------------------------------------------------------------------- 60 | 61 | readMaybe :: Read a => String -> Maybe a 62 | readMaybe str = 63 | case reads str of 64 | [(a, "")] -> Just a 65 | _ -> Nothing 66 | -------------------------------------------------------------------------------- /cardano-explorer-db/src/Explorer/DB/PGConfig.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | module Explorer.DB.PGConfig 4 | ( PGConfig (..) 5 | , PGPassFile (..) 6 | , readPGPassFileEnv 7 | , readPGPassFile 8 | , readPGPassFileExit 9 | , toConnectionString 10 | ) where 11 | 12 | import Control.Exception (IOException) 13 | import qualified Control.Exception as Exception 14 | 15 | import Data.ByteString.Char8 (ByteString) 16 | import qualified Data.ByteString.Char8 as BS 17 | 18 | import Database.Persist.Postgresql (ConnectionString) 19 | 20 | import System.Environment (lookupEnv, setEnv) 21 | import System.Posix.User (getEffectiveUserName) 22 | 23 | -- | PGConfig as specified by https://www.postgresql.org/docs/11/libpq-pgpass.html 24 | -- However, this module expects the config data to be on the first line. 25 | data PGConfig = PGConfig 26 | { pgcHost :: ByteString 27 | , pgcPort :: ByteString 28 | , pgcDbname :: ByteString 29 | , pgcUser :: ByteString 30 | , pgcPassword :: ByteString 31 | } deriving Show 32 | 33 | newtype PGPassFile 34 | = PGPassFile FilePath 35 | 36 | toConnectionString :: PGConfig -> ConnectionString 37 | toConnectionString pgc = 38 | BS.concat 39 | [ "host=", pgcHost pgc, " " 40 | , "port=", pgcPort pgc, " " 41 | , "user=", pgcUser pgc, " " 42 | , "dbname=", pgcDbname pgc, " " 43 | , "password=", pgcPassword pgc 44 | ] 45 | 46 | -- | Read the PostgreSQL configuration from the file at the location specified by the 47 | -- '$PGPASSFILE' environment variable. 48 | readPGPassFileEnv :: IO PGConfig 49 | readPGPassFileEnv = do 50 | mpath <- lookupEnv "PGPASSFILE" 51 | case mpath of 52 | Just fp -> readPGPassFileExit (PGPassFile fp) 53 | Nothing -> error $ "Environment variable 'PGPASSFILE' not set." 54 | 55 | -- | Read the PostgreSQL configuration from the specified file. 56 | readPGPassFile :: PGPassFile -> IO (Maybe PGConfig) 57 | readPGPassFile (PGPassFile fpath) = do 58 | ebs <- Exception.try $ BS.readFile fpath 59 | case ebs of 60 | Left e -> pure $ handler e 61 | Right bs -> extract bs 62 | where 63 | handler :: IOException -> Maybe a 64 | handler = const Nothing 65 | 66 | extract :: ByteString -> IO (Maybe PGConfig) 67 | extract bs = 68 | case BS.lines bs of 69 | (b:_) -> parseConfig b 70 | _ -> pure Nothing 71 | 72 | parseConfig :: ByteString -> IO (Maybe PGConfig) 73 | parseConfig bs = 74 | case BS.split ':' bs of 75 | [h, pt, d, u, pwd] -> Just <$> replaceUser (PGConfig h pt d u pwd) 76 | _ -> pure Nothing 77 | 78 | replaceUser :: PGConfig -> IO PGConfig 79 | replaceUser pgc 80 | | pgcUser pgc /= "*" = pure pgc 81 | | otherwise = do 82 | user <- getEffectiveUserName 83 | pure $ pgc { pgcUser = BS.pack user } 84 | 85 | 86 | -- | Read 'PGPassFile' into 'PGConfig'. 87 | -- If it fails it will raise an error. 88 | -- If it succeeds, it will set the 'PGPASSFILE' environment variable. 89 | readPGPassFileExit :: PGPassFile -> IO PGConfig 90 | readPGPassFileExit pgpassfile@(PGPassFile fpath) = do 91 | mc <- readPGPassFile pgpassfile 92 | case mc of 93 | Nothing -> error $ "Not able to read PGPassFile at " ++ show fpath ++ "." 94 | Just pgc -> do 95 | setEnv "PGPASSFILE" fpath 96 | pure pgc 97 | -------------------------------------------------------------------------------- /cardano-explorer-db/src/Explorer/DB/Types.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveGeneric #-} 2 | {-# LANGUAGE FlexibleContexts #-} 3 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} 4 | {-# LANGUAGE OverloadedStrings #-} 5 | {-# LANGUAGE ScopedTypeVariables #-} 6 | 7 | module Explorer.DB.Types 8 | ( Ada (..) 9 | , lovelaceToAda 10 | , renderAda 11 | , scientificToAda 12 | , word64ToAda 13 | ) where 14 | 15 | 16 | import Data.Aeson.Encoding (unsafeToEncoding) 17 | import Data.Aeson.Types (FromJSON (..), ToJSON (..)) 18 | import qualified Data.Aeson.Types as Aeson 19 | import qualified Data.ByteString.Builder as BS (string8) 20 | import Data.Fixed (Micro, showFixed) 21 | import Data.Scientific (Scientific) 22 | import Data.Text (Text) 23 | import qualified Data.Text as Text 24 | import Data.Word (Word64) 25 | 26 | import GHC.Generics (Generic) 27 | 28 | newtype Ada = Ada 29 | { unAda :: Micro 30 | } deriving (Eq, Num, Ord, Generic) 31 | 32 | instance FromJSON Ada where 33 | parseJSON = 34 | Aeson.withScientific "Ada" (pure . scientificToAda) 35 | 36 | instance ToJSON Ada where 37 | --toJSON (Ada ada) = Data.Aeson.Types.Number $ fromRational $ toRational ada 38 | -- `Number` results in it becoming `7.3112484749601107e10` while the old explorer is returning `73112484749.601107` 39 | toEncoding (Ada ada) = 40 | unsafeToEncoding $ -- convert ByteString to Aeson's Encoding 41 | BS.string8 $ -- convert String to ByteString using Latin1 encoding 42 | showFixed True ada -- convert Micro to String chopping off trailing zeros 43 | 44 | toJSON = error "Ada.toJSON not supported due to numeric issues. Use toEncoding instead." 45 | 46 | 47 | instance Show Ada where 48 | show (Ada ada) = showFixed True ada 49 | 50 | lovelaceToAda :: Micro -> Ada 51 | lovelaceToAda ll = 52 | Ada (ll / 1000000) 53 | 54 | renderAda :: Ada -> Text 55 | renderAda (Ada a) = Text.pack (show a) 56 | 57 | scientificToAda :: Scientific -> Ada 58 | scientificToAda s = 59 | word64ToAda $ floor (s * 1000000) 60 | 61 | word64ToAda :: Word64 -> Ada 62 | word64ToAda w = 63 | Ada (fromIntegral w / 1000000) 64 | 65 | -------------------------------------------------------------------------------- /cardano-explorer-db/test/Test/IO/Explorer/DB/Insert.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module Test.IO.Explorer.DB.Insert 3 | ( tests 4 | ) where 5 | 6 | import Control.Monad (void) 7 | 8 | import Data.ByteString.Char8 (ByteString) 9 | import qualified Data.ByteString.Char8 as BS 10 | 11 | import Explorer.DB 12 | 13 | import Test.Tasty (TestTree, testGroup) 14 | import Test.Tasty.HUnit (testCase) 15 | 16 | import Test.IO.Explorer.DB.Util 17 | 18 | 19 | tests :: TestTree 20 | tests = 21 | testGroup "Insert" 22 | [ testCase "Insert zeroth block" insertZeroTest 23 | , testCase "Insert first block" insertFirstTest 24 | ] 25 | 26 | insertZeroTest :: IO () 27 | insertZeroTest = 28 | runDbNoLogging $ do 29 | -- Delete the blocks if they exist. 30 | slid <- insertSlotLeader testSlotLeader 31 | void $ deleteCascadeBlock (blockOne slid) 32 | void $ deleteCascadeBlock (blockZero slid) 33 | -- Insert the same block twice. The first should be successful (resulting 34 | -- in a 'Right') and the second should return the same value in a 'Left'. 35 | bid0 <- insertBlock (blockZero slid) 36 | bid1 <- insertBlock (blockZero slid) 37 | assertBool (show bid0 ++ " /= " ++ show bid1) (bid0 == bid1) 38 | 39 | 40 | insertFirstTest :: IO () 41 | insertFirstTest = 42 | runDbNoLogging $ do 43 | -- Delete the block if it exists. 44 | slid <- insertSlotLeader testSlotLeader 45 | void $ deleteCascadeBlock (blockOne slid) 46 | -- Insert the same block twice. 47 | bid0 <- insertBlock (blockZero slid) 48 | bid1 <- insertBlock $ (\b -> b { blockPrevious = Just bid0 }) (blockOne slid) 49 | assertBool (show bid0 ++ " == " ++ show bid1) (bid0 /= bid1) 50 | 51 | 52 | blockZero :: SlotLeaderId -> Block 53 | blockZero slid = 54 | Block (mkHash '\0') (Just 0) Nothing Nothing Nothing Nothing slid 42 dummyUTCTime 0 55 | 56 | blockOne :: SlotLeaderId -> Block 57 | blockOne slid = 58 | Block (mkHash '\1') (Just 0) (Just 0) (Just 1) Nothing (Just $ mkMerkelRoot 1) slid 42 dummyUTCTime 0 59 | 60 | mkHash :: Char -> ByteString 61 | mkHash = BS.pack . replicate 32 62 | 63 | -------------------------------------------------------------------------------- /cardano-explorer-db/test/Test/IO/Explorer/DB/Migration.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module Test.IO.Explorer.DB.Migration 3 | ( tests 4 | ) where 5 | 6 | import Explorer.DB 7 | 8 | import Test.Tasty (TestTree, testGroup) 9 | import Test.Tasty.HUnit (testCase) 10 | 11 | 12 | tests :: TestTree 13 | tests = 14 | testGroup "Migration" 15 | [ testCase "Migration is idempotent" migrationTest 16 | ] 17 | 18 | -- Really just make sure that the migrations do actually run correctly. 19 | -- If they fail the file path of the log file (in /tmp) will be printed. 20 | migrationTest :: IO () 21 | migrationTest = 22 | runMigrations id True (MigrationDir "../schema") (LogFileDir "/tmp") 23 | -------------------------------------------------------------------------------- /cardano-explorer-db/test/Test/IO/Explorer/DB/TotalSupply.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE ScopedTypeVariables #-} 2 | 3 | module Test.IO.Explorer.DB.TotalSupply 4 | ( tests 5 | ) where 6 | 7 | import Explorer.DB 8 | 9 | import Test.Tasty (TestTree, testGroup) 10 | import Test.Tasty.HUnit (testCase) 11 | 12 | import Test.IO.Explorer.DB.Util 13 | 14 | tests :: TestTree 15 | tests = 16 | testGroup "TotalSupply" 17 | [ testCase "Initial supply correct" initialSupplyTest 18 | ] 19 | 20 | 21 | initialSupplyTest :: IO () 22 | initialSupplyTest = 23 | runDbNoLogging $ do 24 | -- Delete the blocks if they exist. 25 | deleteAllBlocksCascade 26 | 27 | -- Set up initial supply. 28 | slid <- insertSlotLeader testSlotLeader 29 | bid0 <- insertBlock (mkBlock 0 slid) 30 | (tx0Ids :: [TxId]) <- mapM insertTx $ mkTxs bid0 4 31 | _ <- mapM insertTxOut $ map (mkTxOut bid0) tx0Ids 32 | count <- queryBlockCount 33 | assertBool ("Block count should be 1, got " ++ show count) (count == 1) 34 | supply0 <- queryTotalSupply 35 | assertBool "Total supply should not be > 0" (supply0 > Ada 0) 36 | 37 | -- Spend from the Utxo set. 38 | bid1 <- insertBlock (mkBlock 1 slid) 39 | tx1Id <- insertTx (Tx (mkTxHash bid1 1) bid1 500000000 100 123) 40 | _ <- insertTxIn (TxIn tx1Id (head tx0Ids) 0) 41 | _ <- insertTxOut $ TxOut tx1Id 0 (mkAddressHash bid1 tx1Id) 500000000 42 | supply1 <- queryTotalSupply 43 | assertBool ("Total supply should be < " ++ show supply0) (supply1 < supply0) 44 | -------------------------------------------------------------------------------- /cardano-explorer-db/test/Test/IO/Explorer/DB/Util.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | {-# LANGUAGE ScopedTypeVariables #-} 3 | 4 | module Test.IO.Explorer.DB.Util 5 | ( assertBool 6 | , deleteAllBlocksCascade 7 | , dummyUTCTime 8 | , mkAddressHash 9 | , mkBlock 10 | , mkBlockHash 11 | , mkMerkelRoot 12 | , mkTxHash 13 | , mkTxs 14 | , mkTxOut 15 | , testSlotLeader 16 | , unBlockId 17 | , unTxId 18 | ) where 19 | 20 | import Control.Monad (unless) 21 | import Control.Monad.IO.Class (MonadIO, liftIO) 22 | import Control.Monad.Trans.Reader (ReaderT) 23 | 24 | import Data.ByteString.Char8 (ByteString) 25 | import qualified Data.ByteString.Char8 as BS 26 | import Data.Text (Text) 27 | import qualified Data.Text as Text 28 | import Data.Time.Calendar (Day (..)) 29 | import Data.Time.Clock (UTCTime (..)) 30 | import Data.Word (Word64) 31 | 32 | import Database.Persist.Sql (SqlBackend, deleteCascade, selectKeysList, unSqlBackendKey) 33 | 34 | import Explorer.DB 35 | 36 | import Text.Printf (printf) 37 | 38 | 39 | assertBool :: MonadIO m => String -> Bool -> m () 40 | assertBool msg bool = 41 | liftIO $ unless bool (error msg) 42 | 43 | deleteAllBlocksCascade :: MonadIO m => ReaderT SqlBackend m () 44 | deleteAllBlocksCascade = do 45 | (keys :: [BlockId]) <- selectKeysList [] [] 46 | mapM_ deleteCascade keys 47 | 48 | dummyUTCTime :: UTCTime 49 | dummyUTCTime = UTCTime (ModifiedJulianDay 0) 0 50 | 51 | mkAddressHash :: BlockId -> TxId -> Text 52 | mkAddressHash blkId txId = 53 | Text.pack (take 28 $ printf "tx out #%d, tx #%d" (unBlockId blkId) (unTxId txId) ++ replicate 28 ' ') 54 | 55 | mkBlock :: Word64 -> SlotLeaderId -> Block 56 | mkBlock blk slid = 57 | Block (mkBlockHash blk) (Just 0) Nothing Nothing Nothing Nothing slid 42 dummyUTCTime 0 58 | 59 | mkBlockHash :: Word64 -> ByteString 60 | mkBlockHash blkId = 61 | BS.pack (take 32 $ printf "block #%d" blkId ++ replicate 32 ' ') 62 | 63 | mkMerkelRoot :: Word64 -> ByteString 64 | mkMerkelRoot blkId = 65 | BS.pack (take 32 $ printf "merkel root #%d" blkId ++ replicate 32 ' ') 66 | 67 | mkTxHash :: BlockId -> Word64 -> ByteString 68 | mkTxHash blk tx = 69 | BS.pack (take 32 $ printf "block #%d, tx #%d" (unBlockId blk) tx ++ replicate 32 ' ') 70 | 71 | mkTxs :: BlockId -> Word -> [Tx] 72 | mkTxs blkId count = 73 | take (fromIntegral count) $ map create [ 0 .. ] 74 | where 75 | create w = Tx (mkTxHash blkId w) blkId 2 1 12 76 | 77 | testSlotLeader :: SlotLeader 78 | testSlotLeader = 79 | SlotLeader (BS.pack . take 28 $ "test slot leader" ++ replicate 28 ' ') "Dummy test slot leader" 80 | 81 | mkTxOut :: BlockId -> TxId -> TxOut 82 | mkTxOut blkId txId = 83 | TxOut txId 0 (mkAddressHash blkId txId) 1000000000 84 | 85 | unTxId :: TxId -> Word64 86 | unTxId = fromIntegral . unSqlBackendKey . unTxKey 87 | -------------------------------------------------------------------------------- /cardano-explorer-db/test/Test/Property/Explorer/DB/Migration.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TemplateHaskell #-} 2 | 3 | module Test.Property.Explorer.DB.Migration 4 | ( genMigrationVersion 5 | , tests 6 | ) where 7 | 8 | import Explorer.DB 9 | 10 | import Hedgehog (Gen, Property, (===), discover) 11 | import qualified Hedgehog as H 12 | import qualified Hedgehog.Gen as Gen 13 | import qualified Hedgehog.Range as Range 14 | 15 | 16 | prop_roundtrip_MigrationVersion :: Property 17 | prop_roundtrip_MigrationVersion = 18 | H.property $ do 19 | mv <- H.forAll genMigrationVersion 20 | H.tripping mv renderMigrationVersionFile parseMigrationVersionFromFile 21 | 22 | prop_roundtrip_renderMigrationVersion_no_spaces :: Property 23 | prop_roundtrip_renderMigrationVersion_no_spaces = 24 | H.property $ do 25 | mv <- H.forAll genMigrationVersion 26 | any (== ' ') (renderMigrationVersionFile mv) === False 27 | 28 | -- ----------------------------------------------------------------------------- 29 | 30 | genMigrationVersion :: Gen MigrationVersion 31 | genMigrationVersion = 32 | MigrationVersion 33 | <$> Gen.int (Range.linear 0 10) 34 | <*> Gen.int (Range.linear 0 10000) 35 | <*> genDate 36 | 37 | genDate :: Gen Int 38 | genDate = do 39 | year <- Gen.int (Range.linear 2000 2100) 40 | month <- Gen.int (Range.linear 1 12) 41 | day <- Gen.int (Range.linear 1 12) 42 | pure $ year * 10000 + month * 100 + day 43 | 44 | -- ----------------------------------------------------------------------------- 45 | 46 | tests :: IO Bool 47 | tests = H.checkParallel $$discover 48 | -------------------------------------------------------------------------------- /cardano-explorer-db/test/Test/Property/Explorer/DB/Types.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TemplateHaskell #-} 2 | 3 | module Test.Property.Explorer.DB.Types 4 | ( genAda 5 | , tests 6 | ) where 7 | 8 | import Cardano.Chain.Common (maxLovelaceVal) 9 | 10 | import qualified Data.Aeson as Aeson 11 | import Data.Word (Word64) 12 | 13 | import Explorer.DB 14 | 15 | import Hedgehog (Gen, Property, discover) 16 | import qualified Hedgehog as H 17 | import qualified Hedgehog.Gen as Gen 18 | import qualified Hedgehog.Range as Range 19 | 20 | 21 | prop_roundtrip_Ada_via_JSON :: Property 22 | prop_roundtrip_Ada_via_JSON = 23 | H.withTests 5000 . H.property $ do 24 | mv <- H.forAll genAda 25 | H.tripping mv Aeson.encode Aeson.eitherDecode 26 | 27 | -- ----------------------------------------------------------------------------- 28 | 29 | genAda :: Gen Ada 30 | genAda = 31 | word64ToAda <$> genWord64Ada 32 | where 33 | genWord64Ada :: Gen Word64 34 | genWord64Ada = 35 | Gen.choice 36 | [ Gen.word64 (Range.linear 0 maxLovelaceVal) -- Full range 37 | , Gen.word64 (Range.linear 0 5000) -- Small values 38 | , Gen.word64 (Range.linear (maxLovelaceVal - 5000) maxLovelaceVal) -- Near max. 39 | ] 40 | 41 | -- ----------------------------------------------------------------------------- 42 | 43 | tests :: IO Bool 44 | tests = H.checkParallel $$discover 45 | -------------------------------------------------------------------------------- /cardano-explorer-db/test/Test/Property/Upstream.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | {-# LANGUAGE TemplateHaskell #-} 3 | 4 | module Test.Property.Upstream 5 | ( tests 6 | ) where 7 | 8 | -- Test things that come from upstream packages that have at any stage gone wrong 9 | -- or for which 10 | 11 | import Cardano.Chain.Common (decodeAddressBase58, isRedeemAddress) 12 | 13 | import Hedgehog (Property, (===), discover) 14 | import qualified Hedgehog as H 15 | import qualified Hedgehog.Gen as Gen 16 | 17 | 18 | prop_isRedeemAddress :: Property 19 | prop_isRedeemAddress = 20 | H.withTests 2 . H.property $ do 21 | addr <- H.forAll $ Gen.element 22 | [ "Ae2tdPwUPEYycJ77DaXcWEdM3jaSBg2HgmK9jT1HmBzeLJ94x8mRw33xpBM" 23 | , "Ae2tdPwUPEZ5bWquLHSYARmM5qgmCg1cjAPb7C4tVkRNQXN2BoX2Han8xKj" 24 | , "Ae2tdPwUPEZMB92JqgxAEWfXJo6Ex7wWLoS7REmh81Ue6GgsNrDNs3MeQKA" 25 | , "Ae2tdPwUPEZ43NhMYvw1bkcGnEzdDbm9QTWiRux6Xpy8sorgfSJfazneEsP" 26 | ] 27 | fmap isRedeemAddress (decodeAddressBase58 addr) === Right True 28 | 29 | -- ----------------------------------------------------------------------------- 30 | 31 | tests :: IO Bool 32 | tests = H.checkParallel $$discover 33 | -------------------------------------------------------------------------------- /cardano-explorer-db/test/cardano-explorer-db-test.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: >= 1.10 2 | 3 | -- http://haskell.org/cabal/users-guide/ 4 | 5 | name: cardano-explorer-db-test 6 | version: 0.1.0.0 7 | synopsis: A block explorer for the Cardano chain 8 | description: Code for the Cardano Block Explorer that is shared between the 9 | cardano-explorer-db-node and the cardano-explorer web application. 10 | homepage: https://github.com/input-output-hk/cardano-explorer 11 | bug-reports: https://github.com/input-output-hk/cardano-explorer/issues 12 | license: Apache-2.0 13 | license-file: LICENSE 14 | author: IOHK Engineering Team 15 | maintainer: operations@iohk.io 16 | copyright: (c) 2019 IOHK 17 | category: Cryptocurrency 18 | build-type: Simple 19 | 20 | library 21 | default-language: Haskell2010 22 | ghc-options: -Wall 23 | -Wcompat 24 | -fwarn-redundant-constraints 25 | -fwarn-incomplete-patterns 26 | -fwarn-unused-imports 27 | -Wincomplete-record-updates 28 | -Wincomplete-uni-patterns 29 | 30 | exposed-modules: Test.Property.Explorer.DB.Types 31 | Test.IO.Explorer.DB.Util 32 | 33 | 34 | build-depends: aeson 35 | , base >= 4.12 && < 4.13 36 | , base16-bytestring 37 | , bytestring 38 | , cardano-explorer-db 39 | , cardano-ledger 40 | , conduit 41 | , conduit-extra 42 | , containers 43 | , contra-tracer 44 | , directory 45 | , esqueleto 46 | , extra 47 | , fast-logger 48 | , filepath 49 | , hedgehog 50 | , iohk-monitoring 51 | , monad-logger 52 | , persistent 53 | , persistent-postgresql 54 | , persistent-template >= 2.7.0 55 | , postgresql-simple 56 | , resourcet 57 | , scientific 58 | , text 59 | , template-haskell 60 | , time 61 | , transformers 62 | -- This is never intended to run on non-POSIX systems. 63 | , unix 64 | -------------------------------------------------------------------------------- /cardano-explorer-db/test/test-db.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | import Test.Tasty (defaultMain, testGroup) 4 | 5 | import qualified Test.IO.Explorer.DB.Insert 6 | import qualified Test.IO.Explorer.DB.Migration 7 | import qualified Test.IO.Explorer.DB.TotalSupply 8 | import qualified Test.IO.Explorer.DB.Rollback 9 | 10 | main :: IO () 11 | main = 12 | defaultMain $ 13 | testGroup "Database" 14 | [ Test.IO.Explorer.DB.Migration.tests 15 | , Test.IO.Explorer.DB.Insert.tests 16 | , Test.IO.Explorer.DB.TotalSupply.tests 17 | , Test.IO.Explorer.DB.Rollback.tests 18 | ] 19 | -------------------------------------------------------------------------------- /cardano-explorer-db/test/test.hs: -------------------------------------------------------------------------------- 1 | import Hedgehog.Main (defaultMain) 2 | 3 | import qualified Test.Property.Explorer.DB.Migration 4 | import qualified Test.Property.Explorer.DB.Types 5 | import qualified Test.Property.Upstream 6 | 7 | main :: IO () 8 | main = 9 | defaultMain 10 | [ Test.Property.Upstream.tests 11 | , Test.Property.Explorer.DB.Migration.tests 12 | , Test.Property.Explorer.DB.Types.tests 13 | ] 14 | -------------------------------------------------------------------------------- /cardano-explorer-node/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Revision history for cardano-explorer-node 2 | 3 | ## 1.? -- ? 2020 4 | 5 | * Improve chain sync behavior when database chain is ahead of node. 6 | 7 | ## 1.3.0 -- January 2020 8 | 9 | * Update dependencies to latest versions. 10 | * Docker image: log all runit services to stdout 11 | * Initial documentation on how to use build and run the components in docker 12 | 13 | ## 1.2.2 -- January 2020 14 | 15 | * Update dependencies to latest versions. 16 | 17 | ## 1.2.1 -- January 2020 18 | 19 | * Update dependencies to latest versions. 20 | 21 | ## 1.2.0 -- December 2019 22 | 23 | * Update to latest version of cardano-ledger, ouroboros-network, 24 | ouroboros-consensus, iohk-monitoring-framework, and cardano-shell libs. 25 | 26 | ## 1.1.0 -- December 2019 27 | 28 | * Updated to latest network library version and simpler API 29 | 30 | ## 1.0.0 -- November 2019 31 | 32 | * First release of new explorer based on new Cardano node. 33 | * Syncs chain data from a local node into a PostgreSQL DB. 34 | * Compatible with new Cardano node for Byron era. 35 | -------------------------------------------------------------------------------- /cardano-explorer-node/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /cardano-explorer-node/app/cardano-explorer-node.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE NoImplicitPrelude #-} 2 | 3 | import Cardano.Prelude 4 | 5 | import Explorer.DB (MigrationDir (..)) 6 | import Explorer.Node (ConfigFile (..), ExplorerNodeParams (..), GenesisFile (..), 7 | SocketPath (..), defExplorerNodePlugin, runExplorer) 8 | 9 | import Options.Applicative (Parser, ParserInfo) 10 | import qualified Options.Applicative as Opt 11 | 12 | main :: IO () 13 | main = do 14 | runExplorer defExplorerNodePlugin =<< Opt.execParser opts 15 | 16 | -- ------------------------------------------------------------------------------------------------- 17 | 18 | opts :: ParserInfo ExplorerNodeParams 19 | opts = 20 | Opt.info (pCommandLine <**> Opt.helper) 21 | ( Opt.fullDesc 22 | <> Opt.progDesc "Cardano explorer database node." 23 | ) 24 | 25 | pCommandLine :: Parser ExplorerNodeParams 26 | pCommandLine = 27 | ExplorerNodeParams 28 | <$> pConfigFile 29 | <*> pGenesisFile 30 | <*> pSocketPath 31 | <*> pMigrationDir 32 | 33 | pConfigFile :: Parser ConfigFile 34 | pConfigFile = 35 | ConfigFile <$> Opt.strOption 36 | ( Opt.long "config" 37 | <> Opt.help "Path to the explorer node config file" 38 | <> Opt.completer (Opt.bashCompleter "file") 39 | <> Opt.metavar "FILEPATH" 40 | ) 41 | 42 | pGenesisFile :: Parser GenesisFile 43 | pGenesisFile = 44 | GenesisFile <$> Opt.strOption 45 | ( Opt.long "genesis-file" 46 | <> Opt.help "Path to the genesis JSON file" 47 | <> Opt.completer (Opt.bashCompleter "file") 48 | <> Opt.metavar "FILEPATH" 49 | ) 50 | 51 | pMigrationDir :: Parser MigrationDir 52 | pMigrationDir = 53 | MigrationDir <$> Opt.strOption 54 | ( Opt.long "schema-dir" 55 | <> Opt.help "The directory containing the migrations." 56 | <> Opt.completer (Opt.bashCompleter "directory") 57 | <> Opt.metavar "FILEPATH" 58 | ) 59 | 60 | pSocketPath :: Parser SocketPath 61 | pSocketPath = 62 | SocketPath <$> Opt.strOption 63 | ( Opt.long "socket-path" 64 | <> Opt.help "Path to a cardano-node socket" 65 | <> Opt.completer (Opt.bashCompleter "file") 66 | <> Opt.metavar "FILEPATH" 67 | ) 68 | -------------------------------------------------------------------------------- /cardano-explorer-node/shell.nix: -------------------------------------------------------------------------------- 1 | let 2 | self = import ../. {}; 3 | in self.haskellPackages.shellFor { 4 | name = "cardano-explorer-db-node"; 5 | packages = ps: [ ps.cardano-explorer-node ]; 6 | buildInputs = with self.pkgs.haskellPackages; [ hlint stylish-haskell ghcid ]; 7 | } 8 | -------------------------------------------------------------------------------- /cardano-explorer-node/src/Explorer/Node/Config.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleInstances #-} 2 | {-# LANGUAGE NoImplicitPrelude #-} 3 | {-# LANGUAGE OverloadedStrings #-} 4 | {-# LANGUAGE ScopedTypeVariables #-} 5 | 6 | module Explorer.Node.Config 7 | ( ExplorerNodeConfig 8 | , GenesisHash (..) 9 | , GenExplorerNodeConfig (..) 10 | , NetworkName (..) 11 | , readExplorerNodeConfig 12 | ) where 13 | 14 | import qualified Cardano.BM.Configuration as Logging 15 | import qualified Cardano.BM.Configuration.Model as Logging 16 | import qualified Cardano.BM.Data.Configuration as Logging 17 | 18 | import Cardano.Crypto (RequiresNetworkMagic (..)) 19 | 20 | import Cardano.Prelude 21 | 22 | import Data.Aeson (FromJSON (..), Object, Value (..), (.:)) 23 | import Data.Aeson.Types (Parser) 24 | import qualified Data.Aeson as Aeson 25 | import qualified Data.ByteString.Char8 as BS 26 | import Data.Text (Text) 27 | import qualified Data.Text as Text 28 | import qualified Data.Yaml as Yaml 29 | 30 | import Explorer.Node.Util 31 | 32 | type ExplorerNodeConfig = GenExplorerNodeConfig Logging.Configuration 33 | 34 | data GenExplorerNodeConfig a = GenExplorerNodeConfig 35 | { encNetworkName :: !NetworkName 36 | , encLoggingConfig :: !a 37 | , encGenesisHash :: !GenesisHash 38 | , encEnableLogging :: !Bool 39 | , encEnableMetrics :: !Bool 40 | , encRequiresNetworkMagic :: !RequiresNetworkMagic 41 | } 42 | 43 | newtype GenesisHash = GenesisHash 44 | { unGenesisHash :: Text 45 | } 46 | 47 | newtype NetworkName = NetworkName 48 | { unNetworkName :: Text 49 | } 50 | 51 | 52 | readExplorerNodeConfig :: FilePath -> IO ExplorerNodeConfig 53 | readExplorerNodeConfig fp = do 54 | res <- Yaml.decodeEither' <$> readLoggingConfig 55 | case res of 56 | Left err -> panic $ "readExplorerNodeConfig: Error parsing config: " <> textShow err 57 | Right icr -> convertLogging icr 58 | where 59 | readLoggingConfig :: IO ByteString 60 | readLoggingConfig = 61 | catch (BS.readFile fp) $ \(_ :: IOException) -> 62 | panic $ "Cannot find the logging configuration file at : " <> Text.pack fp 63 | 64 | convertLogging :: GenExplorerNodeConfig Logging.Representation -> IO ExplorerNodeConfig 65 | convertLogging enp = do 66 | lc <- Logging.setupFromRepresentation $ encLoggingConfig enp 67 | pure $ enp { encLoggingConfig = lc } 68 | 69 | -- ------------------------------------------------------------------------------------------------- 70 | 71 | instance FromJSON (GenExplorerNodeConfig Logging.Representation) where 72 | parseJSON o = 73 | Aeson.withObject "top-level" parseGenExplorerNodeConfig o 74 | 75 | parseGenExplorerNodeConfig :: Object -> Parser (GenExplorerNodeConfig Logging.Representation) 76 | parseGenExplorerNodeConfig o = 77 | GenExplorerNodeConfig 78 | <$> fmap NetworkName (o .: "NetworkName") 79 | <*> parseJSON (Object o) 80 | <*> fmap GenesisHash (o .: "GenesisHash") 81 | <*> o .: "EnableLogging" 82 | <*> o .: "EnableLogMetrics" 83 | <*> o .: "RequiresNetworkMagic" 84 | -------------------------------------------------------------------------------- /cardano-explorer-node/src/Explorer/Node/Error.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE NoImplicitPrelude #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | 4 | module Explorer.Node.Error 5 | ( ExplorerInvariant (..) 6 | , ExplorerNodeError (..) 7 | , annotateInvariantTx 8 | , bsBase16Encode 9 | , explorerError 10 | , explorerInvariant 11 | , liftLookupFail 12 | , renderExplorerInvariant 13 | , renderExplorerNodeError 14 | ) where 15 | 16 | import qualified Cardano.Chain.UTxO as Ledger 17 | import qualified Cardano.Crypto as Crypto 18 | 19 | import Cardano.Prelude 20 | 21 | import Control.Monad.Trans.Except.Extra (firstExceptT, left, newExceptT) 22 | 23 | import qualified Data.ByteString.Base16 as Base16 24 | import Data.Text (Text) 25 | import qualified Data.Text as Text 26 | import qualified Data.Text.Encoding as Text 27 | 28 | import Explorer.DB (LookupFail (..), renderLookupFail) 29 | import Explorer.Node.Util 30 | 31 | data ExplorerInvariant 32 | = EInvInOut !Word64 !Word64 33 | | EInvTxInOut !Ledger.Tx !Word64 !Word64 34 | 35 | data ExplorerNodeError 36 | = ENELookup !Text !LookupFail 37 | | ENEError !Text 38 | | ENEInvariant !Text !ExplorerInvariant 39 | | ENEBlockMismatch !Word64 !ByteString !ByteString 40 | | ENEEpochLookup Word64 41 | 42 | annotateInvariantTx :: Ledger.Tx -> ExplorerInvariant -> ExplorerInvariant 43 | annotateInvariantTx tx ei = 44 | case ei of 45 | EInvInOut inval outval -> EInvTxInOut tx inval outval 46 | _other -> ei 47 | 48 | explorerError :: Monad m => Text -> ExceptT ExplorerNodeError m a 49 | explorerError = left . ENEError 50 | 51 | explorerInvariant :: Monad m => Text -> ExplorerInvariant -> ExceptT ExplorerNodeError m a 52 | explorerInvariant loc = left . ENEInvariant loc 53 | 54 | liftLookupFail :: Monad m => Text -> m (Either LookupFail a) -> ExceptT ExplorerNodeError m a 55 | liftLookupFail loc = 56 | firstExceptT (ENELookup loc) . newExceptT 57 | 58 | renderExplorerInvariant :: ExplorerInvariant -> Text 59 | renderExplorerInvariant ei = 60 | case ei of 61 | EInvInOut inval outval -> 62 | mconcat [ "input value ", textShow inval, " < output value ", textShow outval ] 63 | EInvTxInOut tx inval outval -> 64 | mconcat 65 | [ "tx ", bsBase16Encode (unTxHash $ Crypto.hash tx) 66 | , " : input value ", textShow inval, " < output value ", textShow outval 67 | , "\n", textShow tx 68 | ] 69 | 70 | renderExplorerNodeError :: ExplorerNodeError -> Text 71 | renderExplorerNodeError ede = 72 | case ede of 73 | ENELookup loc lf -> mconcat [ "DB lookup fail in ", loc, ": ", renderLookupFail lf ] 74 | ENEError t -> "Error: " <> t 75 | ENEInvariant loc i -> mconcat [ loc, ": " <> renderExplorerInvariant i ] 76 | ENEBlockMismatch blkNo hashDb hashBlk -> 77 | mconcat 78 | [ "Block mismatch for block number ", textShow blkNo, ", db has " 79 | , bsBase16Encode hashDb, " but chain provided ", bsBase16Encode hashBlk 80 | ] 81 | ENEEpochLookup e -> mconcat ["Unable to query epoch number ", textShow e] 82 | 83 | -- Lifted from cardano-explorer/src/Explorer/Web/Server/Util.hs 84 | -- Probably should be in cardano-explorer-db 85 | bsBase16Encode :: ByteString -> Text 86 | bsBase16Encode bs = 87 | case Text.decodeUtf8' (Base16.encode bs) of 88 | Left _ -> Text.pack $ "UTF-8 decode failed for " ++ show bs 89 | Right txt -> txt 90 | -------------------------------------------------------------------------------- /cardano-explorer-node/src/Explorer/Node/Metrics.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE NoImplicitPrelude #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | 4 | module Explorer.Node.Metrics 5 | ( Metrics (..) 6 | , makeMetrics 7 | , registerMetricsServer 8 | ) where 9 | 10 | import Cardano.Prelude 11 | 12 | import System.Metrics.Prometheus.Concurrent.RegistryT (RegistryT (..), registerGauge, 13 | runRegistryT, unRegistryT) 14 | import System.Metrics.Prometheus.Metric.Gauge (Gauge) 15 | import System.Metrics.Prometheus.Http.Scrape (serveHttpTextMetricsT) 16 | 17 | 18 | data Metrics = Metrics 19 | { mDbHeight :: !Gauge 20 | , mNodeHeight :: !Gauge 21 | , mQueuePre :: !Gauge 22 | , mQueuePost :: !Gauge 23 | , mQueuePostWrite :: !Gauge 24 | } 25 | 26 | registerMetricsServer :: IO (Metrics, Async ()) 27 | registerMetricsServer = 28 | runRegistryT $ do 29 | metrics <- makeMetrics 30 | registry <- RegistryT ask 31 | server <- liftIO . async $ runReaderT (unRegistryT $ serveHttpTextMetricsT 8080 []) registry 32 | pure (metrics, server) 33 | 34 | makeMetrics :: RegistryT IO Metrics 35 | makeMetrics = 36 | Metrics 37 | <$> registerGauge "db_block_height" mempty 38 | <*> registerGauge "remote_tip_height" mempty 39 | <*> registerGauge "action_queue_length_pre" mempty 40 | <*> registerGauge "action_queue_length_post" mempty 41 | <*> registerGauge "action_queue_length_post_write" mempty 42 | 43 | -------------------------------------------------------------------------------- /cardano-explorer-node/src/Explorer/Node/Plugin.hs: -------------------------------------------------------------------------------- 1 | module Explorer.Node.Plugin 2 | ( ExplorerNodePlugin (..) 3 | ) where 4 | 5 | import Cardano.BM.Trace (Trace) 6 | 7 | import Cardano.Prelude 8 | 9 | import Control.Monad.Logger (LoggingT) 10 | import Control.Monad.Trans.Reader (ReaderT) 11 | 12 | import Database.Persist.Sql (SqlBackend) 13 | 14 | import Explorer.Node.Error 15 | 16 | import Ouroboros.Network.Block (Point (..), Tip) 17 | import Ouroboros.Consensus.Ledger.Byron (ByronBlock) 18 | 19 | 20 | -- | This plugin system allows access to the database to be extended by running one or more 21 | -- actions on a block insert or rollback. 22 | -- 23 | -- The insert and rollback actions are applied from the head of the list to the tail. 24 | -- THe default ExplorerNodePlugin is 'Explorer.Node.Plugin.Default.defExplorerNodePlugin'. This 25 | -- allows clients to insert db actions both before and after the default action. 26 | 27 | -- Plugins are free to read from the existing tables but should not modify them. Plugins however 28 | -- are free to operation on their own tables. 29 | 30 | -- Usually, insert plugins would be added after the default (so that the default action happens 31 | -- first, and then the added ones) whereas for rollback plugins, the rollback actions would 32 | -- normally happen *before* the default rollback. 33 | -- That is why the following data type does not have a Semigroup/Monoid instances. 34 | 35 | data ExplorerNodePlugin = ExplorerNodePlugin 36 | { -- A function run each time the application starts. Can be used to do a one time update/setup 37 | -- of a table. 38 | plugOnStartup 39 | :: [Trace IO Text -> ReaderT SqlBackend (LoggingT IO) ()] 40 | -- Called for each block recieved from the network. 41 | -- This will not be called for the original genesis block, but will be called for 42 | -- all subsequent blocks. 43 | -- Blocks (including epoch boundary blocks) are called in sequence from the oldest to the newest. 44 | , plugInsertBlock 45 | :: [Trace IO Text -> ByronBlock -> Tip ByronBlock -> ReaderT SqlBackend (LoggingT IO) (Either ExplorerNodeError ())] 46 | 47 | -- Rollback to the specified SlotNumber/HeaderHash. 48 | , plugRollbackBlock 49 | :: [Trace IO Text -> Point ByronBlock -> IO (Either ExplorerNodeError ())] 50 | } 51 | -------------------------------------------------------------------------------- /cardano-explorer-node/src/Explorer/Node/Plugin/Default.hs: -------------------------------------------------------------------------------- 1 | module Explorer.Node.Plugin.Default 2 | ( defExplorerNodePlugin 3 | ) where 4 | 5 | import Explorer.Node.Plugin 6 | import Explorer.Node.Plugin.Default.Rollback (rollbackToPoint) 7 | import Explorer.Node.Plugin.Default.Insert (insertByronBlock) 8 | 9 | -- | The default ExplorerNodePlugin. 10 | -- Does exactly what the explorer node did before the plugin system was added. 11 | defExplorerNodePlugin :: ExplorerNodePlugin 12 | defExplorerNodePlugin = 13 | ExplorerNodePlugin 14 | { plugOnStartup = [] 15 | , plugInsertBlock = [insertByronBlock] 16 | , plugRollbackBlock = [rollbackToPoint] 17 | } 18 | -------------------------------------------------------------------------------- /cardano-explorer-node/src/Explorer/Node/Plugin/Default/Rollback.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE NoImplicitPrelude #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | module Explorer.Node.Plugin.Default.Rollback 4 | ( rollbackToPoint 5 | ) where 6 | 7 | import Cardano.BM.Trace (Trace, logInfo) 8 | 9 | import Cardano.Prelude 10 | 11 | import qualified Cardano.Chain.Block as Ledger 12 | import qualified Cardano.Chain.Slotting as Ledger 13 | 14 | import Control.Monad.IO.Class (MonadIO) 15 | import Control.Monad.Trans.Except.Extra (runExceptT) 16 | 17 | import Data.Text (Text) 18 | import qualified Data.Text as Text 19 | 20 | import Database.Persist.Sql (SqlBackend) 21 | 22 | import qualified Explorer.DB as DB 23 | import Explorer.Node.Error 24 | import Explorer.Node.Util 25 | 26 | import Ouroboros.Consensus.Ledger.Byron (ByronBlock) 27 | import Ouroboros.Network.Block (Point) 28 | 29 | 30 | rollbackToPoint :: Trace IO Text -> Point ByronBlock -> IO (Either ExplorerNodeError ()) 31 | rollbackToPoint trce point = 32 | case pointToSlotHash point of 33 | Nothing -> pure $ Right () 34 | Just (slot, hash) -> 35 | DB.runDbNoLogging $ runExceptT (action slot hash) 36 | where 37 | action :: MonadIO m => Ledger.SlotNumber -> Ledger.HeaderHash -> ExceptT ExplorerNodeError (ReaderT SqlBackend m) () 38 | action slot hash = do 39 | blk <- liftLookupFail "rollbackToPoint" $ DB.queryMainBlock (unHeaderHash hash) 40 | case (DB.blockSlotNo blk, DB.blockBlockNo blk) of 41 | (Nothing, _) -> explorerError "rollbackToPoint: slot number is Nothing" 42 | (_, Nothing) -> explorerError "rollbackToPoint: block number is Nothing" 43 | (Just slotNo, Just blkNo) -> do 44 | if slotNo <= Ledger.unSlotNumber slot 45 | then liftIO . logInfo trce $ mconcat 46 | [ "No rollback required: chain tip slot is ", textShow slotNo ] 47 | else do 48 | liftIO . logInfo trce $ Text.concat 49 | [ "Rollbacking to slot ", textShow (Ledger.unSlotNumber slot) 50 | , ", hash ", renderAbstractHash hash 51 | ] 52 | unless (Just (Ledger.unSlotNumber slot) == DB.blockSlotNo blk) $ 53 | explorerError ("rollbackToPoint: slot mismatch " <> textShow (slotNo, blkNo)) 54 | -- This will be a cascading delete. 55 | void . lift $ DB.deleteCascadeBlockNo blkNo 56 | -------------------------------------------------------------------------------- /cardano-explorer-node/src/Explorer/Node/Tracing/ToObjectOrphans.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleInstances #-} 2 | {-# LANGUAGE MultiParamTypeClasses #-} 3 | {-# LANGUAGE OverloadedStrings #-} 4 | 5 | {-# OPTIONS_GHC -fno-warn-orphans #-} 6 | 7 | module Explorer.Node.Tracing.ToObjectOrphans () where 8 | 9 | import Data.Text 10 | import Data.Aeson ((.=)) 11 | 12 | import Cardano.BM.Data.LogItem 13 | import Cardano.BM.Data.Severity 14 | import Cardano.BM.Data.Tracer 15 | 16 | import qualified Network.Socket as Socket 17 | import Ouroboros.Network.NodeToClient 18 | (WithAddr(..), ErrorPolicyTrace(..)) 19 | 20 | instance DefinePrivacyAnnotation (WithAddr Socket.SockAddr ErrorPolicyTrace) 21 | instance DefineSeverity (WithAddr Socket.SockAddr ErrorPolicyTrace) where 22 | defineSeverity (WithAddr _ ev) = case ev of 23 | ErrorPolicySuspendPeer {} -> Warning -- peer misbehaved 24 | ErrorPolicySuspendConsumer {} -> Notice -- peer temporarily not useful 25 | ErrorPolicyLocalNodeError {} -> Error 26 | ErrorPolicyResumePeer {} -> Debug 27 | ErrorPolicyKeepSuspended {} -> Debug 28 | ErrorPolicyResumeConsumer {} -> Debug 29 | ErrorPolicyResumeProducer {} -> Debug 30 | ErrorPolicyUnhandledApplicationException {} -> Error 31 | ErrorPolicyUnhandledConnectionException {} -> Error 32 | ErrorPolicyAcceptException {} -> Error 33 | 34 | 35 | -- transform @ErrorPolicyTrace@ 36 | instance Transformable Text IO (WithAddr Socket.SockAddr ErrorPolicyTrace) where 37 | trTransformer StructuredLogging verb tr = trStructured verb tr 38 | trTransformer TextualRepresentation _verb tr = Tracer $ \s -> 39 | traceWith tr =<< LogObject <$> pure mempty 40 | <*> mkLOMeta (defineSeverity s) (definePrivacyAnnotation s) 41 | <*> pure (LogMessage $ pack $ show s) 42 | trTransformer UserdefinedFormatting verb tr = trStructured verb tr 43 | 44 | instance ToObject (WithAddr Socket.SockAddr ErrorPolicyTrace) where 45 | toObject _verb (WithAddr addr ev) = 46 | mkObject [ "kind" .= ("ErrorPolicyTrace" :: String) 47 | , "address" .= show addr 48 | , "event" .= show ev ] 49 | -------------------------------------------------------------------------------- /cardano-explorer-node/test/test.hs: -------------------------------------------------------------------------------- 1 | module Main (main) where 2 | 3 | main :: IO () 4 | main = putStrLn "Test suite not yet implemented." 5 | -------------------------------------------------------------------------------- /cardano-explorer-webapi/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Revision history for cardano-explorer-webapi 2 | 3 | ## 1.3.0 -- January 2020 4 | 5 | * Update dependencies to latest versions. 6 | * Docker image: log all runit services to stdout 7 | * Initial documentation on how to use build and run the components in docker 8 | 9 | ## 1.2.2 -- January 2020 10 | 11 | * Swagger docs https://input-output-hk.github.io/cardano-explorer/ 12 | * Fix /api/blocks/txs/{blkHash} endpoint (#195) 13 | * Fix Ada/Lovelace denomination bug (#197) 14 | * Fix JSON rendering for addresses to match old API 15 | * Add validation for genesis address paging (#219) 16 | * Add additional tests (#222, #227) 17 | * Update dependencies to latest versions. 18 | 19 | ## 1.2.1 -- January 2020 20 | 21 | * Update dependencies to latest versions. 22 | 23 | ## 1.2.0 -- December 2019 24 | 25 | * Update dependencies to latest versions. 26 | 27 | ## 1.1.0 -- December 2019 28 | 29 | * Renamed from cardano-explorer to cardano-explorer-webapi (#193). 30 | 31 | * Remove unused/unsupported endpoints (#198) 32 | 33 | * Provide more info in /api/txs/summary/{txHash} endpoint (#174). 34 | 35 | Specifically, for each transaction replace '(address, coin)' with 36 | a struct that also contains the transaction '(hash, index)' pair. 37 | 38 | * Provide more info about transactions in some endpoints (#174, #131). 39 | 40 | Specifically: 41 | * /api/blocks/txs/{blockHash} 42 | * /api/txs/summary/{txHash} 43 | 44 | * Add ChainTip info to address endpoints (#177, #130) 45 | 46 | * Run all transactions at an isolation level of Serializable (#189, #133) 47 | 48 | * Document atomicity of API interactions 49 | 50 | * Fix validation error (#191) 51 | 52 | * Add additional tests to validiate against the old API (#190) 53 | 54 | ## 1.0.0 -- November 2019 55 | 56 | * First release of new explorer API server based on new cardano-explorer-node. 57 | * New modular design, no longer integrated with the cardano-sl node. 58 | * Gets all data from a local PostgreSQL DB. 59 | * Compatible with the old explorer HTTP API and old web frontend. 60 | * Some small compatible API extensions 61 | -------------------------------------------------------------------------------- /cardano-explorer-webapi/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /cardano-explorer-webapi/app/Explorer/Web/Validate.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | module Explorer.Web.Validate 4 | ( runValidation 5 | ) where 6 | 7 | import Control.Monad.IO.Class (liftIO) 8 | import Control.Monad.Logger (runNoLoggingT) 9 | 10 | import Data.Text.ANSI (yellow) 11 | import qualified Data.Text.IO as Text 12 | 13 | import Database.Persist.Postgresql (withPostgresqlConn) 14 | import Database.Persist.Sql (SqlBackend) 15 | 16 | import Explorer.DB (readPGPassFileEnv, toConnectionString) 17 | 18 | import Explorer.Web.Api.Legacy.Util (textShow) 19 | import Explorer.Web.Validate.Address (validateAddressSummary, validateRedeemAddressSummary) 20 | import Explorer.Web.Validate.BlocksTxs (validateBlocksTxs) 21 | import Explorer.Web.Validate.GenesisAddress (validateGenesisAddressPaging) 22 | 23 | runValidation :: Word -> IO () 24 | runValidation count = do 25 | pgconfig <- readPGPassFileEnv 26 | putStrLn "" 27 | runNoLoggingT . 28 | withPostgresqlConn (toConnectionString pgconfig) $ \backend -> 29 | liftIO $ loop backend 1 30 | where 31 | loop :: SqlBackend -> Word -> IO () 32 | loop backend n 33 | | n > count = pure () 34 | | otherwise = do 35 | Text.putStrLn $ yellow ("Test #" <> textShow n <> ":") 36 | validate backend 37 | loop backend (n + 1) 38 | 39 | validate :: SqlBackend -> IO () 40 | validate backend = do 41 | validateRedeemAddressSummary backend 42 | validateAddressSummary backend 43 | validateGenesisAddressPaging backend 44 | validateBlocksTxs backend 45 | putStrLn "" 46 | -------------------------------------------------------------------------------- /cardano-explorer-webapi/app/Explorer/Web/Validate/BlocksTxs.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | {-# LANGUAGE TupleSections #-} 3 | 4 | module Explorer.Web.Validate.BlocksTxs 5 | ( validateBlocksTxs 6 | ) where 7 | 8 | import qualified Data.List as List 9 | import Data.Text (Text) 10 | import qualified Data.Text as Text 11 | import Data.Text.ANSI (green, red) 12 | import qualified Data.Text.IO as Text 13 | 14 | import Database.Persist.Sql (SqlBackend) 15 | 16 | import Explorer.Web (CTxBrief (..), CTxAddressBrief (..), CTxBrief (..), 17 | queryBlocksTxs, runQuery) 18 | import Explorer.Web.Api.Legacy.Util (bsBase16Encode) 19 | import Explorer.Web.Validate.Random (queryRandomBlockHash) 20 | import Explorer.Web.Validate.ErrorHandling (handleLookupFail, handleExplorerError) 21 | 22 | import System.Exit (exitFailure) 23 | 24 | import Text.Show.Pretty (ppShow) 25 | 26 | validateBlocksTxs :: SqlBackend -> IO () 27 | validateBlocksTxs backend = do 28 | (blkHash, txs) <- runQuery backend $ do 29 | blkHash <- handleLookupFail =<< queryRandomBlockHash 30 | (blkHash,) <$> (handleExplorerError =<< queryBlocksTxs blkHash 100 0) 31 | 32 | validateInputsUnique (bsBase16Encode blkHash) $ List.sortOn ctaAddress (concatMap ctbInputs txs) 33 | validateOutputsUnique (bsBase16Encode blkHash) $ List.sortOn ctaAddress (concatMap ctbOutputs txs) 34 | 35 | 36 | -- ------------------------------------------------------------------------------------------------- 37 | 38 | validateInputsUnique :: Text -> [CTxAddressBrief] -> IO () 39 | validateInputsUnique blkHash tabs = do 40 | mapM_ Text.putStr [ " Inputs for block " , shortenTxHash blkHash, " are unique: " ] 41 | if length tabs == length (List.nub tabs) 42 | then Text.putStrLn $ green "ok" 43 | else do 44 | Text.putStrLn $ red "validateInputsUnique failed" 45 | exitFailure 46 | 47 | -- https://github.com/input-output-hk/cardano-explorer/issues/195 48 | validateOutputsUnique :: Text -> [CTxAddressBrief] -> IO () 49 | validateOutputsUnique blkHash tabs = do 50 | mapM_ Text.putStr [ " Outputs for block " , shortenTxHash blkHash, " are unique: " ] 51 | if length tabs == length (List.nub tabs) 52 | then Text.putStrLn $ green "ok" 53 | else do 54 | Text.putStrLn $ red "failed\n Duplicate entries in:" 55 | mapM_ (\x -> putStrLn $ " " ++ x) $ lines (ppShow tabs) 56 | exitFailure 57 | 58 | -- ------------------------------------------------------------------------------------------------- 59 | 60 | shortenTxHash :: Text -> Text 61 | shortenTxHash txh = 62 | mconcat [Text.take 10 txh, "...", Text.drop (Text.length txh - 10) txh] 63 | -------------------------------------------------------------------------------- /cardano-explorer-webapi/app/Explorer/Web/Validate/ErrorHandling.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | module Explorer.Web.Validate.ErrorHandling 4 | ( handleLookupFail 5 | , handleExplorerError 6 | ) where 7 | 8 | import Control.Monad.IO.Class (MonadIO, liftIO) 9 | 10 | import Data.Text.ANSI (red) 11 | import qualified Data.Text.IO as Text 12 | 13 | import Explorer.DB (LookupFail, renderLookupFail) 14 | 15 | import Explorer.Web.Error (ExplorerError (..), renderExplorerError) 16 | 17 | 18 | import System.Exit (exitFailure) 19 | 20 | handleLookupFail :: MonadIO m => Either LookupFail a -> m a 21 | handleLookupFail ela = 22 | case ela of 23 | Left err -> liftIO $ do 24 | Text.putStrLn $ red (renderLookupFail err) 25 | exitFailure 26 | Right v -> pure v 27 | 28 | handleExplorerError :: MonadIO m => Either ExplorerError a -> m a 29 | handleExplorerError eea = 30 | case eea of 31 | Left err -> liftIO $ do 32 | Text.putStrLn $ red (renderExplorerError err) 33 | exitFailure 34 | Right v -> pure v 35 | -------------------------------------------------------------------------------- /cardano-explorer-webapi/app/Explorer/Web/Validate/GenesisAddress.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | module Explorer.Web.Validate.GenesisAddress 4 | ( validateGenesisAddressPaging 5 | ) where 6 | 7 | import Control.Monad.IO.Class (MonadIO, liftIO) 8 | import Control.Monad.Trans.Reader (ReaderT) 9 | 10 | import qualified Data.List as List 11 | import Data.Text.ANSI (green, red) 12 | import qualified Data.Text.IO as Text 13 | 14 | import Database.Esqueleto (Value (..), InnerJoin (..), (==.), (^.), 15 | countRows, from, isNothing, on, select, where_) 16 | import Database.Persist.Sql (SqlBackend) 17 | 18 | import Explorer.DB (EntityField (..), LookupFail (..), listToMaybe) 19 | 20 | import Explorer.Web (CAddress (..), CGenesisAddressInfo (..), 21 | queryAllGenesisAddresses, runQuery) 22 | import Explorer.Web.Api.Legacy.Types (PageNo (..), PageSize (..)) 23 | import Explorer.Web.Validate.ErrorHandling (handleLookupFail) 24 | 25 | import System.Exit (exitFailure) 26 | import System.Random (randomRIO) 27 | 28 | -- | Validate that all address have a balance >= 0. 29 | validateGenesisAddressPaging :: SqlBackend -> IO () 30 | validateGenesisAddressPaging backend = do 31 | (addr1, addr2) <- runQuery backend $ do 32 | pageSize <- genRandomPageSize 33 | pageNo <- handleLookupFail =<< genRandomPageNo pageSize 34 | page1 <- queryAllGenesisAddresses pageNo pageSize 35 | page2 <- queryAllGenesisAddresses (nextPageNo pageNo) pageSize 36 | pure (extractAddresses page1, extractAddresses page2) 37 | if length (List.nub $ addr1 ++ addr2) == length addr1 + length addr2 38 | then Text.putStrLn $ " Adjacent pages for Genesis addresses do not overlap: " <> green "ok" 39 | else reportIntersectFail addr1 addr2 40 | 41 | 42 | extractAddresses :: [CGenesisAddressInfo] -> [CAddress] 43 | extractAddresses = List.map cgaiCardanoAddress 44 | 45 | genRandomPageNo :: MonadIO m => PageSize -> ReaderT SqlBackend m (Either LookupFail PageNo) 46 | genRandomPageNo (PageSize pageSize) = do 47 | res <- select . from $ \ (txOut `InnerJoin` tx `InnerJoin` blk) -> do 48 | on (blk ^. BlockId ==. tx ^. TxBlock) 49 | on (tx ^. TxId ==. txOut ^. TxOutTxId) 50 | where_ (isNothing (blk ^. BlockPrevious)) 51 | pure countRows 52 | case listToMaybe res of 53 | Nothing -> pure $ Left (DbLookupMessage "genRandomPageNo: Empty Block table") 54 | Just (Value addrCount) 55 | | addrCount <= 3 * pageSize -> 56 | pure $ Left (DbLookupMessage "genRandomPageNo: Genesis address count is too low") 57 | | otherwise -> do 58 | offset <- max addrCount <$> liftIO (randomRIO (1, addrCount - 3 * pageSize)) 59 | pure $ Right (PageNo $ offset `div` pageSize) 60 | 61 | genRandomPageSize :: MonadIO m => ReaderT SqlBackend m PageSize 62 | genRandomPageSize = PageSize <$> liftIO (randomRIO (2, 50)) 63 | 64 | nextPageNo :: PageNo -> PageNo 65 | nextPageNo (PageNo x) = PageNo (x + 1) 66 | 67 | reportIntersectFail :: [CAddress] -> [CAddress] -> IO () 68 | reportIntersectFail _addr1 _addr2 = do 69 | Text.putStrLn $ " Adjacent pages for Genesis addresses do not overlap: " <> red "fail" 70 | exitFailure 71 | -------------------------------------------------------------------------------- /cardano-explorer-webapi/app/cardano-explorer-webapi.hs: -------------------------------------------------------------------------------- 1 | 2 | import Explorer.Web (runServer) 3 | 4 | main :: IO () 5 | main = runServer 6 | -------------------------------------------------------------------------------- /cardano-explorer-webapi/app/cardano-webapi-compare.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | import Control.Concurrent.Async (concurrently) 4 | 5 | import Data.Algorithm.Diff (Diff, PolyDiff (..), getDiff) 6 | import qualified Data.ByteString.Char8 as BS 7 | import Data.Char (isSpace) 8 | import Data.Text.ANSI (green, red) 9 | import Data.Text (Text) 10 | import qualified Data.Text as Text 11 | import qualified Data.Text.Encoding as Text 12 | import qualified Data.Text.IO as Text 13 | 14 | import Network.HTTP.Simple (getResponseBody, httpBS, parseRequest) 15 | 16 | import System.Environment (getArgs, getProgName) 17 | 18 | 19 | -- A quick and dirty tool to submit the same query to the old and new cardano webapis 20 | -- and display the differences between the two. 21 | 22 | main :: IO () 23 | main = do 24 | uri <- getArgs 25 | case uri of 26 | [path] -> compareOutputs path 27 | _ -> usageError 28 | 29 | 30 | usageError :: IO () 31 | usageError = do 32 | pn <- getProgName 33 | mapM_ putStrLn 34 | [ "Usage: \n" 35 | , " " ++ pn ++ " \n" 36 | , "where is something like '/api/blocks/pages'" 37 | ] 38 | 39 | compareOutputs :: String -> IO () 40 | compareOutputs path = do 41 | (old, new) <- concurrently 42 | (httpLines $ "http://cardanoexplorer.com" ++ path) 43 | (httpLines $ "http://127.0.0.1:8100" ++ path) 44 | let diff = getDiff old new 45 | if any isDiff diff 46 | then reportDiff diff 47 | else do 48 | Text.putStrLn $ green "Outputs are the same:" 49 | mapM_ Text.putStrLn old 50 | 51 | reportDiff :: [Diff Text] -> IO () 52 | reportDiff xs = do 53 | Text.putStrLn $ Text.concat [ "Explorer web API differences: ", green "old", " vs ", red "new", "." ] 54 | mapM_ display xs 55 | where 56 | display :: Diff Text -> IO () 57 | display db = 58 | case db of 59 | First a -> Text.putStrLn $ green a 60 | Second a -> Text.putStrLn $ red a 61 | Both a _ -> Text.putStrLn a 62 | 63 | isDiff :: Diff a -> Bool 64 | isDiff d = 65 | case d of 66 | First _ -> True 67 | Second _ -> True 68 | Both _ _ -> False 69 | 70 | httpLines :: String -> IO [Text] 71 | httpLines url = do 72 | req <- parseRequest url 73 | -- Remove whitespace, convert to Text and split into lines on commas. 74 | Text.lines . Text.replace "," "\n," 75 | . Text.decodeUtf8 . BS.filter (not . isSpace) 76 | . getResponseBody <$> httpBS req 77 | -------------------------------------------------------------------------------- /cardano-explorer-webapi/app/cardano-webapi-validate.hs: -------------------------------------------------------------------------------- 1 | 2 | import Data.Monoid ((<>)) 3 | 4 | import Explorer.Web.Validate 5 | 6 | import Options.Applicative (Parser, ParserInfo, ParserPrefs) 7 | import qualified Options.Applicative as Opt 8 | 9 | 10 | main :: IO () 11 | main = do 12 | Opt.customExecParser p opts >>= runCommand 13 | where 14 | opts :: ParserInfo Command 15 | opts = Opt.info (Opt.helper <*> pVersion <*> pCommand) 16 | ( Opt.fullDesc 17 | <> Opt.header "cardano-webapi-validate - Run validations on the webapi" 18 | ) 19 | 20 | p :: ParserPrefs 21 | p = Opt.prefs Opt.showHelpOnEmpty 22 | 23 | -- ----------------------------------------------------------------------------- 24 | 25 | data Command 26 | = Validate Word 27 | 28 | runCommand :: Command -> IO () 29 | runCommand cmd = do 30 | case cmd of 31 | Validate count -> runValidation count 32 | 33 | pVersion :: Parser (a -> a) 34 | pVersion = 35 | Opt.infoOption "cardano-webapi-validate version 0.1.0.0" 36 | ( Opt.long "version" 37 | <> Opt.short 'v' 38 | <> Opt.help "Print the version and exit" 39 | ) 40 | 41 | pCommand :: Parser Command 42 | pCommand = 43 | Opt.subparser 44 | ( Opt.command "validate" 45 | ( Opt.info pValidate 46 | $ Opt.progDesc "Run validation checks against the webapi." 47 | ) 48 | ) 49 | where 50 | pValidate :: Parser Command 51 | pValidate = 52 | Validate <$> pCount 53 | 54 | pCount :: Parser Word 55 | pCount = 56 | read <$> Opt.strOption 57 | ( Opt.long "count" 58 | <> Opt.help "The number of validations to run (default is 10)." 59 | ) 60 | -------------------------------------------------------------------------------- /cardano-explorer-webapi/shell.nix: -------------------------------------------------------------------------------- 1 | let 2 | self = import ../. {}; 3 | in self.haskellPackages.shellFor { 4 | name = "cardano-explorer-webapi"; 5 | packages = ps: [ ps.cardano-explorer-webapi ]; 6 | buildInputs = with self.pkgs.haskellPackages; [ hlint stylish-haskell ghcid ]; 7 | shellHook = '' 8 | compare_api() { 9 | curl http://localhost:8100/api/$1 -o /tmp/compare-local 10 | curl https://cardanoexplorer.com/api/$1 -o /tmp/compare-remote 11 | echo "old explorer:" 12 | ${self.pkgs.jq}/bin/jq < /tmp/compare-remote "$2" 13 | ${self.pkgs.jq}/bin/jq < /tmp/compare-local "$2" 14 | ${self.pkgs.haskellPackages.aeson-diff}/bin/json-diff /tmp/compare-remote /tmp/compare-local | jq . 15 | } 16 | ''; 17 | } 18 | -------------------------------------------------------------------------------- /cardano-explorer-webapi/src/Explorer/Web.hs: -------------------------------------------------------------------------------- 1 | module Explorer.Web 2 | ( runServer 3 | 4 | -- For testing. 5 | , CAddress (..) 6 | , CTxAddressBrief (..) 7 | , CAddressSummary (..) 8 | , CCoin (..) 9 | , CGenesisAddressInfo (..) 10 | , CHash (..) 11 | , CTxBrief (..) 12 | , CTxHash (..) 13 | , queryAddressSummary 14 | , queryAllGenesisAddresses 15 | , queryBlocksTxs 16 | , queryChainTip 17 | , runQuery 18 | ) where 19 | 20 | import Explorer.Web.Api.Legacy.AddressSummary (queryAddressSummary) 21 | import Explorer.Web.Api.Legacy.BlocksTxs (queryBlocksTxs) 22 | import Explorer.Web.Api.Legacy.GenesisAddress (queryAllGenesisAddresses) 23 | import Explorer.Web.Api.Legacy.Util (runQuery) 24 | import Explorer.Web.ClientTypes (CAddress (..), CTxAddressBrief (..), CAddressSummary (..), 25 | CCoin (..), CGenesisAddressInfo (..), CHash (..), CTxBrief (..), CTxHash (..)) 26 | import Explorer.Web.Query (queryChainTip) 27 | import Explorer.Web.Server (runServer) 28 | -------------------------------------------------------------------------------- /cardano-explorer-webapi/src/Explorer/Web/Api.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DataKinds #-} 2 | {-# LANGUAGE TypeOperators #-} 3 | 4 | module Explorer.Web.Api 5 | ( ExplorerApi 6 | , explorerApi 7 | ) where 8 | 9 | import Data.Proxy (Proxy (..)) 10 | 11 | import Explorer.Web.Api.HttpBridge (HttpBridgeApi) 12 | import Explorer.Web.Api.Legacy (ExplorerApiRecord) 13 | 14 | import Servant.API ((:>), (:<|>)) 15 | import Servant.API.Generic (ToServantApi) 16 | 17 | -- | Servant API which provides access to explorer 18 | type ExplorerApi 19 | = "api" :> ToServantApi ExplorerApiRecord 20 | -- This one needs to be last in the list because it does not start with 21 | -- as static string. 22 | :<|> ToServantApi HttpBridgeApi 23 | 24 | -- | Helper Proxy 25 | explorerApi :: Proxy ExplorerApi 26 | explorerApi = Proxy 27 | 28 | -------------------------------------------------------------------------------- /cardano-explorer-webapi/src/Explorer/Web/Api/HttpBridge.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TypeOperators #-} 2 | {-# LANGUAGE DataKinds #-} 3 | {-# LANGUAGE DeriveGeneric #-} 4 | 5 | module Explorer.Web.Api.HttpBridge 6 | ( HttpBridgeApi (..) 7 | ) where 8 | 9 | import GHC.Generics (Generic) 10 | 11 | import Explorer.Web.ClientTypes (CAddress, CNetwork, CAddressBalanceError) 12 | 13 | import Servant.API ((:>), Capture, Get, JSON, Summary) 14 | import Servant.API.Generic ((:-)) 15 | 16 | data HttpBridgeApi route = HttpBridgeApi 17 | { _addressBalance :: route 18 | :- Summary "Get current balance of provided address." 19 | :> Capture "network" CNetwork 20 | :> "utxos" 21 | :> Capture "address" CAddress 22 | :> Get '[JSON] CAddressBalanceError 23 | } deriving Generic 24 | -------------------------------------------------------------------------------- /cardano-explorer-webapi/src/Explorer/Web/Api/HttpBridge/AddressBalance.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DataKinds #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | 4 | module Explorer.Web.Api.HttpBridge.AddressBalance 5 | ( addressBalance 6 | ) where 7 | 8 | import Control.Monad.IO.Class (MonadIO) 9 | import Control.Monad.Trans.Reader (ReaderT) 10 | 11 | import Data.ByteString.Char8 (ByteString) 12 | import Data.Text (Text) 13 | import Data.Word (Word16, Word64) 14 | 15 | import Database.Esqueleto (InnerJoin (..), Value (..), 16 | (^.), (==.), from, on, select, val, where_) 17 | import Database.Persist.Sql (SqlBackend) 18 | 19 | import Explorer.DB (EntityField (..), txOutUnspentP, queryNetworkName) 20 | import Explorer.Web.ClientTypes (CAddress (..), CNetwork (..), CAddressBalance (..), 21 | CAddressBalanceError (..)) 22 | import Explorer.Web.Api.Legacy.Util (bsBase16Encode, decodeTextAddress, runQuery) 23 | 24 | import Servant (Handler) 25 | 26 | -- This endpoint emulates the Rust cardano-http-bridge endpoint: 27 | -- 28 | -- GET: /:network/utxos/:address 29 | -- 30 | -- and returns the current Utxo output details: 31 | -- 32 | -- [ { "address": "2cWKMJemoBamE3kYCuVLq6pwWwNBJVZmv471Zcb2ok8cH9NjJC4JUkq5rV5ss9ALXWCKN" 33 | -- , "coin": 310025 34 | -- , "index": 0 35 | -- , "txid": "89eb0d6a8a691dae2cd15ed0369931ce0a949ecafa5c3f93f8121833646e15c3" 36 | -- } 37 | -- ] 38 | 39 | 40 | -- This endpoint always returns a list (which may be empty). 41 | -- There are a number of potential failures and wyat 42 | addressBalance 43 | :: SqlBackend -> CNetwork -> CAddress 44 | -> Handler CAddressBalanceError 45 | addressBalance backend (CNetwork networkName) (CAddress addrTxt) = 46 | -- Currently ignore the 'CNetwork' parameter (eg mainnet, testnet etc) as the explorer only 47 | -- supports a single network and returns a result for whichever network its running on. 48 | case decodeTextAddress addrTxt of 49 | Left _ -> pure $ CABError "Invalid address" 50 | Right _ -> runQuery backend $ do 51 | mNetName <- queryNetworkName 52 | case mNetName of 53 | Nothing -> pure $ CABError "Invalid network name" 54 | Just name -> if name /= networkName 55 | then pure $ CABError "Network name mismatch" 56 | else CABValue <$> queryAddressBalance addrTxt 57 | 58 | -- ------------------------------------------------------------------------------------------------- 59 | 60 | queryAddressBalance :: MonadIO m => Text -> ReaderT SqlBackend m [CAddressBalance] 61 | queryAddressBalance addrTxt = do 62 | rows <- select . from $ \ (tx `InnerJoin` txOut) -> do 63 | on (tx ^. TxId ==. txOut ^. TxOutTxId) 64 | txOutUnspentP txOut 65 | where_ (txOut ^. TxOutAddress ==. val addrTxt) 66 | pure (txOut ^. TxOutAddress, tx ^. TxHash, txOut ^. TxOutIndex, txOut ^. TxOutValue) 67 | pure $ map convert rows 68 | where 69 | convert :: (Value Text, Value ByteString, Value Word16, Value Word64) -> CAddressBalance 70 | convert (Value addr, Value txhash, Value index, Value coin) = 71 | CAddressBalance 72 | { cuaAddress = addr 73 | , cuaTxHash = bsBase16Encode txhash 74 | , cuaIndex = index 75 | , cuaCoin = coin 76 | } 77 | -------------------------------------------------------------------------------- /cardano-explorer-webapi/src/Explorer/Web/Api/Legacy/BlockPagesTotal.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE MultiWayIf #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | module Explorer.Web.Api.Legacy.BlockPagesTotal 4 | ( blockPagesTotal 5 | ) where 6 | 7 | import Control.Monad.IO.Class (MonadIO) 8 | import Control.Monad.Trans.Reader (ReaderT) 9 | 10 | import Data.Maybe (listToMaybe) 11 | 12 | import Database.Esqueleto ((^.), countRows, from, select, unValue, where_) 13 | import Database.Persist.Sql (SqlBackend) 14 | 15 | import Explorer.DB (EntityField (..), isJust) 16 | 17 | import Explorer.Web.Error (ExplorerError (..)) 18 | import Explorer.Web.Api.Legacy (PageNumber) 19 | import Explorer.Web.Api.Legacy.Util (divRoundUp, runQuery, toPageSize) 20 | import Explorer.Web.Api.Legacy.Types (PageSize (..)) 21 | 22 | import Servant (Handler) 23 | 24 | 25 | blockPagesTotal 26 | :: SqlBackend -> Maybe PageSize 27 | -> Handler (Either ExplorerError PageNumber) 28 | blockPagesTotal backend mPageSize = 29 | runQuery backend $ do 30 | blockCount <- queryMainBlockCount 31 | if | blockCount < 1 -> pure $ Left (Internal "There are currently no block to display.") 32 | | pageSize < 1 -> pure $ Left (Internal "Page size must be greater than 1 if you want to display blocks.") 33 | | otherwise -> pure $ Right $ divRoundUp blockCount pageSize 34 | where 35 | pageSize = unPageSize $ toPageSize mPageSize 36 | 37 | queryMainBlockCount :: MonadIO m => ReaderT SqlBackend m Word 38 | queryMainBlockCount = do 39 | res <- select . from $ \ blk -> do 40 | where_ (isJust $ blk ^. BlockBlockNo) 41 | pure countRows 42 | pure $ maybe 0 unValue (listToMaybe res) 43 | -------------------------------------------------------------------------------- /cardano-explorer-webapi/src/Explorer/Web/Api/Legacy/GenesisPages.hs: -------------------------------------------------------------------------------- 1 | module Explorer.Web.Api.Legacy.GenesisPages 2 | ( genesisPages 3 | ) where 4 | 5 | import Control.Monad.IO.Class (MonadIO) 6 | import Control.Monad.Trans.Reader (ReaderT) 7 | 8 | import Data.Maybe (listToMaybe) 9 | import Database.Esqueleto (InnerJoin (..), Value, 10 | (^.), (==.), countRows, from, on, select, unValue, val, where_) 11 | import Database.Persist.Sql (SqlBackend) 12 | 13 | import Explorer.DB (EntityField (..), txOutSpentP, txOutUnspentP) 14 | 15 | import Explorer.Web.ClientTypes (CAddressesFilter (..)) 16 | import Explorer.Web.Error (ExplorerError (..)) 17 | import Explorer.Web.Api.Legacy (PageNumber) 18 | import Explorer.Web.Api.Legacy.Util (divRoundUp, runQuery, toPageSize) 19 | import Explorer.Web.Api.Legacy.Types (PageSize (..)) 20 | 21 | import Servant (Handler) 22 | 23 | 24 | genesisPages 25 | :: SqlBackend -> Maybe PageSize 26 | -> Maybe CAddressesFilter 27 | -> Handler (Either ExplorerError PageNumber) 28 | genesisPages backend mPageSize mAddrFilter = 29 | runQuery backend $ 30 | case mAddrFilter of 31 | Just RedeemedAddresses -> Right <$> queryRedeemedGenesisAddressCount pageSize 32 | Just NonRedeemedAddresses -> Right <$> queryUnRedeemedGenesisAddressCount pageSize 33 | _ -> Right <$> queryGenesisAddressCount pageSize 34 | where 35 | pageSize = toPageSize mPageSize 36 | 37 | queryGenesisAddressCount :: MonadIO m => PageSize -> ReaderT SqlBackend m Word 38 | queryGenesisAddressCount (PageSize pageSize) = do 39 | res <- select . from $ \ (blk `InnerJoin` tx `InnerJoin` txOut) -> do 40 | on (tx ^. TxId ==. txOut ^. TxOutTxId) 41 | on (blk ^. BlockId ==. tx ^. TxBlock) 42 | -- Only the initial genesis block has a size of 0. 43 | where_ (blk ^. BlockSize ==. val 0) 44 | pure countRows 45 | pure $ maybe 0 (dividePageSize pageSize) (listToMaybe res) 46 | 47 | queryRedeemedGenesisAddressCount :: MonadIO m => PageSize -> ReaderT SqlBackend m Word 48 | queryRedeemedGenesisAddressCount (PageSize pageSize) = do 49 | res <- select . from $ \ (blk `InnerJoin` tx `InnerJoin` txOut) -> do 50 | on (tx ^. TxId ==. txOut ^. TxOutTxId) 51 | on (blk ^. BlockId ==. tx ^. TxBlock) 52 | txOutSpentP txOut 53 | -- Only the initial genesis block has a size of 0. 54 | where_ (blk ^. BlockSize ==. val 0) 55 | pure countRows 56 | pure $ maybe 0 (dividePageSize pageSize) (listToMaybe res) 57 | 58 | queryUnRedeemedGenesisAddressCount :: MonadIO m => PageSize -> ReaderT SqlBackend m Word 59 | queryUnRedeemedGenesisAddressCount (PageSize pageSize) = do 60 | res <- select . from $ \ (blk `InnerJoin` tx `InnerJoin` txOut) -> do 61 | on (tx ^. TxId ==. txOut ^. TxOutTxId) 62 | on (blk ^. BlockId ==. tx ^. TxBlock) 63 | txOutUnspentP txOut 64 | -- Only the initial genesis block has a size of 0. 65 | where_ (blk ^. BlockSize ==. val 0) 66 | pure countRows 67 | pure $ maybe 0 (dividePageSize pageSize) (listToMaybe res) 68 | 69 | 70 | dividePageSize :: Word -> Value Word -> Word 71 | dividePageSize pageSize vw = 72 | divRoundUp (unValue vw) pageSize 73 | -------------------------------------------------------------------------------- /cardano-explorer-webapi/src/Explorer/Web/Api/Legacy/GenesisSummary.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DataKinds #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | 4 | module Explorer.Web.Api.Legacy.GenesisSummary 5 | ( genesisSummary 6 | ) where 7 | 8 | import Control.Monad.IO.Class (MonadIO) 9 | import Control.Monad.Trans.Reader (ReaderT) 10 | 11 | import Data.Fixed (Fixed (..), Uni) 12 | import Data.Maybe (listToMaybe) 13 | 14 | import Database.Esqueleto (InnerJoin (..), Value, 15 | (^.), (==.), countRows, from, on, select, sum_, unValue, 16 | val, where_) 17 | import Database.Persist.Sql (SqlBackend) 18 | 19 | import Explorer.DB (EntityField (..), txOutSpentP) 20 | 21 | import Explorer.Web.ClientTypes (CGenesisSummary (..), mkCCoin) 22 | import Explorer.Web.Error (ExplorerError (..)) 23 | import Explorer.Web.Api.Legacy.Util (runQuery) 24 | 25 | import Servant (Handler) 26 | 27 | 28 | genesisSummary :: SqlBackend -> Handler (Either ExplorerError CGenesisSummary) 29 | genesisSummary backend = 30 | runQuery backend $ Right <$> do 31 | (numTotal,valTotal) <- queryInitialGenesis 32 | (redTotal, valRedeemed) <- queryGenesisRedeemed 33 | pure $ CGenesisSummary 34 | { cgsNumTotal = numTotal 35 | , cgsNumRedeemed = redTotal 36 | , cgsNumNotRedeemed = numTotal - redTotal 37 | , cgsRedeemedAmountTotal = mkCCoin valRedeemed 38 | , cgsNonRedeemedAmountTotal = mkCCoin $ valTotal - valRedeemed 39 | } 40 | 41 | queryInitialGenesis :: MonadIO m => ReaderT SqlBackend m (Word, Integer) 42 | queryInitialGenesis = do 43 | res <- select . from $ \ (blk `InnerJoin` tx `InnerJoin` txOut) -> do 44 | on (tx ^. TxId ==. txOut ^. TxOutTxId) 45 | on (blk ^. BlockId ==. tx ^. TxBlock) 46 | -- Only the initial genesis block has a size of 0. 47 | where_ (blk ^. BlockSize ==. val 0) 48 | pure (countRows, sum_ (txOut ^. TxOutValue)) 49 | pure $ maybe (0, 0) convertPair (listToMaybe res) 50 | 51 | queryGenesisRedeemed :: MonadIO m => ReaderT SqlBackend m (Word, Integer) 52 | queryGenesisRedeemed = do 53 | res <- select . from $ \ (blk `InnerJoin` tx `InnerJoin` txOut) -> do 54 | on (tx ^. TxId ==. txOut ^. TxOutTxId) 55 | on (blk ^. BlockId ==. tx ^. TxBlock) 56 | txOutSpentP txOut 57 | -- Only the initial genesis block has a size of 0. 58 | where_ (blk ^. BlockSize ==. val 0) 59 | pure (countRows, sum_ (txOut ^. TxOutValue)) 60 | pure $ maybe (0, 0) convertPair (listToMaybe res) 61 | 62 | convertPair :: (Value Word, Value (Maybe Uni)) -> (Word, Integer) 63 | convertPair (vcount, vtotal) = (unValue vcount, unTotal vtotal) 64 | 65 | unTotal :: Value (Maybe Uni) -> Integer 66 | unTotal mvi = 67 | case unValue mvi of 68 | Just (MkFixed x) -> x 69 | _ -> 0 70 | -------------------------------------------------------------------------------- /cardano-explorer-webapi/src/Explorer/Web/Api/Legacy/TxLast.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | module Explorer.Web.Api.Legacy.TxLast 4 | ( getLastTxs 5 | ) where 6 | 7 | import Control.Monad.IO.Class (MonadIO) 8 | import Control.Monad.Trans.Reader (ReaderT) 9 | 10 | import Data.ByteString.Char8 (ByteString) 11 | import Data.Fixed (Fixed (..), Uni) 12 | import Data.Time.Clock (UTCTime) 13 | import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds) 14 | 15 | import Database.Esqueleto (Entity, InnerJoin(..), SqlExpr, Value, 16 | (^.), (==.), 17 | desc, from, limit, on, orderBy, select, subSelectUnsafe, sum_, where_, unValue) 18 | import Database.Persist.Sql (SqlBackend) 19 | 20 | import Explorer.DB (EntityField (..), Tx, isJust) 21 | import Explorer.Web.ClientTypes (CHash (..), CTxEntry (..), CTxHash (..), mkCCoin) 22 | import Explorer.Web.Error (ExplorerError (..)) 23 | import Explorer.Web.Api.Legacy.Util 24 | 25 | import Servant (Handler) 26 | 27 | 28 | getLastTxs :: SqlBackend -> Handler (Either ExplorerError [CTxEntry]) 29 | getLastTxs backend = 30 | runQuery backend $ Right <$> queryCTxEntry 31 | 32 | 33 | queryCTxEntry :: MonadIO m => ReaderT SqlBackend m [CTxEntry] 34 | queryCTxEntry = do 35 | txRows <- select . from $ \ (blk `InnerJoin` tx) -> do 36 | on (blk ^. BlockId ==. tx ^. TxBlock) 37 | where_ (isJust $ blk ^. BlockSlotNo) 38 | orderBy [desc (blk ^. BlockSlotNo)] 39 | limit 20 40 | pure (blk ^. BlockTime, tx ^. TxHash, txOutValue tx) 41 | pure $ map convert txRows 42 | where 43 | convert :: (Value UTCTime, Value ByteString, Value (Maybe Uni)) -> CTxEntry 44 | convert (vtime, vhash, vtotal) = 45 | CTxEntry 46 | { cteId = CTxHash . CHash $ bsBase16Encode (unValue vhash) 47 | , cteTimeIssued = Just $ utcTimeToPOSIXSeconds (unValue vtime) 48 | , cteAmount = mkCCoin (unTotal vtotal) 49 | } 50 | 51 | txOutValue :: SqlExpr (Entity Tx) -> SqlExpr (Value (Maybe Uni)) 52 | txOutValue tx = 53 | -- This actually is safe. 54 | subSelectUnsafe . from $ \ txOut -> do 55 | where_ (txOut ^. TxOutTxId ==. tx ^. TxId) 56 | pure $ sum_ (txOut ^. TxOutValue) 57 | 58 | unTotal :: Value (Maybe Uni) -> Integer 59 | unTotal mvi = 60 | case unValue mvi of 61 | Just (MkFixed x) -> x 62 | _ -> 0 63 | -------------------------------------------------------------------------------- /cardano-explorer-webapi/src/Explorer/Web/Api/Legacy/Types.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} 2 | {-# LANGUAGE StandaloneDeriving #-} 3 | module Explorer.Web.Api.Legacy.Types 4 | ( PageNo (..) 5 | , PageSize (..) 6 | ) where 7 | 8 | import Servant.API (FromHttpApiData) 9 | 10 | 11 | newtype PageNo = PageNo 12 | { unPageNo :: Word 13 | } deriving Show 14 | 15 | deriving instance FromHttpApiData PageNo 16 | 17 | newtype PageSize = PageSize 18 | { unPageSize :: Word 19 | } deriving Show 20 | 21 | deriving instance FromHttpApiData PageSize 22 | -------------------------------------------------------------------------------- /cardano-explorer-webapi/src/Explorer/Web/Error.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveGeneric #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | {-# LANGUAGE TemplateHaskell #-} 4 | 5 | -- | Types describing runtime errors related to Explorer 6 | 7 | module Explorer.Web.Error 8 | ( ExplorerError (..) 9 | , renderExplorerError 10 | ) where 11 | 12 | import Data.Aeson (ToJSON (..), Value (..)) 13 | import Data.Text (Text) 14 | 15 | import Explorer.DB (LookupFail (..), renderLookupFail) 16 | 17 | import Formatting (bprint, stext, (%)) 18 | import Formatting.Buildable (Buildable) 19 | 20 | import qualified Formatting.Buildable 21 | 22 | import GHC.Generics (Generic) 23 | 24 | data ExplorerError 25 | = Internal Text -- Stupid error constructor from the old code base. 26 | | EELookupFail !LookupFail 27 | deriving (Generic) 28 | 29 | instance Buildable ExplorerError where 30 | build ee = 31 | case ee of 32 | Internal msg -> bprint ("Internal explorer error ("%stext%")") msg 33 | EELookupFail err -> bprint stext $ renderLookupFail err 34 | 35 | renderExplorerError :: ExplorerError -> Text 36 | renderExplorerError ee = 37 | case ee of 38 | Internal msg -> mconcat [ "Internal explorer error: ", msg ] 39 | EELookupFail err -> renderLookupFail err 40 | 41 | 42 | instance ToJSON ExplorerError where 43 | toJSON ee = 44 | case ee of 45 | Internal msg -> String msg 46 | EELookupFail err -> String $ renderLookupFail err 47 | -------------------------------------------------------------------------------- /cardano-explorer-webapi/test/Test/Explorer/Web/Property/Types.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | {-# LANGUAGE TemplateHaskell #-} 3 | 4 | module Test.Explorer.Web.Property.Types 5 | ( tests 6 | ) where 7 | 8 | import Explorer.Web.ClientTypes (adaToCCoin, cCoinToAda) 9 | 10 | import Hedgehog (Property, discover) 11 | import qualified Hedgehog as H 12 | 13 | import Test.Property.Explorer.DB.Types (genAda) 14 | 15 | 16 | prop_roundtrip_ada_to_ccoin :: Property 17 | prop_roundtrip_ada_to_ccoin = 18 | H.withTests 1000 . H.property $ do 19 | ada <- H.forAll genAda 20 | H.tripping ada adaToCCoin (Just . cCoinToAda) 21 | 22 | -- ----------------------------------------------------------------------------- 23 | 24 | tests :: IO Bool 25 | tests = H.checkParallel $$discover 26 | -------------------------------------------------------------------------------- /cardano-explorer-webapi/test/test.hs: -------------------------------------------------------------------------------- 1 | import Hedgehog.Main (defaultMain) 2 | 3 | import qualified Test.Explorer.Web.Property.Types 4 | 5 | main :: IO () 6 | main = 7 | defaultMain 8 | [ Test.Explorer.Web.Property.Types.tests 9 | ] 10 | -------------------------------------------------------------------------------- /cardano-extended-db-node/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Revision history for cardano-extended-db-node 2 | 3 | ## ? -- ? 2020 4 | 5 | * First release of the cardano-extended-db-node 6 | -------------------------------------------------------------------------------- /cardano-extended-db-node/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /cardano-extended-db-node/app/cardano-extended-db-node.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE NoImplicitPrelude #-} 2 | 3 | import Cardano.Prelude 4 | 5 | import Explorer.DB (MigrationDir (..)) 6 | import Explorer.Node (ConfigFile (..), ExplorerNodeParams (..), GenesisFile (..), 7 | SocketPath (..), runExplorer) 8 | import Explorer.Plugin.Extended (extendedExplorerNodePlugin) 9 | 10 | import Options.Applicative (Parser, ParserInfo) 11 | import qualified Options.Applicative as Opt 12 | 13 | main :: IO () 14 | main = do 15 | runExplorer extendedExplorerNodePlugin =<< Opt.execParser opts 16 | 17 | -- ------------------------------------------------------------------------------------------------- 18 | 19 | opts :: ParserInfo ExplorerNodeParams 20 | opts = 21 | Opt.info (pCommandLine <**> Opt.helper) 22 | ( Opt.fullDesc 23 | <> Opt.progDesc "Cardano explorer database node." 24 | ) 25 | 26 | pCommandLine :: Parser ExplorerNodeParams 27 | pCommandLine = 28 | ExplorerNodeParams 29 | <$> pConfigFile 30 | <*> pGenesisFile 31 | <*> pSocketPath 32 | <*> pMigrationDir 33 | 34 | pConfigFile :: Parser ConfigFile 35 | pConfigFile = 36 | ConfigFile <$> Opt.strOption 37 | ( Opt.long "config" 38 | <> Opt.help "Path to the explorer node config file" 39 | <> Opt.completer (Opt.bashCompleter "file") 40 | <> Opt.metavar "FILEPATH" 41 | ) 42 | 43 | pGenesisFile :: Parser GenesisFile 44 | pGenesisFile = 45 | GenesisFile <$> Opt.strOption 46 | ( Opt.long "genesis-file" 47 | <> Opt.help "Path to the genesis JSON file" 48 | <> Opt.completer (Opt.bashCompleter "file") 49 | <> Opt.metavar "FILEPATH" 50 | ) 51 | 52 | pMigrationDir :: Parser MigrationDir 53 | pMigrationDir = 54 | MigrationDir <$> Opt.strOption 55 | ( Opt.long "schema-dir" 56 | <> Opt.help "The directory containing the migrations." 57 | <> Opt.completer (Opt.bashCompleter "directory") 58 | <> Opt.metavar "FILEPATH" 59 | ) 60 | 61 | pSocketPath :: Parser SocketPath 62 | pSocketPath = 63 | SocketPath <$> Opt.strOption 64 | ( Opt.long "socket-path" 65 | <> Opt.help "Path to a cardano-node socket" 66 | <> Opt.completer (Opt.bashCompleter "file") 67 | <> Opt.metavar "FILEPATH" 68 | ) 69 | -------------------------------------------------------------------------------- /cardano-extended-db-node/src/Explorer/Plugin/Extended.hs: -------------------------------------------------------------------------------- 1 | module Explorer.Plugin.Extended 2 | ( extendedExplorerNodePlugin 3 | ) where 4 | 5 | 6 | import Explorer.Node (ExplorerNodePlugin (..), defExplorerNodePlugin) 7 | import Explorer.Node.Plugin.Epoch (epochPluginOnStartup, epochPluginInsertBlock, 8 | epochPluginRollbackBlock) 9 | 10 | extendedExplorerNodePlugin :: ExplorerNodePlugin 11 | extendedExplorerNodePlugin = 12 | defExplorerNodePlugin 13 | { plugOnStartup = 14 | plugOnStartup defExplorerNodePlugin 15 | ++ [epochPluginOnStartup] 16 | , plugInsertBlock = 17 | plugInsertBlock defExplorerNodePlugin 18 | ++ [epochPluginInsertBlock] 19 | , plugRollbackBlock = 20 | plugRollbackBlock defExplorerNodePlugin 21 | ++ [epochPluginRollbackBlock] 22 | } 23 | 24 | -------------------------------------------------------------------------------- /cardano-tx-submit/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Revision history for cardano-tx-submit 2 | 3 | ## 1.3.0 -- January 2020 4 | 5 | * API change: require content-type `application/cbor` for posted transactions. 6 | * API change: require raw transaction binary format for posted transactions. 7 | * Add more specific error message when posted transaction is hex encoded. 8 | * Include example testnet configuration and update README on using it. 9 | * Documentation for submission API and how to generate example transactions. 10 | * Update dependencies to latest versions. 11 | * Docker image: log all runit services to stdout 12 | * Initial documentation on how to use build and run the components in docker 13 | 14 | ## 1.2.2 -- January 2020 15 | 16 | * Update dependencies to latest versions. 17 | * Service added to docker files. 18 | 19 | ## 1.2.1 -- January 2020 20 | 21 | * Update dependencies to latest versions. 22 | * Improve logging of tx submit responses. 23 | * Add a README. 24 | * Add QA document on how to test the component. 25 | 26 | ## 1.2.0 -- December 2019 27 | 28 | * First release of the Tx Submission endpoint 29 | -------------------------------------------------------------------------------- /cardano-tx-submit/Readme.md: -------------------------------------------------------------------------------- 1 | # cardano-tx-submit-webapi 2 | 3 | The `cardano-tx-submit-webapi` is a program that provides a webapi that allows transactions 4 | (generated using the `cardano-cli` program in the `cardano-node` repository) to be HTTP POSTed 5 | to the Cardano blockchain. 6 | 7 | In order to submit a transaction to the network (mainnet, staging or any of the testnets), the 8 | webapi needs a running `cardano-node` and the Genesis file and Genesis hash value for the network. 9 | 10 | The config for `mainnet` is provided at `config/tx-submit-mainnet-config.yaml` of this repository. 11 | 12 | Configuration for other networks can be generated using the above as a template using: 13 | ``` 14 | scripts/gen-tx-submit-config.sh --require-magic --genesis-hash --output config.yaml 15 | ``` 16 | where `--require-magic` is required for all test nets, but should be replaced with 17 | `--require-no-magic` for mainnet or staging. 18 | 19 | Once the correct configuration has been generated, the webapi can be run using: 20 | ``` 21 | cabal run cardano-tx-submit-webapi -- \ 22 | --config config.yaml \ 23 | --genesis-file \ 24 | --socket-path \ 25 | --port 8101 26 | ``` 27 | 28 | With the webapi running, it is possible to submit a preformed transaction using: 29 | ``` 30 | curl -X POST \ 31 | --header "Content-Type:application/octet-stream" \ 32 | --data-binary @transaction.bin http://localhost:8101/api/submit/tx 33 | ``` 34 | where `transaction.bin` is the preformed transaction. 35 | -------------------------------------------------------------------------------- /cardano-tx-submit/app/cardano-tx-submit-webapi.hs: -------------------------------------------------------------------------------- 1 | import Cardano.TxSubmit (ConfigFile (..), GenesisFile (..), SocketPath (..), 2 | TxSubmitNodeParams (..), TxSubmitPort (..), runTxSubmitWebapi) 3 | 4 | import Options.Applicative (Parser, ParserInfo, (<**>)) 5 | import qualified Options.Applicative as Opt 6 | 7 | main :: IO () 8 | main = 9 | runTxSubmitWebapi =<< Opt.execParser opts 10 | 11 | -- ------------------------------------------------------------------------------------------------- 12 | 13 | opts :: ParserInfo TxSubmitNodeParams 14 | opts = 15 | Opt.info (pCommandLine <**> Opt.helper) 16 | ( Opt.fullDesc 17 | <> Opt.progDesc "Cardano transaction submission webapi." 18 | ) 19 | 20 | pCommandLine :: Parser TxSubmitNodeParams 21 | pCommandLine = 22 | TxSubmitNodeParams 23 | <$> pConfigFile 24 | <*> pGenesisFile 25 | <*> pSocketPath 26 | <*> pWebPort 27 | 28 | 29 | pConfigFile :: Parser ConfigFile 30 | pConfigFile = 31 | ConfigFile <$> Opt.strOption 32 | ( Opt.long "config" 33 | <> Opt.help "Path to the tx-submit webapi config file" 34 | <> Opt.completer (Opt.bashCompleter "file") 35 | <> Opt.metavar "FILEPATH" 36 | ) 37 | 38 | pGenesisFile :: Parser GenesisFile 39 | pGenesisFile = 40 | GenesisFile <$> Opt.strOption 41 | ( Opt.long "genesis-file" 42 | <> Opt.help "Path to the genesis JSON file" 43 | <> Opt.completer (Opt.bashCompleter "file") 44 | <> Opt.metavar "FILEPATH" 45 | ) 46 | 47 | pSocketPath :: Parser SocketPath 48 | pSocketPath = 49 | SocketPath <$> Opt.strOption 50 | ( Opt.long "socket-path" 51 | <> Opt.help "Path to a cardano-node socket" 52 | <> Opt.completer (Opt.bashCompleter "file") 53 | <> Opt.metavar "FILEPATH" 54 | ) 55 | 56 | pWebPort :: Parser TxSubmitPort 57 | pWebPort = 58 | TxSubmitPort . read <$> Opt.strOption 59 | ( Opt.long "port" 60 | <> Opt.help "The port the webapi should listen on" 61 | <> Opt.metavar "PORT" 62 | ) 63 | -------------------------------------------------------------------------------- /cardano-tx-submit/src/Cardano/TxSubmit.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE LambdaCase #-} 2 | {-# LANGUAGE NoImplicitPrelude #-} 3 | {-# LANGUAGE OverloadedStrings #-} 4 | module Cardano.TxSubmit 5 | ( module X 6 | , runTxSubmitWebapi 7 | ) where 8 | 9 | import qualified Cardano.BM.Setup as Logging 10 | import qualified Cardano.BM.Trace as Logging 11 | import Cardano.BM.Trace (Trace, logInfo) 12 | 13 | import qualified Cardano.Chain.Genesis as Genesis 14 | import Cardano.Crypto (decodeAbstractHash) 15 | 16 | import Cardano.Prelude 17 | 18 | import Cardano.Shell.Lib (GeneralException (ConfigurationError)) 19 | 20 | import Cardano.TxSubmit.Config as X 21 | import Cardano.TxSubmit.Node as X 22 | import Cardano.TxSubmit.Tx as X 23 | import Cardano.TxSubmit.Types as X 24 | import Cardano.TxSubmit.Util as X 25 | import Cardano.TxSubmit.Web as X 26 | 27 | import qualified Control.Concurrent.Async as Async 28 | import Control.Monad.IO.Class (liftIO) 29 | 30 | import Data.Text (Text) 31 | 32 | 33 | runTxSubmitWebapi :: TxSubmitNodeParams -> IO () 34 | runTxSubmitWebapi tsnp = do 35 | tsnc <- readTxSubmitNodeConfig (unConfigFile $ tspConfigFile tsnp) 36 | gc <- readGenesisConfig tsnp tsnc 37 | trce <- mkTracer tsnc 38 | tsv <- newTxSubmitVar 39 | Async.race_ 40 | (runTxSubmitNode tsv trce gc (tspSocketPath tsnp)) 41 | (runTxSubmitServer tsv trce (tspWebPort tsnp)) 42 | logInfo trce "runTxSubmitWebapi: Async.race_ returned" 43 | 44 | mkTracer :: TxSubmitNodeConfig -> IO (Trace IO Text) 45 | mkTracer enc = 46 | if not (tscEnableLogging enc) 47 | then pure Logging.nullTracer 48 | else liftIO $ Logging.setupTrace (Right $ tscLoggingConfig enc) "cardano-tx-submit" 49 | 50 | readGenesisConfig :: TxSubmitNodeParams -> TxSubmitNodeConfig -> IO Genesis.Config 51 | readGenesisConfig enp enc = do 52 | genHash <- either (throwIO . ConfigurationError) pure $ 53 | decodeAbstractHash (unGenesisHash $ tscGenesisHash enc) 54 | convert =<< runExceptT (Genesis.mkConfigFromFile (tscRequiresNetworkMagic enc) 55 | (unGenesisFile $ tspGenesisFile enp) genHash) 56 | where 57 | convert :: Either Genesis.ConfigurationError Genesis.Config -> IO Genesis.Config 58 | convert = 59 | \case 60 | Left err -> panic $ show err 61 | Right x -> pure x 62 | -------------------------------------------------------------------------------- /cardano-tx-submit/src/Cardano/TxSubmit/Config.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleInstances #-} 2 | {-# LANGUAGE NoImplicitPrelude #-} 3 | {-# LANGUAGE OverloadedStrings #-} 4 | {-# LANGUAGE ScopedTypeVariables #-} 5 | 6 | module Cardano.TxSubmit.Config 7 | ( TxSubmitNodeConfig 8 | , GenesisHash (..) 9 | , GenTxSubmitNodeConfig (..) 10 | , readTxSubmitNodeConfig 11 | ) where 12 | 13 | import qualified Cardano.BM.Configuration as Logging 14 | import qualified Cardano.BM.Configuration.Model as Logging 15 | import qualified Cardano.BM.Data.Configuration as Logging 16 | 17 | import Cardano.Crypto (RequiresNetworkMagic (..)) 18 | 19 | import Cardano.Prelude 20 | 21 | import Cardano.TxSubmit.Util 22 | 23 | import Data.Aeson (FromJSON (..), Object, Value (..), (.:)) 24 | import Data.Aeson.Types (Parser) 25 | import qualified Data.Aeson as Aeson 26 | import qualified Data.ByteString.Char8 as BS 27 | import Data.Text (Text) 28 | import qualified Data.Text as Text 29 | import qualified Data.Yaml as Yaml 30 | 31 | type TxSubmitNodeConfig = GenTxSubmitNodeConfig Logging.Configuration 32 | 33 | data GenTxSubmitNodeConfig a = GenTxSubmitNodeConfig 34 | { tscLoggingConfig :: !a 35 | , tscGenesisHash :: !GenesisHash 36 | , tscEnableLogging :: !Bool 37 | , tscEnableMetrics :: !Bool 38 | , tscRequiresNetworkMagic :: !RequiresNetworkMagic 39 | } 40 | 41 | newtype GenesisHash = GenesisHash 42 | { unGenesisHash :: Text 43 | } 44 | 45 | 46 | readTxSubmitNodeConfig :: FilePath -> IO TxSubmitNodeConfig 47 | readTxSubmitNodeConfig fp = do 48 | res <- Yaml.decodeEither' <$> readLoggingConfig 49 | case res of 50 | Left err -> panic $ "readTxSubmitNodeConfig: Error parsing config: " <> textShow err 51 | Right icr -> convertLogging icr 52 | where 53 | readLoggingConfig :: IO ByteString 54 | readLoggingConfig = 55 | catch (BS.readFile fp) $ \(_ :: IOException) -> 56 | panic $ "Cannot find the logging configuration file at : " <> Text.pack fp 57 | 58 | convertLogging :: GenTxSubmitNodeConfig Logging.Representation -> IO TxSubmitNodeConfig 59 | convertLogging tsc = do 60 | lc <- Logging.setupFromRepresentation $ tscLoggingConfig tsc 61 | pure $ tsc { tscLoggingConfig = lc } 62 | 63 | -- ------------------------------------------------------------------------------------------------- 64 | 65 | instance FromJSON (GenTxSubmitNodeConfig Logging.Representation) where 66 | parseJSON o = 67 | Aeson.withObject "top-level" parseGenTxSubmitNodeConfig o 68 | 69 | parseGenTxSubmitNodeConfig :: Object -> Parser (GenTxSubmitNodeConfig Logging.Representation) 70 | parseGenTxSubmitNodeConfig o = 71 | GenTxSubmitNodeConfig 72 | <$> parseJSON (Object o) 73 | <*> fmap GenesisHash (o .: "GenesisHash") 74 | <*> o .: "EnableLogging" 75 | <*> o .: "EnableLogMetrics" 76 | <*> o .: "RequiresNetworkMagic" 77 | -------------------------------------------------------------------------------- /cardano-tx-submit/src/Cardano/TxSubmit/ErrorRender.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module Cardano.TxSubmit.ErrorRender 3 | ( renderApplyMempoolPayloadErr 4 | ) where 5 | 6 | -- This file contains error renders. The should hve defined at a lower level, with the error 7 | -- type definitions, but for some reason have not been. 8 | -- They will be defined here for now and then moved where they are supposed to be once they 9 | -- are working. 10 | 11 | import Cardano.Chain.UTxO.Validation (TxValidationError (..), UTxOValidationError (..)) 12 | import Cardano.Chain.UTxO.UTxO (UTxOError(..)) 13 | import Cardano.Chain.Byron.API (ApplyMempoolPayloadErr (..)) 14 | 15 | import Data.Text (Text) 16 | import qualified Data.Text as Text 17 | 18 | import Formatting ((%), build, sformat, stext) 19 | 20 | 21 | renderApplyMempoolPayloadErr :: ApplyMempoolPayloadErr -> Text 22 | renderApplyMempoolPayloadErr err = 23 | case err of 24 | MempoolTxErr ve -> renderValidationError ve 25 | MempoolDlgErr {} -> "Delegation error" 26 | MempoolUpdateProposalErr {} -> "Update proposal error" 27 | MempoolUpdateVoteErr {} -> "Update vote error" 28 | 29 | 30 | renderValidationError :: UTxOValidationError -> Text 31 | renderValidationError ve = 32 | case ve of 33 | UTxOValidationTxValidationError tve -> renderTxValidationError tve 34 | UTxOValidationUTxOError ue -> renderUTxOError ue 35 | 36 | 37 | renderTxValidationError :: TxValidationError -> Text 38 | renderTxValidationError tve = 39 | "Tx Validation: " <> 40 | case tve of 41 | TxValidationLovelaceError txt e -> 42 | sformat ("Lovelace error "% stext %": "% build) txt e 43 | TxValidationFeeTooSmall tx expected actual -> 44 | sformat ("Tx "% build %" fee "% build %"too low, expected "% build) tx actual expected 45 | TxValidationWitnessWrongSignature wit pmid sig -> 46 | sformat ("Bad witness "% build %" for signature "% stext %" protocol magic id "% stext) wit (textShow sig) (textShow pmid) 47 | TxValidationWitnessWrongKey wit addr -> 48 | sformat ("Bad witness "% build %" for address "% build) wit addr 49 | TxValidationMissingInput tx -> 50 | sformat ("Validation cannot find input tx "% build) tx 51 | -- Fields are 52 | TxValidationNetworkMagicMismatch expected actual -> 53 | mconcat [ "Bad network magic ", textShow actual, ", expected ", textShow expected ] 54 | TxValidationTxTooLarge expected actual -> 55 | mconcat [ "Tx is ", textShow actual, " bytes, but expected < ", textShow expected, " bytes" ] 56 | TxValidationUnknownAddressAttributes -> 57 | "Unknown address attributes" 58 | TxValidationUnknownAttributes -> 59 | "Unknown attributes" 60 | 61 | renderUTxOError :: UTxOError -> Text 62 | renderUTxOError ue = 63 | "UTxOError: " <> 64 | case ue of 65 | UTxOMissingInput tx -> sformat ("Lookup of tx "% build %" failed") tx 66 | UTxOOverlappingUnion -> "Union or two overlapping UTxO sets" 67 | 68 | textShow :: Show a => a -> Text 69 | textShow = Text.pack . show 70 | 71 | -------------------------------------------------------------------------------- /cardano-tx-submit/src/Cardano/TxSubmit/Metrics.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE NoImplicitPrelude #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | 4 | module Cardano.TxSubmit.Metrics 5 | ( TxSubmitMetrics (..) 6 | , makeMetrics 7 | , registerMetricsServer 8 | ) where 9 | 10 | import Cardano.Prelude 11 | 12 | import System.Metrics.Prometheus.Concurrent.RegistryT (RegistryT (..), registerGauge, 13 | runRegistryT, unRegistryT) 14 | import System.Metrics.Prometheus.Metric.Gauge (Gauge) 15 | import System.Metrics.Prometheus.Http.Scrape (serveHttpTextMetricsT) 16 | 17 | 18 | data TxSubmitMetrics = TxSubmitMetrics 19 | { tsmCount :: !Gauge 20 | } 21 | 22 | registerMetricsServer :: IO (TxSubmitMetrics, Async ()) 23 | registerMetricsServer = 24 | runRegistryT $ do 25 | metrics <- makeMetrics 26 | registry <- RegistryT ask 27 | server <- liftIO . async $ runReaderT (unRegistryT $ serveHttpTextMetricsT 8081 []) registry 28 | pure (metrics, server) 29 | 30 | makeMetrics :: RegistryT IO TxSubmitMetrics 31 | makeMetrics = 32 | TxSubmitMetrics 33 | <$> registerGauge "tx_submit_count" mempty 34 | 35 | -------------------------------------------------------------------------------- /cardano-tx-submit/src/Cardano/TxSubmit/Tracing/ToObjectOrphans.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleInstances #-} 2 | {-# LANGUAGE MultiParamTypeClasses #-} 3 | {-# LANGUAGE OverloadedStrings #-} 4 | 5 | {-# OPTIONS_GHC -fno-warn-orphans #-} 6 | 7 | module Cardano.TxSubmit.Tracing.ToObjectOrphans () where 8 | 9 | import Data.Text 10 | import Data.Aeson ((.=)) 11 | 12 | import Cardano.BM.Data.LogItem 13 | import Cardano.BM.Data.Severity 14 | import Cardano.BM.Data.Tracer 15 | 16 | import qualified Network.Socket as Socket 17 | import Ouroboros.Network.NodeToClient (WithAddr(..), ErrorPolicyTrace(..)) 18 | 19 | instance DefinePrivacyAnnotation (WithAddr Socket.SockAddr ErrorPolicyTrace) 20 | instance DefineSeverity (WithAddr Socket.SockAddr ErrorPolicyTrace) where 21 | defineSeverity (WithAddr _ ev) = case ev of 22 | ErrorPolicySuspendPeer {} -> Warning -- peer misbehaved 23 | ErrorPolicySuspendConsumer {} -> Notice -- peer temporarily not useful 24 | ErrorPolicyLocalNodeError {} -> Error 25 | ErrorPolicyResumePeer {} -> Debug 26 | ErrorPolicyKeepSuspended {} -> Debug 27 | ErrorPolicyResumeConsumer {} -> Debug 28 | ErrorPolicyResumeProducer {} -> Debug 29 | ErrorPolicyUnhandledApplicationException {} -> Error 30 | ErrorPolicyUnhandledConnectionException {} -> Error 31 | ErrorPolicyAcceptException {} -> Error 32 | 33 | 34 | -- transform @ErrorPolicyTrace@ 35 | instance Transformable Text IO (WithAddr Socket.SockAddr ErrorPolicyTrace) where 36 | trTransformer StructuredLogging verb tr = trStructured verb tr 37 | trTransformer TextualRepresentation _verb tr = Tracer $ \s -> 38 | traceWith tr =<< LogObject <$> pure mempty 39 | <*> mkLOMeta (defineSeverity s) (definePrivacyAnnotation s) 40 | <*> pure (LogMessage $ pack $ show s) 41 | trTransformer UserdefinedFormatting verb tr = trStructured verb tr 42 | 43 | instance ToObject (WithAddr Socket.SockAddr ErrorPolicyTrace) where 44 | toObject _verb (WithAddr addr ev) = 45 | mkObject [ "kind" .= ("ErrorPolicyTrace" :: String) 46 | , "address" .= show addr 47 | , "event" .= show ev ] 48 | -------------------------------------------------------------------------------- /cardano-tx-submit/src/Cardano/TxSubmit/Tx.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE NoImplicitPrelude #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | 4 | module Cardano.TxSubmit.Tx 5 | ( TxSubmitVar (..) 6 | , newTxSubmitVar 7 | , readTxSubmit 8 | , writeTxSubmitResponse 9 | , submitTx 10 | ) where 11 | 12 | import Cardano.Prelude hiding (atomically) 13 | 14 | import Cardano.TxSubmit.Types 15 | 16 | import Control.Monad.Class.MonadSTM.Strict (StrictTMVar, 17 | atomically, newEmptyTMVarM, putTMVar, takeTMVar) 18 | 19 | import Ouroboros.Consensus.Ledger.Byron (ByronBlock (..), GenTx (..)) 20 | import Cardano.Chain.Byron.API (ApplyMempoolPayloadErr) 21 | 22 | -- The type of 'reject' (determined by ouroboros-network) is currently 'Maybe String'. 23 | -- Hopefully that will be fixed to make it a concrete type. 24 | -- See: https://github.com/input-output-hk/ouroboros-network/issues/1335 25 | data TxSubmitVar = TxSubmitVar 26 | { txSubmit :: !(StrictTMVar IO (GenTx ByronBlock)) 27 | , txRespond :: !(StrictTMVar IO (Maybe ApplyMempoolPayloadErr)) 28 | } 29 | 30 | newTxSubmitVar :: IO (TxSubmitVar) 31 | newTxSubmitVar = 32 | TxSubmitVar <$> newEmptyTMVarM <*> newEmptyTMVarM 33 | 34 | -- | Read a previously submitted tx from the TMVar. 35 | readTxSubmit :: TxSubmitVar -> IO (GenTx ByronBlock) 36 | readTxSubmit tsv = 37 | atomically $ takeTMVar (txSubmit tsv) 38 | 39 | -- | Write the response recieved when tx has been submitted. 40 | writeTxSubmitResponse :: TxSubmitVar -> (Maybe ApplyMempoolPayloadErr) -> IO () 41 | writeTxSubmitResponse tsv merr = 42 | atomically $ putTMVar (txRespond tsv) merr 43 | 44 | -- | Submit a tx and wait for the response. This is done as a pair of atomic 45 | -- operations, to allow the tx to be read in one operation, submmited and then 46 | -- the response written as a second operation. Doing this as a single atmomic 47 | -- operation would not work as the other end of the submit/response pair need 48 | -- to be operated on independently. 49 | submitTx :: TxSubmitVar -> GenTx ByronBlock -> IO TxSubmitStatus 50 | submitTx tsv tx = 51 | case tx of 52 | ByronTx txid _ -> do 53 | atomically $ putTMVar (txSubmit tsv) tx 54 | maybe (TxSubmitOk txid) TxSubmitFail <$> atomically (takeTMVar $ txRespond tsv) 55 | ByronDlg {} -> pure $ TxSubmitBadTx "Delegation" 56 | ByronUpdateProposal {} -> pure $ TxSubmitBadTx "Proposal" 57 | ByronUpdateVote {} -> pure $ TxSubmitBadTx "UpdateVote" 58 | -------------------------------------------------------------------------------- /cardano-tx-submit/src/Cardano/TxSubmit/Util.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE NoImplicitPrelude #-} 2 | 3 | module Cardano.TxSubmit.Util 4 | ( logException 5 | , textShow 6 | ) where 7 | 8 | import Cardano.BM.Trace (Trace, logError) 9 | 10 | import Cardano.Prelude 11 | 12 | import Data.Text (Text) 13 | import qualified Data.Text as Text 14 | 15 | 16 | -- | ouroboros-network catches 'SomeException' and if a 'nullTracer' is passed into that 17 | -- code, the caught exception will not be logged. Therefore wrap all tx submission code that 18 | -- is called from network with an exception logger so at least the exception will be 19 | -- logged (instead of silently swallowed) and then rethrown. 20 | logException :: Trace IO Text -> Text -> IO a -> IO a 21 | logException tracer txt action = 22 | action `catch` logger 23 | where 24 | logger :: SomeException -> IO a 25 | logger e = do 26 | logError tracer $ txt <> textShow e 27 | throwIO e 28 | 29 | textShow :: Show a => a -> Text 30 | textShow = Text.pack . show 31 | -------------------------------------------------------------------------------- /cardano-tx-submit/test/test.hs: -------------------------------------------------------------------------------- 1 | 2 | main :: IO () 3 | main = putStrLn "cardano-tx-submit test" 4 | -------------------------------------------------------------------------------- /config/explorer-mainnet-config.yaml: -------------------------------------------------------------------------------- 1 | # Explorer DB Node configuration 2 | 3 | NetworkName: mainnet 4 | RequiresNetworkMagic: RequiresNoMagic 5 | GenesisHash: 5f20df933584822601f9e3f8c024eb5eb252fe8cefb24d1317dc3d432e940ebb 6 | EnableLogMetrics: False 7 | EnableLogging: True 8 | 9 | # ------------------------------------------------------------------------------ 10 | # Logging configuration follows. 11 | 12 | # global filter; messages must have at least this severity to pass: 13 | minSeverity: Info 14 | 15 | # global file rotation settings: 16 | rotation: 17 | rpLogLimitBytes: 5000000 18 | rpKeepFilesNum: 10 19 | rpMaxAgeHours: 24 20 | 21 | # these backends are initialized: 22 | setupBackends: 23 | - AggregationBK 24 | - KatipBK 25 | # - EditorBK 26 | # - EKGViewBK 27 | 28 | # if not indicated otherwise, then messages are passed to these backends: 29 | defaultBackends: 30 | - KatipBK 31 | 32 | # if wanted, the GUI is listening on this port: 33 | # hasGUI: 12787 34 | 35 | # if wanted, the EKG interface is listening on this port: 36 | # hasEKG: 12788 37 | 38 | # here we set up outputs of logging in 'katip': 39 | setupScribes: 40 | - scKind: StdoutSK 41 | scName: stdout 42 | scFormat: ScText 43 | scRotation: null 44 | 45 | # if not indicated otherwise, then log output is directed to this: 46 | defaultScribes: 47 | - - StdoutSK 48 | - stdout 49 | 50 | # more options which can be passed as key-value pairs: 51 | options: 52 | cfokey: 53 | value: "Release-1.0.0" 54 | mapSubtrace: 55 | benchmark: 56 | contents: 57 | - GhcRtsStats 58 | - MonotonicClock 59 | subtrace: ObservableTrace 60 | '#ekgview': 61 | contents: 62 | - - tag: Contains 63 | contents: 'cardano.epoch-validation.benchmark' 64 | - - tag: Contains 65 | contents: .monoclock.basic. 66 | - - tag: Contains 67 | contents: 'cardano.epoch-validation.benchmark' 68 | - - tag: Contains 69 | contents: diff.RTS.cpuNs.timed. 70 | - - tag: StartsWith 71 | contents: '#ekgview.#aggregation.cardano.epoch-validation.benchmark' 72 | - - tag: Contains 73 | contents: diff.RTS.gcNum.timed. 74 | subtrace: FilterTrace 75 | 'cardano.epoch-validation.utxo-stats': 76 | # Change the `subtrace` value to `Neutral` in order to log 77 | # `UTxO`-related messages during epoch validation. 78 | subtrace: NoTrace 79 | '#messagecounters.aggregation': 80 | subtrace: NoTrace 81 | '#messagecounters.ekgview': 82 | subtrace: NoTrace 83 | '#messagecounters.switchboard': 84 | subtrace: NoTrace 85 | '#messagecounters.katip': 86 | subtrace: NoTrace 87 | '#messagecounters.monitoring': 88 | subtrace: NoTrace 89 | 'cardano.#messagecounters.aggregation': 90 | subtrace: NoTrace 91 | 'cardano.#messagecounters.ekgview': 92 | subtrace: NoTrace 93 | 'cardano.#messagecounters.switchboard': 94 | subtrace: NoTrace 95 | 'cardano.#messagecounters.katip': 96 | subtrace: NoTrace 97 | 'cardano.#messagecounters.monitoring': 98 | subtrace: NoTrace 99 | mapBackends: 100 | cardano.epoch-validation.benchmark: 101 | - AggregationBK 102 | '#aggregation.cardano.epoch-validation.benchmark': 103 | - EKGViewBK 104 | 105 | -------------------------------------------------------------------------------- /config/explorer-testnet-config.yaml: -------------------------------------------------------------------------------- 1 | # Explorer DB Node configuration 2 | 3 | NetworkName: testnet 4 | RequiresNetworkMagic: RequiresMagic 5 | GenesisHash: 96fceff972c2c06bd3bb5243c39215333be6d56aaf4823073dca31afe5038471 6 | EnableLogMetrics: False 7 | EnableLogging: True 8 | 9 | # ------------------------------------------------------------------------------ 10 | # Logging configuration follows. 11 | 12 | # global filter; messages must have at least this severity to pass: 13 | minSeverity: Info 14 | 15 | # global file rotation settings: 16 | rotation: 17 | rpLogLimitBytes: 5000000 18 | rpKeepFilesNum: 10 19 | rpMaxAgeHours: 24 20 | 21 | # these backends are initialized: 22 | setupBackends: 23 | - AggregationBK 24 | - KatipBK 25 | # - EditorBK 26 | # - EKGViewBK 27 | 28 | # if not indicated otherwise, then messages are passed to these backends: 29 | defaultBackends: 30 | - KatipBK 31 | 32 | # if wanted, the GUI is listening on this port: 33 | # hasGUI: 12787 34 | 35 | # if wanted, the EKG interface is listening on this port: 36 | # hasEKG: 12788 37 | 38 | # here we set up outputs of logging in 'katip': 39 | setupScribes: 40 | - scKind: StdoutSK 41 | scName: stdout 42 | scFormat: ScText 43 | scRotation: null 44 | 45 | # if not indicated otherwise, then log output is directed to this: 46 | defaultScribes: 47 | - - StdoutSK 48 | - stdout 49 | 50 | # more options which can be passed as key-value pairs: 51 | options: 52 | cfokey: 53 | value: "Release-1.0.0" 54 | mapSubtrace: 55 | benchmark: 56 | contents: 57 | - GhcRtsStats 58 | - MonotonicClock 59 | subtrace: ObservableTrace 60 | '#ekgview': 61 | contents: 62 | - - tag: Contains 63 | contents: 'cardano.epoch-validation.benchmark' 64 | - - tag: Contains 65 | contents: .monoclock.basic. 66 | - - tag: Contains 67 | contents: 'cardano.epoch-validation.benchmark' 68 | - - tag: Contains 69 | contents: diff.RTS.cpuNs.timed. 70 | - - tag: StartsWith 71 | contents: '#ekgview.#aggregation.cardano.epoch-validation.benchmark' 72 | - - tag: Contains 73 | contents: diff.RTS.gcNum.timed. 74 | subtrace: FilterTrace 75 | 'cardano.epoch-validation.utxo-stats': 76 | # Change the `subtrace` value to `Neutral` in order to log 77 | # `UTxO`-related messages during epoch validation. 78 | subtrace: NoTrace 79 | '#messagecounters.aggregation': 80 | subtrace: NoTrace 81 | '#messagecounters.ekgview': 82 | subtrace: NoTrace 83 | '#messagecounters.switchboard': 84 | subtrace: NoTrace 85 | '#messagecounters.katip': 86 | subtrace: NoTrace 87 | '#messagecounters.monitoring': 88 | subtrace: NoTrace 89 | 'cardano.#messagecounters.aggregation': 90 | subtrace: NoTrace 91 | 'cardano.#messagecounters.ekgview': 92 | subtrace: NoTrace 93 | 'cardano.#messagecounters.switchboard': 94 | subtrace: NoTrace 95 | 'cardano.#messagecounters.katip': 96 | subtrace: NoTrace 97 | 'cardano.#messagecounters.monitoring': 98 | subtrace: NoTrace 99 | mapBackends: 100 | cardano.epoch-validation.benchmark: 101 | - AggregationBK 102 | '#aggregation.cardano.epoch-validation.benchmark': 103 | - EKGViewBK 104 | 105 | -------------------------------------------------------------------------------- /config/pgpass: -------------------------------------------------------------------------------- 1 | /var/run/postgresql:5432:cexplorer:*:* 2 | -------------------------------------------------------------------------------- /config/tx-submit-mainnet-config.yaml: -------------------------------------------------------------------------------- 1 | # Explorer DB Node configuration 2 | 3 | RequiresNetworkMagic: RequiresNoMagic 4 | GenesisHash: 5f20df933584822601f9e3f8c024eb5eb252fe8cefb24d1317dc3d432e940ebb 5 | EnableLogMetrics: False 6 | EnableLogging: True 7 | 8 | # ------------------------------------------------------------------------------ 9 | # Logging configuration follows. 10 | 11 | # global filter; messages must have at least this severity to pass: 12 | minSeverity: Info 13 | 14 | # global file rotation settings: 15 | rotation: 16 | rpLogLimitBytes: 5000000 17 | rpKeepFilesNum: 10 18 | rpMaxAgeHours: 24 19 | 20 | # these backends are initialized: 21 | setupBackends: 22 | - AggregationBK 23 | - KatipBK 24 | # - EditorBK 25 | # - EKGViewBK 26 | 27 | # if not indicated otherwise, then messages are passed to these backends: 28 | defaultBackends: 29 | - KatipBK 30 | 31 | # if wanted, the GUI is listening on this port: 32 | # hasGUI: 12787 33 | 34 | # if wanted, the EKG interface is listening on this port: 35 | # hasEKG: 12788 36 | 37 | # here we set up outputs of logging in 'katip': 38 | setupScribes: 39 | - scKind: StdoutSK 40 | scName: stdout 41 | scFormat: ScText 42 | scRotation: null 43 | 44 | # if not indicated otherwise, then log output is directed to this: 45 | defaultScribes: 46 | - - StdoutSK 47 | - stdout 48 | 49 | # more options which can be passed as key-value pairs: 50 | options: 51 | cfokey: 52 | value: "Release-1.0.0" 53 | mapSubtrace: 54 | benchmark: 55 | contents: 56 | - GhcRtsStats 57 | - MonotonicClock 58 | subtrace: ObservableTrace 59 | '#ekgview': 60 | contents: 61 | - - tag: Contains 62 | contents: 'cardano.epoch-validation.benchmark' 63 | - - tag: Contains 64 | contents: .monoclock.basic. 65 | - - tag: Contains 66 | contents: 'cardano.epoch-validation.benchmark' 67 | - - tag: Contains 68 | contents: diff.RTS.cpuNs.timed. 69 | - - tag: StartsWith 70 | contents: '#ekgview.#aggregation.cardano.epoch-validation.benchmark' 71 | - - tag: Contains 72 | contents: diff.RTS.gcNum.timed. 73 | subtrace: FilterTrace 74 | 'cardano.epoch-validation.utxo-stats': 75 | # Change the `subtrace` value to `Neutral` in order to log 76 | # `UTxO`-related messages during epoch validation. 77 | subtrace: NoTrace 78 | '#messagecounters.aggregation': 79 | subtrace: NoTrace 80 | '#messagecounters.ekgview': 81 | subtrace: NoTrace 82 | '#messagecounters.switchboard': 83 | subtrace: NoTrace 84 | '#messagecounters.katip': 85 | subtrace: NoTrace 86 | '#messagecounters.monitoring': 87 | subtrace: NoTrace 88 | 'cardano.#messagecounters.aggregation': 89 | subtrace: NoTrace 90 | 'cardano.#messagecounters.ekgview': 91 | subtrace: NoTrace 92 | 'cardano.#messagecounters.switchboard': 93 | subtrace: NoTrace 94 | 'cardano.#messagecounters.katip': 95 | subtrace: NoTrace 96 | 'cardano.#messagecounters.monitoring': 97 | subtrace: NoTrace 98 | mapBackends: 99 | cardano.epoch-validation.benchmark: 100 | - AggregationBK 101 | '#aggregation.cardano.epoch-validation.benchmark': 102 | - EKGViewBK 103 | 104 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | # cardano-explorer Nix build 3 | # 4 | # fixme: document top-level attributes and how to build them 5 | # 6 | ############################################################################ 7 | 8 | { system ? builtins.currentSystem 9 | , crossSystem ? null 10 | , config ? {} 11 | # Import IOHK common nix lib 12 | , iohkLib ? import ./lib.nix { inherit system crossSystem config; } 13 | # Use nixpkgs pin from iohkLib 14 | , pkgs ? iohkLib.pkgs 15 | , customConfig ? {} 16 | }: 17 | 18 | let 19 | sources = import ./nix/sources.nix; 20 | haskell_nix = pkgs.fetchgit (builtins.removeAttrs (builtins.fromJSON (builtins.readFile "${sources.iohk-nix}/pins/haskell-nix.json")) [ "date" ]); 21 | haskell = pkgs.callPackage haskell_nix { 22 | hackageSourceJSON = ./nix/hackage-nix.json; 23 | }; 24 | src = iohkLib.cleanSourceHaskell ./.; 25 | util = pkgs.callPackage ./nix/util.nix {}; 26 | 27 | # Example of using a package from iohk-nix 28 | # TODO: Declare packages required by the build. 29 | inherit (iohkLib.rust-packages.pkgs) jormungandr; 30 | 31 | # Import the Haskell package set. 32 | haskellPackages = import ./nix/pkgs.nix { 33 | inherit pkgs haskell src; 34 | # Pass in any extra programs necessary for the build as function arguments. 35 | # Provide cross-compiling secret sauce 36 | inherit (iohkLib.nix-tools) iohk-extras iohk-module; 37 | }; 38 | 39 | in { 40 | inherit pkgs iohkLib src haskellPackages; 41 | inherit (haskellPackages.cardano-explorer-webapi.identifier) version; 42 | 43 | # Grab the executable component of our package. 44 | inherit (haskellPackages.cardano-explorer-webapi.components.exes) cardano-explorer-webapi; 45 | inherit (haskellPackages.cardano-tx-submit.components.exes) cardano-tx-submit-webapi; 46 | 47 | cardano-sl-core = haskellPackages.cardano-explorer-db.components.library; 48 | cardano-explorer-node = haskellPackages.cardano-explorer-node.components.exes.cardano-explorer-node; 49 | cardano-explorer-db-tool = haskellPackages.cardano-explorer-db.components.exes.cardano-explorer-db-tool; 50 | 51 | tests = util.collectComponents "tests" util.isIohkSkeleton haskellPackages; 52 | benchmarks = util.collectComponents "benchmarks" util.isIohkSkeleton haskellPackages; 53 | 54 | scripts = pkgs.callPackage ./nix/scripts.nix { 55 | inherit iohkLib customConfig; 56 | }; 57 | 58 | # This provides a development environment that can be used with nix-shell or 59 | # lorri. See https://input-output-hk.github.io/haskell.nix/user-guide/development/ 60 | shell = haskellPackages.shellFor { 61 | name = "iohk-skeleton-shell"; 62 | # TODO: List all local packages in the project. 63 | packages = ps: with ps; [ 64 | cardano-explorer-webapi 65 | ]; 66 | # These programs will be available inside the nix-shell. 67 | buildInputs = 68 | with pkgs.haskellPackages; [ hlint stylish-haskell weeder ghcid lentil ]; 69 | }; 70 | 71 | # Example of a linting script used by Buildkite. 72 | checks.lint-fuzz = pkgs.callPackage ./nix/check-lint-fuzz.nix {}; 73 | 74 | # Attrset of PDF builds of LaTeX documentation. 75 | docs = pkgs.callPackage ./docs/default.nix {}; 76 | } 77 | -------------------------------------------------------------------------------- /doc/building-running.md: -------------------------------------------------------------------------------- 1 | **Validated: 2020/02/19** 2 | 3 | # Building and Running the Explorer 4 | 5 | The explorer is built and tested to run on Linux. It may run on Mac OS X or Windows but that is 6 | unsupported. 7 | 8 | Running the explorer will require Nix and and either multiple terminals or a multi terminal 9 | emulator like GNU Screen or TMux. 10 | 11 | The Explorer is designed to work with a locally running Cardano Node. Currently the node also 12 | needs a locally running Byron proxy. The three git repositories need to be checked out so that 13 | they are all at the same level. eg: 14 | 15 | ``` 16 | > tree -L 1 17 | . 18 | ├── cardano-byron-proxy 19 | ├── cardano-explorer 20 | ├── cardano-node 21 | ``` 22 | To setup and run explorer for testnet replace **mainnet** with **testnet** in all examples below. 23 | 24 | ### Set up and run the byron proxy 25 | ``` 26 | git clone https://github.com/input-output-hk/cardano-byron-proxy 27 | cd cardano-byron-proxy 28 | nix-build -A scripts.mainnet.proxy -o mainnet-byron-proxy 29 | ./mainnet-byron-proxy 30 | ``` 31 | 32 | ### Set up and run a local node that connects to the byron proxy 33 | ``` 34 | git clone https://github.com/input-output-hk/cardano-node 35 | cd cardano-node 36 | nix-build -A scripts.mainnet.node -o mainnet-node-local --arg customConfig '{ useProxy = true; }' 37 | ./mainnet-node-local 38 | ``` 39 | 40 | ### Set up and run the explorer node 41 | ``` 42 | git clone https://github.com/input-output-hk/cardano-explorer 43 | cd cardano-explorer 44 | nix-build -A cardano-explorer-node -o explorer-node 45 | scripts/postgresql-setup.sh --createdb 46 | PGPASSFILE=config/pgpass explorer-node/bin/cardano-explorer-node \ 47 | --config config/explorer-mainnet-config.yaml \ 48 | --genesis-file ../cardano-node/configuration/mainnet-genesis.json \ 49 | --socket-path ../cardano-node/state-node-mainnet/node.socket \ 50 | --schema-dir schema/ 51 | ``` 52 | 53 | ### Set up and run the explorer webapi 54 | In the same `cardano-explorer` directory but a new terminal: 55 | ``` 56 | nix-build -A cardano-explorer-webapi -o explorer-webapi 57 | PGPASSFILE=config/pgpass ./explorer-webapi/bin/cardano-explorer-webapi 58 | ``` 59 | 60 | ### Set up and run the transaction submission webapi 61 | 62 | Make sure you have: 63 | - `cabal` 3.0 or higher (`cabal --version` to see currently installed version) 64 | - `ghc` 8.6.5 or higher (`ghc --version`) 65 | 66 | You may also need the following native libraries 67 | ``` 68 | sudo apt install libsystemd-dev 69 | sudo apt-get install libz-dev 70 | sudo apt-get install libpq-dev 71 | sudo apt install libssl-dev 72 | ``` 73 | 74 | In the same `cardano-explorer` directory but in a new terminal: 75 | ``` 76 | cabal run cardano-tx-submit-webapi -- \ 77 | --config config/tx-submit-mainnet-config.yaml \ 78 | --genesis-file ../cardano-node/configuration/mainnet-genesis.json \ 79 | --socket-path ../cardano-node/state-node-mainnet/node.socket \ 80 | --port 8101 81 | ``` 82 | -------------------------------------------------------------------------------- /doc/docker.md: -------------------------------------------------------------------------------- 1 | to build against mainnet: 2 | `time docker build --build-arg environment=mainnet -t cardano-explorer-mainnet .` 3 | 4 | when the build is done, the last few lines of output should look similar to: 5 | ``` 6 | Step 24/25 : EXPOSE 80 7 | ---> Running in f1c87c519146 8 | Removing intermediate container f1c87c519146 9 | ---> 513fcf74049f 10 | Step 25/25 : ENTRYPOINT [ "/nix/var/nix/profiles/per-user/cardano/profile/bin/runit" ] 11 | ---> Running in bfb064b25056 12 | Removing intermediate container bfb064b25056 13 | ---> 20605fdffcdc 14 | Successfully built 20605fdffcdc 15 | Successfully tagged cardano-explorer-mainnet:latest 16 | 17 | real 34m26.889s 18 | user 0m2.095s 19 | sys 0m3.377s 20 | ``` 21 | 22 | you can then start the container with: `docker run --rm -t -i -p 80:80 --name test-image --volume explorer-mainnet:/var cardano-explorer-mainnet:latest` 23 | 24 | http://localhost/grafana/login and login as admin/admin to view the performance metrics and sync state 25 | 26 | example of using the explorer api: 27 | ``` 28 | $ curl http://localhost/api/supply/ada 29 | {"Right":31112479913.565959} 30 | ``` 31 | 32 | http://localhost/api/submit/tx is the tx submission endpoint 33 | 34 | 35 | to build an image that can target multiple clusters: 36 | `time docker build --build-arg environment=all -t cardano-explorer-all .` 37 | 38 | then to select a cluster, use one of the following: 39 | ``` 40 | docker run --rm -i -t -p 81:80 --name explorer-mainnet --volume explorer-mainnet:/var -e ENVIRONMENT=mainnet cardano-explorer-all:latest 41 | docker run --rm -i -t -p 81:80 --name explorer-testnet --volume explorer-testnet:/var -e ENVIRONMENT=testnet cardano-explorer-all:latest 42 | ``` 43 | -------------------------------------------------------------------------------- /doc/schema-management.md: -------------------------------------------------------------------------------- 1 | # Schema Management 2 | 3 | Schema management for the Cardano Explorer database is a little more complicated than we would like, 4 | but the scheme chosen allows for easy development, evolution and management of the database. 5 | 6 | The database schema is defined in three stages, each stage consisting of one or more SQL migrations. 7 | The stages are: 8 | 9 | 1. Hand written SQL to set up custom SQL data types (using `DOMAIN` statements) and schema 10 | versioning. 11 | 2. SQL generated using the schema defined as Haskell data types (using the [Persistent][Persistent] 12 | library) to create the database tables. 13 | 3. Hand written SQL to create views into the tables defined in stage 2. 14 | 15 | All of the schema migrations in these three stages are written to be idempotent (so that they 16 | "know" if they have already been applied). 17 | 18 | The migration files all have file names of the form: 19 | ``` 20 | migration-1-0000-20190730.sql 21 | ``` 22 | where the `1` denotes "stage 1" of the SQL migration, the `0000` is the migration version and the 23 | last number is the date. Listing the directory containing the schema and sorting the list will 24 | order them in the correct order for applying to the database. 25 | 26 | ## Creating a Migration 27 | 28 | Whenever the Haskell schema definition in `Explorer.DB.Schema` is updated, a schema migration can 29 | be generated using the command: 30 | ``` 31 | cabal run cardano-explorer-db-tool -- create-migration --mdir schema/ 32 | ``` 33 | which will only generate a migration if one is needed. It is usually best to run the test suite 34 | (`cabal test cardano-explorer db` which tests the migrations) first and then generate the migration. 35 | 36 | 37 | 38 | [Persistent]: https://hackage.haskell.org/package/persistent 39 | -------------------------------------------------------------------------------- /doc/tx-submit.md: -------------------------------------------------------------------------------- 1 | # Submitting a Transaction to the tx-submit Web API 2 | 3 | Building and running the `cardano-tx-submit-webap` is described in [this document][BuildDoc]. 4 | 5 | Transactions can be generated using either the `cardano-cli` (in the [cardano-node][NodeRepo]) or 6 | with the [`js-wasm` library][JS-Wasm]. 7 | 8 | The transaction needs to be a raw binary (and not encoded as hex or anything else). Once generated 9 | (in say the file `tx.bin`) the transaction can be submitted to the webapi using: 10 | 11 | ``` 12 | curl -X POST --header "Content-Type:application/cbor" --data-binary @tx.bin http://[host]:[port]/api/submit/tx 13 | ``` 14 | 15 | ## Possible errors 16 | 17 | If the `Content-Type` is not specified as `application/cbor` the webapi server will respond with 18 | a `415 Unsupported Media Type` HTTP status code. 19 | 20 | For other errors, the webapi will respond with a 200 HTTP status code, and a chunk of JSON which 21 | for the success case will be: 22 | ``` 23 | { "status": "success" 24 | , "errorMsg": "No error" 25 | } 26 | ``` 27 | For the fail case, the `"status"` field will contain `"fail"` and the `"errMsg"` field will contain 28 | more information. 29 | 30 | 31 | [BuildDoc]: https://github.com/input-output-hk/cardano-explorer/blob/master/doc/building-running.md 32 | [NodeRepo]: https://github.com/input-output-hk/cardano-node 33 | [JS-Wasm]: https://github.com/input-output-hk/js-cardano-wasm 34 | -------------------------------------------------------------------------------- /doc/webapi-atomic-requests.md: -------------------------------------------------------------------------------- 1 | # Atomicity of PostgreSQL Interactions 2 | 3 | Both the webapi and the node which populates the database operate on the database within a 4 | database transaction. All operations on the database from Haskell code is done in a function 5 | which has a type signatures of : 6 | ``` 7 | ReaderT SqlBackend m a 8 | ``` 9 | Any function without the `ReaderT SqlBackend` component will not be able to access the database 10 | and any attempt to access the database without the required type signature will result in a compile 11 | error at compile time. 12 | 13 | All functions with the required file type are run with the function provided by Haskell's 14 | [Persistent][Persistent] library: 15 | ``` 16 | runSqlConnWithIsolation action backend Serializable 17 | ``` 18 | where: 19 | * `runSqlConnWithIsolation` is the function that runs the provided `action` on a connection to 20 | the database within a database transaction. 21 | * `action` is the action to be performed on the database (eg write or query). 22 | * `backend` contains the database connection data. 23 | * `Serializable` specifies the transaction isolation level. 24 | 25 | In this case the `Serializable` [transaction isolation][PosgresIso] level is used which is the 26 | *maximum* level of transaction isolation. 27 | 28 | [Persistent]: https://hackage.haskell.org/package/persistent 29 | [PosgresIso]: https://www.postgresql.org/docs/current/transaction-iso.html 30 | 31 | -------------------------------------------------------------------------------- /docker/deroot.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | int main (int argc, char **argv) { 10 | assert(argc >= 3); 11 | struct passwd *target_user = 0; 12 | target_user = getpwnam(argv[1]); 13 | 14 | if(setgid(target_user->pw_gid)) { 15 | cerr << "unable to setgid()" << endl; 16 | return -2; 17 | } 18 | if(setuid(target_user->pw_uid)) { 19 | cerr << "unable to setuid()" << endl; 20 | return -2; 21 | } 22 | argv++; 23 | argv++; 24 | execvp(argv[0], argv); 25 | } 26 | -------------------------------------------------------------------------------- /docker/nix.conf: -------------------------------------------------------------------------------- 1 | trusted-public-keys = hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ= cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= 2 | substituters = https://cache.nixos.org/ https://hydra.iohk.io/ 3 | sandbox = false 4 | -------------------------------------------------------------------------------- /docker/runit.patch: -------------------------------------------------------------------------------- 1 | diff -ru admin-orig/runit-2.1.2/src/runit.c admin/runit-2.1.2/src/runit.c 2 | --- admin-orig/src/runit.c 2014-08-10 15:22:35.000000000 -0300 3 | +++ admin/src/runit.c 2019-06-30 23:21:45.947391610 -0300 4 | @@ -299,6 +299,11 @@ 5 | strerr_warn2(INFO, "sending KILL signal to all processes...", 0); 6 | kill(-1, SIGKILL); 7 | 8 | + strerr_warn2(INFO, "syncing...", 0); 9 | + //sync(); 10 | + strerr_warn2(INFO, "exiting...", 0); 11 | + _exit(0); 12 | + 13 | pid =fork(); 14 | switch (pid) { 15 | case 0: 16 | @@ -309,27 +314,27 @@ 17 | reboot_system(RB_AUTOBOOT); 18 | } 19 | else { 20 | -#ifdef RB_POWER_OFF 21 | +# ifdef RB_POWER_OFF 22 | strerr_warn2(INFO, "power off...", 0); 23 | sync(); 24 | reboot_system(RB_POWER_OFF); 25 | sleep(2); 26 | -#endif 27 | -#ifdef RB_HALT_SYSTEM 28 | +# endif // RB_POWER_OFF 29 | +# ifdef RB_HALT_SYSTEM 30 | strerr_warn2(INFO, "system halt.", 0); 31 | sync(); 32 | reboot_system(RB_HALT_SYSTEM); 33 | -#else 34 | -#ifdef RB_HALT 35 | +# else // RB_HALT_SYSTEM 36 | +# ifdef RB_HALT 37 | strerr_warn2(INFO, "system halt.", 0); 38 | sync(); 39 | reboot_system(RB_HALT); 40 | -#else 41 | +# else // RB_HALT 42 | strerr_warn2(INFO, "system reboot.", 0); 43 | sync(); 44 | reboot_system(RB_AUTOBOOT); 45 | -#endif 46 | -#endif 47 | +# endif // RB_HALT 48 | +# endif // RB_HALT_SYSTEM 49 | } 50 | if (pid == 0) _exit(0); 51 | break; 52 | @@ -337,7 +342,7 @@ 53 | sig_unblock(sig_child); 54 | while (wait_pid(0, pid) == -1); 55 | } 56 | -#endif 57 | +#endif // RB_AUTOBOOT 58 | 59 | for (;;) sig_pause(); 60 | /* not reached */ 61 | Only in admin/runit-2.1.2/src: .runit.c.swp 62 | diff -ur runit-2.1.2-old/src/runit.c runit-2.1.2/src/runit.c 63 | --- runit-2.1.2-old/src/runit.c 2019-12-02 18:36:02.982346238 -0400 64 | +++ runit-2.1.2/src/runit.c 2019-12-02 18:46:24.483615039 -0400 65 | @@ -66,6 +66,7 @@ 66 | sig_block(sig_hangup); 67 | sig_block(sig_int); 68 | sig_catch(sig_int, sig_int_handler); 69 | + sig_catch(sig_term, sig_int_handler); 70 | sig_block(sig_pipe); 71 | sig_block(sig_term); 72 | 73 | @@ -145,6 +146,7 @@ 74 | sig_unblock(sig_child); 75 | sig_unblock(sig_cont); 76 | sig_unblock(sig_int); 77 | + sig_unblock(sig_term); 78 | #ifdef IOPAUSE_POLL 79 | poll(&x, 1, 14000); 80 | #else 81 | @@ -156,6 +158,7 @@ 82 | sig_block(sig_cont); 83 | sig_block(sig_child); 84 | sig_block(sig_int); 85 | + sig_block(sig_term); 86 | 87 | while (read(selfpipe[0], &ch, 1) == 1) {} 88 | while ((child =wait_nohang(&wstat)) > 0) 89 | Only in runit-2.1.2-old/src: .runit.c.swp 90 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | ## 2 | ## Makefile for iohk-skeleton example, based on: 3 | ## 4 | ## https://tex.stackexchange.com/questions/40738/how-to-properly-make-a-latex-project 5 | ## 6 | 7 | # Document name 8 | DOCNAME = iohk-skeleton 9 | 10 | # You want latexmk to *always* run, because make does not have all the info. 11 | # Also, include non-file targets in .PHONY so they are run regardless of any 12 | # file of the given name existing. 13 | .PHONY: $(DOCNAME).pdf all clean 14 | 15 | # The first rule in a Makefile is the one executed by default ("make"). It 16 | # should always be the "all" rule, so that "make" and "make all" are identical. 17 | all: $(DOCNAME).pdf 18 | 19 | ## 20 | ## CUSTOM BUILD RULES 21 | ## 22 | 23 | 24 | ## 25 | ## MAIN LATEXMK RULE 26 | ## 27 | 28 | # -pdf tells latexmk to generate PDF directly (instead of DVI). 29 | # -pdflatex="" tells latexmk to call a specific backend with specific options. 30 | # -use-make tells latexmk to call make for generating missing files. 31 | 32 | # -interaction=nonstopmode keeps the pdflatex backend from stopping at a 33 | # missing file reference and interactively asking you for an alternative. 34 | 35 | $(DOCNAME).pdf: $(DOCNAME).tex 36 | latexmk -pdf -pdflatex="pdflatex -interaction=nonstopmode" -use-make $(DOCNAME).tex 37 | 38 | watch: $(DOCNAME).tex 39 | latexmk -pvc -pdf -pdflatex="pdflatex -interaction=nonstopmode -synctex=1" -use-make $(DOCNAME).tex 40 | 41 | clean: 42 | latexmk -CA 43 | 44 | install: 45 | mkdir -pv ${out}/nix-support/ 46 | cp $(DOCNAME).pdf ${out}/ 47 | echo "doc-pdf $(DOCNAME) ${out}/$(DOCNAME).pdf" > ${out}/nix-support/hydra-build-products 48 | -------------------------------------------------------------------------------- /docs/default.nix: -------------------------------------------------------------------------------- 1 | { iohkSkeletonPackages ? import ./.. {} 2 | , pkgs ? iohkSkeletonPackages.pkgs 3 | }: 4 | with pkgs; 5 | 6 | { 7 | example = stdenv.mkDerivation { 8 | name = "iohk-skeleton-docs"; 9 | buildInputs = [ (texlive.combine { 10 | inherit (texlive) 11 | scheme-small 12 | 13 | # TODO: replace with your own LaTeX package dependencies 14 | 15 | # libraries 16 | stmaryrd lm-math amsmath extarrows cleveref semantic xcolor appendix 17 | 18 | # bclogo and dependencies 19 | bclogo mdframed xkeyval etoolbox needspace pgf 20 | 21 | # font libraries `mathpazo` seems to depend on palatino 22 | # , but it isn't pulled. 23 | mathpazo palatino microtype 24 | 25 | # libraries for marginal notes 26 | xargs todonotes 27 | 28 | # build tools 29 | latexmk 30 | ; 31 | }) 32 | ]; 33 | src = ./.; 34 | buildPhase = "make"; 35 | 36 | meta = with lib; { 37 | description = "IOHK Skeleton Example Document"; 38 | license = licenses.asl20; 39 | platforms = platforms.linux; 40 | }; 41 | }; 42 | 43 | # This lets Hydra find build jobs in nested attrsets. 44 | recurseForDerivations = true; 45 | } 46 | -------------------------------------------------------------------------------- /docs/iohk-skeleton.tex: -------------------------------------------------------------------------------- 1 | \documentclass{article} 2 | \title{Example document} 3 | \author{IOHK} 4 | 5 | \date{\today} 6 | 7 | \begin{document} 8 | 9 | \maketitle 10 | 11 | 12 | \begin{abstract} 13 | This is an example for building documents in CI. 14 | \end{abstract} 15 | 16 | \section{Section} 17 | 18 | Paragraph. 19 | 20 | \end{document} 21 | -------------------------------------------------------------------------------- /lib.nix: -------------------------------------------------------------------------------- 1 | { ... }@args: 2 | # Imports the iohk-nix library. 3 | # The version can be overridden for debugging purposes by setting 4 | # NIX_PATH=iohk_nix=/path/to/iohk-nix 5 | let 6 | try = builtins.tryEval ; 7 | in 8 | if try.success 9 | then builtins.trace "using host " ((import try.value) args) 10 | else let 11 | sources = import ./nix/sources.nix; 12 | iohkNix = import sources.iohk-nix args; 13 | # Note that this repo is using the iohk-nix nixpkgs by default 14 | # A niv nixpkgs pin can override this with the following line: 15 | #iohkNix = import sources.iohk-nix ({ nixpkgsOverride = sources.nixpkgs; } // args); 16 | in iohkNix // { inherit sources; } 17 | -------------------------------------------------------------------------------- /niv-shell.nix: -------------------------------------------------------------------------------- 1 | { sources ? import ./nix/sources.nix 2 | , system ? __currentSystem 3 | , iohkLib ? import ./lib.nix { inherit system; } 4 | , pkgs ? iohkLib.pkgs 5 | }: 6 | let 7 | niv = (import sources.niv {}).niv; 8 | in pkgs.mkShell { 9 | buildInputs = [ niv ]; 10 | } 11 | -------------------------------------------------------------------------------- /nix/.stack.nix/contra-tracer.nix: -------------------------------------------------------------------------------- 1 | let 2 | buildDepError = pkg: 3 | builtins.throw '' 4 | The Haskell package set does not contain the package: ${pkg} (build dependency). 5 | 6 | If you are using Stackage, make sure that you are using a snapshot that contains the package. Otherwise you may need to update the Hackage snapshot you are using, usually by updating haskell.nix. 7 | ''; 8 | sysDepError = pkg: 9 | builtins.throw '' 10 | The Nixpkgs package set does not contain the package: ${pkg} (system dependency). 11 | 12 | You may need to augment the system package mapping in haskell.nix so that it can be found. 13 | ''; 14 | pkgConfDepError = pkg: 15 | builtins.throw '' 16 | The pkg-conf packages does not contain the package: ${pkg} (pkg-conf dependency). 17 | 18 | You may need to augment the pkg-conf package mapping in haskell.nix so that it can be found. 19 | ''; 20 | exeDepError = pkg: 21 | builtins.throw '' 22 | The local executable components do not include the component: ${pkg} (executable dependency). 23 | ''; 24 | legacyExeDepError = pkg: 25 | builtins.throw '' 26 | The Haskell package set does not contain the package: ${pkg} (executable dependency). 27 | 28 | If you are using Stackage, make sure that you are using a snapshot that contains the package. Otherwise you may need to update the Hackage snapshot you are using, usually by updating haskell.nix. 29 | ''; 30 | buildToolDepError = pkg: 31 | builtins.throw '' 32 | Neither the Haskell package set or the Nixpkgs package set contain the package: ${pkg} (build tool dependency). 33 | 34 | If this is a system dependency: 35 | You may need to augment the system package mapping in haskell.nix so that it can be found. 36 | 37 | If this is a Haskell dependency: 38 | If you are using Stackage, make sure that you are using a snapshot that contains the package. Otherwise you may need to update the Hackage snapshot you are using, usually by updating haskell.nix. 39 | ''; 40 | in { system, compiler, flags, pkgs, hsPkgs, pkgconfPkgs, ... }: 41 | { 42 | flags = {}; 43 | package = { 44 | specVersion = "1.10"; 45 | identifier = { name = "contra-tracer"; version = "0.1.0.0"; }; 46 | license = "Apache-2.0"; 47 | copyright = "2019 IOHK"; 48 | maintainer = "operations@iohk.io"; 49 | author = "Neil Davies, Alexander Diemand, Andreas Triantafyllos"; 50 | homepage = ""; 51 | url = ""; 52 | synopsis = "A simple interface for logging, tracing or monitoring."; 53 | description = ""; 54 | buildType = "Simple"; 55 | isLocal = true; 56 | }; 57 | components = { 58 | "library" = { 59 | depends = [ 60 | (hsPkgs."base" or (buildDepError "base")) 61 | ] ++ (pkgs.lib).optional (compiler.isGhc && (compiler.version).lt "8.5") (hsPkgs."contravariant" or (buildDepError "contravariant")); 62 | buildable = true; 63 | }; 64 | }; 65 | } // { 66 | src = (pkgs.lib).mkDefault (pkgs.fetchgit { 67 | url = "https://github.com/input-output-hk/iohk-monitoring-framework"; 68 | rev = "49b347d892d82dce23eb692722649cd8a1149406"; 69 | sha256 = "0m6sa0lrqzfxhq7v5ncimlkd869pnq53khgpkivk0izsy46kfrq6"; 70 | }); 71 | postUnpack = "sourceRoot+=/contra-tracer; echo source root reset to \$sourceRoot"; 72 | } -------------------------------------------------------------------------------- /nix/cardano-graphql/default.nix: -------------------------------------------------------------------------------- 1 | let pkgs = import ./pkgs.nix {}; in pkgs.packages 2 | -------------------------------------------------------------------------------- /nix/cardano-graphql/packages.nix: -------------------------------------------------------------------------------- 1 | { mkYarnPackage, stdenv, lib, python, nodejs, cardano-graphql-src }: 2 | 3 | { 4 | cardano-graphql = mkYarnPackage { 5 | pname = "cardano-graphql"; 6 | name = "cardano-graphql"; 7 | version = "0.4.0"; 8 | packageJSON = cardano-graphql-src + "/package.json"; 9 | yarnLock = cardano-graphql-src + "/yarn.lock"; 10 | src = cardano-graphql-src; 11 | yarnPreBuild = '' 12 | mkdir -p $HOME/.node-gyp/${nodejs.version} 13 | echo 9 > $HOME/.node-gyp/${nodejs.version}/installVersion 14 | ln -sfv ${nodejs}/include $HOME/.node-gyp/${nodejs.version} 15 | ''; 16 | pkgConfig = { 17 | node-sass = { 18 | buildInputs = [ python ]; 19 | postInstall = '' 20 | yarn --offline run build 21 | ''; 22 | }; 23 | }; 24 | 25 | installPhase = '' 26 | unpackPhase 27 | cd $sourceRoot 28 | 29 | export PATH="$PATH:$node_modules/.bin" 30 | 31 | yarn run build 32 | 33 | cp -r ../deps/cardano-graphql/dist $out 34 | 35 | mkdir -p $out/bin 36 | cat < $out/bin/cardano-graphql 37 | #!${stdenv.shell} 38 | exec ${nodejs}/bin/node $out/index.js 39 | EOF 40 | chmod +x $out/bin/cardano-graphql 41 | ln -s $node_modules $out/node_modules 42 | ''; 43 | 44 | distPhase = '' 45 | cp -r . $out 46 | ''; 47 | }; 48 | } 49 | -------------------------------------------------------------------------------- /nix/cardano-graphql/pkgs.nix: -------------------------------------------------------------------------------- 1 | { sources ? import ../sources.nix }: 2 | with 3 | { overlay = self: super: 4 | { inherit (import sources.niv {}) niv; 5 | packages = self.callPackages ./packages.nix { cardano-graphql-src = sources.cardano-graphql; }; 6 | node = super.nodejs-12_x; 7 | inherit (import sources.yarn2nix { pkgs = self; }) yarn2nix mkYarnModules mkYarnPackage; 8 | }; 9 | }; 10 | import sources.nixpkgs 11 | { overlays = [ overlay ] ; config = {}; } 12 | -------------------------------------------------------------------------------- /nix/check-lint-fuzz.nix: -------------------------------------------------------------------------------- 1 | # This is an example CI check script. 2 | # TODO: Remove or replace with your own check scripts. 3 | 4 | { stdenv, writeScript, coreutils, nixStable, git, gawk }: 5 | 6 | with stdenv.lib; 7 | 8 | writeScript "check-lint-fuzz.sh" '' 9 | #!${stdenv.shell} 10 | 11 | set -euo pipefail 12 | 13 | export PATH="${makeBinPath [ stdenv.shellPackage coreutils nixStable git gawk ]}:$PATH" 14 | 15 | cd $(git rev-parse --show-toplevel) 16 | 17 | git ls-files | awk '/.*hs$/ { print "Checking " $0; }' 18 | '' 19 | -------------------------------------------------------------------------------- /nix/hackage-nix.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://github.com/input-output-hk/hackage.nix", 3 | "rev": "4fb8cf05fae7f74b95dd05b4b47102972ae48f63", 4 | "date": "2020-02-09T01:10:46+00:00", 5 | "sha256": "00dza7slgiirl1vbsxymyacgp9xf2j7nqnpy8a32iz8ksp8rfh6j", 6 | "fetchSubmodules": false 7 | } 8 | -------------------------------------------------------------------------------- /nix/nixos/cardano-explorer-everything.nix: -------------------------------------------------------------------------------- 1 | { config, lib, ... }: 2 | 3 | let 4 | cfg = config.services.cardano-explorer; 5 | in { 6 | options = { 7 | services.cardano-explorer = { 8 | enable = lib.mkEnableOption "cardano-explorer full setup"; 9 | cluster = lib.mkOption { 10 | type = lib.types.nullOr lib.types.str; 11 | description = "cluster name"; 12 | }; 13 | socketPath = lib.mkOption { 14 | type = lib.types.nullOr lib.types.path; 15 | default = null; 16 | }; 17 | }; 18 | }; 19 | config = lib.mkIf cfg.enable { 20 | services = { 21 | postgresql = { 22 | enable = true; 23 | enableTCPIP = false; 24 | extraConfig = '' 25 | max_connections = 200 26 | shared_buffers = 2GB 27 | effective_cache_size = 6GB 28 | maintenance_work_mem = 512MB 29 | checkpoint_completion_target = 0.7 30 | wal_buffers = 16MB 31 | default_statistics_target = 100 32 | random_page_cost = 1.1 33 | effective_io_concurrency = 200 34 | work_mem = 10485kB 35 | min_wal_size = 1GB 36 | max_wal_size = 2GB 37 | ''; 38 | ensureDatabases = [ "cexplorer" ]; 39 | ensureUsers = [ 40 | { 41 | name = "cexplorer"; 42 | ensurePermissions = { 43 | "DATABASE cexplorer" = "ALL PRIVILEGES"; 44 | }; 45 | } 46 | ]; 47 | identMap = '' 48 | explorer-users root cexplorer 49 | explorer-users cexplorer cexplorer 50 | explorer-users postgres postgres 51 | ''; 52 | authentication = '' 53 | local all all ident map=explorer-users 54 | ''; 55 | }; 56 | cardano-exporter = { 57 | enable = true; 58 | cluster = cfg.cluster; 59 | socketPath = cfg.socketPath; 60 | }; 61 | cardano-explorer-webapi = { 62 | enable = true; 63 | }; 64 | cardano-tx-submit-webapi = { 65 | enable = true; 66 | cluster = cfg.cluster; 67 | socketPath = cfg.socketPath; 68 | }; 69 | }; 70 | }; 71 | } 72 | -------------------------------------------------------------------------------- /nix/nixos/cardano-explorer-frontend.nix: -------------------------------------------------------------------------------- 1 | { config, ... }: 2 | 3 | let 4 | sources = import ../../nix/sources.nix; 5 | iohkLib = import ../../lib.nix { }; 6 | cluster = "mainnet"; 7 | targetEnv = iohkLib.cardanoLib.environments.${cluster}; 8 | host = "explorer.example.com"; 9 | in { 10 | imports = [ 11 | (sources.cardano-node + "/nix/nixos") 12 | ./cardano-exporter-service.nix 13 | ./graphql-engine-service.nix 14 | ./cardano-graphql-service.nix 15 | ]; 16 | services.graphql-engine.enable = true; 17 | services.cardano-graphql.enable = true; 18 | services.cardano-node = { 19 | environment = cluster; 20 | topology = iohkLib.cardanoLib.mkEdgeTopology { edgeNodes = iohkLib.cardanoLib.environments.${cluster}.edgeNodes; edgePort = 7777; }; 21 | enable = true; 22 | }; 23 | services.cardano-exporter = { 24 | enable = true; 25 | inherit (targetEnv) genesisFile genesisHash; 26 | inherit cluster; 27 | socketPath = "/run/cardano-node/node-core-0.socket"; 28 | }; 29 | services.cardano-explorer-api.enable = true; 30 | services.nginx = { 31 | virtualHosts.${host} = { 32 | default = true; 33 | locations."/api/".extraConfig = '' 34 | proxy_pass http://localhost:8100/api/; 35 | proxy_set_header Host $host; 36 | proxy_set_header REMOTE_ADDR $remote_addr; 37 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 38 | proxy_set_header X-Forwarded-Proto $scheme; 39 | ''; 40 | }; 41 | }; 42 | systemd.services = { 43 | cardano-explorer-node = { 44 | wants = [ "cardano-node.service" ]; 45 | serviceConfig.PermissionsStartOnly = "true"; 46 | preStart = '' 47 | for x in {1..24}; do 48 | [ -S "${config.services.cardano-exporter.socketPath}" ] && break 49 | echo loop $x: waiting for "${config.services.cardano-exporter.socketPath}" 5 sec... 50 | sleep 5 51 | done 52 | chgrp cexplorer "${config.services.cardano-exporter.socketPath}" 53 | chmod g+w "${config.services.cardano-exporter.socketPath}" 54 | ''; 55 | }; 56 | }; 57 | } 58 | -------------------------------------------------------------------------------- /nix/nixos/cardano-explorer-webapi.nix: -------------------------------------------------------------------------------- 1 | { config, lib, pkgs, ... }: 2 | 3 | let 4 | cfg = config.services.cardano-explorer-webapi; 5 | self = import ../.. { }; 6 | in { 7 | options = { 8 | services.cardano-explorer-webapi = { 9 | enable = lib.mkEnableOption "enable the cardano-explorer web api"; 10 | script = lib.mkOption { 11 | internal = true; 12 | type = lib.types.package; 13 | }; 14 | pgpass = lib.mkOption { 15 | type = lib.types.path; 16 | }; 17 | postgres = { 18 | socketdir = lib.mkOption { 19 | type = lib.types.str; 20 | default = "/run/postgresql"; 21 | description = "the path to the postgresql socket"; 22 | }; 23 | port = lib.mkOption { 24 | type = lib.types.int; 25 | default = 5432; 26 | description = "the postgresql port"; 27 | }; 28 | database = lib.mkOption { 29 | type = lib.types.str; 30 | default = "cexplorer"; 31 | description = "the postgresql database to use"; 32 | }; 33 | user = lib.mkOption { 34 | type = lib.types.str; 35 | default = "cexplorer"; 36 | description = "the postgresql user to use"; 37 | }; 38 | }; 39 | }; 40 | }; 41 | config = lib.mkIf cfg.enable { 42 | services.cardano-explorer-webapi = { 43 | pgpass = builtins.toFile "pgpass" "${cfg.postgres.socketdir}:${toString cfg.postgres.port}:${cfg.postgres.database}:${cfg.postgres.user}:*"; 44 | script = pkgs.writeShellScript "cardano-explorer-webapi" '' 45 | export PGPASSFILE=${cfg.pgpass} 46 | exec ${self.cardano-explorer-webapi}/bin/cardano-explorer-webapi 47 | ''; 48 | }; 49 | systemd.services.cardano-explorer-webapi = { 50 | wantedBy = [ "multi-user.target" ]; 51 | requires = [ "postgresql.service" ]; 52 | path = [ pkgs.netcat ]; 53 | preStart = '' 54 | for x in {1..10}; do 55 | nc -z localhost ${toString cfg.postgres.port} && break 56 | echo loop $x: waiting for postgresql 2 sec... 57 | sleep 2 58 | done 59 | ''; 60 | serviceConfig = { 61 | ExecStart = cfg.script; 62 | User = "cexplorer"; 63 | }; 64 | }; 65 | }; 66 | } 67 | -------------------------------------------------------------------------------- /nix/nixos/cardano-graphql-service.nix: -------------------------------------------------------------------------------- 1 | # WARNING!!! THIS IS BROKEN! DO NOT USE! 2 | 3 | { lib, pkgs, config, ... }: 4 | let 5 | cfg = config.services.cardano-graphql; 6 | sources = import ../../nix/sources.nix; 7 | in { 8 | options = { 9 | services.cardano-graphql = { 10 | enable = lib.mkEnableOption "cardano-explorer graphql service"; 11 | 12 | dbUser = lib.mkOption { 13 | type = lib.types.str; 14 | default = "cexplorer"; 15 | }; 16 | 17 | db = lib.mkOption { 18 | type = lib.types.str; 19 | default = "cexplorer"; 20 | }; 21 | 22 | enginePort = lib.mkOption { 23 | type = lib.types.int; 24 | default = 9999; 25 | }; 26 | 27 | hasuraIp = lib.mkOption { 28 | type = lib.types.str; 29 | default = "127.0.0.1"; 30 | }; 31 | 32 | hasuraProtocol = lib.mkOption { 33 | type = lib.types.str; 34 | default = "http"; 35 | }; 36 | }; 37 | }; 38 | config = let 39 | frontendBaseSrc = sources.cardano-graphql; 40 | frontend = (import ../cardano-graphql).cardano-graphql; 41 | hasuraBaseUri = cfg.hasuraProtocol + "://" + cfg.hasuraIp + ":" + (toString cfg.enginePort) + "/"; 42 | hasuraDbMetadata = frontendBaseSrc + "/hasura/migrations/metadata.json"; 43 | in lib.mkIf cfg.enable { 44 | systemd.services.cardano-graphql = { 45 | wantedBy = [ "multi-user.target" ]; 46 | requires = [ "graphql-engine.service" ]; 47 | environment = { 48 | HASURA_URI = hasuraBaseUri + "v1/graphql"; 49 | }; 50 | path = with pkgs; [ netcat curl postgresql nodejs-12_x ]; 51 | preStart = '' 52 | for x in {1..12}; do 53 | [ $(curl -s -o /dev/null -w "%{http_code}" http://localhost:8100/api/blocks/pages) == "200" ] && break; 54 | echo loop $x: waiting for cardano exporter tables 10 sec... 55 | sleep 10 56 | done 57 | 58 | for x in {1..10}; do 59 | nc -z ${cfg.hasuraIp} ${toString cfg.enginePort} && break 60 | echo loop $x: waiting for graphql-engine 2 sec... 61 | sleep 2 62 | done 63 | curl -d'{"type":"replace_metadata", "args":'$(cat ${hasuraDbMetadata})'}' ${hasuraBaseUri}v1/query 64 | ''; 65 | script = '' 66 | node --version 67 | node ${frontend}/index.js 68 | ''; 69 | }; 70 | }; 71 | } 72 | -------------------------------------------------------------------------------- /nix/nixos/cardano-tx-submitter.nix: -------------------------------------------------------------------------------- 1 | { config, lib, pkgs, ... }: 2 | 3 | # notes: 4 | # this service exposes an http port, and connects to a cardano-node over a unix socket 5 | let 6 | cfg = config.services.cardano-tx-submit-webapi; 7 | self = import ../.. { }; 8 | envConfig = cfg.environment; 9 | localLib = import ../../lib.nix {}; 10 | in { 11 | options = { 12 | services.cardano-tx-submit-webapi = { 13 | enable = lib.mkEnableOption "enable the cardano-explorer tx submitter api"; 14 | script = lib.mkOption { 15 | internal = true; 16 | type = lib.types.package; 17 | }; 18 | port = lib.mkOption { 19 | type = lib.types.port; 20 | default = 8101; 21 | }; 22 | socketPath = lib.mkOption { 23 | type = lib.types.nullOr lib.types.path; 24 | default = null; 25 | }; 26 | cluster = lib.mkOption { 27 | type = lib.types.nullOr lib.types.str; 28 | description = "cluster name"; 29 | }; 30 | environment = lib.mkOption { 31 | type = lib.types.nullOr lib.types.attrs; 32 | default = localLib.cardanoLib.environments.${cfg.cluster}; 33 | }; 34 | }; 35 | }; 36 | config = lib.mkIf cfg.enable { 37 | services.cardano-tx-submit-webapi.script = pkgs.writeShellScript "cardano-tx-submit-webapi" '' 38 | ${if (cfg.socketPath == null) then ''if [ -z "$CARDANO_NODE_SOCKET_PATH" ] 39 | then 40 | echo "You must set \$CARDANO_NODE_SOCKET_PATH" 41 | exit 1 42 | fi'' else "export \"CARDANO_NODE_SOCKET_PATH=${cfg.socketPath}\""} 43 | exec ${self.cardano-tx-submit-webapi}/bin/cardano-tx-submit-webapi --socket-path "$CARDANO_NODE_SOCKET_PATH" \ 44 | --genesis-file ${envConfig.genesisFile} \ 45 | --port ${toString config.services.cardano-tx-submit-webapi.port} \ 46 | --config ${builtins.toFile "tx-submit.json" (builtins.toJSON cfg.environment.txSubmitConfig)} 47 | ''; 48 | systemd.services.cardano-tx-submit-webapi = { 49 | serviceConfig = { 50 | ExecStart = config.services.cardano-tx-submit-webapi.script; 51 | User = "cexplorer"; 52 | WorkingDirectory = "/var/lib/cexplorer"; 53 | }; 54 | wantedBy = [ "multi-user.target" ]; 55 | after = [ "postgresql.service" "cardano-explorer-node.service" ]; 56 | }; 57 | }; 58 | } 59 | -------------------------------------------------------------------------------- /nix/nixos/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | imports = import ./module-list.nix; 3 | } 4 | -------------------------------------------------------------------------------- /nix/nixos/graphql-engine-service.nix: -------------------------------------------------------------------------------- 1 | { lib, pkgs, config, ... }: 2 | let 3 | cfg = config.services.graphql-engine; 4 | sources = import ../../nix/sources.nix; 5 | 6 | in { 7 | options = { 8 | services.graphql-engine = { 9 | enable = lib.mkEnableOption "graphql engine service"; 10 | 11 | host = lib.mkOption { 12 | type = lib.types.str; 13 | default = "/var/run/postgresql"; 14 | }; 15 | 16 | dbUser = lib.mkOption { 17 | type = lib.types.str; 18 | default = "cexplorer"; 19 | }; 20 | 21 | password = lib.mkOption { 22 | type = lib.types.str; 23 | default = ''""''; 24 | }; 25 | 26 | dbAdminUser = lib.mkOption { 27 | type = lib.types.str; 28 | default = "postgres"; 29 | }; 30 | 31 | db = lib.mkOption { 32 | type = lib.types.str; 33 | default = "cexplorer"; 34 | }; 35 | 36 | dbPort = lib.mkOption { 37 | type = lib.types.int; 38 | default = 5432; 39 | }; 40 | 41 | enginePort = lib.mkOption { 42 | type = lib.types.int; 43 | default = 9999; 44 | }; 45 | }; 46 | }; 47 | config = let 48 | nixpkgsHasuraBaseSrc = sources.nixpkgs-hasura-base; 49 | nixpkgsHasuraAttr = import nixpkgsHasuraBaseSrc {}; 50 | graphqlEngineAttr = nixpkgsHasuraAttr.pkgs.callPackage ./graphql-engine/default.nix {}; 51 | graphqlEngine = graphqlEngineAttr.graphql-engine; 52 | hasuraDbPerms = pkgs.writeScript "hasuraDbPerms.sql" '' 53 | CREATE EXTENSION IF NOT EXISTS pgcrypto; 54 | CREATE SCHEMA IF NOT EXISTS hdb_catalog; 55 | CREATE SCHEMA IF NOT EXISTS hdb_views; 56 | ALTER SCHEMA hdb_catalog OWNER TO ${cfg.dbUser}; 57 | ALTER SCHEMA hdb_views OWNER TO ${cfg.dbUser}; 58 | GRANT SELECT ON ALL TABLES IN SCHEMA information_schema TO ${cfg.dbUser}; 59 | GRANT SELECT ON ALL TABLES IN SCHEMA pg_catalog TO ${cfg.dbUser}; 60 | ''; 61 | postgresqlIp = if ((__head (pkgs.lib.stringToCharacters cfg.host)) == "/") 62 | then "127.0.0.1" 63 | else cfg.host; 64 | in lib.mkIf cfg.enable { 65 | systemd.services.graphql-engine = { 66 | wantedBy = [ "multi-user.target" ]; 67 | requires = [ "postgresql.service" ]; 68 | path = with pkgs; [ curl netcat postgresql sudo ]; 69 | preStart = '' 70 | for x in {1..10}; do 71 | nc -z ${postgresqlIp} ${toString cfg.dbPort} && break 72 | echo loop $x: waiting for postgresql 2 sec... 73 | sleep 2 74 | done 75 | sudo -u ${cfg.dbAdminUser} -- psql ${cfg.db} < ${hasuraDbPerms} 76 | ''; 77 | script = '' 78 | ${graphqlEngine}/bin/graphql-engine \ 79 | --host ${cfg.host} \ 80 | -u ${cfg.dbUser} \ 81 | --password ${cfg.password} \ 82 | -d ${cfg.db} \ 83 | --port ${toString cfg.dbPort} \ 84 | serve \ 85 | --server-port ${toString cfg.enginePort} 86 | ''; 87 | }; 88 | }; 89 | } 90 | -------------------------------------------------------------------------------- /nix/nixos/graphql-engine/ci-info.nix: -------------------------------------------------------------------------------- 1 | { mkDerivation, aeson, aeson-casing, base, fetchgit, hashable 2 | , hpack, stdenv, template-haskell, text, th-lift-instances 3 | , unordered-containers 4 | }: 5 | mkDerivation { 6 | pname = "ci-info"; 7 | version = "0.1.0.0"; 8 | src = fetchgit { 9 | url = "https://github.com/hasura/ci-info-hs.git"; 10 | sha256 = "02qizvdpij1gaj24f6d271110sp0gwfkr4qvgxqx4r27hr7fw55n"; 11 | rev = "ad6df731584dc89b72a6e131687d37ef01714fe8"; 12 | fetchSubmodules = true; 13 | }; 14 | libraryHaskellDepends = [ 15 | aeson aeson-casing base hashable template-haskell text 16 | th-lift-instances unordered-containers 17 | ]; 18 | libraryToolDepends = [ hpack ]; 19 | prePatch = "hpack"; 20 | homepage = "https://github.com/hasura/ci-info-hs#readme"; 21 | license = stdenv.lib.licenses.mit; 22 | } 23 | -------------------------------------------------------------------------------- /nix/nixos/graphql-engine/default.nix: -------------------------------------------------------------------------------- 1 | { haskellPackages, haskell }: 2 | 3 | haskellPackages.override { 4 | overrides = self: super: { 5 | ci-info = self.callPackage ./ci-info.nix {}; 6 | graphql-engine = haskell.lib.dontHaddock (haskell.lib.dontCheck (self.callPackage ./graphql-engine.nix {})); 7 | graphql-parser = self.callPackage ./graphql-parser.nix {}; 8 | pg-client = self.callPackage ./pg-client.nix {}; 9 | shakespeare = self.callHackageDirect { pkg = "shakespeare"; ver = "2.0.22"; sha256 = "1d7byyrc2adyxrgcrlxyyffpr4wjcgcnvdb8916ad6qpqjhqxx72"; } {}; 10 | stm-hamt = haskell.lib.doJailbreak (haskell.lib.unmarkBroken super.stm-hamt); 11 | superbuffer = haskell.lib.doJailbreak (haskell.lib.unmarkBroken super.superbuffer); 12 | Spock-core = haskell.lib.unmarkBroken super.Spock-core; 13 | stm-containers = haskell.lib.unmarkBroken super.stm-containers; 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /nix/nixos/graphql-engine/graphql-engine.nix: -------------------------------------------------------------------------------- 1 | { mkDerivation, aeson, aeson-casing, ansi-wl-pprint, asn1-encoding 2 | , asn1-types, async, attoparsec, attoparsec-iso8601, auto-update 3 | , base, base64-bytestring, byteorder, bytestring, case-insensitive 4 | , ci-info, containers, cryptonite, data-has, ekg-core, ekg-json 5 | , fast-logger, fetchFromGitHub, file-embed, filepath, graphql-parser 6 | , hashable, hspec, http-client, http-client-tls, http-types 7 | , insert-ordered-containers, jose, lens, list-t, mime-types 8 | , monad-control, monad-time, monad-validate, mtl, mustache, network 9 | , network-uri, optparse-applicative, pem, pg-client 10 | , postgresql-binary, postgresql-libpq, process, regex-tdfa 11 | , scientific, semver, shakespeare, split, Spock-core, stdenv, stm 12 | , stm-containers, string-conversions, template-haskell, text 13 | , text-builder, text-conversions, th-lift-instances, time 14 | , transformers, transformers-base, unix, unordered-containers, uuid 15 | , vector, wai, wai-websockets, warp, websockets, wreq, x509, yaml 16 | , zlib 17 | }: 18 | 19 | mkDerivation { 20 | pname = "graphql-engine"; 21 | version = "1.0.0"; 22 | src = fetchFromGitHub { 23 | owner = "hasura"; 24 | repo = "graphql-engine"; 25 | sha256 = "1g4xj9yx7i6nn5w23n0b0g3xsmgczza8rpggkcgs47v7wkp0ny6a"; 26 | rev = "e43be51dc6a57242f12816ce677733c9e8569909"; 27 | }; 28 | postUnpack = "sourceRoot+=/server; echo source root reset to $sourceRoot"; 29 | isLibrary = true; 30 | isExecutable = true; 31 | libraryHaskellDepends = [ 32 | aeson aeson-casing ansi-wl-pprint asn1-encoding asn1-types async 33 | attoparsec attoparsec-iso8601 auto-update base base64-bytestring 34 | byteorder bytestring case-insensitive ci-info containers cryptonite 35 | data-has ekg-core ekg-json fast-logger file-embed filepath 36 | graphql-parser hashable http-client http-types 37 | insert-ordered-containers jose lens list-t mime-types monad-control 38 | monad-time monad-validate mtl mustache network network-uri 39 | optparse-applicative pem pg-client postgresql-binary 40 | postgresql-libpq process regex-tdfa scientific semver shakespeare 41 | split Spock-core stm stm-containers string-conversions 42 | template-haskell text text-builder text-conversions 43 | th-lift-instances time transformers transformers-base 44 | unordered-containers uuid vector wai wai-websockets warp websockets 45 | wreq x509 yaml zlib 46 | ]; 47 | executableHaskellDepends = [ 48 | aeson base bytestring http-client http-client-tls lens mtl 49 | optparse-applicative pg-client stm string-conversions 50 | template-haskell text time unix unordered-containers uuid warp wreq 51 | yaml 52 | ]; 53 | testHaskellDepends = [ 54 | base hspec http-client http-client-tls optparse-applicative 55 | pg-client time 56 | ]; 57 | homepage = "https://www.hasura.io"; 58 | description = "GraphQL API over Postgres"; 59 | license = stdenv.lib.licenses.asl20; 60 | } 61 | -------------------------------------------------------------------------------- /nix/nixos/graphql-engine/graphql-parser.nix: -------------------------------------------------------------------------------- 1 | { mkDerivation, aeson, attoparsec, base, bytestring, containers 2 | , criterion, fetchgit, filepath, hedgehog, hpack, prettyprinter 3 | , protolude, regex-tdfa, scientific, stdenv, template-haskell, text 4 | , text-builder, th-lift-instances, unordered-containers, vector 5 | }: 6 | mkDerivation { 7 | pname = "graphql-parser"; 8 | version = "0.1.0.0"; 9 | src = fetchgit { 10 | url = "https://github.com/hasura/graphql-parser-hs.git"; 11 | sha256 = "1kar4fp6az0b9xr4zw3z4v5lhyq5nvh138zi6wbwyyds0d6h6bdc"; 12 | rev = "f3d9b645efd9adb143e2ad4c6b73bded1578a4e9"; 13 | fetchSubmodules = true; 14 | }; 15 | libraryHaskellDepends = [ 16 | aeson attoparsec base bytestring containers filepath hedgehog 17 | prettyprinter protolude regex-tdfa scientific template-haskell text 18 | text-builder th-lift-instances unordered-containers vector 19 | ]; 20 | libraryToolDepends = [ hpack ]; 21 | testHaskellDepends = [ 22 | aeson attoparsec base bytestring containers filepath hedgehog 23 | prettyprinter protolude regex-tdfa scientific template-haskell text 24 | text-builder th-lift-instances unordered-containers vector 25 | ]; 26 | benchmarkHaskellDepends = [ 27 | aeson attoparsec base bytestring containers criterion filepath 28 | hedgehog prettyprinter protolude regex-tdfa scientific 29 | template-haskell text text-builder th-lift-instances 30 | unordered-containers vector 31 | ]; 32 | prePatch = "hpack"; 33 | homepage = "https://github.com/hasura/graphql-parser-hs#readme"; 34 | license = stdenv.lib.licenses.bsd3; 35 | } 36 | -------------------------------------------------------------------------------- /nix/nixos/graphql-engine/pg-client.nix: -------------------------------------------------------------------------------- 1 | { mkDerivation, aeson, aeson-casing, attoparsec, base, bytestring 2 | , criterion, fetchgit, file-embed, hashable, hashtables, hasql 3 | , hasql-pool, hasql-transaction, monad-control, mtl 4 | , postgresql-binary, postgresql-libpq, resource-pool, retry 5 | , scientific, stdenv, template-haskell, text, text-builder, th-lift 6 | , th-lift-instances, time, transformers-base, uuid, vector 7 | }: 8 | mkDerivation { 9 | pname = "pg-client"; 10 | version = "0.1.0"; 11 | src = fetchgit { 12 | url = "https://github.com/hasura/pg-client-hs.git"; 13 | sha256 = "11ajkz1kbjl5jbcbj0mxzf9vzhjn536d9a60dnl56dl6xvas0ndb"; 14 | rev = "bd21e66d8197af381a6c0b493e22d611ed1fa386"; 15 | fetchSubmodules = true; 16 | }; 17 | libraryHaskellDepends = [ 18 | aeson aeson-casing attoparsec base bytestring hashable hashtables 19 | monad-control mtl postgresql-binary postgresql-libpq resource-pool 20 | retry scientific template-haskell text text-builder th-lift 21 | th-lift-instances time transformers-base uuid vector 22 | ]; 23 | testHaskellDepends = [ base ]; 24 | benchmarkHaskellDepends = [ 25 | base bytestring criterion file-embed hashable hasql hasql-pool 26 | hasql-transaction mtl postgresql-libpq text text-builder 27 | ]; 28 | homepage = "https://github.com/hasura/platform"; 29 | license = stdenv.lib.licenses.bsd3; 30 | } 31 | -------------------------------------------------------------------------------- /nix/nixos/graphql-engine/test.nix: -------------------------------------------------------------------------------- 1 | let 2 | sources = import ../../sources.nix; 3 | nixpkgsHasuraBaseSrc = sources.nixpkgs-hasura-base; 4 | nixpkgsHasuraAttr = import nixpkgsHasuraBaseSrc {}; 5 | graphqlEngineAttr = nixpkgsHasuraAttr.pkgs.callPackage ./. {}; 6 | graphqlEngine = graphqlEngineAttr.graphql-engine; 7 | in graphqlEngine 8 | -------------------------------------------------------------------------------- /nix/nixos/module-list.nix: -------------------------------------------------------------------------------- 1 | [ 2 | ./cardano-exporter-service.nix 3 | ./graphql-engine-service.nix 4 | ./cardano-graphql-service.nix 5 | ./cardano-tx-submitter.nix 6 | ./cardano-explorer-webapi.nix 7 | ./cardano-explorer-everything.nix 8 | ] 9 | -------------------------------------------------------------------------------- /nix/nixos/tests/chairmans-cluster.nix: -------------------------------------------------------------------------------- 1 | { pkgs, commonLib, chairmanScript, ... }: 2 | 3 | let 4 | sources = import ../../sources.nix; 5 | 6 | chairman-runner = chairmanScript { 7 | chairman-config = { 8 | enable = true; 9 | k = 2160; 10 | timeout = 300; 11 | maxBlockNo = 2; 12 | }; 13 | }; 14 | 15 | psql = pkgs.postgresql + "/bin/psql"; 16 | 17 | makeSureBlocksAreExported = pkgs.writeShellScript "block-check.sh" '' 18 | set -xe 19 | test $(su postgres -c '${pkgs.postgresql}/bin/psql cexplorer -t -c "select count(*) from block;"') -ge 2 20 | ''; 21 | in { 22 | name = "chairmans-cluster-test"; 23 | nodes = { 24 | machine = { config, pkgs, ... }: { 25 | virtualisation.memorySize = 1024 * 12; 26 | imports = [ 27 | (sources.cardano-node + "/nix/nixos") 28 | ../cardano-exporter-service.nix 29 | ]; 30 | 31 | services.cardano-cluster = { 32 | enable = true; 33 | node-count = 3; # This must match nixos/scripts.nix:mkChairmanScript 34 | }; 35 | 36 | services.cardano-exporter = { 37 | enable = true; 38 | environment = { 39 | inherit (config.services.cardano-node) genesisFile genesisHash; 40 | }; 41 | cluster = "testnet"; 42 | socketPath = "/run/cardano-node/node-core-0.socket"; 43 | }; 44 | 45 | systemd.services = { 46 | cardano-explorer-node = { 47 | wants = [ "cardano-node.service" ]; 48 | serviceConfig.PermissionsStartOnly = "true"; 49 | preStart = '' 50 | for x in {1..24}; do 51 | [ -S ${config.services.cardano-exporter.socketPath} ] && break 52 | echo loop $x: waiting for ${config.services.cardano-exporter.socketPath} 5 sec... 53 | sleep 5 54 | done 55 | chgrp cexplorer ${config.services.cardano-exporter.socketPath} 56 | chmod g+w ${config.services.cardano-exporter.socketPath} 57 | ''; 58 | }; 59 | }; 60 | }; 61 | }; 62 | 63 | testScript = '' 64 | startAll 65 | $machine->waitForOpenPort(3001); 66 | $machine->waitForOpenPort(3002); 67 | $machine->waitForOpenPort(3003); 68 | $machine->succeed("${chairman-runner} 2>&1 | systemd-cat --identifier=chairman --priority=crit"); 69 | $machine->succeed("${makeSureBlocksAreExported}"); 70 | ''; 71 | } 72 | -------------------------------------------------------------------------------- /nix/nixos/tests/default.nix: -------------------------------------------------------------------------------- 1 | { sources ? import ../../sources.nix, pkgs ? import sources.nixpkgs { } 2 | , cardano-node ? import sources.cardano-node { } 3 | , commonLib ? import ../../lib.nix { }, supportedSystems ? [ "x86_64-linux" ] }: 4 | 5 | let 6 | inherit (pkgs.lib) genAttrs hydraJob; 7 | forAllSystems = genAttrs supportedSystems; 8 | 9 | makeTest = import (pkgs.path + "/nixos/tests/make-test.nix"); 10 | 11 | importTest = fn: args: system: 12 | let 13 | imported = import fn; 14 | test = makeTest imported; 15 | in test ({ inherit system; } // args); 16 | 17 | callTest = fn: args: 18 | forAllSystems (system: hydraJob (importTest fn args system)); 19 | 20 | chairmanScript = cardano-node.scripts.testnet.chairman; 21 | in rec { 22 | chairmansCluster = 23 | callTest ./chairmans-cluster.nix { inherit pkgs commonLib chairmanScript; }; 24 | 25 | chairmansClusterDriver = (makeTest (import ./chairmans-cluster.nix) { 26 | inherit commonLib chairmanScript; 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /nix/pkgs.nix: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | # Builds Haskell packages with Haskell.nix 3 | ############################################################################ 4 | 5 | { pkgs 6 | 7 | # haskell.nix 8 | , haskell 9 | 10 | # Filtered sources of this project 11 | , src 12 | 13 | # Customisations for cross-compiling 14 | , iohk-extras ? {} 15 | , iohk-module ? {} 16 | 17 | }: 18 | 19 | let 20 | preCheck = '' 21 | echo pre-check 22 | initdb --encoding=UTF8 --locale=en_US.UTF-8 --username=postgres $NIX_BUILD_TOP/db-dir 23 | postgres -D $NIX_BUILD_TOP/db-dir & 24 | PSQL_PID=$! 25 | sleep 10 26 | if (echo '\q' | psql postgres postgres); then 27 | echo "PostgreSQL server is verified to be started." 28 | else 29 | echo "Failed to connect to local PostgreSQL server." 30 | exit 2 31 | fi 32 | ls -ltrh $NIX_BUILD_TOP 33 | DBUSER=nixbld 34 | DBNAME=nixbld 35 | export PGPASSFILE=$NIX_BUILD_TOP/pgpass 36 | echo "/tmp:5432:$DBUSER:$DBUSER:*" > $PGPASSFILE 37 | cp -vir ${../schema} ../schema 38 | chmod 600 $PGPASSFILE 39 | psql postgres postgres < {} }: 2 | 3 | with pkgs.lib; 4 | 5 | { 6 | # TODO: Replace this with a function that identifies packages from your 7 | # project. 8 | # As an example, this matches packages called "iohk-skeleton*" and the package 9 | # called "another-package". 10 | # fixme: automate this in nix-tools/Haskell.nix. 11 | isIohkSkeleton = package: 12 | (hasPrefix "cardano-explorer-webapi" package.identifier.name) || 13 | (elem package.identifier.name [ "another-package" ]); 14 | 15 | # fixme: upstream to iohk-nix 16 | collectComponents = group: packageSel: haskellPackages: 17 | (mapAttrs (_: package: package.components.${group} // { recurseForDerivations = true; }) 18 | (filterAttrs (name: package: (package.isHaskell or false) && packageSel package) haskellPackages)) 19 | // { recurseForDerivations = true; }; 20 | } 21 | -------------------------------------------------------------------------------- /release.nix: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | # 3 | # Hydra release jobset. 4 | # 5 | # The purpose of this file is to select jobs defined in default.nix and map 6 | # them to all supported build platforms. 7 | # 8 | ############################################################################ 9 | 10 | # The project sources 11 | { cardano-explorer ? { outPath = ./.; rev = "abcdef"; } 12 | 13 | # Function arguments to pass to the project 14 | , projectArgs ? { config = { allowUnfree = false; inHydra = true; }; } 15 | 16 | # The systems that the jobset will be built for. 17 | , supportedSystems ? [ "x86_64-linux" "x86_64-darwin" ] 18 | 19 | # The systems used for cross-compiling 20 | , supportedCrossSystems ? [ "x86_64-linux" ] 21 | 22 | # A Hydra option 23 | , scrubJobs ? true 24 | 25 | # Import IOHK common nix lib 26 | # TODO: remove line below, and uncomment the next line 27 | , iohkLib ? import ./lib.nix {} 28 | }: 29 | 30 | with (import iohkLib.release-lib) { 31 | inherit (import ./lib.nix {}) pkgs; 32 | 33 | inherit supportedSystems supportedCrossSystems scrubJobs projectArgs; 34 | packageSet = import cardano-explorer; 35 | gitrev = cardano-explorer.rev; 36 | }; 37 | 38 | with pkgs.lib; 39 | 40 | let 41 | testsSupportedSystems = [ "x86_64-linux" ]; 42 | collectTests = ds: filter (d: elem d.system testsSupportedSystems) (collect isDerivation ds); 43 | 44 | inherit (systems.examples) mingwW64 musl64; 45 | #inherit (import ./nix/nixos/tests { }) chairmansCluster; 46 | inherit (import ./docker { }) hydraJob; 47 | 48 | jobs = { 49 | native = mapTestOn (packagePlatforms project); 50 | #"${mingwW64.config}" = mapTestOnCross mingwW64 (packagePlatformsCross project); 51 | 52 | #chairmansCluster = chairmansCluster.x86_64-linux; 53 | docker-inputs = hydraJob; 54 | } 55 | // ( 56 | # This aggregate job is what IOHK Hydra uses to update 57 | # the CI status in GitHub. 58 | mkRequiredJob ( 59 | collectTests jobs.native.tests ++ 60 | collectTests jobs.native.benchmarks ++ 61 | [ 62 | jobs.native.cardano-explorer-webapi.x86_64-linux 63 | jobs.native.cardano-explorer-db-tool.x86_64-linux 64 | jobs.native.cardano-explorer-node.x86_64-linux 65 | jobs.native.cardano-sl-core.x86_64-linux 66 | # TODO: fix chairman cluster once configs stabilize 67 | #jobs.chairmansCluster 68 | jobs.docker-inputs 69 | ] 70 | )) 71 | 72 | # Build the shell derivation in Hydra so that all its dependencies 73 | # are cached. 74 | // mapTestOn (packagePlatforms { inherit (project) shell; }); 75 | 76 | in jobs 77 | -------------------------------------------------------------------------------- /schema/migration-1-0000-20190730.sql: -------------------------------------------------------------------------------- 1 | -- Hand written migration that creates a 'schema_version' table and initializes it. 2 | 3 | CREATE FUNCTION init() RETURNS void AS $$ 4 | 5 | DECLARE 6 | emptyDB boolean; 7 | 8 | BEGIN 9 | SELECT NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name='schema_version') INTO emptyDB; 10 | IF emptyDB THEN 11 | EXECUTE 'CREATE TABLE "schema_version" (id SERIAL PRIMARY KEY UNIQUE, stage_one INT8 NOT NULL, stage_two INT8 NOT NULL, stage_three INT8 NOT NULL);'; 12 | EXECUTE 'INSERT INTO "schema_version" (stage_one, stage_two, stage_three) VALUES (0, 0, 0);'; 13 | 14 | RAISE NOTICE 'DB has been initialized'; 15 | END IF; 16 | END; 17 | 18 | $$ LANGUAGE plpgsql; 19 | 20 | SELECT init(); 21 | 22 | DROP FUNCTION init(); 23 | -------------------------------------------------------------------------------- /schema/migration-1-0001-20190730.sql: -------------------------------------------------------------------------------- 1 | -- Hand written migration to create the custom types with 'DOMAIN' statements. 2 | 3 | CREATE FUNCTION migrate() RETURNS void AS $$ 4 | 5 | DECLARE 6 | next_version int; 7 | 8 | BEGIN 9 | SELECT stage_one + 1 INTO next_version FROM "schema_version"; 10 | IF next_version = 1 THEN 11 | EXECUTE 'CREATE DOMAIN lovelace AS bigint CHECK (VALUE >= 0 AND VALUE <= 45000000000000000);'; 12 | EXECUTE 'CREATE DOMAIN txindex AS smallint CHECK (VALUE >= 0 AND VALUE < 1024);'; 13 | EXECUTE 'CREATE DOMAIN uinteger AS integer CHECK (VALUE >= 0);'; 14 | 15 | -- Blocks, transactions and merkel roots use a 32 byte hash. 16 | EXECUTE 'CREATE DOMAIN hash32type AS bytea CHECK (octet_length (VALUE) = 32);'; 17 | 18 | -- Addresses use a 28 byte hash (as do StakeholdIds). 19 | EXECUTE 'CREATE DOMAIN hash28type AS bytea CHECK (octet_length (VALUE) = 28);'; 20 | 21 | UPDATE "schema_version" SET stage_one = 1; 22 | RAISE NOTICE 'DB has been migrated to stage_one version %', next_version; 23 | END IF; 24 | END; 25 | 26 | $$ LANGUAGE plpgsql; 27 | 28 | SELECT migrate(); 29 | 30 | DROP FUNCTION migrate(); 31 | -------------------------------------------------------------------------------- /schema/migration-1-0002-20190912.sql: -------------------------------------------------------------------------------- 1 | -- Drop all VIEWs linked to the 'cexplorer' table. 2 | 3 | -- Many schema migration changes will fail if they operate on a table that is part of a VIEW. 4 | -- So we drop all VIEWs here and recreate them later. 5 | 6 | CREATE FUNCTION drop_cexplorer_views () RETURNS void language plpgsql as $$ 7 | 8 | DECLARE vname text; 9 | 10 | BEGIN 11 | for vname in 12 | select '"' || table_name || '"' 13 | from information_schema.views 14 | where table_catalog like '%explorer' and table_schema = 'public' 15 | loop 16 | execute format ('drop view if exists %s cascade ;', vname) ; 17 | raise notice 'Dropping view : %', vname ; 18 | end loop ; 19 | end $$ ; 20 | 21 | SELECT drop_cexplorer_views () ; 22 | 23 | DROP FUNCTION drop_cexplorer_views () ; 24 | -------------------------------------------------------------------------------- /schema/migration-1-0003-20200211.sql: -------------------------------------------------------------------------------- 1 | -- Hand written migration to create the custom types with 'DOMAIN' statements. 2 | 3 | CREATE FUNCTION migrate() RETURNS void AS $$ 4 | 5 | DECLARE 6 | next_version int; 7 | 8 | BEGIN 9 | SELECT stage_one + 1 INTO next_version FROM "schema_version"; 10 | IF next_version = 2 THEN 11 | -- Used as the sum of tx outputs for an epoch. 12 | -- Need this to catch possible overflow. 13 | EXECUTE 'CREATE DOMAIN outsum AS bigint CHECK (VALUE >= 0);'; 14 | 15 | UPDATE "schema_version" SET stage_one = next_version; 16 | RAISE NOTICE 'DB has been migrated to stage_one version %', next_version; 17 | END IF; 18 | END; 19 | 20 | $$ LANGUAGE plpgsql; 21 | 22 | SELECT migrate(); 23 | 24 | DROP FUNCTION migrate(); 25 | -------------------------------------------------------------------------------- /schema/migration-2-0001-20190902.sql: -------------------------------------------------------------------------------- 1 | -- Persistent generated migration. 2 | 3 | CREATE FUNCTION migrate() RETURNS void AS $$ 4 | DECLARE 5 | next_version int ; 6 | BEGIN 7 | SELECT stage_two + 1 INTO next_version FROM schema_version ; 8 | IF next_version = 1 THEN 9 | EXECUTE 'CREATe TABLE "slot_leader"("id" SERIAL8 PRIMARY KEY UNIQUE,"hash" hash28type NOT NULL,"desciption" VARCHAR NOT NULL)' ; 10 | EXECUTE 'ALTER TABLE "slot_leader" ADD CONSTRAINT "unique_slot_leader" UNIQUE("hash")' ; 11 | EXECUTE 'CREATe TABLE "block"("id" SERIAL8 PRIMARY KEY UNIQUE,"hash" hash32type NOT NULL,"slot_no" uinteger NULL,"block_no" uinteger,"previous" INT8 NULL,"merkel_root" hash32type NULL,"slot_leader" INT8 NOT NULL,"size" uinteger NOT NULL)' ; 12 | EXECUTE 'ALTER TABLE "block" ADD CONSTRAINT "unique_block" UNIQUE("hash")' ; 13 | EXECUTE 'ALTER TABLE "block" ADD CONSTRAINT "block_previous_fkey" FOREIGN KEY("previous") REFERENCES "block"("id")' ; 14 | EXECUTE 'ALTER TABLE "block" ADD CONSTRAINT "block_slot_leader_fkey" FOREIGN KEY("slot_leader") REFERENCES "slot_leader"("id")' ; 15 | EXECUTE 'CREATe TABLE "tx"("id" SERIAL8 PRIMARY KEY UNIQUE,"hash" hash32type NOT NULL,"block" INT8 NOT NULL,"fee" lovelace NOT NULL)' ; 16 | EXECUTE 'ALTER TABLE "tx" ADD CONSTRAINT "unique_tx" UNIQUE("hash")' ; 17 | EXECUTE 'ALTER TABLE "tx" ADD CONSTRAINT "tx_block_fkey" FOREIGN KEY("block") REFERENCES "block"("id")' ; 18 | EXECUTE 'CREATe TABLE "tx_out"("id" SERIAL8 PRIMARY KEY UNIQUE,"tx_id" INT8 NOT NULL,"index" txindex NOT NULL,"address" VARCHAR NOT NULL,"value" lovelace NOT NULL)' ; 19 | EXECUTE 'ALTER TABLE "tx_out" ADD CONSTRAINT "unique_txout" UNIQUE("tx_id","index")' ; 20 | EXECUTE 'ALTER TABLE "tx_out" ADD CONSTRAINT "tx_out_tx_id_fkey" FOREIGN KEY("tx_id") REFERENCES "tx"("id")' ; 21 | EXECUTE 'CREATe TABLE "tx_in"("id" SERIAL8 PRIMARY KEY UNIQUE,"tx_in_id" INT8 NOT NULL,"tx_out_id" INT8 NOT NULL,"tx_out_index" txindex NOT NULL)' ; 22 | EXECUTE 'ALTER TABLE "tx_in" ADD CONSTRAINT "unique_txin" UNIQUE("tx_out_id","tx_out_index")' ; 23 | EXECUTE 'ALTER TABLE "tx_in" ADD CONSTRAINT "tx_in_tx_in_id_fkey" FOREIGN KEY("tx_in_id") REFERENCES "tx"("id")' ; 24 | EXECUTE 'ALTER TABLE "tx_in" ADD CONSTRAINT "tx_in_tx_out_id_fkey" FOREIGN KEY("tx_out_id") REFERENCES "tx"("id")' ; 25 | EXECUTE 'CREATe TABLE "meta"("id" SERIAL8 PRIMARY KEY UNIQUE,"protocol_const" INT8 NOT NULL,"slot_duration" INT8 NOT NULL,"start_time" TIMESTAMP WITH TIME ZONE NOT NULL)' ; 26 | EXECUTE 'ALTER TABLE "meta" ADD CONSTRAINT "unique_meta" UNIQUE("start_time")' ; 27 | -- Hand written SQL statements can be added here. 28 | UPDATE schema_version SET stage_two = 1 ; 29 | RAISE NOTICE 'DB has been migrated to stage_two version %', next_version ; 30 | END IF ; 31 | END ; 32 | $$ LANGUAGE plpgsql ; 33 | 34 | SELECT migrate() ; 35 | 36 | DROP FUNCTION migrate() ; 37 | -------------------------------------------------------------------------------- /schema/migration-2-0002-20190918.sql: -------------------------------------------------------------------------------- 1 | -- Persistent generated migration. 2 | 3 | CREATE FUNCTION migrate() RETURNS void AS $$ 4 | DECLARE 5 | next_version int ; 6 | BEGIN 7 | SELECT stage_two + 1 INTO next_version FROM schema_version ; 8 | IF next_version = 2 THEN 9 | EXECUTE 'ALTER TABLE "block" ADD COLUMN "epoch_no" uinteger NULL' ; 10 | 11 | -- Hand written SQL to insert the epoch_no field. 12 | -- Update all non-EBBs. 13 | UPDATE block SET epoch_no = div (slot_no, 21600) WHERE slot_no IS NOT NULL ; 14 | 15 | -- Update EBBs. 16 | UPDATE block SET epoch_no = 17 | CASE WHEN previous_block.epoch_no IS NULL 18 | THEN 0 19 | ELSE previous_block.epoch_no + 1 20 | END 21 | FROM block AS previous_block 22 | WHERE block.previous = previous_block.id AND block.epoch_no IS NULL ; 23 | 24 | -- Update the initial EBB. 25 | UPDATE block SET epoch_no = 0 where block.previous = 1 ; 26 | -- 27 | 28 | UPDATE schema_version SET stage_two = 2 ; 29 | RAISE NOTICE 'DB has been migrated to stage_two version %', next_version ; 30 | END IF ; 31 | END ; 32 | $$ LANGUAGE plpgsql ; 33 | 34 | SELECT migrate() ; 35 | 36 | DROP FUNCTION migrate() ; 37 | -------------------------------------------------------------------------------- /schema/migration-2-0003-20191017.sql: -------------------------------------------------------------------------------- 1 | -- Persistent generated migration with hand written SQL. 2 | 3 | CREATE FUNCTION migrate() RETURNS void AS $$ 4 | DECLARE 5 | next_version int ; 6 | BEGIN 7 | SELECT stage_two + 1 INTO next_version FROM schema_version ; 8 | IF next_version = 3 THEN 9 | EXECUTE 'ALTER TABLE "block" ADD COLUMN "time" timestamp NULL' ; 10 | 11 | -- --------------------------------------------------------------------------------------------- 12 | -- Hand written SQL to populate the new column and then make it non-NULL. 13 | -- First timestamp the genesis block. 14 | update block set time = (select start_time from meta) where epoch_no is null ; 15 | 16 | -- Then add timestamps to the EBBs. 17 | update block 18 | set time = 19 | (select cast (quote_literal (epoch_no * (select slot_duration from meta) * 21.6) as interval) 20 | + (select start_time from meta)) 21 | where slot_no is null and epoch_no is not null ; 22 | 23 | -- Then add timestamp to the main blocks. 24 | update block 25 | set time = 26 | (select cast (quote_literal (slot_no * (select slot_duration from meta) * 0.001) as interval) 27 | + (select start_time from meta)) 28 | where slot_no is not null ; 29 | 30 | -- Finally make the timestamp non-NULLable. 31 | alter table block alter column time set not null ; 32 | -- --------------------------------------------------------------------------------------------- 33 | 34 | UPDATE schema_version SET stage_two = 3 ; 35 | RAISE NOTICE 'DB has been migrated to stage_two version %', next_version ; 36 | END IF ; 37 | END ; 38 | $$ LANGUAGE plpgsql ; 39 | 40 | SELECT migrate() ; 41 | 42 | DROP FUNCTION migrate() ; 43 | -------------------------------------------------------------------------------- /schema/migration-2-0004-20191022.sql: -------------------------------------------------------------------------------- 1 | -- Persistent generated migration. 2 | 3 | CREATE FUNCTION migrate() RETURNS void AS $$ 4 | DECLARE 5 | next_version int ; 6 | BEGIN 7 | SELECT stage_two + 1 INTO next_version FROM schema_version ; 8 | IF next_version = 4 THEN 9 | EXECUTE 'ALTER TABLE "meta" ALTER COLUMN "start_time" TYPE timestamp' ; 10 | 11 | -- ------------------------------------------------------------------------- 12 | -- Hand written SQL to update the start_time 13 | update meta set start_time = (select time from block where id = 1) where id =1 ; 14 | -- ------------------------------------------------------------------------- 15 | 16 | UPDATE schema_version SET stage_two = 4 ; 17 | RAISE NOTICE 'DB has been migrated to stage_two version %', next_version ; 18 | END IF ; 19 | END ; 20 | $$ LANGUAGE plpgsql ; 21 | 22 | SELECT migrate() ; 23 | 24 | DROP FUNCTION migrate() ; 25 | -------------------------------------------------------------------------------- /schema/migration-2-0005-20191028.sql: -------------------------------------------------------------------------------- 1 | -- Persistent generated migration. 2 | 3 | CREATE FUNCTION migrate() RETURNS void AS $$ 4 | DECLARE 5 | next_version int ; 6 | BEGIN 7 | SELECT stage_two + 1 INTO next_version FROM schema_version ; 8 | IF next_version = 5 THEN 9 | EXECUTE 'ALTER TABLE "tx" ADD COLUMN "out_sum" lovelace' ; 10 | 11 | -- ------------------------------------------------------------------------- 12 | -- Hand written SQL to update the out_sum column of the tx table. 13 | update tx 14 | set out_sum = 15 | (select sum (tx_out.value) 16 | from tx as tx_inner inner join tx_out on tx_inner.id = tx_out.tx_id 17 | where tx.id = tx_inner.id 18 | ) ; 19 | 20 | -- Make the new column non-NULLable. 21 | alter table tx alter column out_sum set not null ; 22 | -- ------------------------------------------------------------------------- 23 | 24 | UPDATE schema_version SET stage_two = 5 ; 25 | RAISE NOTICE 'DB has been migrated to stage_two version %', next_version ; 26 | END IF ; 27 | END ; 28 | $$ LANGUAGE plpgsql ; 29 | 30 | SELECT migrate() ; 31 | 32 | DROP FUNCTION migrate() ; 33 | -------------------------------------------------------------------------------- /schema/migration-2-0006-20191030.sql: -------------------------------------------------------------------------------- 1 | -- Persistent generated migration. 2 | 3 | CREATE FUNCTION migrate() RETURNS void AS $$ 4 | DECLARE 5 | next_version int ; 6 | BEGIN 7 | SELECT stage_two + 1 INTO next_version FROM schema_version ; 8 | IF next_version = 6 THEN 9 | EXECUTE 'ALTER TABLE "tx" ADD COLUMN "size" uinteger NOT NULL' ; 10 | -- Hand written SQL statements can be added here. 11 | UPDATE schema_version SET stage_two = 6 ; 12 | RAISE NOTICE 'DB has been migrated to stage_two version %', next_version ; 13 | END IF ; 14 | END ; 15 | $$ LANGUAGE plpgsql ; 16 | 17 | SELECT migrate() ; 18 | 19 | DROP FUNCTION migrate() ; 20 | -------------------------------------------------------------------------------- /schema/migration-2-0007-20191031.sql: -------------------------------------------------------------------------------- 1 | -- Persistent generated migration. 2 | 3 | CREATE FUNCTION migrate() RETURNS void AS $$ 4 | DECLARE 5 | next_version int ; 6 | BEGIN 7 | SELECT stage_two + 1 INTO next_version FROM schema_version ; 8 | IF next_version = 7 THEN 9 | EXECUTE 'ALTER TABLE "block" ADD COLUMN "tx_count" uinteger NOT NULL' ; 10 | -- ------------------------------------------------------------------------- 11 | -- It would be possible to write a schema migration here that calculates 12 | -- and inserts values in this column, but the run time of that migration 13 | -- is slower the syncing the chain from scratch. 14 | -- ------------------------------------------------------------------------- 15 | UPDATE schema_version SET stage_two = 7 ; 16 | RAISE NOTICE 'DB has been migrated to stage_two version %', next_version ; 17 | END IF ; 18 | END ; 19 | $$ LANGUAGE plpgsql ; 20 | 21 | SELECT migrate() ; 22 | 23 | DROP FUNCTION migrate() ; 24 | -------------------------------------------------------------------------------- /schema/migration-2-0008-20191112.sql: -------------------------------------------------------------------------------- 1 | -- Persistent generated migration. 2 | 3 | CREATE FUNCTION migrate() RETURNS void AS $$ 4 | DECLARE 5 | next_version int ; 6 | BEGIN 7 | SELECT stage_two + 1 INTO next_version FROM schema_version ; 8 | IF next_version = 8 THEN 9 | EXECUTE 'ALTER TABLE "meta" ADD COLUMN "network_name" VARCHAR' ; 10 | -- Hand written SQL statements can be added here. 11 | UPDATE schema_version SET stage_two = 8 ; 12 | RAISE NOTICE 'DB has been migrated to stage_two version %', next_version ; 13 | END IF ; 14 | END ; 15 | $$ LANGUAGE plpgsql ; 16 | 17 | SELECT migrate() ; 18 | 19 | DROP FUNCTION migrate() ; 20 | -------------------------------------------------------------------------------- /schema/migration-2-0009-20191119.sql: -------------------------------------------------------------------------------- 1 | -- Persistent generated migration. 2 | 3 | CREATE FUNCTION migrate() RETURNS void AS $$ 4 | DECLARE 5 | next_version int ; 6 | BEGIN 7 | SELECT stage_two + 1 INTO next_version FROM schema_version ; 8 | IF next_version = 9 THEN 9 | EXECUTE 'ALTER TABLE "slot_leader" ADD COLUMN "description" VARCHAR' ; 10 | 11 | -- ------------------------------------------------------------------------- 12 | -- Custom SQL to clean up tables. 13 | update slot_leader 14 | set description = 15 | (select desciption 16 | from slot_leader as slot_leader_inner 17 | where slot_leader.id = slot_leader_inner.id 18 | ) ; 19 | 20 | alter table slot_leader alter column description set not null ; 21 | alter table slot_leader drop column desciption ; 22 | -- ------------------------------------------------------------------------- 23 | UPDATE schema_version SET stage_two = 9 ; 24 | RAISE NOTICE 'DB has been migrated to stage_two version %', next_version ; 25 | END IF ; 26 | END ; 27 | $$ LANGUAGE plpgsql ; 28 | 29 | SELECT migrate() ; 30 | 31 | DROP FUNCTION migrate() ; 32 | -------------------------------------------------------------------------------- /schema/migration-2-0010-20191126.sql: -------------------------------------------------------------------------------- 1 | CREATE FUNCTION migrate() RETURNS void AS $$ 2 | DECLARE 3 | next_version int ; 4 | BEGIN 5 | SELECT stage_two + 1 INTO next_version FROM schema_version ; 6 | IF next_version = 10 THEN 7 | -- ------------------------------------------------------------------------- 8 | -- Hand crafted indices for performance 9 | CREATE INDEX idx_block_slot_no 10 | ON block(slot_no); 11 | 12 | CREATE INDEX idx_block_block_no 13 | ON block(block_no); 14 | 15 | CREATE INDEX idx_block_epoch_no 16 | ON block(epoch_no); 17 | 18 | CREATE INDEX idx_block_previous 19 | ON block(previous); 20 | 21 | CREATE INDEX idx_block_hash 22 | ON block(hash); 23 | 24 | CREATE INDEX idx_tx_block 25 | ON tx(block); 26 | 27 | CREATE INDEX idx_tx_hash 28 | ON tx(hash); 29 | 30 | CREATE INDEX idx_tx_in_source_tx 31 | ON tx_in(tx_in_id); 32 | 33 | CREATE INDEX idx_tx_in_consuming_tx 34 | ON tx_in(tx_out_id); 35 | 36 | CREATE INDEX idx_tx_out_tx 37 | ON tx_out(tx_id); 38 | -- ------------------------------------------------------------------------- 39 | 40 | UPDATE schema_version SET stage_two = 10 ; 41 | RAISE NOTICE 'DB has been migrated to stage_two version %', next_version ; 42 | END IF ; 43 | END ; 44 | $$ LANGUAGE plpgsql ; 45 | 46 | SELECT migrate() ; 47 | 48 | DROP FUNCTION migrate() ; 49 | -------------------------------------------------------------------------------- /schema/migration-2-0011-20200128.sql: -------------------------------------------------------------------------------- 1 | -- Persistent generated migration. 2 | 3 | CREATE FUNCTION migrate() RETURNS void AS $$ 4 | DECLARE 5 | next_version int ; 6 | BEGIN 7 | SELECT stage_two + 1 INTO next_version FROM schema_version ; 8 | IF next_version = 11 THEN 9 | EXECUTE 'CREATe TABLE "epoch"("id" SERIAL8 PRIMARY KEY UNIQUE,"out_sum" outsum NOT NULL,"tx_count" uinteger NOT NULL,"no" uinteger NOT NULL,"start_time" timestamp NOT NULL,"end_time" timestamp NOT NULL)' ; 10 | EXECUTE 'ALTER TABLE "epoch" ADD CONSTRAINT "unique_epoch" UNIQUE("no")' ; 11 | -- Hand written SQL statements can be added here. 12 | UPDATE schema_version SET stage_two = 11 ; 13 | RAISE NOTICE 'DB has been migrated to stage_two version %', next_version ; 14 | END IF ; 15 | END ; 16 | $$ LANGUAGE plpgsql ; 17 | 18 | SELECT migrate() ; 19 | 20 | DROP FUNCTION migrate() ; 21 | -------------------------------------------------------------------------------- /scripts/gen-tx-submit-config.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Unoffiical bash strict mode. 4 | # See: http://redsymbol.net/articles/unofficial-bash-strict-mode/ 5 | set -u 6 | set -o pipefail 7 | IFS=$'\n\t' 8 | 9 | if test $# -eq 0 ; then 10 | echo 11 | echo "Usage: $0 [--require-magic|--require-no-magic] --genesis-hash --output " 12 | echo 13 | exit 1 14 | fi 15 | 16 | genesis_hash="" 17 | outfile="" 18 | require_magic=2 19 | 20 | while test $# -gt 0 ; do 21 | case "$1" in 22 | --require-magic) 23 | require_magic=1 24 | shift 25 | ;; 26 | 27 | --require-no-magic) 28 | require_magic=0 29 | shift 30 | ;; 31 | 32 | --genesis-hash) 33 | shift 34 | genesis_hash="$1" 35 | shift 36 | ;; 37 | --output) 38 | shift 39 | outfile="$1" 40 | shift 41 | ;; 42 | *) 43 | ;; 44 | esac 45 | done 46 | 47 | case "$require_magic" in 48 | 0) 49 | require_magic="RequiresNoMagic" 50 | ;; 51 | 1) 52 | require_magic="RequiresMagic" 53 | ;; 54 | *) 55 | echo "Error: Missing --require-magic or --require-no-magic parameter" 56 | exit 1 57 | ;; 58 | esac 59 | 60 | sed "s/^RequiresNetworkMagic.*/RequiresNetworkMagic: ${require_magic}/;s/^GenesisHash.*/GenesisHash: ${genesis_hash}/" \ 61 | config/tx-submit-mainnet-config.yaml > "${outfile}" 62 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | # This file is used by nix-shell. 2 | # It just takes the shell attribute from default.nix. 3 | (import ./default.nix {}).shell 4 | -------------------------------------------------------------------------------- /usage.nix: -------------------------------------------------------------------------------- 1 | with import {}; 2 | let 3 | self = import ./. {}; 4 | in runCommand "usage" { 5 | buildInputs = [ 6 | self.cardano-explorer-node 7 | self.haskellPackages.cardano-explorer-db.components.exes.cardano-explorer-db-tool 8 | self.haskellPackages.cardano-node.components.exes.cardano-node 9 | ]; 10 | shellHook = '' 11 | for EXE in cardano-explorer-node cardano-explorer-db-tool cardano-node; do 12 | source <($EXE --bash-completion-script `type -p $EXE`) 13 | done 14 | ''; 15 | } ''echo 'use "nix-shell usage.nix" to use the CLI tools' '' 16 | --------------------------------------------------------------------------------