diff --git a/.github/bin/check-git-dependencies b/.github/bin/check-git-dependencies index fafbd599d7..8f5c3053b7 100755 --- a/.github/bin/check-git-dependencies +++ b/.github/bin/check-git-dependencies @@ -23,7 +23,7 @@ commits () { done } -grep '\(^source-repository-package\|^ *location:\|^ *tag:\)' cabal.project | sed 's|^source-repository-package|-|g' | \ +cat cabal.project | sed '/-- WASM compilation specific/q' | grep '\(^source-repository-package\|^ *location:\|^ *tag:\)' - | sed 's|^source-repository-package|-|g' | \ yq eval -P -j \ > tmp/repositories.json diff --git a/.github/workflows/haskell-wasm.yml b/.github/workflows/haskell-wasm.yml new file mode 100644 index 0000000000..3222d398cc --- /dev/null +++ b/.github/workflows/haskell-wasm.yml @@ -0,0 +1,183 @@ +name: Haskell CI (WASM) + +on: + merge_group: + pull_request: + push: + # we need this to populate cache for `master` branch to make it available to the child branches, see + # https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#restrictions-for-accessing-a-cache + branches: + - master + # GH caches are removed when not accessed within 7 days - this schedule runs the job every 6 days making + # sure that we always have some caches on master +# schedule: +# - cron: '0 0 */6 * *' + +jobs: + build: + runs-on: ${{ matrix.sys.os }} + + strategy: + fail-fast: false + matrix: + sys: + - { os: ubuntu-latest, shell: bash } +# - { os: macos-latest, shell: bash } + + defaults: + run: + shell: ${{ matrix.sys.shell }} + + env: + # Modify this value to "invalidate" the cabal cache. + CABAL_CACHE_VERSION: "2025-05-29" + + concurrency: + group: > + wasm + a+${{ github.event_name }} + b+${{ github.workflow_ref }} + c+${{ github.job }} + f+${{ matrix.sys.os }} + g+${{ (startsWith(github.ref, 'refs/heads/gh-readonly-queue/') && github.run_id) || github.event.pull_request.number || github.ref }} + cancel-in-progress: true + + steps: + - name: Concurrency group + run: > + echo + wasm + a+${{ github.event_name }} + b+${{ github.workflow_ref }} + c+${{ github.job }} + f+${{ matrix.sys.os }} + g+${{ (startsWith(github.ref, 'refs/heads/gh-readonly-queue/') && github.run_id) || github.event.pull_request.number || github.ref }} + + - uses: actions/checkout@v4 + + - uses: cachix/install-nix-action@v30 + with: + nix_path: nixpkgs=channel:nixos-unstable + extra_nix_config: | + trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ= + substituters = https://cache.iog.io/ https://cache.nixos.org/ + + - uses: rrbutani/use-nix-shell-action@v1 + with: + devShell: .#wasm + + - name: Cabal update + run: | + wasm32-wasi-cabal update + + # A dry run `build all` operation does *NOT* downlaod anything, it just looks at the package + # indices to generate an install plan. + - name: Build dry run + run: | + wasm32-wasi-cabal build cardano-wasm --dry-run + + # From the install plan we generate a dependency list. + - name: Record dependencies + id: record-deps + run: | + cat dist-newstyle/cache/plan.json | jq -r '."install-plan"[] | select(.style != "local") | .id' | sort | uniq > dependencies.txt + + - name: Store month number as environment variable used in cache version + run: | + cat <> $GITHUB_ENV + MONTHNUM=$(date -u '+%m') + GHC=$(ghc --numeric-version) + STORE=$(wasm32-wasi-cabal path --store | tail -n 1) + EOF + +# Cache is disabled because GHA default builders are not able to build all dependencies +# because they lack RAM, so having the cache expire would break the CI check. +# For this reason, we are providing a build of the dependencies instead in +# the "Restore cached deps" step, and we make the check not required. +# When we are able to make this CI check self-sufficient, we should reenable the +# caching and remove the manual restoring of cached deps. + + # From the dependency list we restore the cached dependencies. + # We use the hash of `dependencies.txt` as part of the cache key because that will be stable + # until the `index-state` values in the `cabal.project` file changes. +# - name: Restore cached dependencies +# uses: actions/cache/restore@v4 +# id: cache +# with: +# path: | +# ${{ env.STORE }} +# dist-newstyle +# key: +# wasm-cache-${{ env.CABAL_CACHE_VERSION }}-${{ runner.os }}-${{ env.GHC }}-${{ hashFiles('cardano-wasm/dependencies.txt') }} +# restore-keys: | +# wasm-cache-${{ env.CABAL_CACHE_VERSION }}-${{ runner.os }}-${{ env.GHC }}- + + - name: Restore cached deps + run: | + wget "https://agrius.feralhosting.com/palas/wasm-cache/4c200033737be4736cd2a363d64c49a385937d5ea57d8e52773f65d08bbd1342.tar.bz2" + tar -jxf 4c200033737be4736cd2a363d64c49a385937d5ea57d8e52773f65d08bbd1342.tar.bz2 + rm -fr ~/.ghc-wasm/.cabal/store/ + mv store ~/.ghc-wasm/.cabal/ + + # Now we install the dependencies. If the cache was found and restored in the previous step, + # this should be a no-op, but if the cache key was not found we need to build stuff so we can + # cache it for the next step. + - name: Install dependencies + run: | + wasm32-wasi-cabal build cardano-wasm --only-dependencies --no-semaphore -j1 --ghc-options="-j1" + + # Always store the cabal cache. +# - name: Cache Cabal store +# uses: actions/cache/save@v4 +# if: always() +# with: +# path: | +# ${{ env.STORE }} +# dist-newstyle +# key: +# ${{ steps.cache.outputs.cache-primary-key }} + + # Now we build. + - name: Build all + run: | + wasm32-wasi-cabal build cardano-wasm --no-semaphore -j1 --ghc-options="-j1" + + # - name: Run tests + # env: + # TMPDIR: ${{ runner.temp }} + # TMP: ${{ runner.temp }} + # KEEP_WORKSPACE: 1 + # run: cabal test all --enable-tests --test-show-details=direct + + # Uncomment the following back in for debugging. Remember to launch a `pwsh` from + # the tmux session to debug `pwsh` issues. And be reminded that the `/msys2` and + # `/msys2/mingw64` paths are not in PATH by default for the workflow, but tmate + # will put them in. + # You may also want to run + # + # $env:PATH=("C:\Program Files\PowerShell\7;{0}" -f $env:ORIGINAL_PATH) + # + # to restore the original path. Do note that some test might need msys2 + # and will silently fail if msys2 is not in path. See the "Run tests" step. + # + # - name: Setup tmate session + # if: ${{ failure() }} + # uses: mxschmitt/action-tmate@v3 + # with: + # limit-access-to-actor: true + + wasm-builds-complete: + needs: [build] + if: ${{ always() }} + runs-on: ubuntu-latest + steps: + - name: Check if any previous job failed + run: | + if [[ "${{ needs.build.result }}" == "failure" ]]; then + # this ignores skipped dependencies + echo 'Required jobs failed to build.' + exit 1 + else + echo 'Build complete' + fi + diff --git a/.github/workflows/hls.yml b/.github/workflows/hls.yml index 201f969797..aa5e21cc60 100644 --- a/.github/workflows/hls.yml +++ b/.github/workflows/hls.yml @@ -14,7 +14,7 @@ jobs: test-hls-works: env: # Modify this value to "invalidate" the cache. - HLS_CACHE_VERSION: "2024-07-24" + HLS_CACHE_VERSION: "2025-06-11" runs-on: ubuntu-latest timeout-minutes: 60 diff --git a/.gitignore b/.gitignore index 52c85c4f0c..d871f6b815 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,9 @@ dist/ result* /launch-* stack.yaml.lock +*.hi +*.o +Main /.cache /db @@ -40,12 +43,12 @@ supervisord.pid tags /config /data -./*.skey -./*.vkey -./*.cert +/*.skey +/*.vkey +/*.cert # For now require that users generate their own hie.yaml -hie.yaml +/hie.yaml # Ignore Visual Studio code configuration .vscode/tasks.json diff --git a/cabal.project b/cabal.project index 7056b47e33..e5c81ac386 100644 --- a/cabal.project +++ b/cabal.project @@ -19,6 +19,7 @@ index-state: packages: cardano-api cardano-api-gen + cardano-wasm extra-packages: Cabal, process @@ -59,3 +60,94 @@ if impl (ghc >= 9.12) -- https://github.com/kapralVV/Unique/issues/11 , Unique:hashable +-- WASM compilation specific + +if arch(wasm32) + source-repository-package + type: git + location: https://github.com/amesgen/plutus.git + tag: dc1edea4458d6fb794b245a26c730620265645f3 + subdir: + plutus-core + plutus-ledger-api + plutus-tx + --sha256: sha256-QBtLmoS54b5QMAKIDOJIM6lmRC+1leBpuGKaFc7QQos= + + package plutus-core + flags: +do-not-build-plutus-exec + + source-repository-package + type: git + location: https://github.com/haskell-wasm/hs-memory.git + tag: a198a76c584dc2cfdcde6b431968de92a5fed65e + --sha256: sha256-LRC3L+J921+/moZS7F17vCfM/4usYy/eMR+w/mXsjeA= + + source-repository-package + type: git + location: https://github.com/palas/ouroboros-network.git + tag: ef3e30603e4e45dac336a085114ee22b7aa8c9ed + subdir: + ouroboros-network + ouroboros-network-framework + --sha256: sha256-+IdAmWJqzRy+erKONywtk+5YLrm63q942nZavoEA4E4= + + source-repository-package + type: git + location: https://github.com/palas/criterion.git + tag: dd160d2b5f051e918e72fe1957d77905682b8d6c + subdir: + criterion-measurement + --sha256: sha256-wzEwOUTeFL0C3QnS25/3X1ue2tUuedrLqtT0h1JZW6c= + + source-repository-package + type: git + location: https://github.com/palas/haskell-lmdb-mock.git + tag: c8d61e6eee03ee271e7768c0576110da885aec48 + --sha256: sha256-+gB1MmM6qRApz1p7tFsdvKoAWDrYB4a+bJ9Djm6ieYI= + + source-repository-package + type: git + location: https://github.com/palas/double-conversion.git + tag: b2030245727ee56de76507fe305e3741f6ce3260 + --sha256: sha256-kzwHHQzHPfPnIDtnSDAom7YGSzWjr0113x0zsfI/Tb0= + + source-repository-package + type: git + location: https://github.com/amesgen/cborg + tag: 2dff24d241d9940c5a7f5e817fcf4c1aa4a8d4bf + subdir: cborg + --sha256: sha256-yuz1apKQ0EB9LtJkc/I1EEtB4oZnURMvCckvdFbT6qM= + + source-repository-package + type: git + location: https://github.com/Jimbo4350/foundation.git + tag: b3cb78484fe6f6ce1dfcef59e72ceccc530e86ac + subdir: + basement + foundation + --sha256: sha256-QKKHl/XocxGD7bwAoGe7VaIg9o8x4dA20j3sJOgiTBw= + + source-repository-package + type: git + location: https://github.com/palas/mempack.git + tag: 0211addbbbf51011e5348d3696566eb12ccbef07 + --sha256: sha256-iLc+foF2AM3vG6deuZ51+faI6buMkubMP75md51hMe8= + + source-repository-package + type: git + location: https://github.com/haskell-wasm/network + tag: ab92e48e9fdf3abe214f85fdbe5301c1280e14e9 + --sha256: sha256-U+ln/gbXoQZpNjZHydNa0FG/9GdJFgL1+T3+7KTzDWo= + + package cardano-crypto-praos + flags: -external-libsodium-vrf + + package atomic-counter + flags: +no-cmm + + constraints: time installed + allow-newer: time + + package crypton + ghc-options: -optc-DARGON2_NO_THREADS + diff --git a/cardano-api/cardano-api.cabal b/cardano-api/cardano-api.cabal index 87b12dbb2d..a0ca1240a9 100644 --- a/cardano-api/cardano-api.cabal +++ b/cardano-api/cardano-api.cabal @@ -38,7 +38,7 @@ common project-config -Wunused-packages common maybe-unix - if !os(windows) + if !(os(windows)|| arch(wasm32)) build-depends: unix common maybe-Win32 @@ -236,6 +236,7 @@ library Cardano.Api.IO.Internal.Base Cardano.Api.IO.Internal.Compat Cardano.Api.IO.Internal.Compat.Posix + Cardano.Api.IO.Internal.Compat.Wasm Cardano.Api.IO.Internal.Compat.Win32 Cardano.Api.Internal.Orphans Cardano.Api.Internal.Orphans.Misc diff --git a/cardano-api/src/Cardano/Api/IO/Internal/Compat.hs b/cardano-api/src/Cardano/Api/IO/Internal/Compat.hs index 0ae605175d..439b42077e 100644 --- a/cardano-api/src/Cardano/Api/IO/Internal/Compat.hs +++ b/cardano-api/src/Cardano/Api/IO/Internal/Compat.hs @@ -11,6 +11,7 @@ where import Cardano.Api.Error import Cardano.Api.IO.Internal.Base import Cardano.Api.IO.Internal.Compat.Posix +import Cardano.Api.IO.Internal.Compat.Wasm import Cardano.Api.IO.Internal.Compat.Win32 import Control.Monad.Except (ExceptT) diff --git a/cardano-api/src/Cardano/Api/IO/Internal/Compat/Posix.hs b/cardano-api/src/Cardano/Api/IO/Internal/Compat/Posix.hs index 85d88a13e6..c17fadc8f8 100644 --- a/cardano-api/src/Cardano/Api/IO/Internal/Compat/Posix.hs +++ b/cardano-api/src/Cardano/Api/IO/Internal/Compat/Posix.hs @@ -1,7 +1,7 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE ScopedTypeVariables #-} -#if !defined(mingw32_HOST_OS) +#if !defined(mingw32_HOST_OS) && !defined(wasm32_HOST_ARCH) #define UNIX #endif diff --git a/cardano-api/src/Cardano/Api/IO/Internal/Compat/Wasm.hs b/cardano-api/src/Cardano/Api/IO/Internal/Compat/Wasm.hs new file mode 100644 index 0000000000..42dc66dd6f --- /dev/null +++ b/cardano-api/src/Cardano/Api/IO/Internal/Compat/Wasm.hs @@ -0,0 +1,40 @@ +{-# LANGUAGE CPP #-} + +#if defined(wasm32_HOST_ARCH) +#define WASM +#endif + +module Cardano.Api.IO.Internal.Compat.Wasm + ( +#ifdef WASM + checkVrfFilePermissionsImpl + , handleFileForWritingWithOwnerPermissionImpl + , writeSecretsImpl +#endif + ) +where + +#ifdef WASM + +import Cardano.Api.Error (FileError (..)) +import Cardano.Api.IO.Internal.Base +import Control.Monad.Except (ExceptT) +import Data.ByteString (ByteString) +import System.IO (Handle) + +handleFileForWritingWithOwnerPermissionImpl + :: FilePath + -> (Handle -> IO ()) + -> IO (Either (FileError e) ()) +handleFileForWritingWithOwnerPermissionImpl _path _f = return $ Right () -- Dummy implementation for WASM + +writeSecretsImpl :: FilePath -> [Char] -> [Char] -> (a -> ByteString) -> [a] -> IO () +writeSecretsImpl _outDir _prefix _suffix _secretOp _xs = return () -- Dummy implementation for WASM + +-- | Make sure the VRF private key file is readable only +-- by the current process owner the node is running under. +checkVrfFilePermissionsImpl + :: File content direction -> ExceptT VRFPrivateKeyFilePermissionError IO () +checkVrfFilePermissionsImpl _vrfPrivKeyFile = return () -- Dummy implementation for WASM + +#endif diff --git a/cardano-wasm/README.md b/cardano-wasm/README.md new file mode 100644 index 0000000000..824135dde4 --- /dev/null +++ b/cardano-wasm/README.md @@ -0,0 +1,205 @@ +# cardano-wasm + +Part of an effort at IOG to build Cardano Haskell libraries to Wasm. + +## Building the wasm module + +### Setting up build environment with nix + +Enter the Nix shell by writing `nix develop .#wasm` on a shell in this folder and move on to the [Compiling `cardano-wasm` section](#compiling-cardano-wasm). + +### Setting up build environment without nix + +For the installation we will need some dependencies. In Debian based distros we can install them using apt like this: + +```console +sudo apt install happy pkgconf libtool git wget curl jq unzip zstd tar gzip +``` + +#### Installing `ghc` for wasm + +Then it is necessary to get `ghc` for wasm, and you see how to do that [here](https://gitlab.haskell.org/haskell-wasm/ghc-wasm-meta#getting-started-without-nix). At the moment, it is necessary to install a version of `ghc` that has `base <= 4.20`, so I would recommend installing `wasm32-wasi-9.10` like this: + +```console +wget "https://gitlab.haskell.org/haskell-wasm/ghc-wasm-meta/-/archive/master/ghc-wasm-meta-master.tar.gz" +tar -zxf ghc-wasm-meta-master.tar.gz +cd ghc-wasm-meta-master +FLAVOUR=9.10 ./setup.sh +source ~/.ghc-wasm/env +cd .. +``` + +After installing `ghc` for wasm, `~/.ghc-wasm` should contain all the installed tools, and `~/.ghc-wasm/wasm32-wasi-ghc/bin` should be in your `PATH` environment variable. You can check that this is the case by running the following command: +```console +$ wasm32-wasi-ghc --version +The Glorious Glasgow Haskell Compilation System, version 9.10.1.20250327 +``` + +And that should not return: +```console +wasm32-wasi-ghc: command not found +``` + +Then we need to compile three libraries to WASM: `libblst`, `libsodium`, and `libsecp256k1`. + +In order to not interfere with the system library installation, we will create a folder to serve as our prefix: + +```console +mkdir -p ~/prefix/{lib/pkgconfig,include} +``` + +#### Installing `libblst` + +We can obtain `libblst` from GitHub [here](https://github.com/supranational/blst). So we can use `git` to get its source code: + +```console +git clone "https://github.com/supranational/blst.git" +``` + +Then we get into the downloaded folder and we build it as follows: + +```console +cd blst +./build.sh +``` + +And we copy the result and includes to our prefix as follows: + +```console +cp libblst.a ~/prefix/lib/ +cp bindings/{blst.h,blst_aux.h} ~/prefix/include/ +``` + +We generate a dynamic version of the library: + +```console +wasm32-wasi-clang -shared -Wl,--whole-archive ~/prefix/lib/libblst.a -o ~/prefix/lib/libblst.so +``` + +And finally we write an entry for `pkgconfig`, so that later `ghc` can find our prefix: + +``` +cat < $HOME/prefix/lib/pkgconfig/libblst.pc +prefix=$HOME/prefix +exec_prefix=\${prefix} +libdir=\${exec_prefix}/lib +includedir=\${prefix}/include + +Name: libblst +Description: blst (pronounced 'blast') is a BLS12-381 signature library focused on performance and security +URL: https://github.com/supranational/blst +Version: 0.3.15 + +Cflags: -I\${includedir} +Libs: -L\${libdir} -lblst +Libs.private: +EOF +``` + +Finally we leave the folder: + +```console +cd .. +``` + +#### Installing `libsodium` + +We can also obtain `libsodium` from its website [here](https://libsodium.org). We can use `wget` to get the source code for one of its releases. For example: + +```console +wget "https://download.libsodium.org/libsodium/releases/libsodium-1.0.20-stable.tar.gz" +``` + +Then we extract it, get into the created folder and compile it as follows: + +```console +tar -zxf libsodium-1.0.20-stable.tar.gz +cd libsodium-stable +./configure --host=wasm32-wasi --prefix=$HOME/prefix +make +make install +``` + +Finally we generate a dynamic version of the library, and we leave the folder: + +```console +wasm32-wasi-clang -shared -Wl,--whole-archive ~/prefix/lib/libsodium.a -o ~/prefix/lib/libsodium.so +cd .. +``` + +#### Installing `libsecp256k1` + +We can obtain `libsecp256k1 ` from GitHub [here](https://github.com/bitcoin-core/secp256k1). So we can use `git` to get its source code: + +```console +git clone "https://github.com/bitcoin-core/secp256k1.git" +``` + +Then we get into the downloaded folder, and we build it as follows: + +```console +cd secp256k1 +./autogen.sh +./configure --prefix=$HOME/prefix --host=wasm32-wasi --enable-module-schnorrsig SECP_CFLAGS=-fPIC +make +make install +``` + +Finally we generate a dynamic version of the library, and we leave the folder: + +```console +wasm32-wasi-clang -shared -Wl,--whole-archive ~/prefix/lib/libsecp256k1.a -o ~/prefix/lib/libsecp256k1.so +cd .. +``` + +#### Set up `pkg-config` + +First we make sure we have `pkg-config` installed, in Debian based distros this can be done with apt: + +```console +sudo apt install pkgconf +``` + +And we set the variable `PKG_CONFIG_PATH` to inform `pkg-config` of where the entries for wasm are stored: + +```console +export PKG_CONFIG_PATH=$HOME/prefix/lib/pkgconfig +``` + +Then we get into the `cardano-wasm` subfolder of the clone of `cardano-api`. + +```console +cd cardano-api/cardano-wasm +``` + +### Compiling `cardano-wasm` + +Once we have the environment set up we can procede to build the wasm module as follows: + +```console +wasm32-wasi-cabal update +wasm32-wasi-cabal build +``` + +That will generate the `wasm` module, and you can find where it was generated by using the following command: + +```console +echo "$(env -u CABAL_CONFIG wasm32-wasi-cabal list-bin exe:cardano-wasm | tail -n1)" +``` + +And you can see the exported functions by using the following command: + +```console +wasm-dis "$(env -u CABAL_CONFIG wasm32-wasi-cabal list-bin exe:cardano-wasm | tail -n1)" | grep "export " +``` + +To generate a post-link module with the exports you can write: + +```console +$(wasm32-wasi-ghc --print-libdir)/post-link.mjs -i "$(env -u CABAL_CONFIG wasm32-wasi-cabal list-bin exe:cardano-wasm | tail -n1)" -o cardano-wasm.js +``` + +That will create it with the name `cardano-wasm.js` in the current folder. + +You can find more information in [this url](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/wasm.html). + diff --git a/cardano-wasm/app/Main.hs b/cardano-wasm/app/Main.hs new file mode 100644 index 0000000000..7af3b382a8 --- /dev/null +++ b/cardano-wasm/app/Main.hs @@ -0,0 +1,7 @@ +module Main (main) where + +import Cardano.Api.Experimental qualified as Exp + +main :: IO () +main = + print Exp.ConwayEra diff --git a/cardano-wasm/cardano-wasm.cabal b/cardano-wasm/cardano-wasm.cabal new file mode 100644 index 0000000000..d6c66ece2b --- /dev/null +++ b/cardano-wasm/cardano-wasm.cabal @@ -0,0 +1,43 @@ +cabal-version: 3.4 +name: cardano-wasm +version: 10.0.0.0 +copyright: 2020-2025 Input Output Global Inc (IOG). +author: IOHK +maintainer: operations@iohk.io +license: Apache-2.0 +build-type: Simple + +common project-config + default-language: Haskell2010 + default-extensions: + ImportQualifiedPost + OverloadedStrings + + build-depends: base >=4.18 && <5 + ghc-options: + -Wall + -Wcompat + -Wincomplete-record-updates + -Wincomplete-uni-patterns + -Wno-unticked-promoted-constructors + -Wpartial-fields + -Wredundant-constraints + -Wunused-packages + +executable cardano-wasm + import: project-config + main-is: Main.hs + hs-source-dirs: + app + src + + default-language: Haskell2010 + + if arch(wasm32) + ghc-options: + -no-hs-main + -optl-mexec-model=reactor + "-optl-Wl,--strip-all" + build-depends: + base, + cardano-api, diff --git a/flake.lock b/flake.lock index 6280b2b178..e48d6aca5e 100644 --- a/flake.lock +++ b/flake.lock @@ -169,6 +169,24 @@ "type": "github" } }, + "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, "ghc-8.6.5-iohk": { "flake": false, "locked": { @@ -186,6 +204,27 @@ "type": "github" } }, + "ghc-wasm-meta": { + "inputs": { + "flake-utils": "flake-utils_2", + "nixpkgs": "nixpkgs" + }, + "locked": { + "host": "gitlab.haskell.org", + "lastModified": 1747237596, + "narHash": "sha256-EyzTbLYHKXhEYGcIgYcYHevMjNOlizUL7lDQyv73eN8=", + "owner": "haskell-wasm", + "repo": "ghc-wasm-meta", + "rev": "fe5573f28327d12a1c47ec61d6bbe0cc9d7983dd", + "type": "gitlab" + }, + "original": { + "host": "gitlab.haskell.org", + "owner": "haskell-wasm", + "repo": "ghc-wasm-meta", + "type": "gitlab" + } + }, "hackage-for-stackage": { "flake": false, "locked": { @@ -530,7 +569,7 @@ "iohkNix": { "inputs": { "blst": "blst", - "nixpkgs": "nixpkgs", + "nixpkgs": "nixpkgs_2", "secp256k1": "secp256k1", "sodium": "sodium" }, @@ -567,16 +606,16 @@ }, "nixpkgs": { "locked": { - "lastModified": 1688392541, - "narHash": "sha256-lHrKvEkCPTUO+7tPfjIcb7Trk6k31rz18vkyqmkeJfY=", - "owner": "nixos", + "lastModified": 1744098102, + "narHash": "sha256-tzCdyIJj9AjysC3OuKA+tMD/kDEDAF9mICPDU7ix0JA=", + "owner": "NixOS", "repo": "nixpkgs", - "rev": "ea4c80b39be4c09702b0cb3b42eab59e2ba4f24b", + "rev": "c8cd81426f45942bb2906d5ed2fe21d2f19d95b7", "type": "github" }, "original": { - "owner": "nixos", - "ref": "release-22.11", + "owner": "NixOS", + "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } @@ -678,6 +717,22 @@ } }, "nixpkgs_2": { + "locked": { + "lastModified": 1688392541, + "narHash": "sha256-lHrKvEkCPTUO+7tPfjIcb7Trk6k31rz18vkyqmkeJfY=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "ea4c80b39be4c09702b0cb3b42eab59e2ba4f24b", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "release-22.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_3": { "locked": { "lastModified": 1720181791, "narHash": "sha256-i4vJL12/AdyuQuviMMd1Hk2tsGt02hDNhA0Zj1m16N8=", @@ -715,10 +770,15 @@ "CHaP": "CHaP", "flake-compat": "flake-compat", "flake-utils": "flake-utils", + "ghc-wasm-meta": "ghc-wasm-meta", "hackageNix": "hackageNix", "haskellNix": "haskellNix", "iohkNix": "iohkNix", - "nixpkgs": "nixpkgs_2" + "nixpkgs": "nixpkgs_3", + "wasm-nixpkgs": [ + "ghc-wasm-meta", + "nixpkgs" + ] } }, "secp256k1": { @@ -785,6 +845,21 @@ "repo": "default", "type": "github" } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index d6daf44dd1..c86d328903 100644 --- a/flake.nix +++ b/flake.nix @@ -10,7 +10,7 @@ url = "github:input-output-hk/haskell.nix"; inputs.hackage.follows = "hackageNix"; }; - # blst fails to build for x86_64-darwin + # blst fails to build for x86_64-darwin # nixpkgs.follows = "haskellNix/nixpkgs-unstable"; nixpkgs.url = "github:NixOS/nixpkgs/4284c2b73c8bce4b46a6adf23e16d9e2ec8da4bb"; iohkNix.url = "github:input-output-hk/iohk-nix"; @@ -25,6 +25,10 @@ url = "github:intersectmbo/cardano-haskell-packages?ref=repo"; flake = false; }; + + # wasm specific inputs + wasm-nixpkgs.follows = "ghc-wasm-meta/nixpkgs"; + ghc-wasm-meta.url = "gitlab:haskell-wasm/ghc-wasm-meta?host=gitlab.haskell.org"; }; outputs = inputs: let @@ -88,7 +92,8 @@ ''; shell.packages = p: [ # Packages in this repo - p.cardano-api p.cardano-api-gen + p.cardano-api + p.cardano-api-gen # Work around for issue created by our inability to register sublibs. # This package may need to be built and we need to make sure its dependencies # are included in `ghc-pkg list` (in particular `compact`) @@ -106,7 +111,10 @@ fourmolu = "0.18.0.0"; haskell-language-server.src = nixpkgs.haskell-nix.sources."hls-2.9"; # This index-state makes it work for GHC 9.8.2 (it will need to tbe removed for 9.8.4) - hlint = { version = "3.8"; index-state = "2024-12-01T00:00:00Z"; }; + hlint = { + version = "3.8"; + index-state = "2024-12-01T00:00:00Z"; + }; }; # and from nixpkgs or other inputs shell.nativeBuildInputs = with nixpkgs; [gh jq yq-go actionlint shellcheck]; @@ -149,15 +157,45 @@ }); } ); + # wasm shell + wasmShell = let + wasm-pkgs = inputs.wasm-nixpkgs.legacyPackages.${system}; + wasi-sdk = inputs.ghc-wasm-meta.packages.${system}.wasi-sdk; + wasm = { + libsodium = + wasm-pkgs.callPackage ./nix/libsodium.nix {inherit wasi-sdk;}; + secp256k1 = (wasm-pkgs.callPackage ./nix/secp256k1.nix {inherit wasi-sdk;}).overrideAttrs (_: { + src = inputs.iohkNix.inputs.secp256k1; + }); + blst = (wasm-pkgs.callPackage ./nix/blst.nix {inherit wasi-sdk;}).overrideAttrs (_: { + src = inputs.iohkNix.inputs.blst; + }); + }; + in + lib.optionalAttrs (system != "x86_64-darwin") { + wasm = wasm-pkgs.mkShell { + packages = [ + inputs.ghc-wasm-meta.packages.${system}.all_9_10 + wasm-pkgs.pkg-config + wasm.libsodium + wasm.secp256k1 + wasm.blst + ]; + }; + }; + flakeWithWasmShell = nixpkgs.lib.recursiveUpdate flake { + devShells = wasmShell; + hydraJobs = {devShells = wasmShell;}; + }; in - nixpkgs.lib.recursiveUpdate flake rec { + nixpkgs.lib.recursiveUpdate flakeWithWasmShell rec { project = cabalProject; # add a required job, that's basically all hydraJobs. hydraJobs = nixpkgs.callPackages inputs.iohkNix.utils.ciJobsAggregates { ciJobs = - flake.hydraJobs + flakeWithWasmShell.hydraJobs // { # This ensure hydra send a status for the required job (even if no change other than commit hash) revision = nixpkgs.writeText "revision" (inputs.self.rev or "dirty"); @@ -169,6 +207,7 @@ inherit hydraJobs; }; devShells = let + # profiling shell profilingShell = p: { # `nix develop .#profiling` (or `.#ghc927.profiling): a shell with profiling enabled profiling = (p.appendModule {modules = [{enableLibraryProfiling = true;}];}).shell; diff --git a/nix/blst.nix b/nix/blst.nix new file mode 100644 index 0000000000..ae2e203e03 --- /dev/null +++ b/nix/blst.nix @@ -0,0 +1,60 @@ +{ stdenvNoCC +, fetchFromGitHub +, autoreconfHook +, wasi-sdk +}: + +stdenvNoCC.mkDerivation (finalAttrs: { + name = "blst"; + + nativeBuildInputs = [ + wasi-sdk + ]; + + buildPhase = '' + runHook preBuild + + ./build.sh + + runHook postBuild + ''; + installPhase = '' + runHook preInstall + + mkdir -p $out/{lib,include} + for lib in libblst.{a,so,dylib}; do + if [ -f $lib ]; then + cp $lib $out/lib/ + fi + done + cp bindings/{blst.h,blst_aux.h} $out/include + + for lib in blst.dll; do + if [ -f $lib ]; then + mkdir -p $out/bin + cp $lib $out/bin/ + fi + done + + mkdir -p $out/lib/pkgconfig + cat < $out/lib/pkgconfig/libblst.pc + prefix=$out + exec_prefix=''\\''${prefix} + libdir=''\\''${exec_prefix}/lib + includedir=''\\''${prefix}/include + + Name: libblst + Description: bogus + URL: bogus + Version: 0.0 + + Cflags: -I''\\''${includedir} + Libs: -L''\\''${libdir} -lblst + Libs.private: + EOF + + wasm32-wasi-clang -shared -Wl,--whole-archive $out/lib/libblst.a -o $out/lib/libblst.so + + runHook postInstall + ''; +}) diff --git a/nix/libsodium.nix b/nix/libsodium.nix new file mode 100644 index 0000000000..9d44cbae75 --- /dev/null +++ b/nix/libsodium.nix @@ -0,0 +1,29 @@ +{ stdenvNoCC +, fetchFromGitHub +, autoreconfHook +, wasi-sdk +}: + +stdenvNoCC.mkDerivation { + name = "libsodium"; + + src = fetchFromGitHub { + owner = "jedisct1"; + repo = "libsodium"; + rev = "9511c982fb1d046470a8b42aa36556cdb7da15de"; + hash = "sha256-ZPVzKJZRglZT2EJKqdBu94I4TRrF5sujSglUR64ApWA="; + }; + + nativeBuildInputs = [ + wasi-sdk + autoreconfHook + ]; + + configureFlags = [ + "--host=wasm32-wasi" + ]; + + postInstall = '' + wasm32-wasi-clang -shared -Wl,--whole-archive $out/lib/libsodium.a -o $out/lib/libsodium.so + ''; +} diff --git a/nix/secp256k1.nix b/nix/secp256k1.nix new file mode 100644 index 0000000000..dbfd067bbf --- /dev/null +++ b/nix/secp256k1.nix @@ -0,0 +1,30 @@ +{ stdenvNoCC +, fetchFromGitHub +, autoreconfHook +, pkg-config +, wasi-sdk +}: + +stdenvNoCC.mkDerivation { + name = "libsecp256k1"; + + outputs = [ + "out" + "dev" + ]; + + nativeBuildInputs = [ + wasi-sdk + autoreconfHook + ]; + + configureFlags = [ + "--host=wasm32-wasi" + "--enable-module-schnorrsig" + "SECP_CFLAGS=-fPIC" + ]; + + postInstall = '' + wasm32-wasi-clang -shared -Wl,--whole-archive $out/lib/libsecp256k1.a -o $out/lib/libsecp256k1.so + ''; +}