diff --git a/.changes/20260910_195554_cardano-sieve_jordan.millar_release_pipeline.yml b/.changes/20260910_195554_cardano-sieve_jordan.millar_release_pipeline.yml new file mode 100644 index 0000000..b9cd35d --- /dev/null +++ b/.changes/20260910_195554_cardano-sieve_jordan.millar_release_pipeline.yml @@ -0,0 +1,10 @@ +description: 'Initial release. A pattern-filtered chain index for Cardano: follows + a local node over node-to-client chain-sync from Byron onwards, indexes matching + outputs, spends, datums, scripts and (opt-in) redeemers into SQLite, and serves + the HTTP query API (/matches, /datums, /scripts, /checkpoints, /patterns, /health, + /metrics, /metadata) from the same process or read-only from an existing database. + Static Linux binaries for x86_64 and aarch64 are published on every release tag.' +kind: +- feature +pr: 7 +project: cardano-sieve diff --git a/.changes/_TEMPLATE.yml b/.changes/_TEMPLATE.yml new file mode 100644 index 0000000..ac20243 --- /dev/null +++ b/.changes/_TEMPLATE.yml @@ -0,0 +1,16 @@ +# Changelog fragment template - copy this file and fill in the fields. +# Or use 'herald new' for interactive creation. +# +# Files starting with _ are ignored by herald. +# +# Available projects and kinds are defined in .herald.yml + +# Which project this change belongs to (see 'projects' in .herald.yml) +project: cardano-sieve +# Pull request number associated with this change +pr: 0 +# One or more change kinds (see 'kinds' in .herald.yml) +kind: + - bugfix +description: | + Describe your change here. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..87512ac --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,204 @@ +name: Build + +# Builds fully static Linux binaries (x86_64 and aarch64) inside IOG's devx +# static shell when a cardano-sieve-A.B.C.D release tag is pushed (created by +# the herald release flow, see RELEASING.md), and publishes a GitHub Release +# with the tarballs, checksums, and the matching CHANGELOG.md section. +# Run it manually (workflow_dispatch) for a dry run: it builds and uploads the +# tarballs as workflow artifacts without creating a release. + +on: + push: + tags: ['cardano-sieve-*'] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + +env: + # devx flavour: musl GHC 9.8 + cabal, static libsodium-vrf, secp256k1, blst, + # gmp, zlib, openssl. The prebuilt shell closure is fetched from + # ghcr.io/input-output-hk/devx by the setup action; nothing is built with nix. + DEVX_COMPILER: ghc98 + PROJECT: --project-file=cabal.project.release --builddir=dist-static +jobs: + build: + name: Build (${{ matrix.arch }}-linux, static) + strategy: + fail-fast: false + matrix: + arch: [x86_64, aarch64] + runs-on: ${{ matrix.arch == 'x86_64' && 'ubuntu-24.04' || 'ubuntu-24.04-arm' }} + timeout-minutes: 360 + env: + # The musl cross compiler has to be named twice over: --with-compiler / + # --with-hc-pkg drive cabal's project-level compiler probe, while the + # --with-PROG forms reach per-package configure. Supplying only one set + # makes cabal fall back to a plain `ghc`/`ghc-pkg` on PATH for the other + # (none exists in the static shell). The shell's cabal wrapper adds the + # --with-PROG set itself via NIX_CABAL_FLAGS; spelling it out here keeps + # the build independent of that. `build` is deliberately not the first + # word: the wrapper's `cabal build` branch adds --disable-shared, which + # breaks dependency builds. Static linking comes from the project file. + CABAL_FLAGS: >- + --with-compiler=${{ matrix.arch }}-unknown-linux-musl-ghc + --with-hc-pkg=${{ matrix.arch }}-unknown-linux-musl-ghc-pkg + --with-ghc=${{ matrix.arch }}-unknown-linux-musl-ghc + --with-ghc-pkg=${{ matrix.arch }}-unknown-linux-musl-ghc-pkg + --with-hsc2hs=${{ matrix.arch }}-unknown-linux-musl-hsc2hs + + steps: + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 + with: + persist-credentials: false + + - name: Set up devx static shell + # Installs nix, imports the prebuilt closure, and provides a `devx` + # wrapper that runs a script inside the shell (CABAL_DIR=~/.cabal-static). + uses: input-output-hk/actions/devx@dbb6ea6d50ffc37a2d481fd8047aff028bac3223 # 2026-02-28 + with: + platform: ${{ matrix.arch }}-linux + target-platform: -static + compiler-nix-name: ${{ env.DEVX_COMPILER }} + minimal: true + iog: true + + - name: Configure + # devx exports the cabal.project.local snippet static links need + # (pkg-config for zlib/digest/HsOpenSSL). The freeze file keys the cache. + shell: devx {0} + env: + TRIPLE: ${{ matrix.arch }}-unknown-linux-musl + run: | + set -euo pipefail + # cabal still reaches for a plain `ghc-pkg` on PATH in one code path + # regardless of the --with flags, and the static shell has only the + # triple-prefixed tools; give it musl-backed plain names. The runner's + # git is appended for the cardano-api source-repository-package. + mkdir -p "$HOME/.musl-bin" + for t in ghc ghc-pkg hsc2hs; do ln -sf "$(command -v "$TRIPLE-$t")" "$HOME/.musl-bin/$t"; done + export PATH="$HOME/.musl-bin:$PATH:/usr/bin:/bin" + echo "$CABAL_PROJECT_LOCAL_TEMPLATE" > cabal.project.release.local + echo "::group::toolchain" + echo "NIX_CABAL_FLAGS=${NIX_CABAL_FLAGS-}" + type ghc ghc-pkg hsc2hs cabal git + "$TRIPLE-ghc" --version + cabal --version + echo "::endgroup::" + cabal update + cabal $PROJECT $CABAL_FLAGS freeze + + - name: Cache cabal store + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + with: + path: | + ~/.cabal-static/packages + ~/.cabal-static/store + key: cabal-static-${{ matrix.arch }}-linux-${{ env.DEVX_COMPILER }}-${{ hashFiles('cabal.project.release.freeze') }} + restore-keys: | + cabal-static-${{ matrix.arch }}-linux-${{ env.DEVX_COMPILER }}- + + - name: Build + shell: devx {0} + env: + TRIPLE: ${{ matrix.arch }}-unknown-linux-musl + run: | + set -euo pipefail + export PATH="$HOME/.musl-bin:$PATH:/usr/bin:/bin" + cabal $PROJECT $CABAL_FLAGS build cardano-sieve:exe:cardano-sieve -j4 + echo "BIN=$(cabal $PROJECT $CABAL_FLAGS list-bin cardano-sieve:exe:cardano-sieve)" >> "$GITHUB_ENV" + + - name: Verify + env: + REF_TYPE: ${{ github.ref_type }} + REF_NAME: ${{ github.ref_name }} + run: | + set -euo pipefail + file "$BIN" + file "$BIN" | grep -q 'statically linked' + "$BIN" --version + "$BIN" +RTS --info -RTS + if [[ "$REF_TYPE" == tag ]]; then + expected="cardano-sieve ${REF_NAME#cardano-sieve-}" + actual=$("$BIN" --version) + if [[ "$actual" != "$expected" ]]; then + echo "::error::binary reports '$actual' but tag $REF_NAME expects '$expected' (bump version: in cardano-sieve.cabal)" + exit 1 + fi + fi + + - name: Package + id: package + env: + ARCH: ${{ matrix.arch }} + REF_TYPE: ${{ github.ref_type }} + REF_NAME: ${{ github.ref_name }} + SHA: ${{ github.sha }} + run: | + set -euo pipefail + # Tag cardano-sieve-A.B.C.D -> cardano-sieve-A.B.C.D--linux.tar.gz + if [[ "$REF_TYPE" == tag ]]; then label="${REF_NAME#cardano-sieve-}"; else label="${SHA::7}"; fi + name="cardano-sieve-${label}-${ARCH}-linux" + mkdir -p dist/bin dist/share/cardano-sieve \ + dist/share/bash-completion/completions dist/share/zsh/site-functions + cp "$BIN" dist/bin/cardano-sieve + strip dist/bin/cardano-sieve + cp LICENSE dist/share/cardano-sieve/LICENSE + dist/bin/cardano-sieve --bash-completion-script cardano-sieve \ + > dist/share/bash-completion/completions/cardano-sieve + dist/bin/cardano-sieve --zsh-completion-script cardano-sieve \ + > dist/share/zsh/site-functions/_cardano-sieve + tar -czf "$name.tar.gz" -C dist bin share + ls -l "$name.tar.gz" + echo "name=$name" >> "$GITHUB_OUTPUT" + + - name: Upload artifact + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: ${{ steps.package.outputs.name }} + path: ${{ steps.package.outputs.name }}.tar.gz + if-no-files-found: error + + release: + name: Publish GitHub Release + needs: build + if: github.event_name == 'push' && github.ref_type == 'tag' + runs-on: ubuntu-24.04 + permissions: + contents: write + steps: + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 + with: + persist-credentials: false + + - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5 + with: + pattern: cardano-sieve-* + merge-multiple: true + + - name: Checksums and release notes + env: + REF_NAME: ${{ github.ref_name }} + run: | + set -euo pipefail + ls -l cardano-sieve-*.tar.gz + version="${REF_NAME#cardano-sieve-}" + sha256sum cardano-sieve-*.tar.gz | tee "cardano-sieve-${version}-sha256sums.txt" + # Fails if CHANGELOG.md has no section for this version. + scripts/ci/extract-changelog.sh "$version" > RELEASE_NOTES.md + echo "VERSION=$version" >> "$GITHUB_ENV" + + - name: Create release + uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2 + with: + tag_name: ${{ github.ref_name }} + name: ${{ env.VERSION }} + body_path: RELEASE_NOTES.md + draft: false + fail_on_unmatched_files: true + files: | + cardano-sieve-*.tar.gz + cardano-sieve-*-sha256sums.txt diff --git a/.github/workflows/check-pr-changelog.yml b/.github/workflows/check-pr-changelog.yml new file mode 100644 index 0000000..ae6dabf --- /dev/null +++ b/.github/workflows/check-pr-changelog.yml @@ -0,0 +1,33 @@ +name: Check changelog fragments + +# Every PR must carry a herald changelog fragment in .changes/ whose `pr:` +# matches the PR number. Create one with: +# nix run github:input-output-hk/cardano-dev/herald-0.2.0.0#herald -- new + +permissions: + contents: read + +on: + merge_group: + pull_request: + types: [opened, synchronize, ready_for_review] + +jobs: + check-changelog: + # Skip for merge_group: batched diffs would cause false positives and + # the PR number is unavailable. The trigger is kept so the check reports + # "skipped" (passes) when it is a required status check. + if: ${{ github.event_name != 'merge_group' }} + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + fetch-depth: 0 + persist-credentials: false + + - uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31 + with: + extra_nix_config: | + accept-flake-config = true + + - uses: input-output-hk/cardano-dev/actions/herald-validate@9106b37910e40be00cda65051922d6ef939fa67a # herald-validate-0.0.1.1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..b60f6c9 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,46 @@ +name: Release + +# Cuts a release the way cardano-api does, with herald: computes the next PVP +# version from the changelog fragments (or takes an explicit one), creates a +# release/cardano-sieve-A.B.C.D branch with the changelog section and cabal +# version bump, and opens a release PR. Signing the commits and pushing the +# cardano-sieve-A.B.C.D tag is done by hand from the PR's instructions; the +# tag push then triggers the Build workflow, which publishes the binaries. +# See RELEASING.md. + +on: + workflow_dispatch: + inputs: + version: + description: 'Explicit version (A.B.C.D). Leave empty to compute from the changelog fragments.' + required: false + type: string + branch: + description: 'Branch to release from and target for the PR. Leave empty for the selected branch.' + required: false + type: string + +jobs: + release: + runs-on: ubuntu-24.04 + permissions: + contents: write + pull-requests: write + + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + ref: ${{ inputs.branch || github.ref_name }} + fetch-depth: 0 + + - uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31 + with: + extra_nix_config: | + accept-flake-config = true + + - uses: input-output-hk/cardano-dev/actions/herald-release@609046ca0378bdac28ff9abc4b42d7dcf9a96965 # herald-release-0.0.3.0 + with: + package: cardano-sieve + version: ${{ inputs.version }} + base-branch: ${{ inputs.branch || github.ref_name }} + chap-instructions: false diff --git a/.gitignore b/.gitignore index b80913e..c52ad78 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ bench/ .ghc.environment.* *.hi *.o +cabal.project.release.local +cabal.project.release.freeze +dist-static/ diff --git a/.herald.yml b/.herald.yml new file mode 100644 index 0000000..025176f --- /dev/null +++ b/.herald.yml @@ -0,0 +1,68 @@ +# Herald configuration +# Changelog and release automation for PVP-versioned Haskell projects + +# Git repository URL (used for PR links in changelogs) +git-repo: https://github.com/IntersectMBO/cardano-sieve + +# Directory for changelog fragments, relative to repo root +# Fragments are YAML files created by 'herald new' and consumed by 'herald batch' +changes-dir: .changes + +# Change kinds and their properties +# +# Each kind defines: +# notable: if true (default), changes of this kind appear in the changelog +# if false, they still bump the version but are omitted from the changelog text +# bump: PVP version component to bump when this kind is present +# 0.1.0.0 = bump 2nd component (breaking changes) +# 0.0.1.0 = bump 3rd component (features, compatible changes) +# 0.0.0.1 = bump 4th component (patches, internal changes) +# +# The highest bump across all fragment kinds determines the final version bump. +kinds: + breaking: + bump: 0.1.0.0 + description: the API has changed in a breaking way + bugfix: + bump: 0.0.0.1 + description: fixes a defect + compatible: + bump: 0.0.1.0 + description: the API has changed but is non-breaking + documentation: + notable: false + bump: 0.0.0.1 + description: change in code docs, haddocks + feature: + bump: 0.0.1.0 + description: introduces a new feature + maintenance: + notable: false + bump: 0.0.0.1 + description: not directly related to the code + optimisation: + bump: 0.0.0.1 + description: measurable performance improvements + refactoring: + notable: false + bump: 0.0.0.1 + description: code quality improvements + release: + notable: false + bump: 0.0.0.1 + description: related to a new release preparation + test: + notable: false + bump: 0.0.0.1 + description: fixes or modifies tests + +# Projects in this repository +# +# Each project needs: +# changelog: path to the project's CHANGELOG.md (relative to repo root) +# cabal-file: (optional) path to the project's .cabal file (version is read/updated here) +# version-file: (optional) path to a plain-text version file (alternative to cabal-file) +projects: + cardano-sieve: + changelog: CHANGELOG.md + cabal-file: cardano-sieve.cabal diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..670f3fb --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1 @@ +# Changelog for cardano-sieve diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index 7eb88e4..ef73c6d 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,45 @@ tracks every matching UTxO — when it was created, when it was spent, and by what. It follows a local node over node-to-client ChainSync, writes matches into SQLite, and serves them over an HTTP API. +## Installation + +Pre-built, fully static Linux binaries are attached to every +[GitHub Release](https://github.com/IntersectMBO/cardano-sieve/releases). They +have no runtime dependencies and run on any x86_64 or aarch64 Linux distribution. + +| Platform | Asset | +| --------------- | --------------------------------------------- | +| `x86_64-linux` | `cardano-sieve-A.B.C.D-x86_64-linux.tar.gz` | +| `aarch64-linux` | `cardano-sieve-A.B.C.D-aarch64-linux.tar.gz` | +| macOS | build from source (see below) | + +```bash +V=0.1.0.0; ARCH=x86_64 # or aarch64 +BASE=https://github.com/IntersectMBO/cardano-sieve/releases/download/cardano-sieve-$V +curl -fLO "$BASE/cardano-sieve-$V-$ARCH-linux.tar.gz" +curl -fLO "$BASE/cardano-sieve-$V-sha256sums.txt" +sha256sum --check --ignore-missing "cardano-sieve-$V-sha256sums.txt" +tar -xzf "cardano-sieve-$V-$ARCH-linux.tar.gz" +./bin/cardano-sieve --version +``` + +The tarball contains `bin/cardano-sieve`, the licence, and bash/zsh completion +scripts under `share/`. + ## Building +A regular dynamic build needs GHC 9.8, cabal, and the IOG crypto libraries +(libsodium, libsecp256k1, libblst) on the linker path: + ```bash -cabal build cardano-sieve -j4 +cabal build exe:cardano-sieve -j4 ``` +The static release binary is built with the same toolchain CI uses, IOG's +[devx](https://github.com/input-output-hk/devx) shell, via +`cabal.project.release`. See [RELEASING.md](RELEASING.md) for the exact +commands and for how releases are cut. + ## Usage One executable, three modes, decided by which options you pass: @@ -27,7 +60,7 @@ There is also `--build-indexes`, which installs the deferred query indexes on ### Indexing ```bash -cabal run cardano-sieve -- \ +cardano-sieve \ --socket-path ~/node.socket \ --testnet-magic 2 \ --database sieve.sqlite \ @@ -60,13 +93,13 @@ build them separately afterwards — `--build-indexes` does exactly that and then exits: ```bash -cabal run cardano-sieve -- --database sieve.sqlite --build-indexes +cardano-sieve --database sieve.sqlite --build-indexes ``` ### Serving queries ```bash -cabal run cardano-sieve -- --database sieve.sqlite --serve 1442 +cardano-sieve --database sieve.sqlite --serve 1442 ``` Serve-only mode never touches a node: `/health` reports the connection as diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..b6d6253 --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,88 @@ +# Releasing cardano-sieve + +cardano-sieve follows the cardano-api release process, driven by +[herald](https://github.com/input-output-hk/cardano-dev/tree/main/herald), +minus the CHaP upload (cardano-sieve is an executable, not a library). The +binaries are then built and published by CI from the release tag. + +## Versioning + +Haskell [PVP](https://pvp.haskell.org/): `A.B.C.D`. Herald computes the bump +from the changelog fragments' kinds (see `.herald.yml`): `breaking` bumps `B`, +`feature`/`compatible` bump `C`, everything else bumps `D`. The git tag is +`cardano-sieve-A.B.C.D`. + +## Every pull request: add a changelog fragment + +Each PR commits a small YAML file under `.changes/` describing the change. +The **Check changelog fragments** workflow fails the PR otherwise. + +```bash +nix run github:input-output-hk/cardano-dev/herald-0.2.0.0#herald -- new +``` + +Or copy `.changes/_TEMPLATE.yml`. Set `pr:` to the PR number. + +## Cutting a release + +1. Run the **Release** workflow + (Actions > Release > Run workflow, or from the CLI): + + ```bash + gh workflow run release.yml + # or with an explicit version: + gh workflow run release.yml -f version=0.1.0.0 + ``` + + It creates a `release/cardano-sieve-A.B.C.D` branch that batches the + fragments into `CHANGELOG.md`, bumps `version:` in `cardano-sieve.cabal`, + removes the consumed fragments, and opens a release PR. The PR body + contains the exact signing commands for the next step. + +2. Review the release PR: it should contain only the changelog section, the + version bump, and the removed fragments. + +3. Sign and tag, following the commands in the PR body. In short: + + ```bash + git fetch origin release/cardano-sieve-A.B.C.D + git checkout -B release/cardano-sieve-A.B.C.D origin/release/cardano-sieve-A.B.C.D + git rebase --force-rebase --gpg-sign HEAD~2 + git tag -s -m "cardano-sieve A.B.C.D" cardano-sieve-A.B.C.D HEAD~1 + git push --force origin release/cardano-sieve-A.B.C.D + git push origin cardano-sieve-A.B.C.D + ``` + +4. The tag push runs the **Build** workflow (it runs only on release tags, or + by hand as a dry run), which builds the static binaries, + checks that `cardano-sieve --version` matches the tag, and publishes the + GitHub Release at + with: + + - `cardano-sieve-A.B.C.D-x86_64-linux.tar.gz` + - `cardano-sieve-A.B.C.D-aarch64-linux.tar.gz` + - `cardano-sieve-A.B.C.D-sha256sums.txt` + + and the matching `CHANGELOG.md` section as release notes. + +5. Merge the release PR. + +## Building a release binary locally + +CI gets the toolchain as a prebuilt closure from `ghcr.io/input-output-hk/devx` +(via `input-output-hk/actions/devx`). Locally the same shell comes from the devx +flake; note the IOG binary cache does not always carry every musl library for +the current devx revision, so the first entry may compile a few C libraries: + +```bash +nix develop github:input-output-hk/devx#ghc98-static-minimal-iog --no-write-lock-file \ + --command bash -c ' + echo "$CABAL_PROJECT_LOCAL_TEMPLATE" > cabal.project.release.local + cabal --project-file=cabal.project.release update + cabal --project-file=cabal.project.release --builddir=dist-static build cardano-sieve:exe:cardano-sieve -j4' +file "$(cabal --project-file=cabal.project.release --builddir=dist-static list-bin cardano-sieve)" +``` + +`cabal.project.release` imports `cabal.project` and adds the flags that make a +static musl link work (`text -simdutf`, `formatting +no-double-conversion`, +`blockio +serialblockio`). diff --git a/cabal.project.release b/cabal.project.release new file mode 100644 index 0000000..4267680 --- /dev/null +++ b/cabal.project.release @@ -0,0 +1,26 @@ +-- Release build configuration: fully static Linux binaries. +-- +-- Used by CI (.github/workflows/build.yml) via +-- cabal --project-file=cabal.project.release build cardano-sieve:exe:cardano-sieve +-- inside IOG's devx static shell (github:input-output-hk/devx#ghc98-static-minimal-iog), +-- which supplies a musl GHC plus static libsodium-vrf, secp256k1, blst, gmp, zlib. +-- +-- Kept separate from cabal.project so day-to-day dynamic builds are untouched: +-- the flags below would otherwise force a rebuild of most of the dependency closure. +import: cabal.project + +-- The devx static shell already links executables statically; this makes the +-- intent explicit and fails loudly if the toolchain ever stops doing so. +executable-static: True + +-- text's simdutf and formatting's double-conversion pull in a C++ runtime, which +-- breaks static linking with musl. +package text + flags: -simdutf + +package formatting + flags: +no-double-conversion + +-- blockio defaults to io_uring, which needs liburing at link time. +constraints: + , any.blockio +serialblockio diff --git a/cardano-sieve.cabal b/cardano-sieve.cabal index 336ab81..e0f19ab 100644 --- a/cardano-sieve.cabal +++ b/cardano-sieve.cabal @@ -7,9 +7,13 @@ description: matching UTxO — when it was created, when it was spent, and by what. Single-threaded pipelined indexing architecture (ADR-020). license: Apache-2.0 +license-file: LICENSE author: Jordan Millar maintainer: jordan.millar@iohk.io build-type: Simple +tested-with: GHC == 9.8.4 +extra-doc-files: CHANGELOG.md + README.md common project-config default-language: Haskell2010 diff --git a/scripts/ci/extract-changelog.sh b/scripts/ci/extract-changelog.sh new file mode 100755 index 0000000..0fc6b7d --- /dev/null +++ b/scripts/ci/extract-changelog.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# Print the CHANGELOG.md section for one version, without its heading. +# The changelog is written by herald, whose section headings look like +# ## 0.1.0.0 -- 2026-09-10 +# +# scripts/ci/extract-changelog.sh 0.1.0.0 +# +# Exits non-zero (with no output) when the section is missing or empty, so a +# release cannot be cut before the changelog has been batched. +set -euo pipefail + +version="${1:?usage: extract-changelog.sh VERSION}" +changelog="${2:-CHANGELOG.md}" + +body=$(awk -v v="$version" ' + /^## / { if (found) exit; found = ($0 ~ "^## " v "( |$)"); next } + found { print } +' "$changelog") + +# Trim leading/trailing blank lines. +body=$(printf '%s\n' "$body" | sed -e :a -e '/^\n*$/{$d;N;ba' -e '}' | sed '/./,$!d') + +if [[ -z "$body" ]]; then + echo "extract-changelog.sh: no changelog entry for version $version in $changelog" >&2 + exit 1 +fi + +printf '%s\n' "$body" diff --git a/src/Cardano/Sieve.hs b/src/Cardano/Sieve.hs index ce8b658..abd9fe7 100644 --- a/src/Cardano/Sieve.hs +++ b/src/Cardano/Sieve.hs @@ -46,6 +46,7 @@ import Control.Exception (AsyncException (UserInterrupt), bracket, throwTo) import Data.Bifunctor (first) import Data.Text qualified as T import Data.Text.Encoding (encodeUtf8) +import Data.Version (showVersion) import Options.Applicative ( Parser , ParserInfo @@ -63,6 +64,7 @@ import Options.Applicative , progDesc , showDefault , showDefaultWith + , simpleVersioner , strOption , switch , value @@ -73,6 +75,8 @@ import System.IO (BufferMode (LineBuffering), hSetBuffering, stdout) import System.Posix.Signals (Handler (CatchOnce), installHandler, sigTERM) import Text.Read (readMaybe) +import Paths_cardano_sieve (version) + -- | What the process was asked to do. -- -- The three are mutually exclusive and need /different/ inputs, which is what a @@ -275,7 +279,7 @@ invocationOf raw = optionsInfo :: ParserInfo RawOptions optionsInfo = info - (optionsParser <**> helper) + (optionsParser <**> helper <**> simpleVersioner ("cardano-sieve " <> showVersion version)) ( fullDesc <> progDesc "Follow a local node's chain, sieve each block's outputs against the \