From 6164ac506fb57dc8a56ed3fe6072a31e03092f81 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Tue, 10 Feb 2026 12:17:58 +0100 Subject: [PATCH 1/6] fix(devnet): Docker images creation with CI mode --- mithril-aggregator/Dockerfile.ci | 6 +++--- mithril-aggregator/Makefile | 21 ++++++++++++++++++++- mithril-client-cli/Dockerfile.ci | 6 +++--- mithril-client-cli/Makefile | 21 ++++++++++++++++++++- mithril-signer/Dockerfile.ci | 6 +++--- mithril-signer/Makefile | 21 ++++++++++++++++++++- 6 files changed, 69 insertions(+), 12 deletions(-) diff --git a/mithril-aggregator/Dockerfile.ci b/mithril-aggregator/Dockerfile.ci index f0ff2c30e46..12b2634c289 100644 --- a/mithril-aggregator/Dockerfile.ci +++ b/mithril-aggregator/Dockerfile.ci @@ -7,15 +7,15 @@ FROM $DOCKER_IMAGE_FROM # Build-time platform architecture ARG TARGETARCH +# Upgrade +RUN apt-get update -y && apt-get install -y libssl-dev ca-certificates wget adduser && apt-get clean && rm -rf /var/lib/apt/lists/* + # Create appuser RUN adduser --no-create-home --disabled-password appuser # Precreate workdir RUN mkdir -p /app/bin -# Upgrade -RUN apt-get update -y && apt-get install -y libssl-dev ca-certificates wget && apt-get clean && rm -rf /var/lib/apt/lists/* - # Install cardano-cli ARG CARDANO_NODE_VERSION=10.5.1 ARG CARDANO_BIN_URL=https://github.com/input-output-hk/cardano-node/releases/download/$CARDANO_NODE_VERSION/cardano-node-$CARDANO_NODE_VERSION-linux.tar.gz diff --git a/mithril-aggregator/Makefile b/mithril-aggregator/Makefile index 0dddfe0f6d0..b9233945bbb 100644 --- a/mithril-aggregator/Makefile +++ b/mithril-aggregator/Makefile @@ -8,6 +8,25 @@ build: ${CARGO} build --release cp ../target/release/mithril-aggregator . +build-ci: + # Check that we are on Linux, then build + if [ "$$(uname -s)" != "Linux" ]; then \ + echo "Error: this target only supports Linux"; \ + exit 1; \ + fi; \ + RAW_ARCH=$$(uname -m); \ + if [ "$$RAW_ARCH" = "x86_64" ]; then \ + ARCH="amd64"; \ + elif [ "$$RAW_ARCH" = "aarch64" ]; then \ + ARCH="arm64"; \ + else \ + echo "Error: unsupported architecture $$RAW_ARCH"; \ + exit 1; \ + fi; \ + ${CARGO} build --release; \ + mkdir -p ./bin-linux-$${ARCH}; \ + cp ../target/release/mithril-aggregator ./bin-linux-$${ARCH}/mithril-aggregator + run: build ./mithril-aggregator serve @@ -31,7 +50,7 @@ doc: docker-build: cd ../ && docker build -t mithril/mithril-aggregator -f mithril-aggregator/Dockerfile . -docker-build-ci: build +docker-build-ci: build-ci cd ../ && docker build -t mithril/mithril-aggregator -f mithril-aggregator/Dockerfile.ci --build-arg DOCKER_IMAGE_FROM --build-arg CARDANO_NODE_VERSION . docker-run: diff --git a/mithril-client-cli/Dockerfile.ci b/mithril-client-cli/Dockerfile.ci index d7872b8baf8..40c83fe7328 100644 --- a/mithril-client-cli/Dockerfile.ci +++ b/mithril-client-cli/Dockerfile.ci @@ -7,15 +7,15 @@ FROM $DOCKER_IMAGE_FROM # Build-time platform architecture ARG TARGETARCH +# Upgrade +RUN apt-get update -y && apt-get install -y libssl-dev ca-certificates wget adduser && apt-get clean && rm -rf /var/lib/apt/lists/* + # Create appuser RUN adduser --disabled-password appuser # Precreate workdir RUN mkdir -p /app/bin -# Upgrade -RUN apt-get update -y && apt-get install -y libssl-dev ca-certificates wget && apt-get clean && rm -rf /var/lib/apt/lists/* - # Copy the executable COPY mithril-client-cli/bin-linux-$TARGETARCH/mithril-client /app/bin/mithril-client diff --git a/mithril-client-cli/Makefile b/mithril-client-cli/Makefile index 0b911074754..4a94b76411a 100644 --- a/mithril-client-cli/Makefile +++ b/mithril-client-cli/Makefile @@ -13,6 +13,25 @@ build: ${CARGO} build --release cp ../target/release/mithril-client . +build-ci: + # Check that we are on Linux, then build + if [ "$$(uname -s)" != "Linux" ]; then \ + echo "Error: this target only supports Linux"; \ + exit 1; \ + fi; \ + RAW_ARCH=$$(uname -m); \ + if [ "$$RAW_ARCH" = "x86_64" ]; then \ + ARCH="amd64"; \ + elif [ "$$RAW_ARCH" = "aarch64" ]; then \ + ARCH="arm64"; \ + else \ + echo "Error: unsupported architecture $$RAW_ARCH"; \ + exit 1; \ + fi; \ + ${CARGO} build --release; \ + mkdir -p ./bin-linux-$${ARCH}; \ + cp ../target/release/mithril-client ./bin-linux-$${ARCH}/mithril-client + run: build @./mithril-client $(call args,defaultstring) @@ -39,7 +58,7 @@ doc: docker-build: cd ../ && docker build -t mithril/mithril-client -f mithril-client-cli/Dockerfile . -docker-build-ci: build +docker-build-ci: build-ci cd ../ && docker build -t mithril/mithril-client -f mithril-client-cli/Dockerfile.ci --build-arg DOCKER_IMAGE_FROM . docker-run: diff --git a/mithril-signer/Dockerfile.ci b/mithril-signer/Dockerfile.ci index 55f13974e33..cac758b5bb1 100644 --- a/mithril-signer/Dockerfile.ci +++ b/mithril-signer/Dockerfile.ci @@ -7,15 +7,15 @@ FROM $DOCKER_IMAGE_FROM # Build-time platform architecture ARG TARGETARCH +# Upgrade +RUN apt-get update -y && apt-get install -y libssl-dev ca-certificates wget adduser && apt-get clean && rm -rf /var/lib/apt/lists/* + # Create appuser RUN adduser --no-create-home --disabled-password appuser # Precreate workdir RUN mkdir -p /app/bin -# Upgrade -RUN apt-get update -y && apt-get install -y libssl-dev ca-certificates wget && apt-get clean && rm -rf /var/lib/apt/lists/* - # Install cardano-cli ARG CARDANO_NODE_VERSION=10.5.1 ARG CARDANO_BIN_URL=https://github.com/input-output-hk/cardano-node/releases/download/$CARDANO_NODE_VERSION/cardano-node-$CARDANO_NODE_VERSION-linux.tar.gz diff --git a/mithril-signer/Makefile b/mithril-signer/Makefile index c2ac4e7a43a..ca4467e8348 100644 --- a/mithril-signer/Makefile +++ b/mithril-signer/Makefile @@ -11,6 +11,25 @@ build: run: build ./mithril-signer +build-ci: + # Check that we are on Linux, then build + if [ "$$(uname -s)" != "Linux" ]; then \ + echo "Error: this target only supports Linux"; \ + exit 1; \ + fi; \ + RAW_ARCH=$$(uname -m); \ + if [ "$$RAW_ARCH" = "x86_64" ]; then \ + ARCH="amd64"; \ + elif [ "$$RAW_ARCH" = "aarch64" ]; then \ + ARCH="arm64"; \ + else \ + echo "Error: unsupported architecture $$RAW_ARCH"; \ + exit 1; \ + fi; \ + ${CARGO} build --release; \ + mkdir -p ./bin-linux-$${ARCH}; \ + cp ../target/release/mithril-signer ./bin-linux-$${ARCH}/mithril-signer + debug: ${CARGO} run -- -vvv @@ -31,7 +50,7 @@ doc: docker-build: cd ../ && docker build -t mithril/mithril-signer -f mithril-signer/Dockerfile . -docker-build-ci: build +docker-build-ci: build-ci cd ../ && docker build -t mithril/mithril-signer -f mithril-signer/Dockerfile.ci --build-arg DOCKER_IMAGE_FROM --build-arg CARDANO_NODE_VERSION . docker-run: From e9a2c1749dd7bd80f7506e98770eb89d37448be3 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Tue, 10 Feb 2026 12:38:06 +0100 Subject: [PATCH 2/6] fix(devnet): start DMQ nodes after Cardano nodes are ready --- mithril-test-lab/mithril-devnet/devnet-run.sh | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/mithril-test-lab/mithril-devnet/devnet-run.sh b/mithril-test-lab/mithril-devnet/devnet-run.sh index c1ab91ea5bf..970c9ff85e6 100755 --- a/mithril-test-lab/mithril-devnet/devnet-run.sh +++ b/mithril-test-lab/mithril-devnet/devnet-run.sh @@ -31,23 +31,23 @@ echo # Change directory pushd ${ARTIFACTS_DIR} > /dev/null -# Start devnet DMQ nodes -if [[ "${NODES}" = *"dmq"* ]] || [[ "${NODES}" = "*" ]]; then +# Start devnet Cardano nodes +if [[ "${NODES}" = *"cardano"* ]] || [[ "${NODES}" = "*" ]]; then echo "=====================================================================" - echo " Start DMQ nodes" + echo " Start Cardano nodes" echo "=====================================================================" echo - ./start-dmq.sh + ./start-cardano.sh echo fi -# Start devnet Cardano nodes -if [[ "${NODES}" = *"cardano"* ]] || [[ "${NODES}" = "*" ]]; then +# Start devnet DMQ nodes +if [[ "${NODES}" = *"dmq"* ]] || [[ "${NODES}" = "*" ]]; then echo "=====================================================================" - echo " Start Cardano nodes" + echo " Start DMQ nodes" echo "=====================================================================" echo - ./start-cardano.sh + ./start-dmq.sh echo fi From 7baaff0371ef2fbc3d52dfdcc2d9e0037a9ac586 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Tue, 10 Feb 2026 12:47:47 +0100 Subject: [PATCH 3/6] docs(aggregator): make gap aggregator message more helpful --- mithril-aggregator/src/services/certifier/interface.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mithril-aggregator/src/services/certifier/interface.rs b/mithril-aggregator/src/services/certifier/interface.rs index afd559f8791..0570427ba5f 100644 --- a/mithril-aggregator/src/services/certifier/interface.rs +++ b/mithril-aggregator/src/services/certifier/interface.rs @@ -38,7 +38,7 @@ pub enum CertifierServiceError { /// No certificate for this epoch #[error( - "There is an epoch gap between the last certificate epoch ({certificate_epoch:?}) and current epoch ({current_epoch:?})" + "There is an epoch gap between the last certificate epoch ({certificate_epoch:?}) and current epoch ({current_epoch:?}). A leader aggregator must be re-genesis by the owner of the genesis keys, a follower aggregator will automatically catchup with the leader's certificate chain." )] CertificateEpochGap { /// Epoch of the last issued certificate From 29df4f6c2d9d7ea3f066386cd5694da0e316a2a8 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Tue, 10 Feb 2026 13:00:22 +0100 Subject: [PATCH 4/6] fix(devnet): activate DMQ in Mithril nodes --- mithril-test-lab/mithril-devnet/mkfiles/mkfiles-docker.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mithril-test-lab/mithril-devnet/mkfiles/mkfiles-docker.sh b/mithril-test-lab/mithril-devnet/mkfiles/mkfiles-docker.sh index 6d65fc3a410..4fc96264e6a 100644 --- a/mithril-test-lab/mithril-devnet/mkfiles/mkfiles-docker.sh +++ b/mithril-test-lab/mithril-devnet/mkfiles/mkfiles-docker.sh @@ -128,6 +128,7 @@ cat >> docker-compose.yaml <> docker-compose.yaml < Date: Tue, 10 Feb 2026 17:40:50 +0100 Subject: [PATCH 5/6] refactor(devnet): better makefiles for docker ci builds --- mithril-aggregator/Makefile | 37 +++++++++++++++++-------------------- mithril-client-cli/Makefile | 37 +++++++++++++++++-------------------- mithril-relay/Makefile | 16 ++++++++++++++++ mithril-signer/Makefile | 37 +++++++++++++++++-------------------- 4 files changed, 67 insertions(+), 60 deletions(-) diff --git a/mithril-aggregator/Makefile b/mithril-aggregator/Makefile index b9233945bbb..48d5794029a 100644 --- a/mithril-aggregator/Makefile +++ b/mithril-aggregator/Makefile @@ -8,25 +8,6 @@ build: ${CARGO} build --release cp ../target/release/mithril-aggregator . -build-ci: - # Check that we are on Linux, then build - if [ "$$(uname -s)" != "Linux" ]; then \ - echo "Error: this target only supports Linux"; \ - exit 1; \ - fi; \ - RAW_ARCH=$$(uname -m); \ - if [ "$$RAW_ARCH" = "x86_64" ]; then \ - ARCH="amd64"; \ - elif [ "$$RAW_ARCH" = "aarch64" ]; then \ - ARCH="arm64"; \ - else \ - echo "Error: unsupported architecture $$RAW_ARCH"; \ - exit 1; \ - fi; \ - ${CARGO} build --release; \ - mkdir -p ./bin-linux-$${ARCH}; \ - cp ../target/release/mithril-aggregator ./bin-linux-$${ARCH}/mithril-aggregator - run: build ./mithril-aggregator serve @@ -50,7 +31,23 @@ doc: docker-build: cd ../ && docker build -t mithril/mithril-aggregator -f mithril-aggregator/Dockerfile . -docker-build-ci: build-ci +docker-build-ci: build + # Check that we are on Linux, then build + if [ "$$(uname -s)" != "Linux" ]; then \ + echo "Error: this target only supports Linux"; \ + exit 1; \ + fi; \ + RAW_ARCH=$$(uname -m); \ + if [ "$$RAW_ARCH" = "x86_64" ]; then \ + ARCH="amd64"; \ + elif [ "$$RAW_ARCH" = "aarch64" ]; then \ + ARCH="arm64"; \ + else \ + echo "Error: unsupported architecture $$RAW_ARCH"; \ + exit 1; \ + fi; \ + mkdir -p ./bin-linux-$${ARCH}; \ + cp ../target/release/mithril-aggregator ./bin-linux-$${ARCH}/mithril-aggregator cd ../ && docker build -t mithril/mithril-aggregator -f mithril-aggregator/Dockerfile.ci --build-arg DOCKER_IMAGE_FROM --build-arg CARDANO_NODE_VERSION . docker-run: diff --git a/mithril-client-cli/Makefile b/mithril-client-cli/Makefile index 4a94b76411a..376f97a62b1 100644 --- a/mithril-client-cli/Makefile +++ b/mithril-client-cli/Makefile @@ -13,25 +13,6 @@ build: ${CARGO} build --release cp ../target/release/mithril-client . -build-ci: - # Check that we are on Linux, then build - if [ "$$(uname -s)" != "Linux" ]; then \ - echo "Error: this target only supports Linux"; \ - exit 1; \ - fi; \ - RAW_ARCH=$$(uname -m); \ - if [ "$$RAW_ARCH" = "x86_64" ]; then \ - ARCH="amd64"; \ - elif [ "$$RAW_ARCH" = "aarch64" ]; then \ - ARCH="arm64"; \ - else \ - echo "Error: unsupported architecture $$RAW_ARCH"; \ - exit 1; \ - fi; \ - ${CARGO} build --release; \ - mkdir -p ./bin-linux-$${ARCH}; \ - cp ../target/release/mithril-client ./bin-linux-$${ARCH}/mithril-client - run: build @./mithril-client $(call args,defaultstring) @@ -58,7 +39,23 @@ doc: docker-build: cd ../ && docker build -t mithril/mithril-client -f mithril-client-cli/Dockerfile . -docker-build-ci: build-ci +docker-build-ci: build + # Check that we are on Linux, then build + if [ "$$(uname -s)" != "Linux" ]; then \ + echo "Error: this target only supports Linux"; \ + exit 1; \ + fi; \ + RAW_ARCH=$$(uname -m); \ + if [ "$$RAW_ARCH" = "x86_64" ]; then \ + ARCH="amd64"; \ + elif [ "$$RAW_ARCH" = "aarch64" ]; then \ + ARCH="arm64"; \ + else \ + echo "Error: unsupported architecture $$RAW_ARCH"; \ + exit 1; \ + fi; \ + mkdir -p ./bin-linux-$${ARCH}; \ + cp ../target/release/mithril-client ./bin-linux-$${ARCH}/mithril-client cd ../ && docker build -t mithril/mithril-client -f mithril-client-cli/Dockerfile.ci --build-arg DOCKER_IMAGE_FROM . docker-run: diff --git a/mithril-relay/Makefile b/mithril-relay/Makefile index 11550574e13..433697c4d57 100644 --- a/mithril-relay/Makefile +++ b/mithril-relay/Makefile @@ -26,6 +26,22 @@ docker-build: cd ../ && docker build -t mithril/mithril-relay -f mithril-relay/Dockerfile . docker-build-ci: build + # Check that we are on Linux, then build + if [ "$$(uname -s)" != "Linux" ]; then \ + echo "Error: this target only supports Linux"; \ + exit 1; \ + fi; \ + RAW_ARCH=$$(uname -m); \ + if [ "$$RAW_ARCH" = "x86_64" ]; then \ + ARCH="amd64"; \ + elif [ "$$RAW_ARCH" = "aarch64" ]; then \ + ARCH="arm64"; \ + else \ + echo "Error: unsupported architecture $$RAW_ARCH"; \ + exit 1; \ + fi; \ + mkdir -p ./bin-linux-$${ARCH}; \ + cp ../target/release/mithril-relay ./bin-linux-$${ARCH}/mithril-relay cd ../ && docker build -t mithril/mithril-relay -f mithril-relay/Dockerfile.ci --build-arg DOCKER_IMAGE_FROM . docker-run: diff --git a/mithril-signer/Makefile b/mithril-signer/Makefile index ca4467e8348..6110eeef73b 100644 --- a/mithril-signer/Makefile +++ b/mithril-signer/Makefile @@ -11,25 +11,6 @@ build: run: build ./mithril-signer -build-ci: - # Check that we are on Linux, then build - if [ "$$(uname -s)" != "Linux" ]; then \ - echo "Error: this target only supports Linux"; \ - exit 1; \ - fi; \ - RAW_ARCH=$$(uname -m); \ - if [ "$$RAW_ARCH" = "x86_64" ]; then \ - ARCH="amd64"; \ - elif [ "$$RAW_ARCH" = "aarch64" ]; then \ - ARCH="arm64"; \ - else \ - echo "Error: unsupported architecture $$RAW_ARCH"; \ - exit 1; \ - fi; \ - ${CARGO} build --release; \ - mkdir -p ./bin-linux-$${ARCH}; \ - cp ../target/release/mithril-signer ./bin-linux-$${ARCH}/mithril-signer - debug: ${CARGO} run -- -vvv @@ -50,7 +31,23 @@ doc: docker-build: cd ../ && docker build -t mithril/mithril-signer -f mithril-signer/Dockerfile . -docker-build-ci: build-ci +docker-build-ci: build + # Check that we are on Linux, then build + if [ "$$(uname -s)" != "Linux" ]; then \ + echo "Error: this target only supports Linux"; \ + exit 1; \ + fi; \ + RAW_ARCH=$$(uname -m); \ + if [ "$$RAW_ARCH" = "x86_64" ]; then \ + ARCH="amd64"; \ + elif [ "$$RAW_ARCH" = "aarch64" ]; then \ + ARCH="arm64"; \ + else \ + echo "Error: unsupported architecture $$RAW_ARCH"; \ + exit 1; \ + fi; \ + mkdir -p ./bin-linux-$${ARCH}; \ + cp ../target/release/mithril-signer ./bin-linux-$${ARCH}/mithril-signer cd ../ && docker build -t mithril/mithril-signer -f mithril-signer/Dockerfile.ci --build-arg DOCKER_IMAGE_FROM --build-arg CARDANO_NODE_VERSION . docker-run: From 07cb96540018ab24a76b757a681b5d3b4ebfd95f Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Tue, 10 Feb 2026 17:41:25 +0100 Subject: [PATCH 6/6] chore: upgrade crate versions * mithril-aggregator from `0.8.20` to `0.8.21` * mithril-client-cli from `0.12.38` to `0.12.39` * mithril-relay from `0.2.1` to `0.2.2` * mithril-signer from `0.3.13` to `0.3.14` * mithril-test-lab/mithril-devnet/VERSION from `0.4.22` to `0.4.23` --- Cargo.lock | 8 ++++---- mithril-aggregator/Cargo.toml | 2 +- mithril-client-cli/Cargo.toml | 2 +- mithril-relay/Cargo.toml | 2 +- mithril-signer/Cargo.toml | 2 +- mithril-test-lab/mithril-devnet/VERSION | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 98a8af064af..3bf9e80eb9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3887,7 +3887,7 @@ dependencies = [ [[package]] name = "mithril-aggregator" -version = "0.8.20" +version = "0.8.21" dependencies = [ "anyhow", "async-trait", @@ -4130,7 +4130,7 @@ dependencies = [ [[package]] name = "mithril-client-cli" -version = "0.12.38" +version = "0.12.39" dependencies = [ "anyhow", "async-trait", @@ -4356,7 +4356,7 @@ dependencies = [ [[package]] name = "mithril-relay" -version = "0.2.1" +version = "0.2.2" dependencies = [ "anyhow", "bincode", @@ -4418,7 +4418,7 @@ dependencies = [ [[package]] name = "mithril-signer" -version = "0.3.13" +version = "0.3.14" dependencies = [ "anyhow", "async-trait", diff --git a/mithril-aggregator/Cargo.toml b/mithril-aggregator/Cargo.toml index 7c092eb2ce7..74afd7fe63f 100644 --- a/mithril-aggregator/Cargo.toml +++ b/mithril-aggregator/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-aggregator" -version = "0.8.20" +version = "0.8.21" description = "A Mithril Aggregator server" authors = { workspace = true } edition = { workspace = true } diff --git a/mithril-client-cli/Cargo.toml b/mithril-client-cli/Cargo.toml index ff0d663e7b6..29f0fb26ade 100644 --- a/mithril-client-cli/Cargo.toml +++ b/mithril-client-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-client-cli" -version = "0.12.38" +version = "0.12.39" description = "A Mithril Client" authors = { workspace = true } edition = { workspace = true } diff --git a/mithril-relay/Cargo.toml b/mithril-relay/Cargo.toml index 659b8f8d500..9b37b679f2d 100644 --- a/mithril-relay/Cargo.toml +++ b/mithril-relay/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-relay" -version = "0.2.1" +version = "0.2.2" description = "A Mithril relay" authors = { workspace = true } edition = { workspace = true } diff --git a/mithril-signer/Cargo.toml b/mithril-signer/Cargo.toml index d46d9681614..bd35c86b617 100644 --- a/mithril-signer/Cargo.toml +++ b/mithril-signer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-signer" -version = "0.3.13" +version = "0.3.14" description = "A Mithril Signer" authors = { workspace = true } edition = { workspace = true } diff --git a/mithril-test-lab/mithril-devnet/VERSION b/mithril-test-lab/mithril-devnet/VERSION index 5c19aee836d..0809da55316 100644 --- a/mithril-test-lab/mithril-devnet/VERSION +++ b/mithril-test-lab/mithril-devnet/VERSION @@ -1,2 +1,2 @@ -0.4.22 +0.4.23