To setup development tools (ghc
, cabal
& hls
- Haskell Language Server)
we advise you to use ghcup
.
The project can be built with a recent enough version of cabal
and ghc
(see
the GitHub Action workflow file which versions
we currently support.
If you use a different compiler by default, you can pin the compiler version in
cabal.project.local
file, see here. You might want to
use a few other options which make the compilation faster if you want to
develop & contribute to this repository, e.g.
jobs: 2
-- You might want to adjust if you have more processors, note that there are
-- two levels of concurrency when building with `cabal`: this option decides how
-- many packages can be built at the same time, while `GHC`'s option `-j`
-- guards how many modules in a single package can be built concurrently (see
-- below how it can be set).
-- NOTE: for the settings in this file to be effective you need to have
-- 4 real cores.
optimization: 0
-- Building non-optimised code helps to build quicker but some of the tests
-- will take much longer to run.
documentation: False
-- Documentation is built by the CI, you will get feedback if your haddocks are
-- unparsable.
tests: True
benchmarks: True
package ouroboros-network-framework
ghc-options: -j2
package ouroboros-network
ghc-options: -j2
package ouroboros-consensus
ghc-options: -j2
You can also build all the packages with nix
. To install nix
on your
system please follow this guide which contains some IOG
specific
nix
configuration options (e.g. our nix
cache, which considerably speeds up
bootstrapping the project); the official
guide might be helpful too.
To build all the required jobs (which are necessary to pass through CI), you can run:
nix build -j auto .\#hydraJobs.required
To inspect what can be build use nix repl
, for example:
nix-repl> :lf .
nix-repl> hydraJobs.<TAB>
nix-repl> hydraJobs.
hydraJobs.aarch64-darwin hydraJobs.x86_64-darwin hydraJobs.x86_64-linux
In various packages, we use CPP
pragmas to compile different code depending
on the target architecture. Using haskell.nix
cross-compilation pipeline
is very helpful to diagnose build time compiler errors.
Nix allows to build Windows native executables on Linux, e.g. to build
network-mux:lib:network-mux
component one can run this command:
nix build .\#hydraJobs.x86_64-linux.ghc810-x86_64-w64-mingw32.packages.network-mux:lib:network-mux
Not all components are available in cross-compilation right now. All the
test components are disabled in ./scripts/ci/cabal.project.local.Windows.CrossCompile
.
To run all tests from a particular component use the following command, in this
example ouroboros-network
:
cabal run ouroboros-network:test
We use tasty
library which exposes an interface to list & isolate
a test or group of tests to run. The following command will list all the
tests of the ouroboros-network:test
component:
$ cabal run ouroboros-network:test -- -l
ouroboros-network.ChainProducerState.Test Arbitrary instances.ChainProducerStateForkTest's generator
ouroboros-network.ChainProducerState.Test Arbitrary instances.ChainProducerStateForkTest's shrinker
ouroboros-network.ChainProducerState.check initial follower state
...
To match only tests which contain particular string:
$ cabal run ouroboros-network:test -- -p '/governor no livelock/' -l
ouroboros-network.Ouroboros.Network.PeerSelection.governor no livelock
ouroboros-network.Ouroboros.Network.PeerSelection.races.governor no livelock
$ cabal run ouroboros-network:test -- -p '/PeerSelection.governor no livelock/' -l
ouroboros-network.Ouroboros.Network.PeerSelection.governor no livelock
You can also use the test hierarchy, e.g. the last test can also be selected with:
$ cabal run ouroboros-network:test -- -p '$2 == "Ouroboros.Network.PeerSelection" && $3 == "governor no livelock"' -l
ouroboros-network.Ouroboros.Network.PeerSelection.governor no livelock
If you want to run selected tests just avoid the -l
(--list-tests
) switch.
If in doubt you can use -h
or visit tasty documentation.
nix build -j auto .\#hydraJobs.required
will build and run all required
checks. If you want to build only tests for a particular package, e.g.
network-mux
package (on linux
) use:
nix build -j auto .\#hydraJobs.x86_64-linux.packages.network-mux:test:test
The executable will be available at ./results/bin/
directory. You can pass
to it the same options as in the previous section (the options after --
).
Any contributions should be well documented. APIs should have well-written
haddocks
. If a particular function expects a precondition to be satisfied it
should be explicitly mentioned. The inline documentation is published at
https://ouroboros-network.cardano.intersectmbo.org. When writing haddocks
it's always good to put oneself in the position of somebody who hasn't yet
interacted with your code changes. It's good to explain the key design choices
as well as implementation-level comments.
If changes would modify any existing design the contributor might be expected
to be asked to also update the standalone documentation (written in tex
).
There are two documents in two directories:
./doc/network-design
./doc/network-spec
Either go to one of the directories and run pdflatex
& bibtex
(there's a
Makefile
or a script to do that). Note that in you need to install a tex
distribution on your system with all the necessary packages; or build it with
nix
(which will take care about all the dependencies one needs):
# build network-design & network-spec
nix build -f default.nix network-docs
All contributed code should be well-tested. Low-level networking code should
be tested both in simulation (io-sim
) and in IO
(which we run
on different architectures), while top-level code (e.g. the diffusion layer) is
only tested in simulation). We use QuickCheck
, if you are new to property
based testing please check out one of the original John Hughes tutorials, e.g. How
to Specify it!. We combine
QuickCheck
with an in-house built io-sim
library. As a consequence,
almost all of our code is written in a polymorphic way using io-classes
which
comes with io-sim
: io-classes
expose a very similar API that the base
,
async
, stm
and time
packages provides.
-
Please note that across this code base, we are using custom time functions which are measured in seconds not nano or microseconds as similarly named functions in
base
do. This applies to:timeout
threadDelay
registerDelay
registerDelayCancellable
(which does not exist inbase
) They all useDiffTime
rather thanInt
, so you can use fractional values if needed.
Failing to notice this, might lead to bugs where delays which supposed to be in the order of seconds will be measured in months (
3*10^6
seconds ~ one month)!
The network & consensus have slightly different style guides, see
Please take your time to clean and format your PR's git history with the reviewer in mind. Very often complex changes can be split into small refactoring steps followed by new additions which are using refactorisation. Please avoid including random changes in large commits which make it difficult to review. We recognise that formatting git history takes time and effort, but we find it much easier to discuss & review the changes, as well as rebase if there are any other complex changes merged in (or it will make it easier for others to rebase on top of your committed changes). If you need to rebase your branch we prefer to rebase over merge (since then the actually merged changes are more explicit).
We also keep the convention that a source branch's name for a pull request includes github user name of the contributor.
Since the code base of ouroboros-network
is quite large, we don't require
that every commit is buildable across all included packages. You can update
upstream dependencies later in the commit history; although note that if you do
that you are limiting the way tools like git-bisect
can be used, so please be
aware of that. If you decide to do that, please indicate that in the commit
message.
We find it quite helpful if each commit title starts with the component it
modifies. Sometimes referring to package name is good enough, sometimes
something more specific like outbound-governor
is more appropriate. At this
stage, this is only advisory. Please check https://commit.style. Here's an
example from our own git
history.
We require all commits to be signed, see this guide.
If you are thinking about large changes, starting with a discussion with the network/consensus team is a good idea. Depending on the scope, you might be asked to first write a design document or build an experiment/simulation which illustrates the benefits (we follow the same process, e.g. the Ouroboros Leios is a good example). At this stage, there is no rule, so it's much better to talk with the maintainers (via GitHub issue/discussion or via email, etc.).
We maintain changelogs for all our packages.
Maintainers of each package are listed in the corresponding *.cabal
file.
We maintain a CODEOWNERS file which provides information on who should review your code if it touches given projects. Note that you need to get approvals from all code owners (even though GitHub doesn't give a way to enforce it).
For a general architectural overview of the network code contact either: @coot or @dcoutts.
We officially support:
Linux
(x86_64-linux
)MacOS
(x86_64-darwin
andaarch64-darwin
)Windows
(usingmsys2
software distribution, and cross-compiled on linux withnix
)
On 32-bit platforms, you might expect some issues (currently memory requirement
for cardano-node
on 32 architecture are too high).
The networking code is tested using a mixture of GitHub actions on Windows and
Hydra on Linux & MacOS. All supported platforms are tested on our CI,
although only subset of tests are run natively on Windows, only IO
tests
which require native network stack; simulation tests (which are pure) run on
Linux, MacOS, and cross compiled to Windows using wine
(on Linux).
New versions of packages are published to CHaP.
If you have a branch which you want to check if it's releasable, you can run:
./scripts/release-to-chap.sh -t
./scripts/build-with-chap.sh
The last command should fail with an error that the current revision is not on
the master
or a release/*
branch. After running both commands, you will
need to delete branch created in cardano-haskell-packages
.
- First run
./script/release-to-chap.sh -r
to see which changes can be published. - Update versions in
*.cabal
files according to changes inCHANGELOG.md
files. - Update
CHANGELOG.md
files. - Run
./script/release-to-chap.sh
which will create a branch incardano-haskell-packages
repo (pointed byCARDANO_HASKELL_PACKAGES_DIR
environment variable or/tmp/chap
if it's not defined).- To enable pushing this branch, cd to the chap repo and execute:
git remote set-url origin [email protected]:IntersectMBO/cardano-haskell-packages.git
then return to the previous repo (cd -
)
- To enable pushing this branch, cd to the chap repo and execute:
- Before merging that branch, run
./script/build-with-chap.sh
. It will use the new branch incardano-haskell-packages
to restore theourobors-network
repository to the state published inCHaP
.- If you need to re-run this script after fixing errors, you will need to delete the tags created
by the previous run. You can do so with the following command:
or manually, if you have applied new commits on top of the tags.
git tag -d $(git tag --points-at)
- If building fails at resolving dependencies step, it generally means that some component(s) needs at least a patch version bump, even despite not having undergone any code changes. For eg, if cabal build-depends had version bounds changes, then that component itself may need a version bump.
- One must resolve all compilation issues before
merging the
CHaP
branch. On a successful run, the script will create relevant commit(s) in your localCHaP
repo. You should push those and create a PR now.
- If you need to re-run this script after fixing errors, you will need to delete the tags created
by the previous run. You can do so with the following command:
- After the versions were published to
CHaP
, push the tags created by./script/release-to-chap.sh
toorigin
. Usually this command will push all the tags:git push origin $(git tag --points-at=HEAD)
- Update the release board.
When needed we use release branches: release/*
, but most often we just
release from master
.
Note that CHaP
allows us to make releases of packages independently of each
other (especially non-breaking changes), so there might be other release
branches, e.g. release/network-mux-*
. They MUST follow the following
naming convention: release/${package-name}-${version}
.
All commits in the release branch MUST be cherry-picked from master
. Each
time one wants to add new commits from master
, one SHOULD:
- cherry-pick them to a new branch,
- create a PR which targets the right release branch,
- mention in the PR's description from which original PR(s) (to the
master
branch) the commits are coming from (example).
This forces the changes to go through the normal pull request review process & let CI validate the release patch set.
Note that we never merge release branches back to master
.
Our Haskell packages come from two package repositories:
- Hackage
- CHaP (which is essentially another Hackage)
The index-state
of each repository is pinned to a particular time in
cabal.project
. This tells Cabal to treat the repository as if it was
the specified time, ensuring reproducibility. If you want to use a package
version from repository X which was added after the pinned index state time,
you need to bump the index state for X. This is not a big deal, since it
changes what packages cabal
considers to be available when doing
solving, but it will change what package versions cabal picks for the plan, and
so will likely result in significant recompilation, and potentially some
breakage. That typically just means that we need to fix the breakage
(increasing the lower bound on the problematic package if the fix is not backward
compatible), or delay that work and instead decrease the upper-bound on the
problematic package for now.
Note that cabal
's persistent state includes which index states it is
aware of, so when you bump the pinned index state you may need to
call cabal update
for cabal
to be happy.
Whenever using a newer Hackage's index, one needs to run:
nix flake lock --update-input hackageNix
and when using a newer CHaP
's index:
nix flake lock --update-input CHaP
If you fail to do this you may get an error like this from Nix:
error: Unknown index-state 2021-08-08T00:00:00Z, the latest index-state I know about is 2021-08-06T00:00:00Z. You may need to update to a newer hackage.nix.
We can use Cabal's source-repository-package
mechanism to pull in
un-released package versions. This can be useful when debugging/developing
across different repositories. However, we should not release our packages
to CHaP while we depend on a source-repository-package
since downstream
consumers would not be able to build such a package.
If we are stuck in a situation where we need a long-running fork of a package, we should release it to CHaP instead (see the CHaP README for more).
If you do add a temporary source-repository-package
stanza, you need to
provide a --sha256: <HASH>
comment in cabal.project
so that Nix knows the hash
of the content. There are two relatively straightforward ways to do this:
- The TOFU approach: put in the wrong hash and then Nix will tell you the correct one, which you can copy in.
- Calculate the hash with
nix-shell -p nix-prefetch-git --run 'nix-prefetch-git <URL> <COMMIT_HASH>'