mirror of
https://github.com/QuilibriumNetwork/ceremonyclient.git
synced 2026-02-21 10:27:26 +08:00
Feat/2.1 qclient refactor and node install (#429)
* initial auto-update * working link, update, and testing docker container and scripts * refactor packages/folders * move files to proper folders * fix typos Closes #421 * optimize rpm imports * optimize channel imports * Refactor split command to allow testing of split operations Closes #338 * modify split and test for folder changes * remove alias * fix docker warning about FROM and AS being in different letter case Closes #422 * QClient Account Command * Display transaction details and confirmation prompts for transfer and merge commands * build qclient docker improvements * update build args for mpfr.so.6 * update install and node commands * remove NodeConfig check for qclient node commands * udpate * working node commands * update commands * move utils and rename package --------- Co-authored-by: Vasyl Tretiakov <vasyl.tretiakov@gmail.com> Co-authored-by: littleblackcloud <163544315+littleblackcloud@users.noreply.github.com> Co-authored-by: 0xOzgur <29779769+0xOzgur@users.noreply.github.com> Co-authored-by: Cassandra Heart <7929478+CassOnMars@users.noreply.github.com>
This commit is contained in:
parent
7952964d4e
commit
9cfbdef12c
@ -1,4 +1,4 @@
|
||||
FROM golang:1.20.14-alpine3.19 as build
|
||||
FROM golang:1.20.14-alpine3.19 AS build
|
||||
|
||||
ARG NODE_VERSION
|
||||
ARG MAX_KEY_ID
|
||||
|
||||
@ -1,9 +1,16 @@
|
||||
FROM ubuntu:24.04 as build-base
|
||||
# syntax=docker.io/docker/dockerfile:1.7-labs
|
||||
FROM --platform=${BUILDPLATFORM} ubuntu:24.04 AS base
|
||||
|
||||
ENV PATH="${PATH}:/root/.cargo/bin/"
|
||||
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
|
||||
# Install GMP 6.2 (6.3 which MacOS is using only available on Debian unstable)
|
||||
RUN apt-get update && apt-get install -y \
|
||||
RUN --mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,target=/var/lib/apt,sharing=locked \
|
||||
apt-get update && apt-get install -y \
|
||||
build-essential \
|
||||
curl \
|
||||
git \
|
||||
@ -29,24 +36,30 @@ RUN wget https://raw.githubusercontent.com/emp-toolkit/emp-readme/master/scripts
|
||||
|
||||
RUN python install.py --install --tool --ot
|
||||
|
||||
RUN cd emp-tool && sed -i 's/add_library(${NAME} SHARED ${sources})/add_library(${NAME} STATIC ${sources})/g' CMakeLists.txt && mkdir build && cd build && cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local && cd .. && make && make install && cd ..
|
||||
RUN cd emp-ot && mkdir build && cd build && cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local && cd .. && make && make install && cd ..
|
||||
RUN --mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
|
||||
cd emp-tool && sed -i 's/add_library(${NAME} SHARED ${sources})/add_library(${NAME} STATIC ${sources})/g' CMakeLists.txt && mkdir build && cd build && cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local && cd .. && make && make install && cd ..
|
||||
|
||||
RUN apt update && apt install -y wget && \
|
||||
RUN --mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
|
||||
cd emp-ot && mkdir build && cd build && cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local && cd .. && make && make install && cd ..
|
||||
|
||||
ARG GO_VERSION=1.23.5
|
||||
RUN --mount=type=cache,target=/usr/local,id=usr-local-${TARGETOS}-${TARGETARCH} \
|
||||
apt update && apt install -y wget && \
|
||||
ARCH=$(dpkg --print-architecture) && \
|
||||
case ${ARCH} in \
|
||||
amd64) GOARCH=amd64 ;; \
|
||||
arm64) GOARCH=arm64 ;; \
|
||||
*) echo "Unsupported architecture: ${ARCH}" && exit 1 ;; \
|
||||
esac && \
|
||||
wget https://go.dev/dl/go1.23.5.linux-${GOARCH}.tar.gz && \
|
||||
wget https://go.dev/dl/go${GO_VERSION}.linux-${GOARCH}.tar.gz && \
|
||||
rm -rf /usr/local/go && \
|
||||
tar -C /usr/local -xzf go1.23.5.linux-${GOARCH}.tar.gz && \
|
||||
rm go1.23.5.linux-${GOARCH}.tar.gz
|
||||
tar -C /usr/local -xzf go${GO_VERSION}.linux-${GOARCH}.tar.gz && \
|
||||
rm go${GO_VERSION}.linux-${GOARCH}.tar.gz
|
||||
|
||||
ENV PATH=$PATH:/usr/local/go/bin
|
||||
|
||||
RUN git clone https://github.com/flintlib/flint.git && \
|
||||
RUN --mount=type=cache,target=/usr/local,id=usr-local-${TARGETOS}-${TARGETARCH} \
|
||||
git clone https://github.com/flintlib/flint.git && \
|
||||
cd flint && \
|
||||
git checkout flint-3.0 && \
|
||||
./bootstrap.sh && \
|
||||
@ -64,65 +77,197 @@ RUN git clone https://github.com/flintlib/flint.git && \
|
||||
|
||||
COPY docker/rustup-init.sh /opt/rustup-init.sh
|
||||
|
||||
RUN /opt/rustup-init.sh -y --profile minimal
|
||||
RUN --mount=type=cache,target=/root/.cargo,id=cargo-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/opt/ceremonyclient/target/,id=target-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/go/pkg/mod,id=go-mod-${TARGETOS}-${TARGETARCH} \
|
||||
/opt/rustup-init.sh -y --profile minimal
|
||||
|
||||
# Install uniffi-bindgen-go
|
||||
RUN cargo install uniffi-bindgen-go --git https://github.com/NordSecurity/uniffi-bindgen-go --tag v0.2.1+v0.25.0
|
||||
RUN --mount=type=cache,target=/root/.cargo,id=cargo-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/opt/ceremonyclient/target/,id=target-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/go/pkg/mod,id=go-mod-${TARGETOS}-${TARGETARCH} \
|
||||
cargo install uniffi-bindgen-go --git https://github.com/NordSecurity/uniffi-bindgen-go --tag v0.2.1+v0.25.0
|
||||
|
||||
FROM build-base as build
|
||||
FROM base AS build
|
||||
|
||||
ENV GOEXPERIMENT=arenas
|
||||
ENV QUILIBRIUM_SIGNATURE_CHECK=false
|
||||
|
||||
# Install grpcurl before building the node and client
|
||||
# as to avoid needing to redo it on rebuilds
|
||||
RUN --mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/go/pkg/mod,id=go-mod-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/root/.cache/go-build,id=go-build-${TARGETOS}-${TARGETARCH} \
|
||||
go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
|
||||
|
||||
WORKDIR /opt/ceremonyclient
|
||||
|
||||
COPY . .
|
||||
# Copy everything except node and client so as to avoid
|
||||
# invalidating the cache at this point on client or node rebuilds
|
||||
|
||||
WORKDIR /opt/ceremonyclient/ferret
|
||||
RUN ./generate.sh
|
||||
COPY --exclude=node \
|
||||
--exclude=client \
|
||||
--exclude=sidecar . .
|
||||
|
||||
RUN --mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/go/pkg/mod,id=go-mod-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/root/.cache/go-build,id=go-build-${TARGETOS}-${TARGETARCH} \
|
||||
go mod download
|
||||
|
||||
## Generate Rust bindings for VDF
|
||||
WORKDIR /opt/ceremonyclient/vdf
|
||||
RUN ./generate.sh
|
||||
|
||||
RUN --mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/go/pkg/mod,id=go-mod-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/root/.cache/go-build,id=go-build-${TARGETOS}-${TARGETARCH} \
|
||||
go mod download
|
||||
|
||||
RUN --mount=type=cache,target=/root/.cargo,id=cargo-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/opt/ceremonyclient/target/,id=target-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/go/pkg/mod,id=go-mod-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/root/.cache/go-build,id=go-build-${TARGETOS}-${TARGETARCH} \
|
||||
./generate.sh
|
||||
|
||||
|
||||
## Generate Rust bindings for Ferret
|
||||
WORKDIR /opt/ceremonyclient/ferret
|
||||
|
||||
RUN --mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/go/pkg/mod,id=go-mod-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/root/.cache/go-build,id=go-build-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/opt/ceremonyclient/target/,id=target-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/root/.cargo,id=cargo-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/go/bin,id=go-bin-${TARGETOS}-${TARGETARCH} \
|
||||
go mod download
|
||||
|
||||
RUN --mount=type=cache,target=/root/.cargo,id=cargo-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/opt/ceremonyclient/target/,id=target-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/go/pkg/mod,id=go-mod-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/root/.cache/go-build,id=go-build-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/go/bin,id=go-bin-${TARGETOS}-${TARGETARCH} \
|
||||
./generate.sh
|
||||
|
||||
## Generate Rust bindings for BLS48581
|
||||
WORKDIR /opt/ceremonyclient/bls48581
|
||||
RUN ./generate.sh
|
||||
|
||||
RUN --mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/go/pkg/mod,id=go-mod-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/root/.cache/go-build,id=go-build-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/opt/ceremonyclient/target/,id=target-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/root/.cargo,id=cargo-${TARGETOS}-${TARGETARCH} \
|
||||
go mod download
|
||||
|
||||
RUN --mount=type=cache,target=/root/.cargo,id=cargo-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/opt/ceremonyclient/target/,id=target-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/go/pkg/mod,id=go-mod-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/root/.cache/go-build,id=go-build-${TARGETOS}-${TARGETARCH} \
|
||||
./generate.sh
|
||||
|
||||
## Generate Rust bindings for VerEnc
|
||||
WORKDIR /opt/ceremonyclient/verenc
|
||||
RUN ./generate.sh
|
||||
|
||||
RUN --mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/go/pkg/mod,id=go-mod-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/root/.cache/go-build,id=go-build-${TARGETOS}-${TARGETARCH} \
|
||||
go mod download
|
||||
|
||||
RUN --mount=type=cache,target=/root/.cargo,id=cargo-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/opt/ceremonyclient/target/,id=target-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/go/pkg/mod,id=go-mod-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/root/.cache/go-build,id=go-build-${TARGETOS}-${TARGETARCH} \
|
||||
./generate.sh
|
||||
|
||||
FROM build AS build-node
|
||||
|
||||
# Build and install the node
|
||||
COPY ./node /opt/ceremonyclient/node
|
||||
WORKDIR /opt/ceremonyclient/node
|
||||
|
||||
RUN ./build.sh && cp node /usr/bin
|
||||
RUN go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
|
||||
RUN --mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/go/pkg/mod,id=go-mod-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/root/.cache/go-build,id=go-build-${TARGETOS}-${TARGETARCH} \
|
||||
./build.sh && cp node /usr/bin
|
||||
|
||||
FROM build AS build-qclient
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
# Build and install qclient
|
||||
COPY ./node /opt/ceremonyclient/node
|
||||
|
||||
WORKDIR /opt/ceremonyclient/node
|
||||
|
||||
RUN --mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/go/pkg/mod,id=go-mod-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/root/.cache/go-build,id=go-build-${TARGETOS}-${TARGETARCH} \
|
||||
go mod download
|
||||
|
||||
COPY ./client /opt/ceremonyclient/client
|
||||
WORKDIR /opt/ceremonyclient/client
|
||||
|
||||
RUN ./build.sh -o qclient && cp qclient /usr/bin
|
||||
RUN --mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/go/pkg/mod,id=go-mod-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/root/.cache/go-build,id=go-build-${TARGETOS}-${TARGETARCH} \
|
||||
go mod download
|
||||
|
||||
ARG BINARIES_DIR=/opt/ceremonyclient/target/release
|
||||
ENV CGO_ENABLED=1
|
||||
ENV CGO_LDFLAGS="-L/usr/local/lib -lflint -lgmp -lmpfr -ldl -lm -L$BINARIES_DIR -lvdf -lverenc -lbls48581 -static"
|
||||
RUN --mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/go/pkg/mod,id=go-mod-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/opt/ceremonyclient/target,id=target-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/root/.cache/go-build,id=go-build-${TARGETOS}-${TARGETARCH} \
|
||||
GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -ldflags "-linkmode 'external'" -o qclient
|
||||
RUN cp qclient /usr/bin
|
||||
|
||||
FROM build AS build-sidecar
|
||||
# Build and install sidecar
|
||||
COPY ./node /opt/ceremonyclient/node
|
||||
|
||||
WORKDIR /opt/ceremonyclient/node
|
||||
|
||||
RUN --mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/go/pkg/mod,id=go-mod-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/root/.cache/go-build,id=go-build-${TARGETOS}-${TARGETARCH} \
|
||||
go mod download
|
||||
|
||||
COPY ./sidecar /opt/ceremonyclient/sidecar
|
||||
WORKDIR /opt/ceremonyclient/sidecar
|
||||
RUN ./build.sh -o sidecar && cp sidecar /usr/bin
|
||||
|
||||
RUN --mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/go/pkg/mod,id=go-mod-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/root/.cache/go-build,id=go-build-${TARGETOS}-${TARGETARCH} \
|
||||
go mod download
|
||||
|
||||
RUN --mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/go/pkg/mod,id=go-mod-${TARGETOS}-${TARGETARCH} \
|
||||
--mount=type=cache,target=/opt/ceremonyclient/target,id=target-${TARGETOS}-${TARGETARCH} \
|
||||
./build.sh -o sidecar && cp sidecar /usr/bin
|
||||
|
||||
# Allows exporting single binary
|
||||
FROM scratch as qclient
|
||||
COPY --from=build /usr/bin/qclient /qclient
|
||||
ENTRYPOINT [ "/qclient" ]
|
||||
|
||||
# Allows exporting single binary
|
||||
FROM scratch as sidecar
|
||||
COPY --from=build /usr/bin/sidecar /sidecar
|
||||
FROM scratch AS sidecar
|
||||
COPY --from=build-sidecar /usr/bin/sidecar /sidecar
|
||||
ENTRYPOINT [ "/sidecar" ]
|
||||
|
||||
# Allows exporting single binary
|
||||
FROM scratch AS node
|
||||
COPY --from=build /usr/bin/node /node
|
||||
COPY --from=build-node /usr/bin/node /node
|
||||
ENTRYPOINT [ "/node" ]
|
||||
|
||||
# Allows exporting single binary
|
||||
FROM scratch AS qclient-unix
|
||||
COPY --from=build-qclient /usr/bin/qclient /qclient
|
||||
ENTRYPOINT [ "/qclient" ]
|
||||
|
||||
FROM qclient-unix AS qclient-linux
|
||||
FROM qclient-unix AS qclient-darwin
|
||||
FROM qclient-${TARGETOS} AS qclient
|
||||
|
||||
FROM ubuntu:24.04
|
||||
|
||||
RUN apt-get update && apt-get install libflint-dev -y
|
||||
@ -146,8 +291,8 @@ LABEL org.opencontainers.image.revision=$GIT_COMMIT
|
||||
|
||||
RUN apt-get update && apt-get install -y ca-certificates
|
||||
|
||||
COPY --from=build /usr/bin/node /usr/local/bin
|
||||
COPY --from=build /opt/ceremonyclient/client/qclient /usr/local/bin
|
||||
COPY --from=build-node /usr/bin/node /usr/local/bin
|
||||
COPY --from=build-qclient /opt/ceremonyclient/client/qclient /usr/local/bin
|
||||
|
||||
WORKDIR /root
|
||||
|
||||
|
||||
12
Dockerfile.source.dockerignore
Normal file
12
Dockerfile.source.dockerignore
Normal file
@ -0,0 +1,12 @@
|
||||
client/build/
|
||||
target/
|
||||
node/build/
|
||||
node/.vscode/
|
||||
.git
|
||||
.gitignore
|
||||
vdf/generated/
|
||||
bls48581/generated/
|
||||
ferret/generated/
|
||||
sidecar/generated/
|
||||
verenc/generated/
|
||||
Dockerfile*
|
||||
@ -1,4 +1,4 @@
|
||||
FROM ubuntu:24.04 as build-base
|
||||
FROM ubuntu:24.04 AS build-base
|
||||
|
||||
ENV PATH="${PATH}:/root/.cargo/bin/"
|
||||
|
||||
@ -61,7 +61,7 @@ RUN /opt/rustup-init.sh -y --profile minimal
|
||||
# Install uniffi-bindgen-go
|
||||
RUN cargo install uniffi-bindgen-go --git https://github.com/NordSecurity/uniffi-bindgen-go --tag v0.2.1+v0.25.0
|
||||
|
||||
FROM build-base as build
|
||||
FROM build-base AS build
|
||||
|
||||
ENV GOEXPERIMENT=arenas
|
||||
ENV QUILIBRIUM_SIGNATURE_CHECK=false
|
||||
@ -90,7 +90,7 @@ WORKDIR /opt/ceremonyclient/client
|
||||
RUN ./build.sh -o qclient && cp qclient /usr/bin
|
||||
|
||||
# Allows exporting single binary
|
||||
FROM scratch as qclient
|
||||
FROM scratch AS qclient
|
||||
COPY --from=build /usr/bin/qclient /qclient
|
||||
ENTRYPOINT [ "/qclient" ]
|
||||
|
||||
|
||||
@ -85,7 +85,7 @@ tasks:
|
||||
- docker build --platform linux/amd64 -f Dockerfile.sourceavx512 --output node/build/amd64_avx512_linux --target=node .
|
||||
|
||||
build_qclient_amd64_linux:
|
||||
desc: Build the QClient node binary for AMD64 Linux. Outputs to node/build.
|
||||
desc: Build the QClient node binary for AMD64 Linux. Outputs to client/build.
|
||||
cmds:
|
||||
- docker build --platform linux/amd64 -f Dockerfile.source --output client/build/amd64_linux --target=qclient .
|
||||
|
||||
@ -142,3 +142,8 @@ tasks:
|
||||
cmds:
|
||||
- docker push ${QUILIBRIUM_IMAGE_NAME:-quilibrium}:{{.VERSION}}
|
||||
- docker push ${QUILIBRIUM_IMAGE_NAME:-quilibrium}:latest
|
||||
|
||||
test:qclient:
|
||||
desc: Test the Quilibrium docker image.
|
||||
cmds:
|
||||
- client/test/run_tests.sh -d 'ubuntu' -v '24.04'
|
||||
|
||||
@ -26,7 +26,8 @@ case "$os_type" in
|
||||
fi
|
||||
;;
|
||||
"Linux")
|
||||
go build -ldflags "-linkmode 'external' -extldflags '-L$BINARIES_DIR -Wl,-Bstatic -lvdf -lbls48581 -lverenc -Wl,-Bdynamic -lstdc++ -ldl -lm -lflint -lgmp -lmpfr'" "$@"
|
||||
export CGO_LDFLAGS="-L/usr/local/lib -lflint -lgmp -lmpfr -ldl -lm -L$BINARIES_DIR -lvdf -lverenc -lbls48581 -static"
|
||||
go build -ldflags "-linkmode 'external'" "$@"
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported platform"
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
var configCmd = &cobra.Command{
|
||||
Use: "config",
|
||||
Short: "Performs a configuration operation",
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(configCmd)
|
||||
}
|
||||
44
client/cmd/config/config.go
Normal file
44
client/cmd/config/config.go
Normal file
@ -0,0 +1,44 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
)
|
||||
|
||||
var ConfigCmd = &cobra.Command{
|
||||
Use: "config",
|
||||
Short: "Performs a configuration operation",
|
||||
}
|
||||
|
||||
var printConfigCmd = &cobra.Command{
|
||||
Use: "print",
|
||||
Short: "Print the current configuration",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
config, err := utils.LoadClientConfig()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error reading config: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Print the config in a readable format
|
||||
fmt.Printf("Data Directory: %s\n", config.DataDir)
|
||||
fmt.Printf("Symlink Path: %s\n", config.SymlinkPath)
|
||||
fmt.Printf("Signature Check: %v\n", config.SignatureCheck)
|
||||
},
|
||||
}
|
||||
|
||||
var createDefaultConfigCmd = &cobra.Command{
|
||||
Use: "create-default",
|
||||
Short: "Create a default configuration file",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
utils.CreateDefaultConfig()
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
ConfigCmd.AddCommand(printConfigCmd)
|
||||
ConfigCmd.AddCommand(createDefaultConfigCmd)
|
||||
}
|
||||
58
client/cmd/config/signatureCheck.go
Normal file
58
client/cmd/config/signatureCheck.go
Normal file
@ -0,0 +1,58 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
)
|
||||
|
||||
var signatureCheckCmd = &cobra.Command{
|
||||
Use: "signature-check [true|false]",
|
||||
Short: "Set signature check setting",
|
||||
Long: `Set the signature check setting in the client configuration.
|
||||
When disabled, signature verification will be bypassed (not recommended for production use).
|
||||
Use 'true' to enable or 'false' to disable. If no argument is provided, it toggles the current setting.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
config, err := utils.LoadClientConfig()
|
||||
if err != nil {
|
||||
fmt.Printf("Error loading config: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
// Set the signature check based on the provided argument
|
||||
value := strings.ToLower(args[0])
|
||||
if value == "true" {
|
||||
config.SignatureCheck = true
|
||||
} else if value == "false" {
|
||||
config.SignatureCheck = false
|
||||
} else {
|
||||
// If the value is not true or false, print error message and exit
|
||||
fmt.Printf("Error: Invalid value '%s'. Please use 'true' or 'false'.\n", value)
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
// Toggle the signature check setting if no arguments are provided
|
||||
config.SignatureCheck = !config.SignatureCheck
|
||||
}
|
||||
|
||||
// Save the updated config
|
||||
if err := utils.SaveClientConfig(config); err != nil {
|
||||
fmt.Printf("Error saving config: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
status := "enabled"
|
||||
if !config.SignatureCheck {
|
||||
status = "disabled"
|
||||
}
|
||||
fmt.Printf("Signature check has been set to %s and will be persisted across future commands unless reset.\n", status)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
ConfigCmd.AddCommand(signatureCheckCmd)
|
||||
}
|
||||
1
client/cmd/config/utils.go
Normal file
1
client/cmd/config/utils.go
Normal file
@ -0,0 +1 @@
|
||||
package config
|
||||
51
client/cmd/download-signatures.go
Normal file
51
client/cmd/download-signatures.go
Normal file
@ -0,0 +1,51 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
"source.quilibrium.com/quilibrium/monorepo/node/config"
|
||||
)
|
||||
|
||||
var versionFlag string
|
||||
|
||||
var downloadSignaturesCmd = &cobra.Command{
|
||||
Use: "download-signatures",
|
||||
Short: "Download signature files for the current binary",
|
||||
Long: `Download signature files for the current binary. This command will download
|
||||
the digest file and all signature files needed for verification. If --version is specified,
|
||||
it will download signatures for that version. Otherwise, it will download signatures for
|
||||
the latest version.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
var version string
|
||||
|
||||
if versionFlag != "" {
|
||||
// Use specified version
|
||||
version = versionFlag
|
||||
} else {
|
||||
// Get the current version
|
||||
version = config.GetVersionString()
|
||||
}
|
||||
|
||||
// Download signature files
|
||||
if err := utils.DownloadReleaseSignatures(utils.ReleaseTypeQClient, version); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error downloading signature files: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("Successfully downloaded signature files for version %s\n", version)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
downloadSignaturesCmd.Flags().StringVarP(
|
||||
&versionFlag,
|
||||
"version",
|
||||
"v",
|
||||
"",
|
||||
"Version to download signatures for (defaults to latest version)",
|
||||
)
|
||||
rootCmd.AddCommand(downloadSignaturesCmd)
|
||||
}
|
||||
100
client/cmd/link.go
Normal file
100
client/cmd/link.go
Normal file
@ -0,0 +1,100 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
)
|
||||
|
||||
var symlinkPath = "/usr/local/bin/qclient"
|
||||
|
||||
var linkCmd = &cobra.Command{
|
||||
Use: "link",
|
||||
Short: "Create a symlink to qclient in PATH",
|
||||
Long: `Create a symlink to the qclient binary in the directory /usr/local/bin/.
|
||||
|
||||
Example: qclient link`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
// Get the path to the current executable
|
||||
execPath, err := os.Executable()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get executable path: %w", err)
|
||||
}
|
||||
|
||||
IsSudo := utils.IsSudo()
|
||||
if IsSudo {
|
||||
fmt.Println("Running as sudo, creating symlink at /usr/local/bin/qclient")
|
||||
} else {
|
||||
fmt.Println("Cannot create symlink at /usr/local/bin/qclient, please run this command with sudo")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Check if the current executable is in the expected location
|
||||
expectedPrefix := utils.ClientDataPath
|
||||
|
||||
// Check if the current executable is in the expected location
|
||||
if !strings.HasPrefix(execPath, expectedPrefix) {
|
||||
fmt.Printf("Current executable is not in the expected location: %s\n", execPath)
|
||||
fmt.Printf("Expected location should start with: %s\n", expectedPrefix)
|
||||
|
||||
// Ask user if they want to move it
|
||||
fmt.Print("Would you like to move the executable to the standard location? (y/n): ")
|
||||
var response string
|
||||
fmt.Scanln(&response)
|
||||
|
||||
if strings.ToLower(response) == "y" || strings.ToLower(response) == "yes" {
|
||||
if err := moveExecutableToStandardLocation(execPath); err != nil {
|
||||
return fmt.Errorf("failed to move executable: %w", err)
|
||||
}
|
||||
// Update execPath to the new location
|
||||
execPath, err = os.Executable()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get new executable path: %w", err)
|
||||
}
|
||||
} else {
|
||||
fmt.Println("Continuing with current location...")
|
||||
}
|
||||
}
|
||||
|
||||
// Create the symlink (handles existing symlinks)
|
||||
if err := utils.CreateSymlink(execPath, symlinkPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Symlink created at %s\n", symlinkPath)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func moveExecutableToStandardLocation(execPath string) error {
|
||||
// Get the directory of the current executable
|
||||
version, err := GetVersionInfo(false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get version info: %w", err)
|
||||
}
|
||||
destDir := filepath.Join(utils.ClientDataPath, "bin", version.Version)
|
||||
|
||||
// Create the standard location directory if it doesn't exist
|
||||
currentUser, err := utils.GetCurrentSudoUser()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get current user: %w", err)
|
||||
}
|
||||
if err := utils.ValidateAndCreateDir(destDir, currentUser); err != nil {
|
||||
return fmt.Errorf("failed to create directory: %w", err)
|
||||
}
|
||||
|
||||
// Move the executable to the standard location
|
||||
if err := os.Rename(execPath, filepath.Join(destDir, StandardizedQClientFileName)); err != nil {
|
||||
return fmt.Errorf("failed to move executable: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(linkCmd)
|
||||
}
|
||||
296
client/cmd/node/autoUpdate.go
Normal file
296
client/cmd/node/autoUpdate.go
Normal file
@ -0,0 +1,296 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// autoUpdateCmd represents the command to setup automatic updates
|
||||
var autoUpdateCmd = &cobra.Command{
|
||||
Use: "auto-update [enable|disable|status]",
|
||||
Short: "Setup automatic update checks",
|
||||
Long: `Setup, remove, or check status of a cron job to automatically check for Quilibrium node updates every 10 minutes.
|
||||
|
||||
This command will create, remove, or check a cron entry that runs 'qclient node update' every 10 minutes
|
||||
to check for and apply any available updates.
|
||||
|
||||
Example:
|
||||
# Setup automatic update checks
|
||||
qclient node auto-update enable
|
||||
|
||||
# Remove automatic update checks
|
||||
qclient node auto-update disable
|
||||
|
||||
# Check if automatic update is enabled
|
||||
qclient node auto-update status`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 1 || (args[0] != "enable" && args[0] != "disable" && args[0] != "status") {
|
||||
fmt.Fprintf(os.Stderr, "Error: must specify 'enable', 'disable', or 'status'\n")
|
||||
cmd.Help()
|
||||
return
|
||||
}
|
||||
|
||||
if args[0] == "enable" {
|
||||
setupCronJob()
|
||||
} else if args[0] == "disable" {
|
||||
removeCronJob()
|
||||
} else if args[0] == "status" {
|
||||
checkAutoUpdateStatus()
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
NodeCmd.AddCommand(autoUpdateCmd)
|
||||
}
|
||||
|
||||
func setupCronJob() {
|
||||
// Get full path to qclient executable
|
||||
qclientPath, err := exec.LookPath("qclient")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: qclient executable not found in PATH: %v\n", err)
|
||||
fmt.Fprintf(os.Stderr, "Please ensure qclient is properly installed and in your PATH (use 'sudo qclient link')\n")
|
||||
return
|
||||
}
|
||||
|
||||
// Absolute path for qclient
|
||||
qclientAbsPath, err := filepath.Abs(qclientPath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error getting absolute path for qclient: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// OS-specific setup
|
||||
if runtime.GOOS == "darwin" || runtime.GOOS == "linux" {
|
||||
setupUnixCron(qclientAbsPath)
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "Error: auto-update is only supported on macOS and Linux\n")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func removeCronJob() {
|
||||
// OS-specific removal
|
||||
if runtime.GOOS == "darwin" || runtime.GOOS == "linux" {
|
||||
removeUnixCron()
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "Error: auto-update is only supported on macOS and Linux\n")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func isCrontabInstalled() bool {
|
||||
// Check if crontab is installed
|
||||
_, err := exec.LookPath("crontab")
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func installCrontab() {
|
||||
fmt.Fprintf(os.Stdout, "Installing cron package...\n")
|
||||
// Install crontab
|
||||
updateCmd := exec.Command("sudo", "apt-get", "update")
|
||||
updateCmd.Stdout = nil
|
||||
updateCmd.Stderr = nil
|
||||
if err := updateCmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error updating package lists: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
installCmd := exec.Command("sudo", "apt-get", "install", "-y", "cron")
|
||||
installCmd.Stdout = nil
|
||||
installCmd.Stderr = nil
|
||||
if err := installCmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error installing cron package: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// verify crontab is installed
|
||||
if isCrontabInstalled() {
|
||||
fmt.Fprintf(os.Stdout, "Cron package installed\n")
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "Error: cron package not installed\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func setupUnixCron(qclientPath string) {
|
||||
if !isCrontabInstalled() {
|
||||
fmt.Fprintf(os.Stdout, "Crontab command not found, attempting to install cron package...\n")
|
||||
installCrontab()
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stdout, "Setting up cron job...\n")
|
||||
// Create cron expression: run every 10 minutes
|
||||
cronExpression := fmt.Sprintf("*/10 * * * * %s node update > /dev/null 2>&1", qclientPath)
|
||||
|
||||
// Check existing crontab
|
||||
checkCmd := exec.Command("crontab", "-l")
|
||||
checkOutput, err := checkCmd.CombinedOutput()
|
||||
|
||||
var currentCrontab string
|
||||
if err != nil {
|
||||
// If there's no crontab yet, this is fine, start with empty crontab
|
||||
currentCrontab = ""
|
||||
} else {
|
||||
currentCrontab = string(checkOutput)
|
||||
}
|
||||
|
||||
// Check if our update command is already in crontab
|
||||
if strings.Contains(currentCrontab, "### qclient-auto-update") {
|
||||
fmt.Fprintf(os.Stdout, "Automatic update check is already configured in crontab\n")
|
||||
return
|
||||
}
|
||||
|
||||
// Add new cron entry with indicators
|
||||
newCrontab := currentCrontab
|
||||
if strings.TrimSpace(newCrontab) != "" && !strings.HasSuffix(newCrontab, "\n") {
|
||||
newCrontab += "\n"
|
||||
}
|
||||
newCrontab += "### qclient-auto-update\n" +
|
||||
cronExpression + "\n" +
|
||||
"### end-qclient-auto-update\n"
|
||||
|
||||
// Write to temporary file
|
||||
tempFile, err := os.CreateTemp("", "qclient-crontab")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error creating temporary file: %v\n", err)
|
||||
return
|
||||
}
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
||||
if _, err := tempFile.WriteString(newCrontab); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error writing to temporary file: %v\n", err)
|
||||
return
|
||||
}
|
||||
tempFile.Close()
|
||||
|
||||
// Install new crontab
|
||||
installCmd := exec.Command("crontab", tempFile.Name())
|
||||
if err := installCmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error installing crontab: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stdout, "Successfully configured cron job to check for updates every 10 minutes\n")
|
||||
fmt.Fprintf(os.Stdout, "Added: %s\n", cronExpression)
|
||||
}
|
||||
|
||||
func removeUnixCron() {
|
||||
if !isCrontabInstalled() {
|
||||
fmt.Fprintf(os.Stderr, "Error: crontab command not found\n")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stdout, "Removing auto-update cron job...\n")
|
||||
|
||||
// Check existing crontab
|
||||
checkCmd := exec.Command("crontab", "-l")
|
||||
checkOutput, err := checkCmd.CombinedOutput()
|
||||
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error checking existing crontab: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
currentCrontab := string(checkOutput)
|
||||
|
||||
// No crontab or doesn't contain our section
|
||||
if currentCrontab == "" || !strings.Contains(currentCrontab, "### qclient-auto-update") {
|
||||
fmt.Fprintf(os.Stdout, "No auto-update job found in crontab\n")
|
||||
return
|
||||
}
|
||||
|
||||
// Remove our section
|
||||
startMarker := "### qclient-auto-update"
|
||||
endMarker := "### end-qclient-auto-update"
|
||||
|
||||
startIdx := strings.Index(currentCrontab, startMarker)
|
||||
endIdx := strings.Index(currentCrontab, endMarker)
|
||||
|
||||
var newCrontab string
|
||||
if startIdx >= 0 && endIdx >= 0 {
|
||||
endIdx += len(endMarker)
|
||||
// Remove the section including markers
|
||||
newCrontab = currentCrontab[:startIdx] + currentCrontab[endIdx:]
|
||||
} else {
|
||||
newCrontab = currentCrontab
|
||||
}
|
||||
|
||||
// Clean up any leftover double newlines
|
||||
newCrontab = strings.ReplaceAll(newCrontab, "\n\n\n", "\n\n")
|
||||
if strings.TrimSpace(newCrontab) != "" && !strings.HasSuffix(newCrontab, "\n") {
|
||||
newCrontab += "\n"
|
||||
}
|
||||
|
||||
// Write to temporary file
|
||||
tempFile, err := os.CreateTemp("", "qclient-crontab")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error creating temporary file: %v\n", err)
|
||||
return
|
||||
}
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
||||
if _, err := tempFile.WriteString(newCrontab); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error writing to temporary file: %v\n", err)
|
||||
return
|
||||
}
|
||||
tempFile.Close()
|
||||
|
||||
// Install new crontab
|
||||
installCmd := exec.Command("crontab", tempFile.Name())
|
||||
if err := installCmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error updating crontab: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stdout, "Successfully removed auto-update cron job\n")
|
||||
}
|
||||
|
||||
func checkAutoUpdateStatus() {
|
||||
if !isCrontabInstalled() {
|
||||
fmt.Fprintf(os.Stderr, "Error: crontab command not found\n")
|
||||
fmt.Fprintf(os.Stdout, "Auto-update is not enabled (crontab not installed)\n")
|
||||
return
|
||||
}
|
||||
|
||||
// Check existing crontab
|
||||
checkCmd := exec.Command("crontab", "-l")
|
||||
checkOutput, err := checkCmd.CombinedOutput()
|
||||
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stdout, "Auto-update is not enabled (no crontab found)\n")
|
||||
return
|
||||
}
|
||||
|
||||
currentCrontab := string(checkOutput)
|
||||
|
||||
if strings.Contains(currentCrontab, "### qclient-auto-update") {
|
||||
// Extract the cron expression
|
||||
startMarker := "### qclient-auto-update"
|
||||
endMarker := "### end-qclient-auto-update"
|
||||
|
||||
startIdx := strings.Index(currentCrontab, startMarker) + len(startMarker)
|
||||
endIdx := strings.Index(currentCrontab, endMarker)
|
||||
|
||||
if startIdx >= 0 && endIdx >= 0 {
|
||||
cronSection := currentCrontab[startIdx:endIdx]
|
||||
cronLines := strings.Split(strings.TrimSpace(cronSection), "\n")
|
||||
if len(cronLines) > 0 {
|
||||
fmt.Fprintf(os.Stdout, "Auto-update is enabled.")
|
||||
fmt.Fprintf(os.Stdout, "The installed schedule is: %s\n", strings.TrimSpace(cronLines[0]))
|
||||
} else {
|
||||
fmt.Fprintf(os.Stdout, "Auto-update is enabled\n")
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintf(os.Stdout, "Auto-update is enabled\n")
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintf(os.Stdout, "Auto-update is not enabled\n")
|
||||
}
|
||||
}
|
||||
10
client/cmd/node/clean.go
Normal file
10
client/cmd/node/clean.go
Normal file
@ -0,0 +1,10 @@
|
||||
package node
|
||||
|
||||
// TODO: Implement a clean command that will remove old versions of the node,
|
||||
// signatures, and logs
|
||||
// qlient node clean
|
||||
// qlient node clean --all (all logs, old node binaries and signatures)
|
||||
// qlient node clean --logs (remove all logs)
|
||||
// qlient node clean --node (remove all old node binary versions, including signatures)
|
||||
|
||||
// to remove even current versions, they must run 'qclient node uninstall'
|
||||
46
client/cmd/node/info.go
Normal file
46
client/cmd/node/info.go
Normal file
@ -0,0 +1,46 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
)
|
||||
|
||||
// infoCmd represents the info command
|
||||
var infoCmd = &cobra.Command{
|
||||
Use: "info",
|
||||
Short: "Get information about the Quilibrium node",
|
||||
Long: `Get information about the Quilibrium node.
|
||||
Available subcommands:
|
||||
latest-version Get the latest available version of Quilibrium node
|
||||
|
||||
Examples:
|
||||
# Get the latest version
|
||||
qclient node info latest-version`,
|
||||
}
|
||||
|
||||
// latestVersionCmd represents the latest-version command
|
||||
var latestVersionCmd = &cobra.Command{
|
||||
Use: "latest-version",
|
||||
Short: "Get the latest available version of Quilibrium node",
|
||||
Long: `Get the latest available version of Quilibrium node by querying the releases API.
|
||||
This command fetches the version information from https://releases.quilibrium.com/release.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
version, err := utils.GetLatestVersion(utils.ReleaseTypeNode)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(os.Stdout, "Latest available version: %s\n", version)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Add the latest-version subcommand to the info command
|
||||
infoCmd.AddCommand(latestVersionCmd)
|
||||
|
||||
// Add the info command to the node command
|
||||
NodeCmd.AddCommand(infoCmd)
|
||||
}
|
||||
185
client/cmd/node/install.go
Normal file
185
client/cmd/node/install.go
Normal file
@ -0,0 +1,185 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
)
|
||||
|
||||
// installCmd represents the command to install the Quilibrium node
|
||||
var InstallCmd = &cobra.Command{
|
||||
Use: "install [version]",
|
||||
Short: "Install Quilibrium node",
|
||||
Long: `Install Quilibrium node binary and create a service to run it.
|
||||
|
||||
## Service Management
|
||||
|
||||
You can start, stop, and restart the service with:
|
||||
|
||||
qclient node service start
|
||||
qclient node service stop
|
||||
qclient node service restart
|
||||
qclient node service status
|
||||
qclient node service enable
|
||||
qclient node service disable
|
||||
|
||||
### Mac OS Notes
|
||||
|
||||
On Mac OS, the service is managed by launchd (installed by default)
|
||||
|
||||
### Linux Notes
|
||||
|
||||
On Linux, the service is managed by systemd (will be installed by this command).
|
||||
|
||||
## Config Management
|
||||
|
||||
A config directory will be created in the user's home directory at .quilibrium/configs/.
|
||||
|
||||
To create a default config, run the following command:
|
||||
|
||||
qclient node config create-default [name-for-config]
|
||||
|
||||
or, you can import existing configs one at a timefrom a directory:
|
||||
|
||||
qclient node config import [name-for-config] /path/to/src/config/dir/
|
||||
|
||||
You can then select the config to use when running the node with:
|
||||
|
||||
qclient node set-default [name-for-config]
|
||||
|
||||
## Binary Management
|
||||
Binaries and signatures are installed to /var/quilibrium/bin/node/[version]/
|
||||
|
||||
You can update the node binary with:
|
||||
|
||||
qclient node update [version]
|
||||
|
||||
### Auto-update
|
||||
|
||||
You can enable auto-update with:
|
||||
|
||||
qclient node auto-update enable
|
||||
|
||||
You can disable auto-update with:
|
||||
|
||||
qclient node auto-update disable
|
||||
|
||||
You can check the auto-update status with:
|
||||
|
||||
qclient node auto-update status
|
||||
|
||||
## Log Management
|
||||
Logging uses system logging with logrotate installed by default.
|
||||
|
||||
Logs are installed to /var/log/quilibrium
|
||||
|
||||
The logrotate config is installed to /etc/logrotate.d/quilibrium
|
||||
|
||||
You can view the logs with:
|
||||
|
||||
qclient node logs [version]
|
||||
|
||||
When installing with this command, if no version is specified, the latest version will be installed.
|
||||
|
||||
Sudo is required to install the node to install the node binary, logging,systemd (on Linux), and create the config directory.
|
||||
|
||||
Examples:
|
||||
|
||||
# Install the latest version
|
||||
qclient node install
|
||||
|
||||
# Install a specific version
|
||||
qclient node install 2.1.0
|
||||
`,
|
||||
Args: cobra.RangeArgs(0, 1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// Get system information and validate
|
||||
osType, arch, err := utils.GetSystemInfo()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !utils.IsSudo() {
|
||||
fmt.Println("This command must be run with sudo: sudo qclient node install")
|
||||
fmt.Println("Sudo is required to install the node binary, logging, systemd (on Linux) service, and create the config directory.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Determine version to install
|
||||
version := determineVersion(args)
|
||||
|
||||
// Download and install the node
|
||||
if version == "latest" {
|
||||
latestVersion, err := utils.GetLatestVersion(utils.ReleaseTypeNode)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error getting latest version: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
version = latestVersion
|
||||
fmt.Fprintf(os.Stdout, "Found latest version: %s\n", version)
|
||||
}
|
||||
|
||||
// do a pre-flight check to ensure the release file exists
|
||||
fileName := fmt.Sprintf("%s-%s-%s-%s", utils.ReleaseTypeNode, version, osType, arch)
|
||||
url := fmt.Sprintf("%s/%s", utils.BaseReleaseURL, fileName)
|
||||
|
||||
if !utils.DoesRemoteFileExist(url) {
|
||||
fmt.Printf("The release file %s does not exist on the release server\n", fileName)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stdout, "Installing Quilibrium node for %s-%s, version: %s\n", osType, arch, version)
|
||||
|
||||
// Install the node
|
||||
InstallNode(version)
|
||||
},
|
||||
}
|
||||
|
||||
// installNode installs the Quilibrium node
|
||||
func InstallNode(version string) {
|
||||
// Create installation directory
|
||||
if err := utils.ValidateAndCreateDir(utils.NodeDataPath, NodeUser); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error creating installation directory: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if utils.IsExistingNodeVersion(version) {
|
||||
fmt.Fprintf(os.Stderr, "Error: Node version %s already exists\n", version)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := InstallByVersion(version); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error installing specific version: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
createService()
|
||||
|
||||
finishInstallation(version)
|
||||
}
|
||||
|
||||
// installByVersion installs a specific version of the Quilibrium node
|
||||
func InstallByVersion(version string) error {
|
||||
|
||||
versionDir := filepath.Join(utils.NodeDataPath, version)
|
||||
if err := utils.ValidateAndCreateDir(versionDir, NodeUser); err != nil {
|
||||
return fmt.Errorf("failed to create version directory: %w", err)
|
||||
}
|
||||
|
||||
// Download the release
|
||||
if err := utils.DownloadRelease(utils.ReleaseTypeNode, version); err != nil {
|
||||
return fmt.Errorf("failed to download release: %w", err)
|
||||
}
|
||||
|
||||
// Download signature files
|
||||
if err := utils.DownloadReleaseSignatures(utils.ReleaseTypeNode, version); err != nil {
|
||||
return fmt.Errorf("failed to download signature files: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
3
client/cmd/node/log/clean.go
Normal file
3
client/cmd/node/log/clean.go
Normal file
@ -0,0 +1,3 @@
|
||||
package log
|
||||
|
||||
// TODO: Implement a log subcommand to clean the logs
|
||||
3
client/cmd/node/log/log.go
Normal file
3
client/cmd/node/log/log.go
Normal file
@ -0,0 +1,3 @@
|
||||
package log
|
||||
|
||||
// TODO: Implement a command to view and manage the logs
|
||||
83
client/cmd/node/node.go
Normal file
83
client/cmd/node/node.go
Normal file
@ -0,0 +1,83 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
configCmd "source.quilibrium.com/quilibrium/monorepo/client/cmd/node/nodeconfig"
|
||||
proverCmd "source.quilibrium.com/quilibrium/monorepo/client/cmd/node/prover"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
"source.quilibrium.com/quilibrium/monorepo/node/config"
|
||||
)
|
||||
|
||||
var (
|
||||
// Base URL for Quilibrium releases
|
||||
baseReleaseURL = "https://releases.quilibrium.com"
|
||||
|
||||
// Default symlink path for the node binary
|
||||
defaultSymlinkPath = "/usr/local/bin/quilibrium-node"
|
||||
logPath = "/var/log/quilibrium"
|
||||
|
||||
ServiceName = "quilibrium-node"
|
||||
|
||||
OsType string
|
||||
Arch string
|
||||
|
||||
configDirectory string
|
||||
NodeConfig *config.Config
|
||||
|
||||
NodeUser *user.User
|
||||
ConfigDirs string
|
||||
NodeConfigToRun string
|
||||
)
|
||||
|
||||
// NodeCmd represents the node command
|
||||
var NodeCmd = &cobra.Command{
|
||||
Use: "node",
|
||||
Short: "Quilibrium node commands",
|
||||
Long: `Run Quilibrium node commands.`,
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
// Store reference to parent's PersistentPreRun to call it first
|
||||
parent := cmd.Parent()
|
||||
if parent != nil && parent.PersistentPreRun != nil {
|
||||
parent.PersistentPreRun(parent, args)
|
||||
}
|
||||
|
||||
// Then run the node-specific initialization
|
||||
var userLookup *user.User
|
||||
var err error
|
||||
userLookup, err = utils.GetCurrentSudoUser()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error getting current user: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
NodeUser = userLookup
|
||||
ConfigDirs = filepath.Join(userLookup.HomeDir, ".quilibrium", "configs")
|
||||
NodeConfigToRun = filepath.Join(userLookup.HomeDir, ".quilibrium", "configs", "default")
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd.Help()
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
NodeCmd.PersistentFlags().StringVar(&configDirectory, "config", ".config", "config directory (default is .config/)")
|
||||
|
||||
// Add subcommands
|
||||
NodeCmd.AddCommand(InstallCmd)
|
||||
NodeCmd.AddCommand(configCmd.ConfigCmd)
|
||||
NodeCmd.AddCommand(updateNodeCmd)
|
||||
NodeCmd.AddCommand(nodeServiceCmd)
|
||||
NodeCmd.AddCommand(proverCmd.ProverCmd)
|
||||
localOsType, localArch, err := utils.GetSystemInfo()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
OsType = localOsType
|
||||
Arch = localArch
|
||||
}
|
||||
43
client/cmd/node/nodeconfig/assign-rewards.go
Normal file
43
client/cmd/node/nodeconfig/assign-rewards.go
Normal file
@ -0,0 +1,43 @@
|
||||
package nodeconfig
|
||||
|
||||
// TODO: Implement a command to assign a config's rewards to a config's rewards address
|
||||
// Should be able to assign to a specific address or by name of a config directory
|
||||
// e.g. qclient node config assign-rewards my-config my-throwaway-config
|
||||
// this finds the address from my-throwaway-config and assigns it to my-config
|
||||
// qlient node config assign-rewards my-config --reset will reset the rewards address to the default address
|
||||
// or
|
||||
// qclient node config assign-rewards my-config --address 0x1234567890abcdef1234567890abcdef1234567890abcdef
|
||||
//
|
||||
// If no address is provided, the command will prompt for an address
|
||||
// the prompt should prompt clearly the user for each part, asking if
|
||||
// the user wants to use one of the config files as the source of the address
|
||||
// or if they want to enter a new address manually
|
||||
// if no configs are found locally, it should prompt the user to create a new config
|
||||
// or import one
|
||||
|
||||
// i.e. Which config do you want to re-assign rewards?
|
||||
// (1) my-config
|
||||
// (2) my-other-config
|
||||
// (3) my-throwaway-config
|
||||
//
|
||||
// Enter the number of the config you want to re-assign rewards: 1
|
||||
// Finding address from my-config...
|
||||
// Successfully found address 0x1234567890abcdef1234567890abcdef1234567890abcdef
|
||||
// Which reward address do you want to assign to my-config?
|
||||
// (1) my-other-config
|
||||
// (2) my-throwaway-config
|
||||
// (3) Enter a new address manually
|
||||
// (4) Reset to default address
|
||||
//
|
||||
// Enter the number of the reward address you want to assign: 2
|
||||
//
|
||||
// Finding address from my-throwaway-config...
|
||||
// Successfully found address 0x1234567890abcdef1234567890abcdef1234567890abcdef
|
||||
// Assigning rewards from my-config to my-throwaway-config
|
||||
// Successfully assigned rewards.
|
||||
//
|
||||
// Summary:
|
||||
// Node address: 0x1234567890abcdef1234567890abcdef1234567890abcdef
|
||||
// Rewards address: 0x1234567890abcdef1234567890abcdef1234567890abcdef
|
||||
//
|
||||
// Successfully updated my-config
|
||||
60
client/cmd/node/nodeconfig/config.go
Normal file
60
client/cmd/node/nodeconfig/config.go
Normal file
@ -0,0 +1,60 @@
|
||||
package nodeconfig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
NodeUser *user.User
|
||||
ConfigDirs string
|
||||
NodeConfigToRun string
|
||||
SetDefault bool
|
||||
)
|
||||
|
||||
// ConfigCmd represents the node config command
|
||||
var ConfigCmd = &cobra.Command{
|
||||
Use: "config",
|
||||
Short: "Manage node configuration",
|
||||
Long: `Manage Quilibrium node configuration.
|
||||
|
||||
This command provides utilities for configuring your Quilibrium node, such as:
|
||||
- Setting configuration values
|
||||
- Setting default configuration
|
||||
- Creating default configuration
|
||||
- Importing configuration
|
||||
`,
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
// Check if the config directory exists
|
||||
user, err := utils.GetCurrentSudoUser()
|
||||
if err != nil {
|
||||
fmt.Println("Error getting current user:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
ConfigDirs = filepath.Join(user.HomeDir, ".quilibrium", "configs")
|
||||
NodeConfigToRun = filepath.Join(user.HomeDir, ".quilibrium", "configs", "default")
|
||||
if utils.FileExists(ConfigDirs) {
|
||||
utils.ValidateAndCreateDir(ConfigDirs, user)
|
||||
}
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd.Help()
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
importCmd.Flags().BoolVarP(&SetDefault, "default", "d", false, "Select this config as the default")
|
||||
ConfigCmd.AddCommand(importCmd)
|
||||
|
||||
ConfigCmd.AddCommand(SwitchConfigCmd)
|
||||
|
||||
createCmd.Flags().BoolVarP(&SetDefault, "default", "d", false, "Select this config as the default")
|
||||
ConfigCmd.AddCommand(createCmd)
|
||||
ConfigCmd.AddCommand(setCmd)
|
||||
|
||||
}
|
||||
101
client/cmd/node/nodeconfig/create.go
Normal file
101
client/cmd/node/nodeconfig/create.go
Normal file
@ -0,0 +1,101 @@
|
||||
package nodeconfig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
)
|
||||
|
||||
var createCmd = &cobra.Command{
|
||||
Use: "create [name]",
|
||||
Short: "Create a default configuration file set for a node",
|
||||
Long: fmt.Sprintf(`Create a default configuration by running quilibrium-node with --peer-id and --config flags, then symlink it to the default configuration.
|
||||
|
||||
Example:
|
||||
qclient node config create
|
||||
qclient node config create myconfig
|
||||
|
||||
qclient node config create myconfig --default
|
||||
|
||||
The first example will create a new configuration at %s/default-config.
|
||||
The second example will create a new configuration at %s/myconfig.
|
||||
The third example will create a new configuration at %s/myconfig and symlink it so the node will use it.`,
|
||||
ConfigDirs, ConfigDirs, ConfigDirs),
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// Determine the config name (default-config or user-provided)
|
||||
var configName string
|
||||
if len(args) > 0 {
|
||||
configName = args[0]
|
||||
} else {
|
||||
// Prompt for a name if none provided
|
||||
fmt.Print("Enter a name for the configuration (cannot be 'default'): ")
|
||||
fmt.Scanln(&configName)
|
||||
|
||||
if configName == "" {
|
||||
configName = "default-config"
|
||||
}
|
||||
}
|
||||
|
||||
// Check if trying to use "default" which is reserved for the symlink
|
||||
if configName == "default" {
|
||||
fmt.Println("Error: 'default' is reserved for the symlink. Please use a different name.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Construct the configuration directory path
|
||||
configDir := filepath.Join(ConfigDirs, configName)
|
||||
|
||||
// Create directory if it doesn't exist
|
||||
if err := utils.ValidateAndCreateDir(configDir, NodeUser); err != nil {
|
||||
fmt.Printf("Failed to create config directory: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Run quilibrium-node command to generate config
|
||||
// this is a hack to get the config files to be created
|
||||
// TODO: fix this
|
||||
// to fix this, we need to extrapolate the Node's config and keystore construction
|
||||
// and reuse it for this command
|
||||
nodeCmd := exec.Command("quilibrium-node", "--peer-id", "--config", configDir)
|
||||
nodeCmd.Stdout = os.Stdout
|
||||
nodeCmd.Stderr = os.Stderr
|
||||
|
||||
fmt.Printf("Running quilibrium-node to generate configuration in %s...\n", configName)
|
||||
if err := nodeCmd.Run(); err != nil {
|
||||
// Check if the error is due to the command not being found
|
||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||
fmt.Printf("Error running quilibrium-node: %s\n", exitErr)
|
||||
} else if _, ok := err.(*exec.Error); ok {
|
||||
fmt.Printf("Error: quilibrium-node command not found. Please ensure it is installed and in your PATH.\n")
|
||||
} else {
|
||||
fmt.Printf("Error: %s\n", err)
|
||||
}
|
||||
os.RemoveAll(configDir)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Check if the configuration was created successfully
|
||||
if !HasConfigFiles(configDir) {
|
||||
fmt.Printf("Failed to generate configuration files in: %s\n", configDir)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if SetDefault {
|
||||
// Create the symlink
|
||||
if err := utils.CreateSymlink(configDir, NodeConfigToRun); err != nil {
|
||||
fmt.Printf("Failed to create symlink: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("Successfully created %s configuration and symlinked to default\n", configName)
|
||||
} else {
|
||||
fmt.Printf("Successfully created %s configuration\n", configName)
|
||||
}
|
||||
fmt.Println("The keys.yml file will only contain 'null:' until the node is started.")
|
||||
},
|
||||
}
|
||||
78
client/cmd/node/nodeconfig/import.go
Normal file
78
client/cmd/node/nodeconfig/import.go
Normal file
@ -0,0 +1,78 @@
|
||||
package nodeconfig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
)
|
||||
|
||||
var importCmd = &cobra.Command{
|
||||
Use: "import [name] [source_directory]",
|
||||
Short: "Import config.yml and keys.yml from a source directory",
|
||||
Long: `Import config.yml and keys.yml from a source directory to the QuilibriumRoot config folder.
|
||||
|
||||
Example:
|
||||
qclient node config import mynode /path/to/source
|
||||
|
||||
This will copy config.yml and keys.yml from /path/to/source to /home/quilibrium/configs/mynode/`,
|
||||
Args: cobra.ExactArgs(2),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
name := args[0]
|
||||
sourceDir := args[1]
|
||||
|
||||
// Check if source directory exists
|
||||
if _, err := os.Stat(sourceDir); os.IsNotExist(err) {
|
||||
fmt.Printf("Source directory does not exist: %s\n", sourceDir)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if !HasConfigFiles(sourceDir) {
|
||||
fmt.Printf("Source directory does not contain both config.yml and keys.yml files: %s\n", sourceDir)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Create target directory in the standard location
|
||||
targetDir := filepath.Join(ConfigDirs, name)
|
||||
if err := utils.ValidateAndCreateDir(targetDir, NodeUser); err != nil {
|
||||
fmt.Printf("Failed to create target directory: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Define source file paths
|
||||
sourceConfigPath := filepath.Join(sourceDir, "config.yml")
|
||||
sourceKeysPath := filepath.Join(sourceDir, "keys.yml")
|
||||
|
||||
// Copy config.yml
|
||||
targetConfigPath := filepath.Join(targetDir, "config.yml")
|
||||
if err := utils.CopyFile(sourceConfigPath, targetConfigPath); err != nil {
|
||||
fmt.Printf("Failed to copy config.yml: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Copy keys.yml
|
||||
targetKeysPath := filepath.Join(targetDir, "keys.yml")
|
||||
if err := utils.CopyFile(sourceKeysPath, targetKeysPath); err != nil {
|
||||
fmt.Printf("Failed to copy keys.yml: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if SetDefault {
|
||||
// Create the symlink
|
||||
if err := utils.CreateSymlink(targetDir, NodeConfigToRun); err != nil {
|
||||
fmt.Printf("Failed to create symlink: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("Successfully imported config files to %s and symlinked to default\n", name)
|
||||
} else {
|
||||
fmt.Printf("Successfully imported config files to %s\n", targetDir)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
}
|
||||
82
client/cmd/node/nodeconfig/set.go
Normal file
82
client/cmd/node/nodeconfig/set.go
Normal file
@ -0,0 +1,82 @@
|
||||
package nodeconfig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"source.quilibrium.com/quilibrium/monorepo/node/config"
|
||||
)
|
||||
|
||||
var setCmd = &cobra.Command{
|
||||
Use: "set [name] [key] [value]",
|
||||
Short: "Set a configuration value",
|
||||
Long: `Set a configuration value in the node config.yml file.
|
||||
|
||||
Example:
|
||||
qclient node config set mynode engine.statsMultiaddr /dns/stats.quilibrium.com/tcp/443
|
||||
`,
|
||||
Args: cobra.ExactArgs(3),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
name := args[0]
|
||||
key := args[1]
|
||||
value := args[2]
|
||||
|
||||
// Construct the config directory path
|
||||
configDir := filepath.Join(ConfigDirs, name)
|
||||
configFile := filepath.Join(configDir, "config.yml")
|
||||
|
||||
// Check if config directory exists
|
||||
if _, err := os.Stat(configDir); os.IsNotExist(err) {
|
||||
fmt.Printf("Config directory does not exist: %s\n", configDir)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Check if config file exists
|
||||
if _, err := os.Stat(configFile); os.IsNotExist(err) {
|
||||
fmt.Printf("Config file does not exist: %s\n", configFile)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Load the config
|
||||
cfg, err := config.LoadConfig(configDir, "", false)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to load config: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Update the config based on the key
|
||||
switch key {
|
||||
case "engine.statsMultiaddr":
|
||||
cfg.Engine.StatsMultiaddr = value
|
||||
case "p2p.listenMultiaddr":
|
||||
cfg.P2P.ListenMultiaddr = value
|
||||
case "listenGrpcMultiaddr":
|
||||
cfg.ListenGRPCMultiaddr = value
|
||||
case "listenRestMultiaddr":
|
||||
cfg.ListenRestMultiaddr = value
|
||||
case "engine.autoMergeCoins":
|
||||
if value == "true" {
|
||||
cfg.Engine.AutoMergeCoins = true
|
||||
} else if value == "false" {
|
||||
cfg.Engine.AutoMergeCoins = false
|
||||
} else {
|
||||
fmt.Printf("Invalid value for %s: must be 'true' or 'false'\n", key)
|
||||
os.Exit(1)
|
||||
}
|
||||
default:
|
||||
fmt.Printf("Unsupported configuration key: %s\n", key)
|
||||
fmt.Println("Supported keys: engine.statsMultiaddr, p2p.listenMultiaddr, listenGrpcMultiaddr, listenRestMultiaddr, engine.autoMergeCoins")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Save the updated config
|
||||
if err := config.SaveConfig(configDir, cfg); err != nil {
|
||||
fmt.Printf("Failed to save config: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("Successfully updated %s to %s in %s\n", key, value, configFile)
|
||||
},
|
||||
}
|
||||
82
client/cmd/node/nodeconfig/switch.go
Normal file
82
client/cmd/node/nodeconfig/switch.go
Normal file
@ -0,0 +1,82 @@
|
||||
package nodeconfig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
)
|
||||
|
||||
var SwitchConfigCmd = &cobra.Command{
|
||||
Use: "switch [name]",
|
||||
Short: "Switch the config to be run by the node",
|
||||
Long: fmt.Sprintf(`Switch the configuration to be run by the node by creating a symlink.
|
||||
|
||||
Example:
|
||||
qclient node config switch mynode
|
||||
|
||||
This will symlink %s/mynode to %s`, ConfigDirs, NodeConfigToRun),
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
var name string
|
||||
if len(args) > 0 {
|
||||
name = args[0]
|
||||
} else {
|
||||
// List available configurations
|
||||
configs, err := ListConfigurations()
|
||||
if err != nil {
|
||||
fmt.Printf("Error listing configurations: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(configs) == 0 {
|
||||
fmt.Println("No configurations found. Create one with 'qclient node config create'")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println("Available configurations:")
|
||||
for i, config := range configs {
|
||||
fmt.Printf("%d. %s\n", i+1, config)
|
||||
}
|
||||
|
||||
// Prompt for choice
|
||||
var choice int
|
||||
fmt.Print("Enter the number of the configuration to set as default: ")
|
||||
_, err = fmt.Scanf("%d", &choice)
|
||||
if err != nil || choice < 1 || choice > len(configs) {
|
||||
fmt.Println("Invalid choice. Please enter a valid number.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
name = configs[choice-1]
|
||||
}
|
||||
|
||||
// Construct the source directory path
|
||||
sourceDir := filepath.Join(ConfigDirs, name)
|
||||
|
||||
// Check if source directory exists
|
||||
if _, err := os.Stat(sourceDir); os.IsNotExist(err) {
|
||||
fmt.Printf("Config directory does not exist: %s\n", sourceDir)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Check if the source directory has both config.yml and keys.yml files
|
||||
if !HasConfigFiles(sourceDir) {
|
||||
fmt.Printf("Source directory does not contain both config.yml and keys.yml files: %s\n", sourceDir)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Construct the default directory path
|
||||
defaultDir := filepath.Join(ConfigDirs, "default")
|
||||
|
||||
// Create the symlink
|
||||
if err := utils.CreateSymlink(sourceDir, defaultDir); err != nil {
|
||||
fmt.Printf("Failed to create symlink: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("Successfully set %s as the default configuration\n", name)
|
||||
},
|
||||
}
|
||||
42
client/cmd/node/nodeconfig/utils.go
Normal file
42
client/cmd/node/nodeconfig/utils.go
Normal file
@ -0,0 +1,42 @@
|
||||
package nodeconfig
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// HasConfigFiles checks if a directory contains both config.yml and keys.yml files
|
||||
func HasConfigFiles(dirPath string) bool {
|
||||
configPath := filepath.Join(dirPath, "config.yml")
|
||||
keysPath := filepath.Join(dirPath, "keys.yml")
|
||||
|
||||
// Check if both files exist
|
||||
configExists := false
|
||||
keysExists := false
|
||||
|
||||
if _, err := os.Stat(configPath); !os.IsNotExist(err) {
|
||||
configExists = true
|
||||
}
|
||||
|
||||
if _, err := os.Stat(keysPath); !os.IsNotExist(err) {
|
||||
keysExists = true
|
||||
}
|
||||
|
||||
return configExists && keysExists
|
||||
}
|
||||
|
||||
func ListConfigurations() ([]string, error) {
|
||||
files, err := os.ReadDir(ConfigDirs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
configs := make([]string, 0)
|
||||
for _, file := range files {
|
||||
if file.IsDir() {
|
||||
configs = append(configs, file.Name())
|
||||
}
|
||||
}
|
||||
|
||||
return configs, nil
|
||||
}
|
||||
19
client/cmd/node/prover/prover.go
Normal file
19
client/cmd/node/prover/prover.go
Normal file
@ -0,0 +1,19 @@
|
||||
package prover
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var ConfigDirectory string
|
||||
|
||||
var ProverCmd = &cobra.Command{
|
||||
Use: "prover",
|
||||
Short: "Performs a configuration operation for given prover info",
|
||||
Run: func(cmd *cobra.Command, args []string) {},
|
||||
}
|
||||
|
||||
func init() {
|
||||
ProverCmd.PersistentFlags().StringVar(&ConfigDirectory, "config", ".config", "config directory (default is .config/)")
|
||||
ProverCmd.AddCommand(proverPauseCmd)
|
||||
ProverCmd.AddCommand(proverConfigMergeCmd)
|
||||
}
|
||||
@ -1,22 +1,21 @@
|
||||
package cmd
|
||||
package prover
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/crypto"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/spf13/cobra"
|
||||
"source.quilibrium.com/quilibrium/monorepo/node/config"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
nodeConfig "source.quilibrium.com/quilibrium/monorepo/node/config"
|
||||
)
|
||||
|
||||
var DryRun bool
|
||||
|
||||
var proverConfigMergeCmd = &cobra.Command{
|
||||
Use: "merge",
|
||||
Short: "Merges config data for prover seniority",
|
||||
@ -32,35 +31,35 @@ var proverConfigMergeCmd = &cobra.Command{
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
primaryConfig, err := config.LoadConfig(args[0], "", true)
|
||||
primaryConfig, err := nodeConfig.LoadConfig(args[0], "", true)
|
||||
if err != nil {
|
||||
fmt.Printf("invalid config directory: %s\n", args[0])
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
otherPaths := []string{}
|
||||
peerIds := []string{GetPeerIDFromConfig(primaryConfig).String()}
|
||||
peerIds := []string{utils.GetPeerIDFromConfig(primaryConfig).String()}
|
||||
for _, p := range args[1:] {
|
||||
if !path.IsAbs(p) {
|
||||
fmt.Printf("%s is not an absolute path\n", p)
|
||||
}
|
||||
cfg, err := config.LoadConfig(p, "", true)
|
||||
cfg, err := nodeConfig.LoadConfig(p, "", true)
|
||||
if err != nil {
|
||||
fmt.Printf("invalid config directory: %s\n", p)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
peerId := GetPeerIDFromConfig(cfg).String()
|
||||
peerId := utils.GetPeerIDFromConfig(cfg).String()
|
||||
peerIds = append(peerIds, peerId)
|
||||
otherPaths = append(otherPaths, p)
|
||||
}
|
||||
|
||||
if DryRun {
|
||||
bridged := []*BridgedPeerJson{}
|
||||
firstRetro := []*FirstRetroJson{}
|
||||
secondRetro := []*SecondRetroJson{}
|
||||
thirdRetro := []*ThirdRetroJson{}
|
||||
fourthRetro := []*FourthRetroJson{}
|
||||
bridged := []*utils.BridgedPeerJson{}
|
||||
firstRetro := []*utils.FirstRetroJson{}
|
||||
secondRetro := []*utils.SecondRetroJson{}
|
||||
thirdRetro := []*utils.ThirdRetroJson{}
|
||||
fourthRetro := []*utils.FourthRetroJson{}
|
||||
|
||||
err = json.Unmarshal(bridgedPeersJsonBinary, &bridged)
|
||||
if err != nil {
|
||||
@ -210,7 +209,7 @@ var proverConfigMergeCmd = &cobra.Command{
|
||||
p,
|
||||
)
|
||||
}
|
||||
err := config.SaveConfig(args[0], primaryConfig)
|
||||
err := nodeConfig.SaveConfig(args[0], primaryConfig)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -218,82 +217,22 @@ var proverConfigMergeCmd = &cobra.Command{
|
||||
},
|
||||
}
|
||||
|
||||
func GetPrivKeyFromConfig(cfg *config.Config) (crypto.PrivKey, error) {
|
||||
peerPrivKey, err := hex.DecodeString(cfg.P2P.PeerPrivKey)
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "error unmarshaling peerkey"))
|
||||
}
|
||||
|
||||
privKey, err := crypto.UnmarshalEd448PrivateKey(peerPrivKey)
|
||||
return privKey, err
|
||||
}
|
||||
|
||||
func GetPeerIDFromConfig(cfg *config.Config) peer.ID {
|
||||
peerPrivKey, err := hex.DecodeString(cfg.P2P.PeerPrivKey)
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "error unmarshaling peerkey"))
|
||||
}
|
||||
|
||||
privKey, err := crypto.UnmarshalEd448PrivateKey(peerPrivKey)
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "error unmarshaling peerkey"))
|
||||
}
|
||||
|
||||
pub := privKey.GetPublic()
|
||||
id, err := peer.IDFromPublicKey(pub)
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "error getting peer id"))
|
||||
}
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
type BridgedPeerJson struct {
|
||||
Amount string `json:"amount"`
|
||||
Identifier string `json:"identifier"`
|
||||
Variant string `json:"variant"`
|
||||
}
|
||||
|
||||
type FirstRetroJson struct {
|
||||
PeerId string `json:"peerId"`
|
||||
Reward string `json:"reward"`
|
||||
}
|
||||
|
||||
type SecondRetroJson struct {
|
||||
PeerId string `json:"peerId"`
|
||||
Reward string `json:"reward"`
|
||||
JanPresence bool `json:"janPresence"`
|
||||
FebPresence bool `json:"febPresence"`
|
||||
MarPresence bool `json:"marPresence"`
|
||||
AprPresence bool `json:"aprPresence"`
|
||||
MayPresence bool `json:"mayPresence"`
|
||||
}
|
||||
|
||||
type ThirdRetroJson struct {
|
||||
PeerId string `json:"peerId"`
|
||||
Reward string `json:"reward"`
|
||||
}
|
||||
|
||||
type FourthRetroJson struct {
|
||||
PeerId string `json:"peerId"`
|
||||
Reward string `json:"reward"`
|
||||
}
|
||||
|
||||
//go:embed bridged.json
|
||||
//go:embed premainnet-data/bridged.json
|
||||
var bridgedPeersJsonBinary []byte
|
||||
|
||||
//go:embed first_retro.json
|
||||
//go:embed premainnet-data/first_retro.json
|
||||
var firstRetroJsonBinary []byte
|
||||
|
||||
//go:embed second_retro.json
|
||||
//go:embed premainnet-data/second_retro.json
|
||||
var secondRetroJsonBinary []byte
|
||||
|
||||
//go:embed third_retro.json
|
||||
//go:embed premainnet-data/third_retro.json
|
||||
var thirdRetroJsonBinary []byte
|
||||
|
||||
//go:embed fourth_retro.json
|
||||
//go:embed premainnet-data/fourth_retro.json
|
||||
var fourthRetroJsonBinary []byte
|
||||
|
||||
func init() {
|
||||
proverCmd.AddCommand(proverConfigMergeCmd)
|
||||
proverConfigMergeCmd.Flags().BoolVar(&DryRun, "dry-run", false, "Evaluate seniority score without merging configs")
|
||||
ProverCmd.AddCommand(proverConfigMergeCmd)
|
||||
}
|
||||
@ -1,8 +1,9 @@
|
||||
package cmd
|
||||
package prover
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -13,12 +14,16 @@ import (
|
||||
"go.uber.org/zap"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/types/known/anypb"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
"source.quilibrium.com/quilibrium/monorepo/go-libp2p-blossomsub/pb"
|
||||
nodeConfig "source.quilibrium.com/quilibrium/monorepo/node/config"
|
||||
"source.quilibrium.com/quilibrium/monorepo/node/execution/intrinsics/token/application"
|
||||
"source.quilibrium.com/quilibrium/monorepo/node/p2p"
|
||||
"source.quilibrium.com/quilibrium/monorepo/node/protobufs"
|
||||
)
|
||||
|
||||
var NodeConfig *nodeConfig.Config
|
||||
|
||||
var proverPauseCmd = &cobra.Command{
|
||||
Use: "pause",
|
||||
Short: "Pauses a prover",
|
||||
@ -28,13 +33,23 @@ var proverPauseCmd = &cobra.Command{
|
||||
`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
logger, err := zap.NewProduction()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
NodeConfig, err = utils.LoadNodeConfig(ConfigDirectory)
|
||||
if err != nil {
|
||||
fmt.Printf("invalid config directory: %s\n", ConfigDirectory)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
pubsub := p2p.NewBlossomSub(NodeConfig.P2P, logger)
|
||||
intrinsicFilter := p2p.GetBloomFilter(application.TOKEN_ADDRESS, 256, 3)
|
||||
pubsub.Subscribe(
|
||||
append([]byte{0x00}, intrinsicFilter...),
|
||||
func(message *pb.Message) error { return nil },
|
||||
)
|
||||
key, err := GetPrivKeyFromConfig(NodeConfig)
|
||||
key, err := utils.GetPrivKeyFromConfig(NodeConfig)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -145,5 +160,5 @@ func publishMessage(
|
||||
}
|
||||
|
||||
func init() {
|
||||
proverCmd.AddCommand(proverPauseCmd)
|
||||
ProverCmd.AddCommand(proverPauseCmd)
|
||||
}
|
||||
493
client/cmd/node/service.go
Normal file
493
client/cmd/node/service.go
Normal file
@ -0,0 +1,493 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
)
|
||||
|
||||
// nodeServiceCmd represents the command to manage the Quilibrium node service
|
||||
var nodeServiceCmd = &cobra.Command{
|
||||
Use: "service [command]",
|
||||
Short: "Manage the Quilibrium node service",
|
||||
Long: `Manage the Quilibrium node service.
|
||||
Available commands:
|
||||
start Start the node service
|
||||
stop Stop the node service
|
||||
restart Restart the node service
|
||||
status Check the status of the node service
|
||||
enable Enable the node service to start on boot
|
||||
disable Disable the node service from starting on boot
|
||||
install Install the service for the current OS
|
||||
|
||||
Examples:
|
||||
# Start the node service
|
||||
qclient node service start
|
||||
|
||||
# Check service status
|
||||
qclient node service status
|
||||
|
||||
# Enable service to start on boot
|
||||
qclient node service enable`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
command := args[0]
|
||||
switch command {
|
||||
case "start":
|
||||
startService()
|
||||
case "stop":
|
||||
stopService()
|
||||
case "restart":
|
||||
restartService()
|
||||
case "status":
|
||||
checkServiceStatus()
|
||||
case "enable":
|
||||
enableService()
|
||||
case "disable":
|
||||
disableService()
|
||||
case "reload":
|
||||
reloadService()
|
||||
case "install":
|
||||
installService()
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "Unknown command: %s\n", command)
|
||||
fmt.Fprintf(os.Stderr, "Available commands: start, stop, restart, status, enable, disable, reload, install\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// installService installs the appropriate service configuration for the current OS
|
||||
func installService() {
|
||||
|
||||
if err := utils.CheckAndRequestSudo("Installing service requires root privileges"); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stdout, "Installing Quilibrium node service for %s...\n", OsType)
|
||||
|
||||
if OsType == "darwin" {
|
||||
// launchctl is already installed on macOS by default, so no need to check for it
|
||||
installMacOSService()
|
||||
} else if OsType == "linux" {
|
||||
// systemd is not installed on linux by default, so we need to check for it
|
||||
if !utils.CheckForSystemd() {
|
||||
// install systemd if not found
|
||||
installSystemd()
|
||||
}
|
||||
if err := createSystemdServiceFile(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error creating systemd service file: %v\n", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "Error: Unsupported operating system: %s\n", OsType)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stdout, "Quilibrium node service installed successfully\n")
|
||||
}
|
||||
|
||||
func installSystemd() {
|
||||
fmt.Fprintf(os.Stdout, "Installing systemd...\n")
|
||||
updateCmd := exec.Command("sudo", "apt-get", "update")
|
||||
updateCmd.Stdout = nil
|
||||
updateCmd.Stderr = nil
|
||||
if err := updateCmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error updating package lists: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
installCmd := exec.Command("sudo", "apt-get", "install", "-y", "systemd")
|
||||
installCmd.Stdout = nil
|
||||
installCmd.Stderr = nil
|
||||
if err := installCmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error installing systemd: %v\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// startService starts the Quilibrium node service
|
||||
func startService() {
|
||||
if err := utils.CheckAndRequestSudo("Starting service requires root privileges"); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if OsType == "darwin" {
|
||||
// MacOS launchd command
|
||||
cmd := exec.Command("sudo", "launchctl", "start", fmt.Sprintf("com.quilibrium.%s", ServiceName))
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error starting service: %v\n", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Linux systemd command
|
||||
cmd := exec.Command("sudo", "systemctl", "start", ServiceName)
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error starting service: %v\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stdout, "Started Quilibrium node service\n")
|
||||
}
|
||||
|
||||
// stopService stops the Quilibrium node service
|
||||
func stopService() {
|
||||
if err := utils.CheckAndRequestSudo("Stopping service requires root privileges"); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if OsType == "darwin" {
|
||||
// MacOS launchd command
|
||||
cmd := exec.Command("sudo", "launchctl", "stop", fmt.Sprintf("com.quilibrium.%s", ServiceName))
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error stopping service: %v\n", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Linux systemd command
|
||||
cmd := exec.Command("sudo", "systemctl", "stop", ServiceName)
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error stopping service: %v\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stdout, "Stopped Quilibrium node service\n")
|
||||
}
|
||||
|
||||
// restartService restarts the Quilibrium node service
|
||||
func restartService() {
|
||||
if err := utils.CheckAndRequestSudo("Restarting service requires root privileges"); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if OsType == "darwin" {
|
||||
// MacOS launchd command - stop then start
|
||||
stopCmd := exec.Command("sudo", "launchctl", "stop", fmt.Sprintf("com.quilibrium.%s", ServiceName))
|
||||
if err := stopCmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error stopping service: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
startCmd := exec.Command("sudo", "launchctl", "start", fmt.Sprintf("com.quilibrium.%s", ServiceName))
|
||||
if err := startCmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error starting service: %v\n", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Linux systemd command
|
||||
cmd := exec.Command("sudo", "systemctl", "restart", ServiceName)
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error restarting service: %v\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stdout, "Restarted Quilibrium node service\n")
|
||||
}
|
||||
|
||||
// reloadService reloads the Quilibrium node service
|
||||
func reloadService() {
|
||||
if err := utils.CheckAndRequestSudo("Reloading service requires root privileges"); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if OsType == "darwin" {
|
||||
// MacOS launchd command - unload then load
|
||||
plistPath := fmt.Sprintf("/Library/LaunchDaemons/com.quilibrium.%s.plist", ServiceName)
|
||||
unloadCmd := exec.Command("sudo", "launchctl", "unload", plistPath)
|
||||
if err := unloadCmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error unloading service: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
loadCmd := exec.Command("sudo", "launchctl", "load", plistPath)
|
||||
if err := loadCmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error loading service: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stdout, "Reloaded launchd service\n")
|
||||
} else {
|
||||
// Linux systemd command
|
||||
cmd := exec.Command("sudo", "systemctl", "daemon-reload")
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error reloading service: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stdout, "Reloaded systemd service\n")
|
||||
}
|
||||
}
|
||||
|
||||
// checkServiceStatus checks the status of the Quilibrium node service
|
||||
func checkServiceStatus() {
|
||||
if err := utils.CheckAndRequestSudo("Checking service status requires root privileges"); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if OsType == "darwin" {
|
||||
// MacOS launchd command
|
||||
cmd := exec.Command("sudo", "launchctl", "list", fmt.Sprintf("com.quilibrium.%s", ServiceName))
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error checking service status: %v\n", err)
|
||||
}
|
||||
} else {
|
||||
// Linux systemd command
|
||||
cmd := exec.Command("sudo", "systemctl", "status", ServiceName)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error checking service status: %v\n", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// enableService enables the Quilibrium node service to start on boot
|
||||
func enableService() {
|
||||
if err := utils.CheckAndRequestSudo("Enabling service requires root privileges"); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if OsType == "darwin" {
|
||||
// MacOS launchd command - load with -w flag to enable at boot
|
||||
plistPath := fmt.Sprintf("/Library/LaunchDaemons/com.quilibrium.%s.plist", ServiceName)
|
||||
cmd := exec.Command("sudo", "launchctl", "load", "-w", plistPath)
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error enabling service: %v\n", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Linux systemd command
|
||||
cmd := exec.Command("sudo", "systemctl", "enable", ServiceName)
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error enabling service: %v\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stdout, "Enabled Quilibrium node service to start on boot\n")
|
||||
}
|
||||
|
||||
// disableService disables the Quilibrium node service from starting on boot
|
||||
func disableService() {
|
||||
if err := utils.CheckAndRequestSudo("Disabling service requires root privileges"); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if OsType == "darwin" {
|
||||
// MacOS launchd command - unload with -w flag to disable at boot
|
||||
plistPath := fmt.Sprintf("/Library/LaunchDaemons/com.quilibrium.%s.plist", ServiceName)
|
||||
cmd := exec.Command("sudo", "launchctl", "unload", "-w", plistPath)
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error disabling service: %v\n", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Linux systemd command
|
||||
cmd := exec.Command("sudo", "systemctl", "disable", ServiceName)
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error disabling service: %v\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stdout, "Disabled Quilibrium node service from starting on boot\n")
|
||||
}
|
||||
|
||||
func createService() {
|
||||
// Create systemd service file
|
||||
if OsType == "linux" {
|
||||
if err := createSystemdServiceFile(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: Failed to create systemd service file: %v\n", err)
|
||||
}
|
||||
} else if OsType == "darwin" {
|
||||
installMacOSService()
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "Warning: Background service file creation not supported on %s\n", OsType)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// createSystemdServiceFile creates the systemd service file with environment file support
|
||||
func createSystemdServiceFile() error {
|
||||
if !utils.CheckForSystemd() {
|
||||
installSystemd()
|
||||
}
|
||||
|
||||
// Check if we need sudo privileges
|
||||
if err := utils.CheckAndRequestSudo("Creating systemd service file requires root privileges"); err != nil {
|
||||
return fmt.Errorf("failed to get sudo privileges: %w", err)
|
||||
}
|
||||
|
||||
// Create environment file content
|
||||
envContent := `# Quilibrium Node Environment`
|
||||
|
||||
// Write environment file
|
||||
envPath := filepath.Join(utils.RootQuilibriumPath, "quilibrium.env")
|
||||
if err := os.WriteFile(envPath, []byte(envContent), 0640); err != nil {
|
||||
return fmt.Errorf("failed to create environment file: %w", err)
|
||||
}
|
||||
|
||||
// Set ownership of environment file
|
||||
chownCmd := utils.ChownPath(envPath, NodeUser, false)
|
||||
if chownCmd != nil {
|
||||
return fmt.Errorf("failed to set environment file ownership: %w", chownCmd)
|
||||
}
|
||||
|
||||
// Create systemd service file content
|
||||
serviceContent := fmt.Sprintf(`[Unit]
|
||||
Description=Quilibrium Node Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=quilibrium
|
||||
EnvironmentFile=/opt/quilibrium/config/quilibrium.env
|
||||
ExecStart=/usr/local/bin/quilibrium-node --config %s
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
LimitNOFILE=65535
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
`, ConfigDirs+"/default")
|
||||
|
||||
// Write service file
|
||||
servicePath := "/etc/systemd/system/quilibrium-node.service"
|
||||
if err := utils.WriteFileAuto(servicePath, serviceContent); err != nil {
|
||||
return fmt.Errorf("failed to create service file: %w", err)
|
||||
}
|
||||
|
||||
// Reload systemd daemon
|
||||
reloadCmd := exec.Command("sudo", "systemctl", "daemon-reload")
|
||||
if err := reloadCmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to reload systemd daemon: %w", err)
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stdout, "Created systemd service file at %s\n", servicePath)
|
||||
fmt.Fprintf(os.Stdout, "Created environment file at %s\n", envPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// installMacOSService installs a launchd service on macOS
|
||||
func installMacOSService() {
|
||||
fmt.Println("Installing launchd service for Quilibrium node...")
|
||||
|
||||
// Create plist file content
|
||||
plistTemplate := `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>{{.Label}}</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/usr/local/bin/quilibrium-node</string>
|
||||
<string>--config</string>
|
||||
<string>/opt/quilibrium/config/</string>
|
||||
</array>
|
||||
<key>EnvironmentVariables</key>
|
||||
<dict>
|
||||
<key>QUILIBRIUM_DATA_DIR</key>
|
||||
<string>{{.DataPath}}</string>
|
||||
<key>QUILIBRIUM_LOG_LEVEL</key>
|
||||
<string>info</string>
|
||||
<key>QUILIBRIUM_LISTEN_GRPC_MULTIADDR</key>
|
||||
<string>/ip4/127.0.0.1/tcp/8337</string>
|
||||
<key>QUILIBRIUM_LISTEN_REST_MULTIADDR</key>
|
||||
<string>/ip4/127.0.0.1/tcp/8338</string>
|
||||
<key>QUILIBRIUM_STATS_MULTIADDR</key>
|
||||
<string>/dns/stats.quilibrium.com/tcp/443</string>
|
||||
<key>QUILIBRIUM_NETWORK_ID</key>
|
||||
<string>0</string>
|
||||
<key>QUILIBRIUM_DEBUG</key>
|
||||
<string>false</string>
|
||||
<key>QUILIBRIUM_SIGNATURE_CHECK</key>
|
||||
<string>true</string>
|
||||
</dict>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>{{.LogPath}}/node.err</string>
|
||||
<key>StandardOutPath</key>
|
||||
<string>{{.LogPath}}/node.log</string>
|
||||
</dict>
|
||||
</plist>`
|
||||
|
||||
// Prepare template data
|
||||
data := struct {
|
||||
Label string
|
||||
DataPath string
|
||||
ServiceName string
|
||||
LogPath string
|
||||
}{
|
||||
Label: fmt.Sprintf("com.quilibrium.node"),
|
||||
DataPath: utils.NodeDataPath,
|
||||
ServiceName: "node",
|
||||
LogPath: logPath,
|
||||
}
|
||||
|
||||
// Parse and execute template
|
||||
tmpl, err := template.New("plist").Parse(plistTemplate)
|
||||
if err != nil {
|
||||
fmt.Printf("Error creating plist template: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Determine plist file path
|
||||
var plistPath = fmt.Sprintf("/Library/LaunchDaemons/%s.plist", data.Label)
|
||||
|
||||
// Write plist file
|
||||
file, err := os.Create(plistPath)
|
||||
if err != nil {
|
||||
fmt.Printf("Error creating plist file: %v\n", err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if err := tmpl.Execute(file, data); err != nil {
|
||||
fmt.Printf("Error writing plist file: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Set correct permissions
|
||||
chownCmd := exec.Command("chown", "root:wheel", plistPath)
|
||||
if err := chownCmd.Run(); err != nil {
|
||||
fmt.Printf("Warning: Failed to change ownership of plist file: %v\n", err)
|
||||
}
|
||||
|
||||
// Load the service
|
||||
var loadCmd = exec.Command("launchctl", "load", "-w", plistPath)
|
||||
|
||||
if err := loadCmd.Run(); err != nil {
|
||||
fmt.Printf("Error loading service: %v\n", err)
|
||||
fmt.Println("You may need to load the service manually.")
|
||||
}
|
||||
|
||||
fmt.Printf("Launchd service installed successfully as %s\n", plistPath)
|
||||
fmt.Println("\nTo start the service:")
|
||||
fmt.Printf(" sudo launchctl start %s\n", data.Label)
|
||||
fmt.Println("\nTo stop the service:")
|
||||
fmt.Printf(" sudo launchctl stop %s\n", data.Label)
|
||||
fmt.Println("\nTo view service logs:")
|
||||
fmt.Printf(" cat %s/%s.log\n", data.LogPath, data.ServiceName)
|
||||
}
|
||||
152
client/cmd/node/shared.go
Normal file
152
client/cmd/node/shared.go
Normal file
@ -0,0 +1,152 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
)
|
||||
|
||||
// determineVersion gets the version to install from args or defaults to "latest"
|
||||
func determineVersion(args []string) string {
|
||||
if len(args) > 0 {
|
||||
return args[0]
|
||||
}
|
||||
return "latest"
|
||||
}
|
||||
|
||||
// confirmPaths asks the user to confirm the installation and data paths
|
||||
// func confirmPaths(installPath, dataPath string) bool {
|
||||
// fmt.Print("Do you want to continue with these paths? [Y/n]: ")
|
||||
// reader := bufio.NewReader(os.Stdin)
|
||||
// response, _ := reader.ReadString('\n')
|
||||
// response = strings.TrimSpace(strings.ToLower(response))
|
||||
|
||||
// return response == "" || response == "y" || response == "yes"
|
||||
// }
|
||||
|
||||
// setOwnership sets the ownership of directories to the node user
|
||||
func setOwnership() {
|
||||
|
||||
// Change ownership of installation directory
|
||||
err := utils.ChownPath(utils.NodeDataPath, NodeUser, true)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: Failed to change ownership of %s: %v\n", utils.NodeDataPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
// setupLogRotation creates a logrotate configuration file for the Quilibrium node
|
||||
func setupLogRotation() error {
|
||||
// Check if we need sudo privileges for creating logrotate config
|
||||
if err := utils.CheckAndRequestSudo("Creating logrotate configuration requires root privileges"); err != nil {
|
||||
return fmt.Errorf("failed to get sudo privileges: %w", err)
|
||||
}
|
||||
|
||||
// Create logrotate configuration
|
||||
configContent := fmt.Sprintf(`%s/*.log {
|
||||
daily
|
||||
rotate 7
|
||||
compress
|
||||
delaycompress
|
||||
missingok
|
||||
notifempty
|
||||
create 0640 %s %s
|
||||
postrotate
|
||||
systemctl reload quilibrium-node >/dev/null 2>&1 || true
|
||||
endscript
|
||||
}`, logPath, NodeUser.Username, NodeUser.Username)
|
||||
|
||||
// Write the configuration file
|
||||
configPath := "/etc/logrotate.d/quilibrium-node"
|
||||
if err := utils.WriteFile(configPath, configContent); err != nil {
|
||||
return fmt.Errorf("failed to create logrotate configuration: %w", err)
|
||||
}
|
||||
|
||||
// Create log directory with proper permissions
|
||||
if err := utils.ValidateAndCreateDir(logPath, NodeUser); err != nil {
|
||||
return fmt.Errorf("failed to create log directory: %w", err)
|
||||
}
|
||||
|
||||
// Set ownership of log directory
|
||||
err := utils.ChownPath(logPath, NodeUser, true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set log directory ownership: %w", err)
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stdout, "Created log rotation configuration at %s\n", configPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// finishInstallation completes the installation process by making the binary executable and creating a symlink
|
||||
func finishInstallation(version string) {
|
||||
setOwnership()
|
||||
|
||||
normalizedBinaryName := "node-" + version + "-" + OsType + "-" + Arch
|
||||
|
||||
// Finish installation
|
||||
nodeBinaryPath := filepath.Join(utils.NodeDataPath, version, normalizedBinaryName)
|
||||
fmt.Printf("Making binary executable: %s\n", nodeBinaryPath)
|
||||
// Make the binary executable
|
||||
if err := utils.ChmodPath(nodeBinaryPath, 0755, "executable"); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: Failed to make binary executable: %v\n", err)
|
||||
}
|
||||
|
||||
// Check if we need sudo privileges for creating symlink in system directory
|
||||
if strings.HasPrefix(defaultSymlinkPath, "/usr/") || strings.HasPrefix(defaultSymlinkPath, "/bin/") || strings.HasPrefix(defaultSymlinkPath, "/sbin/") {
|
||||
if err := utils.CheckAndRequestSudo(fmt.Sprintf("Creating symlink at %s requires root privileges", defaultSymlinkPath)); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: Failed to get sudo privileges: %v\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Create symlink using the utils package
|
||||
if err := utils.CreateSymlink(nodeBinaryPath, defaultSymlinkPath); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error creating symlink: %v\n", err)
|
||||
}
|
||||
|
||||
// Set up log rotation
|
||||
if err := setupLogRotation(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: Failed to set up log rotation: %v\n", err)
|
||||
}
|
||||
|
||||
// Print success message
|
||||
printSuccessMessage(version)
|
||||
}
|
||||
|
||||
// printSuccessMessage prints a success message after installation
|
||||
func printSuccessMessage(version string) {
|
||||
fmt.Fprintf(os.Stdout, "\nSuccessfully installed Quilibrium node %s\n", version)
|
||||
fmt.Fprintf(os.Stdout, "Binary download directory: %s\n", filepath.Join(utils.NodeDataPath, version))
|
||||
fmt.Fprintf(os.Stdout, "Binary symlinked to %s\n", defaultSymlinkPath)
|
||||
fmt.Fprintf(os.Stdout, "Log directory: %s\n", logPath)
|
||||
fmt.Fprintf(os.Stdout, "Environment file: /etc/default/quilibrium-node\n")
|
||||
fmt.Fprintf(os.Stdout, "Service file: /etc/systemd/system/quilibrium-node.service\n")
|
||||
|
||||
fmt.Fprintf(os.Stdout, "\nConfiguration:\n")
|
||||
fmt.Fprintf(os.Stdout, " To create a new configuration:\n")
|
||||
fmt.Fprintf(os.Stdout, " qclient node config create [name] --default\n")
|
||||
fmt.Fprintf(os.Stdout, " quilibrium-node --peer-id %s/default-config\n", ConfigDirs)
|
||||
|
||||
fmt.Fprintf(os.Stdout, "\n To use an existing configuration:\n")
|
||||
fmt.Fprintf(os.Stdout, " cp -r /path/to/your/existing/config %s/default-config\n", ConfigDirs)
|
||||
fmt.Fprintf(os.Stdout, " # Or modify the service file to point to your existing config:\n")
|
||||
fmt.Fprintf(os.Stdout, " sudo nano /etc/systemd/system/quilibrium-node.service\n")
|
||||
fmt.Fprintf(os.Stdout, " # Then reload systemd:\n")
|
||||
fmt.Fprintf(os.Stdout, " sudo systemctl daemon-reload\n")
|
||||
|
||||
fmt.Fprintf(os.Stdout, "\nTo select a configuration:\n")
|
||||
fmt.Fprintf(os.Stdout, " qclient node config switch <config-name>\n")
|
||||
fmt.Fprintf(os.Stdout, " # Or use the --default flag when creating a config to automatically select it:\n")
|
||||
fmt.Fprintf(os.Stdout, " qclient node config create --default\n")
|
||||
|
||||
fmt.Fprintf(os.Stdout, "\nTo manually start the node (must create a config first), you can run:\n")
|
||||
fmt.Fprintf(os.Stdout, " %s --config %s/myconfig/\n",
|
||||
ServiceName, ConfigDirs)
|
||||
fmt.Fprintf(os.Stdout, " # Or use systemd service using the default config:\n")
|
||||
fmt.Fprintf(os.Stdout, " sudo systemctl start quilibrium-node\n")
|
||||
|
||||
fmt.Fprintf(os.Stdout, "\nFor more options, run:\n")
|
||||
fmt.Fprintf(os.Stdout, " quilibrium-node --help\n")
|
||||
}
|
||||
10
client/cmd/node/uninstall.go
Normal file
10
client/cmd/node/uninstall.go
Normal file
@ -0,0 +1,10 @@
|
||||
package node
|
||||
|
||||
// TODO: Implement a command to uninstall the current and previous versions
|
||||
// of the node and all files, not including user data
|
||||
// this should NEVER delete the user data, only the node files and logs
|
||||
|
||||
// prompt the user for confirmation or a --force flag to skip the confirmation
|
||||
|
||||
// qlient node uninstall
|
||||
// qlient node uninstall --force (skip the confirmation)
|
||||
68
client/cmd/node/update.go
Normal file
68
client/cmd/node/update.go
Normal file
@ -0,0 +1,68 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
)
|
||||
|
||||
// updateNodeCmd represents the command to update the Quilibrium node
|
||||
var updateNodeCmd = &cobra.Command{
|
||||
Use: "update [version]",
|
||||
Short: "Update Quilibrium node",
|
||||
Long: `Update Quilibrium node to a specified version or the latest version.
|
||||
If no version is specified, the latest version will be installed.
|
||||
|
||||
Examples:
|
||||
# Update to the latest version
|
||||
qclient node update
|
||||
|
||||
# Update to a specific version
|
||||
qclient node update 2.1.0`,
|
||||
Args: cobra.RangeArgs(0, 1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// Get system information and validate
|
||||
|
||||
// Determine version to install
|
||||
version := determineVersion(args)
|
||||
|
||||
// Download and install the node
|
||||
if version == "latest" {
|
||||
latestVersion, err := utils.GetLatestVersion(utils.ReleaseTypeNode)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error getting latest version: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
version = latestVersion
|
||||
fmt.Fprintf(os.Stdout, "Found latest version: %s\n", version)
|
||||
}
|
||||
|
||||
if utils.IsExistingNodeVersion(version) {
|
||||
fmt.Fprintf(os.Stderr, "Error: Node version %s already exists\n", version)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stdout, "Updating Quilibrium node for %s-%s, version: %s\n", OsType, Arch, version)
|
||||
|
||||
// Update the node
|
||||
updateNode(version)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
}
|
||||
|
||||
// updateNode handles the node update process
|
||||
func updateNode(version string) {
|
||||
// Check if we need sudo privileges
|
||||
if err := utils.CheckAndRequestSudo(fmt.Sprintf("Updating node at %s requires root privileges", utils.NodeDataPath)); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
InstallNode(version)
|
||||
}
|
||||
1
client/cmd/node/utils.go
Normal file
1
client/cmd/node/utils.go
Normal file
@ -0,0 +1 @@
|
||||
package node
|
||||
@ -1,12 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
var proverCmd = &cobra.Command{
|
||||
Use: "prover",
|
||||
Short: "Performs a configuration operation for given prover info",
|
||||
}
|
||||
|
||||
func init() {
|
||||
configCmd.AddCommand(proverCmd)
|
||||
}
|
||||
@ -1,37 +1,62 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudflare/circl/sign/ed448"
|
||||
"github.com/multiformats/go-multiaddr"
|
||||
mn "github.com/multiformats/go-multiaddr/net"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/crypto/sha3"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
|
||||
clientConfig "source.quilibrium.com/quilibrium/monorepo/client/cmd/config"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/cmd/node"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/cmd/token"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
"source.quilibrium.com/quilibrium/monorepo/node/config"
|
||||
)
|
||||
|
||||
var configDirectory string
|
||||
var signatureCheck bool = true
|
||||
var byPassSignatureCheck bool = false
|
||||
var NodeConfig *config.Config
|
||||
var simulateFail bool
|
||||
var LightNode bool = false
|
||||
var DryRun bool = false
|
||||
var publicRPC bool = false
|
||||
var ClientConfig *utils.ClientConfig
|
||||
|
||||
var StandardizedQClientFileName string = "qclient-" + config.GetVersionString() + "-" + osType + "-" + arch
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "qclient",
|
||||
Short: "Quilibrium RPC Client",
|
||||
Short: "Quilibrium client",
|
||||
Long: `Quilibrium client is a command-line tool for managing Quilibrium nodes.
|
||||
It provides commands for installing, updating, and managing Quilibrium nodes.`,
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
if cmd.Name() == "help" || cmd.Name() == "download-signatures" {
|
||||
return
|
||||
}
|
||||
|
||||
if !utils.FileExists(utils.GetConfigPath()) {
|
||||
fmt.Println("Client config not found, creating default config")
|
||||
utils.CreateDefaultConfig()
|
||||
}
|
||||
|
||||
clientConfig, err := utils.LoadClientConfig()
|
||||
if err != nil {
|
||||
fmt.Printf("Error loading client config: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if !clientConfig.SignatureCheck || byPassSignatureCheck {
|
||||
signatureCheck = false
|
||||
}
|
||||
|
||||
if signatureCheck {
|
||||
ex, err := os.Executable()
|
||||
if err != nil {
|
||||
@ -48,114 +73,111 @@ var rootCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
checksum := sha3.Sum256(b)
|
||||
digest, err := os.ReadFile(ex + ".dgst")
|
||||
|
||||
// First check var data path for signatures
|
||||
varDataPath := filepath.Join(utils.ClientDataPath, config.GetVersionString())
|
||||
digestPath := filepath.Join(varDataPath, StandardizedQClientFileName+".dgst")
|
||||
|
||||
fmt.Printf("Checking signature for %s\n", digestPath)
|
||||
|
||||
// Try to read digest from var data path first
|
||||
digest, err := os.ReadFile(digestPath)
|
||||
if err != nil {
|
||||
fmt.Println("Digest file not found")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
parts := strings.Split(string(digest), " ")
|
||||
if len(parts) != 2 {
|
||||
fmt.Println("Invalid digest file format")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
digestBytes, err := hex.DecodeString(parts[1][:64])
|
||||
if err != nil {
|
||||
fmt.Println("Invalid digest file format")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if !bytes.Equal(checksum[:], digestBytes) {
|
||||
fmt.Println("Invalid digest for node")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
count := 0
|
||||
|
||||
for i := 1; i <= len(config.Signatories); i++ {
|
||||
signatureFile := fmt.Sprintf(ex+".dgst.sig.%d", i)
|
||||
sig, err := os.ReadFile(signatureFile)
|
||||
// Fall back to checking next to executable
|
||||
digest, err = os.ReadFile(ex + ".dgst")
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
fmt.Println("")
|
||||
fmt.Println("The digest file was not found. Do you want to continue without signature verification? (y/n)")
|
||||
|
||||
pubkey, _ := hex.DecodeString(config.Signatories[i-1])
|
||||
if !ed448.Verify(pubkey, digest, sig, "") {
|
||||
fmt.Printf("Failed signature check for signatory #%d\n", i)
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
response, _ := reader.ReadString('\n')
|
||||
response = strings.ToLower(strings.TrimSpace(response))
|
||||
|
||||
if response != "y" && response != "yes" {
|
||||
fmt.Println("Exiting due to missing digest file")
|
||||
fmt.Println("The signature files (if they exist) can be downloaded with the 'qclient download-signatures' command")
|
||||
fmt.Println("You can also skip this prompt manually by using the --signature-check=false flag or to permanently disable signature checks running 'qclient config signature-check false'")
|
||||
|
||||
os.Exit(1)
|
||||
}
|
||||
// Check if the user is trying to run the config command to disable/enable signature checks
|
||||
if len(os.Args) >= 3 && os.Args[1] == "config" && os.Args[2] != "signature-check" {
|
||||
fmt.Println("The signature files (if they exist) can be downloaded with the 'qclient download-signatures' command")
|
||||
fmt.Println("You can also skip this prompt manually by using the --signature-check=false flag or to permanently disable signature checks running 'qclient config signature-check false'")
|
||||
}
|
||||
|
||||
fmt.Println("Continuing without signature verification")
|
||||
|
||||
signatureCheck = false
|
||||
}
|
||||
}
|
||||
|
||||
if signatureCheck {
|
||||
parts := strings.Split(string(digest), " ")
|
||||
if len(parts) != 2 {
|
||||
fmt.Println("Invalid digest file format")
|
||||
os.Exit(1)
|
||||
}
|
||||
count++
|
||||
}
|
||||
|
||||
if count < ((len(config.Signatories)-4)/2)+((len(config.Signatories)-4)%2) {
|
||||
fmt.Printf("Quorum on signatures not met")
|
||||
os.Exit(1)
|
||||
}
|
||||
digestBytes, err := hex.DecodeString(parts[1][:64])
|
||||
if err != nil {
|
||||
fmt.Println("Invalid digest file format")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println("Signature check passed")
|
||||
if !bytes.Equal(checksum[:], digestBytes) {
|
||||
fmt.Println("Invalid digest for node")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
count := 0
|
||||
|
||||
for i := 1; i <= len(config.Signatories); i++ {
|
||||
// Try var data path first for signature files
|
||||
signatureFile := filepath.Join(varDataPath, fmt.Sprintf("%s.dgst.sig.%d", filepath.Base(ex), i))
|
||||
sig, err := os.ReadFile(signatureFile)
|
||||
if err != nil {
|
||||
// Fall back to checking next to executable
|
||||
signatureFile = fmt.Sprintf(ex+".dgst.sig.%d", i)
|
||||
sig, err = os.ReadFile(signatureFile)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
pubkey, _ := hex.DecodeString(config.Signatories[i-1])
|
||||
if !ed448.Verify(pubkey, digest, sig, "") {
|
||||
fmt.Printf("Failed signature check for signatory #%d\n", i)
|
||||
os.Exit(1)
|
||||
}
|
||||
count++
|
||||
}
|
||||
|
||||
if count < ((len(config.Signatories)-4)/2)+((len(config.Signatories)-4)%2) {
|
||||
fmt.Printf("Quorum on signatures not met")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println("Signature check passed")
|
||||
}
|
||||
} else {
|
||||
fmt.Println("Signature check bypassed, be sure you know what you're doing")
|
||||
fmt.Println("----------------------------------------------------------")
|
||||
fmt.Println("")
|
||||
}
|
||||
|
||||
_, err := os.Stat(configDirectory)
|
||||
if os.IsNotExist(err) {
|
||||
fmt.Printf("config directory doesn't exist: %s\n", configDirectory)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
NodeConfig, err = config.LoadConfig(configDirectory, "", false)
|
||||
if err != nil {
|
||||
fmt.Printf("invalid config directory: %s\n", configDirectory)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if publicRPC {
|
||||
fmt.Println("Public RPC enabled, using light node")
|
||||
LightNode = true
|
||||
}
|
||||
if !LightNode && NodeConfig.ListenGRPCMultiaddr == "" {
|
||||
fmt.Println("No ListenGRPCMultiaddr found in config, using light node")
|
||||
LightNode = true
|
||||
}
|
||||
},
|
||||
PersistentPostRun: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Println("")
|
||||
},
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
err := rootCmd.Execute()
|
||||
if err != nil {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func GetGRPCClient() (*grpc.ClientConn, error) {
|
||||
addr := "rpc.quilibrium.com:8337"
|
||||
credentials := credentials.NewTLS(&tls.Config{InsecureSkipVerify: false})
|
||||
if !LightNode {
|
||||
ma, err := multiaddr.NewMultiaddr(NodeConfig.ListenGRPCMultiaddr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, addr, err = mn.DialArgs(ma)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
credentials = insecure.NewCredentials()
|
||||
}
|
||||
|
||||
return grpc.Dial(
|
||||
addr,
|
||||
grpc.WithTransportCredentials(
|
||||
credentials,
|
||||
),
|
||||
grpc.WithDefaultCallOptions(
|
||||
grpc.MaxCallSendMsgSize(600*1024*1024),
|
||||
grpc.MaxCallRecvMsgSize(600*1024*1024),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func signatureCheckDefault() bool {
|
||||
envVarValue, envVarExists := os.LookupEnv("QUILIBRIUM_SIGNATURE_CHECK")
|
||||
if envVarExists {
|
||||
@ -171,28 +193,23 @@ func signatureCheckDefault() bool {
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.PersistentFlags().StringVar(
|
||||
&configDirectory,
|
||||
"config",
|
||||
".config/",
|
||||
"config directory (default is .config/)",
|
||||
)
|
||||
rootCmd.PersistentFlags().BoolVar(
|
||||
&DryRun,
|
||||
"dry-run",
|
||||
false,
|
||||
"runs the command (if applicable) without actually mutating state (printing effect output)",
|
||||
)
|
||||
rootCmd.PersistentFlags().BoolVar(
|
||||
&signatureCheck,
|
||||
"signature-check",
|
||||
signatureCheckDefault(),
|
||||
"bypass signature check (not recommended for binaries) (default true or value of QUILIBRIUM_SIGNATURE_CHECK env var)",
|
||||
)
|
||||
rootCmd.PersistentFlags().BoolVar(
|
||||
&publicRPC,
|
||||
"public-rpc",
|
||||
|
||||
rootCmd.PersistentFlags().BoolVarP(
|
||||
&byPassSignatureCheck,
|
||||
"yes",
|
||||
"y",
|
||||
false,
|
||||
"uses the public RPC",
|
||||
"auto-approve and bypass signature check (sets signature-check=false)",
|
||||
)
|
||||
|
||||
// Add the node command
|
||||
rootCmd.AddCommand(node.NodeCmd)
|
||||
rootCmd.AddCommand(clientConfig.ConfigCmd)
|
||||
rootCmd.AddCommand(token.TokenCmd)
|
||||
}
|
||||
|
||||
@ -1,93 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/spf13/cobra"
|
||||
"source.quilibrium.com/quilibrium/monorepo/node/protobufs"
|
||||
)
|
||||
|
||||
var splitCmd = &cobra.Command{
|
||||
Use: "split",
|
||||
Short: "Splits a coin into multiple coins",
|
||||
Long: `Splits a coin into multiple coins:
|
||||
|
||||
split <OfCoin> <Amounts>...
|
||||
|
||||
OfCoin - the address of the coin to split
|
||||
Amounts - the sets of amounts to split
|
||||
`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) < 3 {
|
||||
fmt.Println("invalid command")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
coinaddrHex, _ := strings.CutPrefix(args[0], "0x")
|
||||
coinaddr, err := hex.DecodeString(coinaddrHex)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
coin := &protobufs.CoinRef{
|
||||
Address: coinaddr,
|
||||
}
|
||||
|
||||
conversionFactor, _ := new(big.Int).SetString("1DCD65000", 16)
|
||||
amounts := [][]byte{}
|
||||
for _, amt := range args[1:] {
|
||||
amount, err := decimal.NewFromString(amt)
|
||||
if err != nil {
|
||||
fmt.Println("invalid amount")
|
||||
os.Exit(1)
|
||||
}
|
||||
amount = amount.Mul(decimal.NewFromBigInt(conversionFactor, 0))
|
||||
amountBytes := amount.BigInt().FillBytes(make([]byte, 32))
|
||||
amounts = append(amounts, amountBytes)
|
||||
}
|
||||
|
||||
conn, err := GetGRPCClient()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
client := protobufs.NewNodeServiceClient(conn)
|
||||
privKey, err := GetPrivKeyFromConfig(NodeConfig)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
pubKeyBytes, err := privKey.GetPublic().Raw()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
split := &protobufs.SplitCoinRequest{
|
||||
OfCoin: coin,
|
||||
Amounts: amounts,
|
||||
}
|
||||
if err := split.SignED448(pubKeyBytes, privKey.Sign); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := split.Validate(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, err = client.SendMessage(
|
||||
context.Background(),
|
||||
split.TokenRequest(),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
tokenCmd.AddCommand(splitCmd)
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var tokenCmd = &cobra.Command{
|
||||
Use: "token",
|
||||
Short: "Performs a token operation",
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(tokenCmd)
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package cmd
|
||||
package token
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@ -21,5 +21,5 @@ var acceptCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func init() {
|
||||
tokenCmd.AddCommand(acceptCmd)
|
||||
TokenCmd.AddCommand(acceptCmd)
|
||||
}
|
||||
28
client/cmd/token/account.go
Normal file
28
client/cmd/token/account.go
Normal file
@ -0,0 +1,28 @@
|
||||
package token
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/iden3/go-iden3-crypto/poseidon"
|
||||
"github.com/spf13/cobra"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
)
|
||||
|
||||
var accountCmd = &cobra.Command{
|
||||
Use: "account",
|
||||
Short: "Shows the account address of the managing account",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
peerId := utils.GetPeerIDFromConfig(NodeConfig)
|
||||
addr, err := poseidon.HashBytes([]byte(peerId))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
addrBytes := addr.FillBytes(make([]byte, 32))
|
||||
fmt.Printf("Account: 0x%x\n", addrBytes)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
TokenCmd.AddCommand(accountCmd)
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package cmd
|
||||
package token
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -16,6 +16,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
"go.uber.org/zap"
|
||||
"google.golang.org/grpc"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
"source.quilibrium.com/quilibrium/monorepo/node/config"
|
||||
"source.quilibrium.com/quilibrium/monorepo/node/execution/intrinsics/token/application"
|
||||
"source.quilibrium.com/quilibrium/monorepo/node/p2p"
|
||||
@ -44,8 +45,8 @@ var allCmd = &cobra.Command{
|
||||
db := store.NewPebbleDB(NodeConfig.DB)
|
||||
logger, _ := zap.NewProduction()
|
||||
dataProofStore := store.NewPebbleDataProofStore(db, logger)
|
||||
peerId := GetPeerIDFromConfig(NodeConfig)
|
||||
privKey, err := GetPrivKeyFromConfig(NodeConfig)
|
||||
peerId := utils.GetPeerIDFromConfig(NodeConfig)
|
||||
privKey, err := utils.GetPrivKeyFromConfig(NodeConfig)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package cmd
|
||||
package token
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/iden3/go-iden3-crypto/poseidon"
|
||||
"github.com/spf13/cobra"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
"source.quilibrium.com/quilibrium/monorepo/node/protobufs"
|
||||
)
|
||||
|
||||
@ -21,8 +22,8 @@ var balanceCmd = &cobra.Command{
|
||||
defer conn.Close()
|
||||
|
||||
client := protobufs.NewNodeServiceClient(conn)
|
||||
peerId := GetPeerIDFromConfig(NodeConfig)
|
||||
privKey, err := GetPrivKeyFromConfig(NodeConfig)
|
||||
peerId := utils.GetPeerIDFromConfig(NodeConfig)
|
||||
privKey, err := utils.GetPrivKeyFromConfig(NodeConfig)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -88,5 +89,5 @@ var balanceCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func init() {
|
||||
tokenCmd.AddCommand(balanceCmd)
|
||||
TokenCmd.AddCommand(balanceCmd)
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package cmd
|
||||
package token
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/iden3/go-iden3-crypto/poseidon"
|
||||
"github.com/spf13/cobra"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
"source.quilibrium.com/quilibrium/monorepo/node/protobufs"
|
||||
)
|
||||
|
||||
@ -35,8 +36,8 @@ var coinsCmd = &cobra.Command{
|
||||
defer conn.Close()
|
||||
|
||||
client := protobufs.NewNodeServiceClient(conn)
|
||||
peerId := GetPeerIDFromConfig(NodeConfig)
|
||||
privKey, err := GetPrivKeyFromConfig(NodeConfig)
|
||||
peerId := utils.GetPeerIDFromConfig(NodeConfig)
|
||||
privKey, err := utils.GetPrivKeyFromConfig(NodeConfig)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -136,5 +137,5 @@ var coinsCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func init() {
|
||||
tokenCmd.AddCommand(coinsCmd)
|
||||
TokenCmd.AddCommand(coinsCmd)
|
||||
}
|
||||
@ -1,12 +1,14 @@
|
||||
package cmd
|
||||
package token
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/iden3/go-iden3-crypto/poseidon"
|
||||
"github.com/spf13/cobra"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
"source.quilibrium.com/quilibrium/monorepo/node/protobufs"
|
||||
)
|
||||
|
||||
@ -31,8 +33,8 @@ var mergeCmd = &cobra.Command{
|
||||
defer conn.Close()
|
||||
|
||||
client := protobufs.NewNodeServiceClient(conn)
|
||||
peerId := GetPeerIDFromConfig(NodeConfig)
|
||||
privKey, err := GetPrivKeyFromConfig(NodeConfig)
|
||||
peerId := utils.GetPeerIDFromConfig(NodeConfig)
|
||||
privKey, err := utils.GetPrivKeyFromConfig(NodeConfig)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -112,6 +114,29 @@ var mergeCmd = &cobra.Command{
|
||||
merge := &protobufs.MergeCoinRequest{
|
||||
Coins: coinaddrs,
|
||||
}
|
||||
|
||||
// Display merge details and confirmation prompt
|
||||
fmt.Printf("\nMerge Transaction Details:\n")
|
||||
fmt.Printf("Number of coins to merge: %d\n", len(coinaddrs))
|
||||
fmt.Println("Coins to be merged:")
|
||||
for i, coin := range coinaddrs {
|
||||
fmt.Printf("%d. 0x%x\n", i+1, coin.Address)
|
||||
}
|
||||
fmt.Print("\nDo you want to proceed with this merge? (yes/no): ")
|
||||
|
||||
var response string
|
||||
fmt.Scanln(&response)
|
||||
|
||||
if strings.ToLower(response) != "yes" {
|
||||
fmt.Println("Merge transaction cancelled by user.")
|
||||
return
|
||||
}
|
||||
|
||||
// Create payload for merge operation
|
||||
payload := []byte("merge")
|
||||
for _, coinRef := range coinaddrs {
|
||||
payload = append(payload, coinRef.Address...)
|
||||
}
|
||||
if err := merge.SignED448(pubKeyBytes, privKey.Sign); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -128,10 +153,10 @@ var mergeCmd = &cobra.Command{
|
||||
panic(err)
|
||||
}
|
||||
|
||||
println("Merge request sent successfully")
|
||||
fmt.Println("Merge transaction sent successfully.")
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
tokenCmd.AddCommand(mergeCmd)
|
||||
TokenCmd.AddCommand(mergeCmd)
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package cmd
|
||||
package token
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
@ -10,5 +10,5 @@ var mintCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func init() {
|
||||
tokenCmd.AddCommand(mintCmd)
|
||||
TokenCmd.AddCommand(mintCmd)
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package cmd
|
||||
package token
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@ -21,5 +21,5 @@ var mutualReceiveCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func init() {
|
||||
tokenCmd.AddCommand(mutualReceiveCmd)
|
||||
TokenCmd.AddCommand(mutualReceiveCmd)
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package cmd
|
||||
package token
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@ -25,5 +25,5 @@ var mutualTransferCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func init() {
|
||||
tokenCmd.AddCommand(mutualTransferCmd)
|
||||
TokenCmd.AddCommand(mutualTransferCmd)
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package cmd
|
||||
package token
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@ -21,5 +21,5 @@ var rejectCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func init() {
|
||||
tokenCmd.AddCommand(rejectCmd)
|
||||
TokenCmd.AddCommand(rejectCmd)
|
||||
}
|
||||
318
client/cmd/token/split.go
Normal file
318
client/cmd/token/split.go
Normal file
@ -0,0 +1,318 @@
|
||||
package token
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/iden3/go-iden3-crypto/poseidon"
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/spf13/cobra"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
"source.quilibrium.com/quilibrium/monorepo/node/protobufs"
|
||||
)
|
||||
|
||||
var parts int
|
||||
var partAmount string
|
||||
var splitCmd = &cobra.Command{
|
||||
Use: "split",
|
||||
Short: "Splits a coin into multiple coins",
|
||||
Long: `Splits a coin into multiple coins:
|
||||
|
||||
split <OfCoin> <Amounts>...
|
||||
split <--parts PARTS> [--part-amount AMOUNT] <OfCoin>
|
||||
|
||||
OfCoin - the address of the coin to split
|
||||
Amounts - the sets of amounts to split
|
||||
|
||||
Example - Split a coin into the specified amounts:
|
||||
$ qclient token coins
|
||||
1.000000000000 QUIL (Coin 0x1234)
|
||||
$ qclient token split 0x1234 0.5 0.25 0.25
|
||||
$ qclient token coins
|
||||
0.250000000000 QUIL (Coin 0x1111)
|
||||
0.250000000000 QUIL (Coin 0x2222)
|
||||
0.500000000000 QUIL (Coin 0x3333)
|
||||
|
||||
Example - Split a coin into three parts:
|
||||
$ qclient token coins
|
||||
1.000000000000 QUIL (Coin 0x1234)
|
||||
$ qclient token split 0x1234 --parts 3
|
||||
$ qclient token coins
|
||||
0.000000000250 QUIL (Coin 0x1111)
|
||||
0.333333333250 QUIL (Coin 0x2222)
|
||||
0.333333333250 QUIL (Coin 0x3333)
|
||||
0.333333333250 QUIL (Coin 0x4444)
|
||||
|
||||
**Note:** Coin 0x1111 is the remainder.
|
||||
|
||||
Example - Split a coin into two parts using the specified amounts:
|
||||
$ qclient token coins
|
||||
1.000000000000 QUIL (Coin 0x1234)
|
||||
$ qclient token split 0x1234 --parts 2 --part-amount 0.35
|
||||
$ qclient token coins
|
||||
0.300000000000 QUIL (Coin 0x1111)
|
||||
0.350000000000 QUIL (Coin 0x2222)
|
||||
0.350000000000 QUIL (Coin 0x3333)
|
||||
|
||||
**Note:** Coin 0x1111 is the remainder.
|
||||
`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) < 3 && parts == 1 {
|
||||
fmt.Println("did you forget to specify <OfCoin> and <Amounts>?")
|
||||
os.Exit(1)
|
||||
}
|
||||
if len(args) < 1 && parts > 1 {
|
||||
fmt.Println("did you forget to specify <OfCoin>?")
|
||||
os.Exit(1)
|
||||
}
|
||||
if len(args) > 1 && parts > 1 {
|
||||
fmt.Println("-p/--parts can't be combined with <Amounts>")
|
||||
os.Exit(1)
|
||||
}
|
||||
if len(args) > 1 && partAmount != "" {
|
||||
fmt.Println("-a/--part-amount can't be combined with <Amounts>")
|
||||
os.Exit(1)
|
||||
}
|
||||
if parts > 100 {
|
||||
fmt.Println("too many parts, maximum is 100")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
payload := []byte("split")
|
||||
coinaddrHex, _ := strings.CutPrefix(args[0], "0x")
|
||||
coinaddr, err := hex.DecodeString(coinaddrHex)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
coin := &protobufs.CoinRef{
|
||||
Address: coinaddr,
|
||||
}
|
||||
payload = append(payload, coinaddr...)
|
||||
|
||||
// Get the amount of the coin to be split
|
||||
totalAmount := getCoinAmount(coinaddr)
|
||||
|
||||
amounts := [][]byte{}
|
||||
|
||||
// Split the coin into the user specified amounts
|
||||
if parts == 1 {
|
||||
amounts, payload, err = Split(args[1:], amounts, payload, totalAmount)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Split the coin into parts
|
||||
if parts > 1 && partAmount == "" {
|
||||
amounts, payload = SplitIntoParts(amounts, payload, totalAmount, parts)
|
||||
}
|
||||
|
||||
// Split the coin into parts of the user specified amount
|
||||
if parts > 1 && partAmount != "" {
|
||||
amounts, payload, err = SplitIntoPartsAmount(amounts, payload, totalAmount, parts, partAmount)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
conn, err := GetGRPCClient()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
client := protobufs.NewNodeServiceClient(conn)
|
||||
key, err := utils.GetPrivKeyFromConfig(NodeConfig)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
sig, err := key.Sign(payload)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
pub, err := key.GetPublic().Raw()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, err = client.SendMessage(
|
||||
context.Background(),
|
||||
&protobufs.TokenRequest{
|
||||
Request: &protobufs.TokenRequest_Split{
|
||||
Split: &protobufs.SplitCoinRequest{
|
||||
OfCoin: coin,
|
||||
Amounts: amounts,
|
||||
Signature: &protobufs.Ed448Signature{
|
||||
Signature: sig,
|
||||
PublicKey: &protobufs.Ed448PublicKey{
|
||||
KeyValue: pub,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
splitCmd.Flags().IntVarP(&parts, "parts", "p", 1, "number of parts to split the coin into")
|
||||
splitCmd.Flags().StringVarP(&partAmount, "part-amount", "a", "", "amount of each part")
|
||||
TokenCmd.AddCommand(splitCmd)
|
||||
}
|
||||
|
||||
func Split(args []string, amounts [][]byte, payload []byte, totalAmount *big.Int) ([][]byte, []byte, error) {
|
||||
conversionFactor, _ := new(big.Int).SetString("1DCD65000", 16)
|
||||
inputAmount := new(big.Int)
|
||||
for _, amt := range args {
|
||||
amount, err := decimal.NewFromString(amt)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("invalid amount, must be a decimal number like 0.02 or 2")
|
||||
}
|
||||
amount = amount.Mul(decimal.NewFromBigInt(conversionFactor, 0))
|
||||
inputAmount = inputAmount.Add(inputAmount, amount.BigInt())
|
||||
amountBytes := amount.BigInt().FillBytes(make([]byte, 32))
|
||||
amounts = append(amounts, amountBytes)
|
||||
payload = append(payload, amountBytes...)
|
||||
}
|
||||
|
||||
// Check if the user specified amounts sum to the total amount of the coin
|
||||
if inputAmount.Cmp(totalAmount) != 0 {
|
||||
return nil, nil, fmt.Errorf("the specified amounts must sum to the total amount of the coin")
|
||||
}
|
||||
return amounts, payload, nil
|
||||
}
|
||||
|
||||
func SplitIntoParts(amounts [][]byte, payload []byte, totalAmount *big.Int, parts int) ([][]byte, []byte) {
|
||||
amount := new(big.Int).Div(totalAmount, big.NewInt(int64(parts)))
|
||||
amountBytes := amount.FillBytes(make([]byte, 32))
|
||||
for i := int64(0); i < int64(parts); i++ {
|
||||
amounts = append(amounts, amountBytes)
|
||||
payload = append(payload, amountBytes...)
|
||||
}
|
||||
|
||||
// If there is a remainder, we need to add it as a separate amount
|
||||
// because the amounts must sum to the original coin amount.
|
||||
remainder := new(big.Int).Mod(totalAmount, big.NewInt(int64(parts)))
|
||||
if remainder.Cmp(big.NewInt(0)) != 0 {
|
||||
remainderBytes := remainder.FillBytes(make([]byte, 32))
|
||||
amounts = append(amounts, remainderBytes)
|
||||
payload = append(payload, remainderBytes...)
|
||||
}
|
||||
return amounts, payload
|
||||
}
|
||||
|
||||
func SplitIntoPartsAmount(amounts [][]byte, payload []byte, totalAmount *big.Int, parts int, partAmount string) ([][]byte, []byte, error) {
|
||||
conversionFactor, _ := new(big.Int).SetString("1DCD65000", 16)
|
||||
amount, err := decimal.NewFromString(partAmount)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("invalid amount, must be a decimal number like 0.02 or 2")
|
||||
}
|
||||
amount = amount.Mul(decimal.NewFromBigInt(conversionFactor, 0))
|
||||
inputAmount := new(big.Int).Mul(amount.BigInt(), big.NewInt(int64(parts)))
|
||||
amountBytes := amount.BigInt().FillBytes(make([]byte, 32))
|
||||
for i := int64(0); i < int64(parts); i++ {
|
||||
amounts = append(amounts, amountBytes)
|
||||
payload = append(payload, amountBytes...)
|
||||
}
|
||||
|
||||
// If there is a remainder, we need to add it as a separate amount
|
||||
// because the amounts must sum to the original coin amount.
|
||||
remainder := new(big.Int).Sub(totalAmount, inputAmount)
|
||||
if remainder.Cmp(big.NewInt(0)) != 0 {
|
||||
remainderBytes := remainder.FillBytes(make([]byte, 32))
|
||||
amounts = append(amounts, remainderBytes)
|
||||
payload = append(payload, remainderBytes...)
|
||||
}
|
||||
|
||||
// Check if the user specified amounts sum to the total amount of the coin
|
||||
if new(big.Int).Add(inputAmount, new(big.Int).Abs(remainder)).Cmp(totalAmount) != 0 {
|
||||
return nil, nil, fmt.Errorf("the specified amounts must sum to the total amount of the coin")
|
||||
}
|
||||
return amounts, payload, nil
|
||||
}
|
||||
|
||||
func getCoinAmount(coinaddr []byte) *big.Int {
|
||||
conn, err := GetGRPCClient()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
client := protobufs.NewNodeServiceClient(conn)
|
||||
peerId := utils.GetPeerIDFromConfig(NodeConfig)
|
||||
privKey, err := utils.GetPrivKeyFromConfig(NodeConfig)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
pub, err := privKey.GetPublic().Raw()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
addr, err := poseidon.HashBytes([]byte(peerId))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
addrBytes := addr.FillBytes(make([]byte, 32))
|
||||
resp, err := client.GetTokensByAccount(
|
||||
context.Background(),
|
||||
&protobufs.GetTokensByAccountRequest{
|
||||
Address: addrBytes,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if len(resp.Coins) != len(resp.FrameNumbers) {
|
||||
panic("invalid response from RPC")
|
||||
}
|
||||
|
||||
altAddr, err := poseidon.HashBytes([]byte(pub))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
altAddrBytes := altAddr.FillBytes(make([]byte, 32))
|
||||
resp2, err := client.GetTokensByAccount(
|
||||
context.Background(),
|
||||
&protobufs.GetTokensByAccountRequest{
|
||||
Address: altAddrBytes,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if len(resp.Coins) != len(resp.FrameNumbers) {
|
||||
panic("invalid response from RPC")
|
||||
}
|
||||
|
||||
var amount *big.Int
|
||||
for i, coin := range resp.Coins {
|
||||
if bytes.Equal(resp.Addresses[i], coinaddr) {
|
||||
amount = new(big.Int).SetBytes(coin.Amount)
|
||||
}
|
||||
}
|
||||
for i, coin := range resp2.Coins {
|
||||
if bytes.Equal(resp.Addresses[i], coinaddr) {
|
||||
amount = new(big.Int).SetBytes(coin.Amount)
|
||||
}
|
||||
}
|
||||
return amount
|
||||
}
|
||||
232
client/cmd/token/tests/split_test.go
Normal file
232
client/cmd/token/tests/split_test.go
Normal file
@ -0,0 +1,232 @@
|
||||
package token_test
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/cmd/token"
|
||||
)
|
||||
|
||||
func TestSplit(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
totalAmount string
|
||||
amounts [][]byte
|
||||
payload []byte
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "Valid split - specified amounts",
|
||||
args: []string{"0x1234", "0.5", "0.25", "0.25"},
|
||||
totalAmount: "1.0",
|
||||
amounts: [][]byte{
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 238, 107, 40, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 119, 53, 148, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 119, 53, 148, 0},
|
||||
},
|
||||
payload: []byte{
|
||||
115, 112, 108, 105, 116,
|
||||
18, 52,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 238, 107, 40, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 119, 53, 148, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 119, 53, 148, 0,
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "Invalid split - amounts do not sum to the total amount of the coin",
|
||||
args: []string{"0x1234", "0.5", "0.25"},
|
||||
totalAmount: "1.0",
|
||||
amounts: [][]byte{},
|
||||
payload: []byte{},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "Invalid split - amounts exceed total amount of the coin",
|
||||
args: []string{"0x1234", "0.5", "1"},
|
||||
totalAmount: "1.0",
|
||||
amounts: [][]byte{},
|
||||
payload: []byte{},
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
payload := []byte("split")
|
||||
coinaddrHex, _ := strings.CutPrefix(tc.args[0], "0x")
|
||||
coinaddr, err := hex.DecodeString(coinaddrHex)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
payload = append(payload, coinaddr...)
|
||||
|
||||
conversionFactor, _ := new(big.Int).SetString("1DCD65000", 16)
|
||||
totalAmount, _ := decimal.NewFromString(tc.totalAmount)
|
||||
totalAmount = totalAmount.Mul(decimal.NewFromBigInt(conversionFactor, 0))
|
||||
|
||||
amounts := [][]byte{}
|
||||
|
||||
if tc.expectError {
|
||||
_, _, err = token.Split(tc.args[1:], amounts, payload, totalAmount.BigInt())
|
||||
if err == nil {
|
||||
t.Errorf("want error for invalid split, got nil")
|
||||
}
|
||||
} else {
|
||||
amounts, payload, err = token.Split(tc.args[1:], amounts, payload, totalAmount.BigInt())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(tc.amounts, amounts) {
|
||||
t.Errorf("expected amounts: %v, got: %v", tc.amounts, amounts)
|
||||
}
|
||||
if !reflect.DeepEqual(tc.payload, payload) {
|
||||
t.Errorf("expected payloads: %v, got: %v", tc.payload, payload)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitParts(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
parts int
|
||||
totalAmount string
|
||||
amounts [][]byte
|
||||
payload []byte
|
||||
}{
|
||||
{
|
||||
name: "Valid split - into parts",
|
||||
args: []string{"0x1234"},
|
||||
parts: 3,
|
||||
totalAmount: "1.0",
|
||||
amounts: [][]byte{
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 158, 242, 26, 170},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 158, 242, 26, 170},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 158, 242, 26, 170},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2},
|
||||
},
|
||||
payload: []byte{
|
||||
115, 112, 108, 105, 116,
|
||||
18, 52,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 158, 242, 26, 170,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 158, 242, 26, 170,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 158, 242, 26, 170,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
payload := []byte("split")
|
||||
coinaddrHex, _ := strings.CutPrefix(tc.args[0], "0x")
|
||||
coinaddr, err := hex.DecodeString(coinaddrHex)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
payload = append(payload, coinaddr...)
|
||||
|
||||
conversionFactor, _ := new(big.Int).SetString("1DCD65000", 16)
|
||||
totalAmount, _ := decimal.NewFromString(tc.totalAmount)
|
||||
totalAmount = totalAmount.Mul(decimal.NewFromBigInt(conversionFactor, 0))
|
||||
|
||||
amounts := [][]byte{}
|
||||
|
||||
amounts, payload = token.SplitIntoParts(amounts, payload, totalAmount.BigInt(), tc.parts)
|
||||
if !reflect.DeepEqual(tc.amounts, amounts) {
|
||||
t.Errorf("expected amounts: %v, got: %v", tc.amounts, amounts)
|
||||
}
|
||||
if !reflect.DeepEqual(tc.payload, payload) {
|
||||
t.Errorf("expected payloads: %v, got: %v", tc.payload, payload)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitIntoPartsAmount(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
parts int
|
||||
partAmount string
|
||||
totalAmount string
|
||||
amounts [][]byte
|
||||
payload []byte
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "Valid split - into parts of specified amount",
|
||||
args: []string{"0x1234"},
|
||||
parts: 2,
|
||||
partAmount: "0.35",
|
||||
totalAmount: "1.0",
|
||||
amounts: [][]byte{
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 166, 228, 156, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 166, 228, 156, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 143, 13, 24, 0},
|
||||
},
|
||||
payload: []byte{
|
||||
115, 112, 108, 105, 116,
|
||||
18, 52,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 166, 228, 156, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 166, 228, 156, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 143, 13, 24, 0,
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "Invalid split - amounts exceed total amount of the coin",
|
||||
args: []string{"0x1234"},
|
||||
parts: 3,
|
||||
partAmount: "0.5",
|
||||
totalAmount: "1.0",
|
||||
amounts: [][]byte{},
|
||||
payload: []byte{},
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
payload := []byte("split")
|
||||
coinaddrHex, _ := strings.CutPrefix(tc.args[0], "0x")
|
||||
coinaddr, err := hex.DecodeString(coinaddrHex)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
payload = append(payload, coinaddr...)
|
||||
|
||||
conversionFactor, _ := new(big.Int).SetString("1DCD65000", 16)
|
||||
totalAmount, _ := decimal.NewFromString(tc.totalAmount)
|
||||
totalAmount = totalAmount.Mul(decimal.NewFromBigInt(conversionFactor, 0))
|
||||
|
||||
amounts := [][]byte{}
|
||||
|
||||
if tc.expectError {
|
||||
_, _, err = token.SplitIntoPartsAmount(amounts, payload, totalAmount.BigInt(), tc.parts, tc.partAmount)
|
||||
if err == nil {
|
||||
t.Errorf("want error for invalid split, got nil")
|
||||
}
|
||||
} else {
|
||||
amounts, payload, err = token.SplitIntoPartsAmount(amounts, payload, totalAmount.BigInt(), tc.parts, tc.partAmount)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(tc.amounts, amounts) {
|
||||
t.Errorf("expected amounts: %v, got: %v", tc.amounts, amounts)
|
||||
}
|
||||
if !reflect.DeepEqual(tc.payload, payload) {
|
||||
t.Errorf("expected payloads: %v, got: %v", tc.payload, payload)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
49
client/cmd/token/token.go
Normal file
49
client/cmd/token/token.go
Normal file
@ -0,0 +1,49 @@
|
||||
package token
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"source.quilibrium.com/quilibrium/monorepo/node/config"
|
||||
)
|
||||
|
||||
var LightNode bool = false
|
||||
var publicRPC bool = false
|
||||
var NodeConfig *config.Config
|
||||
var configDirectory string
|
||||
|
||||
var TokenCmd = &cobra.Command{
|
||||
Use: "token",
|
||||
Short: "Performs a token operation",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
// These commands handle their own configuration
|
||||
_, err := os.Stat(configDirectory)
|
||||
if os.IsNotExist(err) {
|
||||
fmt.Printf("config directory doesn't exist: %s\n", configDirectory)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
NodeConfig, err = config.LoadConfig(configDirectory, "", false)
|
||||
if err != nil {
|
||||
fmt.Printf("invalid config directory: %s\n", configDirectory)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if publicRPC {
|
||||
fmt.Println("Public RPC enabled, using light node")
|
||||
LightNode = true
|
||||
}
|
||||
|
||||
if !LightNode && NodeConfig.ListenGRPCMultiaddr == "" {
|
||||
fmt.Println("No ListenGRPCMultiaddr found in config, using light node")
|
||||
LightNode = true
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
TokenCmd.PersistentFlags().BoolVar(&publicRPC, "public-rpc", false, "Use public RPC for token operations")
|
||||
TokenCmd.PersistentFlags().StringVar(&configDirectory, "config", ".config", "config directory (default is .config/)")
|
||||
}
|
||||
116
client/cmd/token/transfer.go
Normal file
116
client/cmd/token/transfer.go
Normal file
@ -0,0 +1,116 @@
|
||||
package token
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
"source.quilibrium.com/quilibrium/monorepo/node/protobufs"
|
||||
)
|
||||
|
||||
var transferCmd = &cobra.Command{
|
||||
Use: "transfer",
|
||||
Short: "Creates a pending transfer of coin",
|
||||
Long: `Creates a pending transfer of coin:
|
||||
transfer <ToAccount> <OfCoin>
|
||||
ToAccount – account address, must be specified
|
||||
OfCoin – the address of the coin to send in whole`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 2 {
|
||||
panic("invalid arguments")
|
||||
}
|
||||
|
||||
conn, err := GetGRPCClient()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
client := protobufs.NewNodeServiceClient(conn)
|
||||
privKey, err := utils.GetPrivKeyFromConfig(NodeConfig)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var coinaddr *protobufs.CoinRef
|
||||
payload := []byte("transfer")
|
||||
toaddr := []byte{}
|
||||
|
||||
for i, arg := range args {
|
||||
addrHex, _ := strings.CutPrefix(arg, "0x")
|
||||
addr, err := hex.DecodeString(addrHex)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if i == 0 {
|
||||
toaddr = addr
|
||||
continue
|
||||
}
|
||||
coinaddr = &protobufs.CoinRef{
|
||||
Address: addr,
|
||||
}
|
||||
payload = append(payload, addr...)
|
||||
}
|
||||
payload = append(payload, coinaddr.Address...)
|
||||
|
||||
// Display transaction details and confirmation prompt
|
||||
fmt.Printf("\nTransaction Details:\n")
|
||||
fmt.Printf("To Address: 0x%x\n", toaddr)
|
||||
fmt.Printf("Coin Address: 0x%x\n", coinaddr.Address)
|
||||
fmt.Print("\nDo you want to proceed with this transaction? (yes/no): ")
|
||||
|
||||
var response string
|
||||
fmt.Scanln(&response)
|
||||
|
||||
if strings.ToLower(response) != "yes" {
|
||||
fmt.Println("Transaction cancelled by user.")
|
||||
return
|
||||
}
|
||||
|
||||
sig, err := privKey.Sign(payload)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
pub, err := privKey.GetPublic().Raw()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, err = client.SendMessage(
|
||||
context.Background(),
|
||||
&protobufs.TokenRequest{
|
||||
Request: &protobufs.TokenRequest_Transfer{
|
||||
Transfer: &protobufs.TransferCoinRequest{
|
||||
OfCoin: coinaddr,
|
||||
ToAccount: &protobufs.AccountRef{
|
||||
Account: &protobufs.AccountRef_ImplicitAccount{
|
||||
ImplicitAccount: &protobufs.ImplicitAccount{
|
||||
Address: toaddr,
|
||||
},
|
||||
},
|
||||
},
|
||||
Signature: &protobufs.Ed448Signature{
|
||||
Signature: sig,
|
||||
PublicKey: &protobufs.Ed448PublicKey{
|
||||
KeyValue: pub,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println("Transaction sent successfully.")
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
TokenCmd.AddCommand(transferCmd)
|
||||
}
|
||||
40
client/cmd/token/utils.go
Normal file
40
client/cmd/token/utils.go
Normal file
@ -0,0 +1,40 @@
|
||||
package token
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
|
||||
"github.com/multiformats/go-multiaddr"
|
||||
mn "github.com/multiformats/go-multiaddr/net"
|
||||
)
|
||||
|
||||
func GetGRPCClient() (*grpc.ClientConn, error) {
|
||||
addr := "rpc.quilibrium.com:8337"
|
||||
credentials := credentials.NewTLS(&tls.Config{InsecureSkipVerify: false})
|
||||
if !LightNode {
|
||||
ma, err := multiaddr.NewMultiaddr(NodeConfig.ListenGRPCMultiaddr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, addr, err = mn.DialArgs(ma)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
credentials = insecure.NewCredentials()
|
||||
}
|
||||
|
||||
return grpc.Dial(
|
||||
addr,
|
||||
grpc.WithTransportCredentials(
|
||||
credentials,
|
||||
),
|
||||
grpc.WithDefaultCallOptions(
|
||||
grpc.MaxCallSendMsgSize(600*1024*1024),
|
||||
grpc.MaxCallRecvMsgSize(600*1024*1024),
|
||||
),
|
||||
)
|
||||
}
|
||||
@ -1,90 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"source.quilibrium.com/quilibrium/monorepo/node/protobufs"
|
||||
)
|
||||
|
||||
var transferCmd = &cobra.Command{
|
||||
Use: "transfer",
|
||||
Short: "Creates a pending transfer of coin",
|
||||
Long: `Creates a pending transfer of coin:
|
||||
|
||||
transfer <ToAccount> <OfCoin>
|
||||
|
||||
ToAccount – account address, must be specified
|
||||
OfCoin – the address of the coin to send in whole
|
||||
`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 2 {
|
||||
panic("invalid arguments")
|
||||
}
|
||||
|
||||
conn, err := GetGRPCClient()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
client := protobufs.NewNodeServiceClient(conn)
|
||||
privKey, err := GetPrivKeyFromConfig(NodeConfig)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
pubKeyBytes, err := privKey.GetPublic().Raw()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var coinaddr *protobufs.CoinRef
|
||||
toaddr := []byte{}
|
||||
for i, arg := range args {
|
||||
addrHex, _ := strings.CutPrefix(arg, "0x")
|
||||
addr, err := hex.DecodeString(addrHex)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if i == 0 {
|
||||
toaddr = addr
|
||||
continue
|
||||
}
|
||||
|
||||
coinaddr = &protobufs.CoinRef{
|
||||
Address: addr,
|
||||
}
|
||||
}
|
||||
|
||||
transfer := &protobufs.TransferCoinRequest{
|
||||
OfCoin: coinaddr,
|
||||
ToAccount: &protobufs.AccountRef{
|
||||
Account: &protobufs.AccountRef_ImplicitAccount{
|
||||
ImplicitAccount: &protobufs.ImplicitAccount{
|
||||
Address: toaddr,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := transfer.SignED448(pubKeyBytes, privKey.Sign); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := transfer.Validate(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, err = client.SendMessage(
|
||||
context.Background(),
|
||||
transfer.TokenRequest(),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
tokenCmd.AddCommand(transferCmd)
|
||||
}
|
||||
167
client/cmd/update.go
Normal file
167
client/cmd/update.go
Normal file
@ -0,0 +1,167 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
"source.quilibrium.com/quilibrium/monorepo/node/config"
|
||||
)
|
||||
|
||||
var (
|
||||
osType = runtime.GOOS
|
||||
arch = runtime.GOARCH
|
||||
)
|
||||
|
||||
// updateCmd represents the command to update the Quilibrium client
|
||||
var updateCmd = &cobra.Command{
|
||||
Use: "update [version]",
|
||||
Short: "Update Quilibrium client",
|
||||
Long: `Update Quilibrium client to a specified version or the latest version.
|
||||
If no version is specified, the latest version will be installed.
|
||||
|
||||
If the current version is already the latest version, the command will exit with a message.
|
||||
|
||||
Examples:
|
||||
# Update to the latest version
|
||||
qclient update
|
||||
|
||||
# Update to a specific version
|
||||
qclient update 2.1.0`,
|
||||
Args: cobra.RangeArgs(0, 1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// Get system information and validate
|
||||
localOsType, localArch, err := utils.GetSystemInfo()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
osType = localOsType
|
||||
arch = localArch
|
||||
|
||||
// Determine version to install
|
||||
version := determineVersion(args)
|
||||
|
||||
fmt.Fprintf(os.Stdout, "Updating Quilibrium client for %s-%s, version: %s\n", osType, arch, version)
|
||||
|
||||
// Update the client
|
||||
updateClient(version)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(updateCmd)
|
||||
}
|
||||
|
||||
// determineVersion gets the version to install from args or defaults to "latest"
|
||||
func determineVersion(args []string) string {
|
||||
if len(args) > 0 {
|
||||
return args[0]
|
||||
}
|
||||
return "latest"
|
||||
}
|
||||
|
||||
// updateClient handles the client update process
|
||||
func updateClient(version string) {
|
||||
|
||||
currentVersion := config.GetVersionString()
|
||||
|
||||
// If version is "latest", get the latest version
|
||||
if version == "latest" {
|
||||
latestVersion, err := utils.GetLatestVersion(utils.ReleaseTypeQClient)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error getting latest version: %v\n", err)
|
||||
return
|
||||
}
|
||||
version = latestVersion
|
||||
fmt.Fprintf(os.Stdout, "Latest version: %s\n", version)
|
||||
}
|
||||
|
||||
if version == currentVersion {
|
||||
fmt.Fprintf(os.Stdout, "Already on version %s\n", currentVersion)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if we need sudo privileges
|
||||
if err := utils.CheckAndRequestSudo(fmt.Sprintf("Updating client at %s requires root privileges", utils.ClientInstallPath)); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Create version-specific installation directory
|
||||
versionDir := filepath.Join(utils.ClientInstallPath, version)
|
||||
if err := os.MkdirAll(versionDir, 0755); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error creating installation directory: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Create data directory
|
||||
versionDataDir := filepath.Join(utils.ClientDataPath, version)
|
||||
if err := os.MkdirAll(versionDataDir, 0755); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error creating data directory: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Construct the expected filename for the specified version
|
||||
// Remove 'v' prefix if present for filename construction
|
||||
versionWithoutV := strings.TrimPrefix(version, "v")
|
||||
|
||||
// Download the release directly
|
||||
err := utils.DownloadRelease(utils.ReleaseTypeQClient, versionWithoutV)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error downloading version %s: %v\n", version, err)
|
||||
fmt.Fprintf(os.Stderr, "The specified version %s does not exist for %s-%s\n", version, osType, arch)
|
||||
// Clean up the created directories since installation failed
|
||||
os.RemoveAll(versionDir)
|
||||
os.RemoveAll(versionDataDir)
|
||||
return
|
||||
}
|
||||
|
||||
// Download signature files
|
||||
if err := utils.DownloadReleaseSignatures(utils.ReleaseTypeQClient, versionWithoutV); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: Failed to download signature files: %v\n", err)
|
||||
fmt.Fprintf(os.Stdout, "Continuing with installation...\n")
|
||||
}
|
||||
|
||||
// Successfully downloaded the specific version
|
||||
finishInstallation(version)
|
||||
}
|
||||
|
||||
// finishInstallation completes the update process
|
||||
func finishInstallation(version string) {
|
||||
|
||||
// Construct executable path
|
||||
execPath := filepath.Join(utils.ClientDataPath, version, "qclient-"+version+"-"+osType+"-"+arch)
|
||||
|
||||
// Make the binary executable
|
||||
if err := os.Chmod(execPath, 0755); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error making binary executable: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Create symlink to the new version
|
||||
symlinkPath := utils.DefaultQClientSymlinkPath
|
||||
|
||||
// Check if we need sudo privileges for creating symlink in system directory
|
||||
if strings.HasPrefix(symlinkPath, "/usr/") || strings.HasPrefix(symlinkPath, "/bin/") || strings.HasPrefix(symlinkPath, "/sbin/") {
|
||||
if err := utils.CheckAndRequestSudo(fmt.Sprintf("Creating symlink at %s requires root privileges", symlinkPath)); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: Failed to get sudo privileges: %v\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Create symlink
|
||||
if err := utils.CreateSymlink(execPath, symlinkPath); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error creating symlink: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stdout, "Successfully updated qclient to version %s\n", version)
|
||||
fmt.Fprintf(os.Stdout, "Executable: %s\n", execPath)
|
||||
fmt.Fprintf(os.Stdout, "Symlink: %s\n", symlinkPath)
|
||||
}
|
||||
89
client/cmd/version.go
Normal file
89
client/cmd/version.go
Normal file
@ -0,0 +1,89 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
"source.quilibrium.com/quilibrium/monorepo/node/config"
|
||||
)
|
||||
|
||||
// Version information - fallback if executable name doesn't contain version
|
||||
var (
|
||||
DefaultVersion = config.GetVersionString()
|
||||
)
|
||||
|
||||
// VersionInfo holds version and hash information
|
||||
type VersionInfo struct {
|
||||
Version string
|
||||
SHA256 string
|
||||
MD5 string
|
||||
}
|
||||
|
||||
// GetVersionInfo extracts version from executable and optionally calculates hashes
|
||||
func GetVersionInfo(calcChecksum bool) (VersionInfo, error) {
|
||||
executable, err := os.Executable()
|
||||
if err != nil {
|
||||
return VersionInfo{Version: DefaultVersion}, fmt.Errorf("error getting executable path: %v", err)
|
||||
}
|
||||
|
||||
// Extract version from executable name (e.g. qclient-2.0.3-linux-amd)
|
||||
baseName := filepath.Base(executable)
|
||||
versionPattern := regexp.MustCompile(`qclient-([0-9]+\.[0-9]+\.[0-9]+)`)
|
||||
matches := versionPattern.FindStringSubmatch(baseName)
|
||||
|
||||
version := DefaultVersion
|
||||
if len(matches) > 1 {
|
||||
version = matches[1]
|
||||
}
|
||||
|
||||
// If version not found or checksum requested, calculate hash
|
||||
if len(matches) <= 1 || calcChecksum {
|
||||
sha256Hash, md5Hash, err := utils.CalculateFileHashes(executable)
|
||||
if err != nil {
|
||||
return VersionInfo{Version: version}, fmt.Errorf("error calculating file hashes: %v", err)
|
||||
}
|
||||
|
||||
return VersionInfo{
|
||||
Version: version,
|
||||
SHA256: sha256Hash,
|
||||
MD5: md5Hash,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return VersionInfo{
|
||||
Version: version,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Display the qclient version",
|
||||
Long: `Display the qclient version and optionally calculate SHA256 and MD5 hashes of the executable.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
showChecksum, _ := cmd.Flags().GetBool("checksum")
|
||||
|
||||
info, err := GetVersionInfo(showChecksum)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("%s\n", info.Version)
|
||||
|
||||
if showChecksum {
|
||||
if info.SHA256 != "" && info.MD5 != "" {
|
||||
fmt.Printf("SHA256: %s\n", info.SHA256)
|
||||
fmt.Printf("MD5: %s\n", info.MD5)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
versionCmd.Flags().Bool("checksum", false, "Show SHA256 and MD5 checksums of the executable")
|
||||
rootCmd.AddCommand(versionCmd)
|
||||
}
|
||||
31
client/test/Dockerfile
Normal file
31
client/test/Dockerfile
Normal file
@ -0,0 +1,31 @@
|
||||
# Use build argument to specify the base image
|
||||
ARG DISTRO=ubuntu
|
||||
ARG VERSION=24.04
|
||||
|
||||
# Base stage with common setup
|
||||
FROM --platform=$BUILDPLATFORM ${DISTRO}:${VERSION} AS base
|
||||
|
||||
ARG TARGETARCH
|
||||
ARG TARGETOS
|
||||
|
||||
RUN echo "TARGETARCH: $TARGETARCH"
|
||||
RUN echo "TARGETOS: $TARGETOS"
|
||||
|
||||
# Install required packages
|
||||
RUN apt-get update && apt-get install -y \
|
||||
curl \
|
||||
sudo \
|
||||
bash-completion \
|
||||
lsb-release \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Create a non-root user for testing
|
||||
RUN useradd -m -s /bin/bash testuser && \
|
||||
echo "testuser ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
|
||||
|
||||
# Final test stage
|
||||
FROM base AS qclient-test
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
CMD ["/app/test_install.sh"]
|
||||
165
client/test/README.md
Normal file
165
client/test/README.md
Normal file
@ -0,0 +1,165 @@
|
||||
# Quil Test Runner Documentation
|
||||
|
||||
This document describes the usage and functionality of the Quil test runner script (`run_tests.sh`), which is designed to run tests across different Linux distributions using Docker containers.
|
||||
|
||||
## Overview
|
||||
|
||||
The test runner allows you to:
|
||||
- Run tests on multiple Linux distributions simultaneously
|
||||
- Test on a specific distribution and version
|
||||
- Customize container tags for test runs
|
||||
- Build the client binary using a standardized Docker build process
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Docker installed and running on your system
|
||||
- Bash shell
|
||||
- Access to the Quil client source code
|
||||
- Sufficient disk space for building the client (approximately 2GB recommended)
|
||||
|
||||
## Build Environment
|
||||
|
||||
The test runner uses a multi-stage build process based on `Dockerfile.qclient` which includes:
|
||||
|
||||
### Base Build Environment
|
||||
- Ubuntu 24.04 as the base image
|
||||
- Essential build tools (gcc, g++, make, etc.)
|
||||
- GMP 6.2 and MPFR libraries
|
||||
- Go 1.22.0 (amd64)
|
||||
- Rust toolchain (via rustup)
|
||||
- FLINT library (version 3.0)
|
||||
- uniffi-bindgen-go (v0.2.1+v0.25.0)
|
||||
|
||||
### Build Process
|
||||
1. Generates Rust bindings for:
|
||||
- VDF (Verifiable Delay Function)
|
||||
- BLS48581 (Boneh-Lynn-Shacham signature scheme)
|
||||
- VerEnc (Verifiable Encryption)
|
||||
2. Builds and installs:
|
||||
- qclient binary
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
To run tests on all supported distributions (Ubuntu 22.04, Ubuntu 24.04, and Debian 12):
|
||||
|
||||
```bash
|
||||
./run_tests.sh
|
||||
```
|
||||
|
||||
### Custom Test Configuration
|
||||
|
||||
To run tests on a specific distribution and version:
|
||||
|
||||
```bash
|
||||
./run_tests.sh -d DISTRO -v VERSION [-t TAG]
|
||||
```
|
||||
|
||||
#### Parameters:
|
||||
- `-d, --distro`: The Linux distribution to test (e.g., ubuntu, debian)
|
||||
- `-v, --version`: The version of the distribution (e.g., 22.04, 12)
|
||||
- `-t, --tag`: (Optional) Custom tag for the test container. If not provided, a tag will be automatically generated
|
||||
|
||||
#### Examples:
|
||||
|
||||
```bash
|
||||
# Test Ubuntu 22.04 with auto-generated tag
|
||||
./run_tests.sh -d ubuntu -v 22.04
|
||||
|
||||
# Test Debian 12 with custom tag
|
||||
./run_tests.sh -d debian -v 12 -t my-custom-test
|
||||
|
||||
# Show help message
|
||||
./run_tests.sh --help
|
||||
```
|
||||
|
||||
## Supported Distributions
|
||||
|
||||
By default, the script tests the following distributions:
|
||||
- Ubuntu 22.04
|
||||
- Ubuntu 24.04
|
||||
- Debian 12
|
||||
|
||||
## How It Works
|
||||
|
||||
1. The script first builds the client binary using `Dockerfile.qclient`:
|
||||
- Creates a build container with all required dependencies
|
||||
- Generates necessary Rust bindings
|
||||
- Builds the qclient binary
|
||||
- Extracts the binary to the test directory
|
||||
- Cleans up the build container
|
||||
|
||||
2. For each test run:
|
||||
- Creates a Docker container using the specified distribution and version
|
||||
- Copies the built client binary into the test container
|
||||
- Builds the test environment
|
||||
- Runs the tests
|
||||
- Cleans up the container after completion
|
||||
|
||||
## Error Handling
|
||||
|
||||
- The script uses `set -e` to exit on any error
|
||||
- If any test fails, the script will exit with status code 1
|
||||
- Docker containers are automatically removed after test completion using the `--rm` flag
|
||||
- Build errors in `Dockerfile.qclient` will be clearly displayed in the output
|
||||
|
||||
## Notes
|
||||
|
||||
- When running all tests simultaneously, the script uses background processes to parallelize the test runs
|
||||
- The script automatically generates container tags if not specified, using the format `distroversion` (e.g., `ubuntu2204`)
|
||||
- Make sure you have sufficient system resources when running multiple tests simultaneously
|
||||
- The build process requires significant disk space due to the multi-stage build and dependencies
|
||||
- The client binary is built specifically for amd64 architecture
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you encounter issues:
|
||||
|
||||
1. Ensure Docker is running:
|
||||
```bash
|
||||
systemctl status docker
|
||||
```
|
||||
|
||||
2. Check Docker logs for container-specific issues:
|
||||
```bash
|
||||
docker logs quil-test-[tag]
|
||||
```
|
||||
|
||||
3. Verify you have sufficient disk space and memory for running multiple containers
|
||||
|
||||
4. For build-related issues:
|
||||
- Check if all required dependencies are available in the target distribution
|
||||
- Verify the build environment has sufficient resources
|
||||
- Check the build logs for specific error messages
|
||||
- Ensure you're running on an amd64 system or using appropriate Docker platform settings
|
||||
|
||||
## Direct Docker Build
|
||||
|
||||
If you just want to build the qclient in a Docker container without running the tests (useful if you can't build for the target testing environment):
|
||||
|
||||
```bash
|
||||
# in the project root directory
|
||||
## Will take awhile to build flint on initial build
|
||||
sudo task build_qclient_amd64_linux
|
||||
sudo task build_qclient_arm64_linux
|
||||
# for mac, you will need to build on a mac
|
||||
```
|
||||
|
||||
## Run a test container
|
||||
|
||||
```
|
||||
sudo docker run -it \
|
||||
-v "/home/user/ceremonyclient/client/test/:/app" \
|
||||
-v "/home/user/ceremenoyclient/client/build/amd64_linux/qclient:/opt/quilibrium/bin/qclient" \
|
||||
quil-test /bin/bash
|
||||
|
||||
|
||||
This command builds the Docker image with the qclient binary according to the specifications in `Dockerfile.source`. The resulting image will be tagged as `qclient`.
|
||||
|
||||
## Contributing
|
||||
|
||||
When adding new distributions or versions:
|
||||
1. Update the default test configurations in the script
|
||||
2. Ensure the corresponding Dockerfile supports the new distribution/version
|
||||
3. Test the changes thoroughly before committing
|
||||
@ -0,0 +1,3 @@
|
||||
dataDir: /var/quilibrium/data/qclient
|
||||
symlinkPath: /usr/local/bin/qclient
|
||||
signatureCheck: true
|
||||
124
client/test/run_tests.sh
Executable file
124
client/test/run_tests.sh
Executable file
@ -0,0 +1,124 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
CLIENT_DIR="${CLIENT_DIR:-$( cd "$(dirname "$(realpath "$( dirname "${BASH_SOURCE[0]}" )")")" >/dev/null 2>&1 && pwd )}"
|
||||
|
||||
echo "CLIENT_DIR: $CLIENT_DIR"
|
||||
|
||||
# Help function
|
||||
show_help() {
|
||||
echo "Usage: $0 [OPTIONS]"
|
||||
echo "Run tests on specified Linux distributions"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " -d, --distro DISTRO Specify the distribution (e.g., ubuntu, debian)"
|
||||
echo " -v, --version VERSION Specify the version (e.g., 22.04, 12)"
|
||||
echo " -t, --tag TAG Specify a custom tag for the test container"
|
||||
echo " -h, --help Show this help message"
|
||||
echo " --no-cache Disable all Docker build cache"
|
||||
echo ""
|
||||
echo "If no arguments are provided, runs tests on all supported distributions"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Parse command line arguments
|
||||
DISTRO=""
|
||||
VERSION=""
|
||||
TAG=""
|
||||
NO_CACHE=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-d|--distro)
|
||||
DISTRO="$2"
|
||||
shift 2
|
||||
;;
|
||||
-v|--version)
|
||||
VERSION="$2"
|
||||
shift 2
|
||||
;;
|
||||
-t|--tag)
|
||||
TAG="$2"
|
||||
shift 2
|
||||
;;
|
||||
--no-cache)
|
||||
NO_CACHE="--no-cache"
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
show_help
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
show_help
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Function to run tests for a specific distribution
|
||||
run_distro_test() {
|
||||
local distro=$1
|
||||
local version=$2
|
||||
local tag=$3
|
||||
echo "Testing on $distro $version..."
|
||||
|
||||
# Build the base stage first (this can be cached)
|
||||
docker build \
|
||||
$NO_CACHE \
|
||||
--build-arg DISTRO=$distro \
|
||||
--build-arg VERSION=$version \
|
||||
-t quil-test-$tag-base \
|
||||
--target base \
|
||||
-f client/test/Dockerfile .
|
||||
|
||||
# Build the final test stage
|
||||
docker build \
|
||||
--build-arg DISTRO=$distro \
|
||||
--build-arg VERSION=$version \
|
||||
-t quil-test-$tag \
|
||||
--target qclient-test \
|
||||
-f client/test/Dockerfile .
|
||||
|
||||
# Ensure test files are executable
|
||||
chmod +x "$CLIENT_DIR/test/test_install.sh"
|
||||
chmod +x "$CLIENT_DIR/test/test_utils.sh"
|
||||
chmod +x "$CLIENT_DIR/build/amd64_linux/qclient"
|
||||
|
||||
# Set ownership to match testuser (uid:gid 1000:1000)
|
||||
chown 1000:1000 "$CLIENT_DIR/build/amd64_linux/qclient"
|
||||
|
||||
# Run the container with mounted test directory and binary
|
||||
docker run --rm \
|
||||
-v "$CLIENT_DIR/test:/app" \
|
||||
-v "$CLIENT_DIR/build/amd64_linux/qclient:/opt/quilibrium/bin/qclient" \
|
||||
quil-test-$tag
|
||||
}
|
||||
|
||||
# If custom distro/version/tag is provided, run single test
|
||||
if [ ! -z "$DISTRO" ] && [ ! -z "$VERSION" ]; then
|
||||
if [ -z "$TAG" ]; then
|
||||
TAG="${DISTRO}${VERSION//./}"
|
||||
fi
|
||||
echo "Running custom test configuration..."
|
||||
run_distro_test "$DISTRO" "$VERSION" "$TAG"
|
||||
else
|
||||
# Run tests on all distributions simultaneously
|
||||
echo "Running tests on all distributions simultaneously..."
|
||||
run_distro_test "ubuntu" "22.04" "ubuntu22" &
|
||||
UBUNTU22_PID=$!
|
||||
|
||||
run_distro_test "ubuntu" "24.04" "ubuntu24" &
|
||||
UBUNTU24_PID=$!
|
||||
|
||||
run_distro_test "debian" "12" "debian12" &
|
||||
DEBIAN12_PID=$!
|
||||
|
||||
# Wait for all tests to complete
|
||||
wait $UBUNTU22_PID $UBUNTU24_PID $DEBIAN12_PID
|
||||
|
||||
# Check exit status of each test
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "One or more tests failed!"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "All distribution tests completed!"
|
||||
127
client/test/test_install.sh
Executable file
127
client/test/test_install.sh
Executable file
@ -0,0 +1,127 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
|
||||
# Source the test utilities
|
||||
source "$(dirname "$0")/test_utils.sh"
|
||||
|
||||
# Get distribution information
|
||||
DISTRO=$(lsb_release -si 2>/dev/null || echo "Unknown")
|
||||
VERSION=$(lsb_release -sr 2>/dev/null || echo "Unknown")
|
||||
|
||||
echo "Starting Quilibrium node installation test on $DISTRO $VERSION..."
|
||||
|
||||
# Test: Link the qclient binary to ensure it's in the PATH
|
||||
echo "Linking qclient binary for testing..."
|
||||
if [ -f "/opt/quilibrium/bin/qclient" ]; then
|
||||
echo "qclient binary already exists at /opt/quilibrium/bin/qclient"
|
||||
else
|
||||
echo "qclient binary not found at /opt/quilibrium/bin/qclient"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test: Link the qclient binary to the system PATH
|
||||
echo "Testing qclient link command..."
|
||||
run_test_with_format "sudo /opt/quilibrium/bin/qclient link"
|
||||
|
||||
# Verify qclient is now in PATH
|
||||
echo "Verifying qclient is in PATH after link command..."
|
||||
run_test_with_format "which qclient" | grep -q "/usr/local/bin/qclient" && echo "SUCCESS: qclient found in PATH" || echo "FAILURE: qclient not found in PATH"
|
||||
|
||||
# Test qclient can be executed directly
|
||||
echo "Testing qclient can be executed directly..."
|
||||
run_test_with_format "qclient --help" | grep -q "Usage:" && echo "SUCCESS: qclient executable works" || echo "FAILURE: qclient executable not working properly"
|
||||
|
||||
|
||||
# Test: Ensure no config file exists initially
|
||||
echo "Testing no config file exists initially..."
|
||||
run_test_with_format "test ! -f /etc/quilibrium/config/qclient.yaml"
|
||||
|
||||
# Test: Create default config
|
||||
echo "Testing default config creation..."
|
||||
run_test_with_format "qclient config create-default --signature-check=false"
|
||||
|
||||
# Test: Verify config file was created
|
||||
echo "Verifying config file was created..."
|
||||
run_test_with_format "test -f /etc/quilibrium/config/qclient.yaml"
|
||||
|
||||
# Test: Excec arbitrary qclient command and verify signature check
|
||||
echo "Testing config print command..."
|
||||
run_test_with_format "qclient config print" | grep -v "Checking signature for"
|
||||
|
||||
# Test: Toggle signature check
|
||||
echo "Testing toggle-signature-check command..."
|
||||
run_test_with_format "qclient config toggle-signature-check --signature-check=false"
|
||||
run_test_with_format "qclient config print" | grep -v "Checking signature for"
|
||||
|
||||
|
||||
# Test: Ensure qclient is in the PATH
|
||||
echo "Testing qclient in PATH..."
|
||||
run_test_with_format "sudo /opt/quilibrium/bin/qclient link"
|
||||
run_test_with_format "which qclient"
|
||||
run_test_with_format "qclient version"
|
||||
|
||||
run_test_with_format "qclient config print"
|
||||
|
||||
|
||||
# Test 0: Install latest version
|
||||
# Check if download-signatures command exists in qclient help
|
||||
run_test_with_format "qclient help | grep -q 'download-signatures'"
|
||||
|
||||
# Test downloading signatures
|
||||
run_test_with_format "sudo qclient download-signatures"
|
||||
|
||||
# Test 1: Install latest version
|
||||
run_test_with_format "sudo qclient node install"
|
||||
|
||||
get_latest_version() {
|
||||
# Fetch the latest version from the releases API
|
||||
local latest_version=$(curl -s https://releases.quilibrium.com/release | head -n 1 | cut -d'-' -f2)
|
||||
echo "$latest_version"
|
||||
}
|
||||
|
||||
LATEST_VERSION=$(get_latest_version)
|
||||
|
||||
# Verify installation
|
||||
run_test_with_format "test -f /opt/quilibrium/$LATEST_VERSION/node-$LATEST_VERSION-linux-amd64"
|
||||
|
||||
# Verify latest version matches
|
||||
run_test_with_format "get_latest_version"
|
||||
|
||||
# Test 2: Install specific version
|
||||
run_test_with_format "qclient node install '2.0.6.2' --signature-check=false"
|
||||
|
||||
# Verify specific version installation
|
||||
run_test_with_format "test -f /opt/quilibrium/2.0.6.2/node-2.0.6.2-linux-amd64"
|
||||
|
||||
# Test 3: Verify service file creation
|
||||
run_test_with_format "test -f /etc/systemd/system/quilibrium-node.service"
|
||||
|
||||
# Verify service file content
|
||||
run_test_with_format "grep -q 'EnvironmentFile=/etc/default/quilibrium-node' /etc/systemd/system/quilibrium-node.service"
|
||||
|
||||
# Test 4: Verify environment file
|
||||
run_test_with_format "test -f /etc/default/quilibrium-node"
|
||||
|
||||
# Verify environment file permissions
|
||||
run_test_with_format "test '$(stat -c %a /etc/default/quilibrium-node)' = '640'"
|
||||
|
||||
# Test 5: Verify data directory
|
||||
run_test_with_format "test -d /var/lib/quilibrium"
|
||||
|
||||
# Verify data directory permissions
|
||||
run_test_with_format "test '$(stat -c %a /var/lib/quilibrium)' = '755'"
|
||||
|
||||
# Test 6: Verify config file
|
||||
run_test_with_format "test -f /var/lib/quilibrium/config/node.yaml"
|
||||
|
||||
# Verify config file permissions
|
||||
run_test_with_format "test '$(stat -c %a /var/lib/quilibrium/config/node.yaml)' = '644'"
|
||||
|
||||
# Test 7: Verify binary symlink
|
||||
run_test_with_format "test -L /usr/local/bin/quilibrium-node"
|
||||
|
||||
# Test 8: Verify binary execution
|
||||
run_test_with_format "quilibrium-node --version"
|
||||
|
||||
echo "All tests passed successfully on $DISTRO $VERSION!"
|
||||
49
client/test/test_utils.sh
Executable file
49
client/test/test_utils.sh
Executable file
@ -0,0 +1,49 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ANSI color codes
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Function to run a test command and format its output
|
||||
run_test_with_format() {
|
||||
local test_cmd="$1"
|
||||
local indent=" " # 4 spaces for indentation
|
||||
|
||||
echo -e "${BLUE}Running test: $test_cmd${NC}"
|
||||
echo "----------------------------------------"
|
||||
|
||||
# Run the command and capture stdout and stderr separately
|
||||
local stdout
|
||||
local stderr
|
||||
stdout=$(eval "$test_cmd" 2> >(tee /dev/stderr))
|
||||
exit_code=$?
|
||||
stderr=$(cat)
|
||||
|
||||
# Format and print the stdout with indentation
|
||||
if [ -n "$stdout" ]; then
|
||||
echo "$stdout" | while IFS= read -r line; do
|
||||
echo -e "${GREEN}$indent$line${NC}"
|
||||
done
|
||||
fi
|
||||
|
||||
# Check for stderr output and exit code
|
||||
if [ -n "$stderr" ] || [ $exit_code -ne 0 ]; then
|
||||
echo -e "${RED}${indent}Test failed:${NC}"
|
||||
if [ -n "$stderr" ]; then
|
||||
echo "$stderr" | while IFS= read -r line; do
|
||||
echo -e "${RED}${indent}$line${NC}"
|
||||
done
|
||||
fi
|
||||
if [ $exit_code -ne 0 ]; then
|
||||
echo -e "${RED}${indent}Exit code: $exit_code${NC}"
|
||||
fi
|
||||
echo "----------------------------------------"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}${indent}Test completed successfully${NC}"
|
||||
echo "----------------------------------------"
|
||||
return 0
|
||||
}
|
||||
91
client/utils/config.go
Normal file
91
client/utils/config.go
Normal file
@ -0,0 +1,91 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
var ClientConfigDir = filepath.Join(os.Getenv("HOME"), ".quilibrium")
|
||||
var ClientConfigFile = string(ReleaseTypeQClient) + "-config.yaml"
|
||||
var ClientConfigPath = filepath.Join(ClientConfigDir, ClientConfigFile)
|
||||
|
||||
func CreateDefaultConfig() {
|
||||
configPath := GetConfigPath()
|
||||
|
||||
fmt.Printf("Creating default config: %s\n", configPath)
|
||||
SaveClientConfig(&ClientConfig{
|
||||
DataDir: ClientDataPath,
|
||||
SymlinkPath: DefaultQClientSymlinkPath,
|
||||
SignatureCheck: true,
|
||||
})
|
||||
|
||||
sudoUser, err := GetCurrentSudoUser()
|
||||
if err != nil {
|
||||
fmt.Println("Error getting current sudo user")
|
||||
os.Exit(1)
|
||||
}
|
||||
ChownPath(GetUserQuilibriumDir(), sudoUser, true)
|
||||
}
|
||||
|
||||
// LoadClientConfig loads the client configuration from the config file
|
||||
func LoadClientConfig() (*ClientConfig, error) {
|
||||
configPath := GetConfigPath()
|
||||
|
||||
// Create default config if it doesn't exist
|
||||
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
||||
config := &ClientConfig{
|
||||
DataDir: ClientDataPath,
|
||||
SymlinkPath: filepath.Join(ClientDataPath, "current"),
|
||||
SignatureCheck: true,
|
||||
}
|
||||
if err := SaveClientConfig(config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// Read existing config
|
||||
data, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config := &ClientConfig{}
|
||||
if err := yaml.Unmarshal(data, config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// SaveClientConfig saves the client configuration to the config file
|
||||
func SaveClientConfig(config *ClientConfig) error {
|
||||
data, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Ensure the config directory exists
|
||||
if err := os.MkdirAll(GetConfigDir(), 0755); err != nil {
|
||||
return fmt.Errorf("failed to create config directory: %w", err)
|
||||
}
|
||||
|
||||
return os.WriteFile(GetConfigPath(), data, 0644)
|
||||
}
|
||||
|
||||
// GetConfigPath returns the path to the client configuration file
|
||||
func GetConfigPath() string {
|
||||
return filepath.Join(GetConfigDir(), ClientConfigFile)
|
||||
}
|
||||
|
||||
func GetConfigDir() string {
|
||||
return filepath.Join(GetUserQuilibriumDir())
|
||||
}
|
||||
|
||||
// IsClientConfigured checks if the client is configured
|
||||
func IsClientConfigured() bool {
|
||||
return FileExists(ClientConfigPath)
|
||||
}
|
||||
208
client/utils/download.go
Normal file
208
client/utils/download.go
Normal file
@ -0,0 +1,208 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var BaseReleaseURL = "https://releases.quilibrium.com"
|
||||
|
||||
// DownloadRelease downloads a specific release file
|
||||
func DownloadRelease(releaseType ReleaseType, version string) error {
|
||||
fileName := fmt.Sprintf("%s-%s-%s-%s", releaseType, version, osType, arch)
|
||||
fmt.Printf("Getting binary %s...\n", fileName)
|
||||
fmt.Println("Will save to", filepath.Join(BinaryPath, string(releaseType), version))
|
||||
url := fmt.Sprintf("%s/%s", BaseReleaseURL, fileName)
|
||||
|
||||
if !DoesRemoteFileExist(url) {
|
||||
fmt.Printf("the release file %s does not exist on the release server\n", fileName)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return DownloadReleaseFile(releaseType, fileName, version, true)
|
||||
}
|
||||
|
||||
// GetLatestVersion fetches the latest version from the releases API
|
||||
func GetLatestVersion(releaseType ReleaseType) (string, error) {
|
||||
// Determine the appropriate URL based on the release type
|
||||
releaseURL := fmt.Sprintf("%s/release", BaseReleaseURL)
|
||||
if releaseType == ReleaseTypeQClient {
|
||||
releaseURL = fmt.Sprintf("%s/qclient-release", BaseReleaseURL)
|
||||
}
|
||||
|
||||
resp, err := http.Get(releaseURL)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to fetch latest version: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
scanner := bufio.NewScanner(resp.Body)
|
||||
if !scanner.Scan() {
|
||||
return "", fmt.Errorf("no response data found")
|
||||
}
|
||||
|
||||
// Get the first line which contains the filename
|
||||
filename := scanner.Text()
|
||||
|
||||
// Split the filename by "-" and get the version part
|
||||
parts := strings.Split(filename, "-")
|
||||
if len(parts) < 2 {
|
||||
return "", fmt.Errorf("invalid filename format: %s", filename)
|
||||
}
|
||||
|
||||
// The version is the second part (index 1)
|
||||
version := parts[1]
|
||||
return version, nil
|
||||
}
|
||||
|
||||
// DownloadReleaseFile downloads a release file from the Quilibrium releases server
|
||||
func DownloadReleaseFile(releaseType ReleaseType, fileName string, version string, showError bool) error {
|
||||
url := fmt.Sprintf("%s/%s", BaseReleaseURL, fileName)
|
||||
destDir := filepath.Join(BinaryPath, string(releaseType), version)
|
||||
os.MkdirAll(destDir, 0755)
|
||||
destPath := filepath.Join(destDir, fileName)
|
||||
|
||||
fmt.Printf("Downloading %s...", fileName)
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
if showError {
|
||||
return fmt.Errorf("failed to download file: %s", resp.Status)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
out, err := os.Create(destPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
_, err = io.Copy(out, resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Print(" done\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
// DownloadReleaseSignatures downloads signature files for a release
|
||||
func DownloadReleaseSignatures(releaseType ReleaseType, version string) error {
|
||||
var files []string
|
||||
baseName := fmt.Sprintf("%s-%s-%s-%s", releaseType, version, osType, arch)
|
||||
fmt.Printf("Searching for signatures for %s from %s\n", baseName, BaseReleaseURL)
|
||||
fmt.Println("Will save to", filepath.Join(BinaryPath, string(releaseType), version))
|
||||
|
||||
// Add digest file URL
|
||||
files = append(files, baseName+".dgst")
|
||||
|
||||
// Add signature file URLs
|
||||
signerNums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}
|
||||
for _, num := range signerNums {
|
||||
// Check if the remote signature file exists
|
||||
sigFile := fmt.Sprintf("%s.dgst.sig.%d", baseName, num)
|
||||
remoteURL := fmt.Sprintf("%s/%s", BaseReleaseURL, sigFile)
|
||||
|
||||
if !DoesRemoteFileExist(remoteURL) {
|
||||
continue
|
||||
}
|
||||
fmt.Printf("Found signature file %s\n", sigFile)
|
||||
files = append(files, fmt.Sprintf("%s.dgst.sig.%d", baseName, num))
|
||||
}
|
||||
|
||||
if len(files) == 0 {
|
||||
fmt.Printf("No signature files found for %s\n", baseName)
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
err := DownloadReleaseFile(releaseType, file, version, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetLatestReleaseFiles fetches the list of available release files
|
||||
func GetLatestReleaseFiles(releaseType ReleaseType) ([]string, error) {
|
||||
releaseURL := fmt.Sprintf("%s/release", BaseReleaseURL)
|
||||
if releaseType == ReleaseTypeQClient {
|
||||
releaseURL = fmt.Sprintf("%s/qclient-release", BaseReleaseURL)
|
||||
}
|
||||
resp, err := http.Get(releaseURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("failed to fetch releases: %s", resp.Status)
|
||||
}
|
||||
|
||||
// Read the response body and parse it
|
||||
var releases []string
|
||||
|
||||
scanner := bufio.NewScanner(resp.Body)
|
||||
for scanner.Scan() {
|
||||
releases = append(releases, scanner.Text())
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, fmt.Errorf("error reading response: %w", err)
|
||||
}
|
||||
|
||||
return releases, nil
|
||||
}
|
||||
|
||||
// FilterReleasesByOSArch filters releases by OS and architecture
|
||||
func FilterReleasesByOSArch(releases []string, osType, arch string) []string {
|
||||
var filtered []string
|
||||
for _, release := range releases {
|
||||
if strings.Contains(release, osType) && strings.Contains(release, arch) {
|
||||
filtered = append(filtered, release)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
// ExtractVersionFromFileName extracts the version from a release filename
|
||||
func ExtractVersionFromFileName(releaseType ReleaseType, fileName, osType, arch string) string {
|
||||
version := strings.TrimPrefix(fileName, string(releaseType)+"-")
|
||||
version = strings.TrimSuffix(version, "-"+osType+"-"+arch)
|
||||
return version
|
||||
}
|
||||
|
||||
// DownloadAllReleaseFiles downloads all release files
|
||||
func DownloadAllReleaseFiles(releaseType ReleaseType, fileNames []string, installDir string) bool {
|
||||
for _, fileName := range fileNames {
|
||||
filePath := filepath.Join(installDir, fileName)
|
||||
if err := DownloadReleaseFile(releaseType, fileName, filePath, true); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error downloading release file %s: %v\n", fileName, err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func DoesRemoteFileExist(url string) bool {
|
||||
resp, err := http.Head(url)
|
||||
if err != nil || resp.StatusCode != http.StatusOK {
|
||||
return false
|
||||
}
|
||||
if resp != nil && resp.Body != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
return true
|
||||
}
|
||||
237
client/utils/fileUtils.go
Normal file
237
client/utils/fileUtils.go
Normal file
@ -0,0 +1,237 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var ClientInstallPath = filepath.Join("/opt/quilibrium/", string(ReleaseTypeQClient))
|
||||
var RootQuilibriumPath = filepath.Join("/var/quilibrium/")
|
||||
var BinaryPath = filepath.Join(RootQuilibriumPath, "bin")
|
||||
var ClientDataPath = filepath.Join(BinaryPath, string(ReleaseTypeQClient))
|
||||
var NodeDataPath = filepath.Join(BinaryPath, string(ReleaseTypeNode))
|
||||
var DefaultSymlinkDir = "/usr/local/bin"
|
||||
var DefaultNodeSymlinkPath = filepath.Join(DefaultSymlinkDir, string(ReleaseTypeNode))
|
||||
var DefaultQClientSymlinkPath = filepath.Join(DefaultSymlinkDir, string(ReleaseTypeQClient))
|
||||
var osType = runtime.GOOS
|
||||
var arch = runtime.GOARCH
|
||||
|
||||
// CalculateFileHashes calculates SHA256 and MD5 hashes for a file
|
||||
func CalculateFileHashes(filePath string) (string, string, error) {
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("error opening file: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Calculate SHA256
|
||||
sha256Hash := sha256.New()
|
||||
if _, err := io.Copy(sha256Hash, file); err != nil {
|
||||
return "", "", fmt.Errorf("error calculating SHA256: %w", err)
|
||||
}
|
||||
|
||||
// Reset file position to beginning for MD5 calculation
|
||||
if _, err := file.Seek(0, 0); err != nil {
|
||||
return "", "", fmt.Errorf("error seeking file: %w", err)
|
||||
}
|
||||
|
||||
// Calculate MD5
|
||||
md5Hash := md5.New()
|
||||
if _, err := io.Copy(md5Hash, file); err != nil {
|
||||
return "", "", fmt.Errorf("error calculating MD5: %w", err)
|
||||
}
|
||||
|
||||
return hex.EncodeToString(sha256Hash.Sum(nil)), hex.EncodeToString(md5Hash.Sum(nil)), nil
|
||||
}
|
||||
|
||||
// CreateSymlink creates a symlink, handling the case where it already exists
|
||||
func CreateSymlink(execPath, targetPath string) error {
|
||||
// Check if the symlink already exists
|
||||
if _, err := os.Lstat(targetPath); err == nil {
|
||||
// Symlink exists, ask if user wants to overwrite
|
||||
if !ConfirmSymlinkOverwrite(targetPath) {
|
||||
fmt.Println("Operation cancelled.")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove existing symlink
|
||||
if err := os.Remove(targetPath); err != nil {
|
||||
return fmt.Errorf("failed to remove existing symlink: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("Creating symlink %s -> %s\n", targetPath, execPath)
|
||||
|
||||
// Create the symlink
|
||||
if err := os.Symlink(execPath, targetPath); err != nil {
|
||||
return fmt.Errorf("failed to create symlink: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateAndCreateDir validates a directory path and creates it if it doesn't exist
|
||||
func ValidateAndCreateDir(path string, user *user.User) error {
|
||||
// Check if the directory exists
|
||||
info, err := os.Stat(path)
|
||||
if err == nil {
|
||||
// Path exists, check if it's a directory
|
||||
if !info.IsDir() {
|
||||
return fmt.Errorf("%s exists but is not a directory", path)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Directory doesn't exist, try to create it
|
||||
if os.IsNotExist(err) {
|
||||
fmt.Printf("Creating directory %s\n", path)
|
||||
if err := os.MkdirAll(path, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create directory %s: %v", path, err)
|
||||
}
|
||||
if user != nil {
|
||||
ChownPath(path, user, false)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Some other error occurred
|
||||
return fmt.Errorf("error checking directory %s: %v", path, err)
|
||||
}
|
||||
|
||||
// IsWritable checks if a directory is writable
|
||||
func IsWritable(dir string) bool {
|
||||
// Check if directory exists
|
||||
info, err := os.Stat(dir)
|
||||
if err != nil || !info.IsDir() {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if directory is writable by creating a temporary file
|
||||
tempFile := filepath.Join(dir, ".quilibrium_write_test")
|
||||
file, err := os.Create(tempFile)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
file.Close()
|
||||
os.Remove(tempFile)
|
||||
return true
|
||||
}
|
||||
|
||||
// CanCreateAndWrite checks if we can create and write to a directory
|
||||
func CanCreateAndWrite(dir string) bool {
|
||||
// Try to create the directory
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if we can write to it
|
||||
return IsWritable(dir)
|
||||
}
|
||||
|
||||
// FileExists checks if a file exists
|
||||
func FileExists(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return !os.IsNotExist(err)
|
||||
}
|
||||
|
||||
func IsSudo() bool {
|
||||
user, err := user.Current()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return user.Username == "root"
|
||||
}
|
||||
|
||||
// ChownPath changes the owner of a file or directory to the specified user
|
||||
func ChownPath(path string, user *user.User, isRecursive bool) error {
|
||||
// Change ownership of the path
|
||||
if isRecursive {
|
||||
fmt.Printf("Changing ownership of %s (recursive) to %s\n", path, user.Username)
|
||||
if err := exec.Command("chown", "-R", user.Uid+":"+user.Gid, path).Run(); err != nil {
|
||||
return fmt.Errorf("failed to change ownership of %s to %s (requires sudo): %v", path, user.Uid, err)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("Changing ownership of %s to %s\n", path, user.Username)
|
||||
if err := exec.Command("chown", user.Uid+":"+user.Gid, path).Run(); err != nil {
|
||||
return fmt.Errorf("failed to change ownership of %s to %s (requires sudo): %v", path, user.Uid, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ChmodPath(path string, mode os.FileMode, description string) error {
|
||||
fmt.Printf("Changing path: %s to %s (%s)\n", path, mode, description)
|
||||
return os.Chmod(path, mode)
|
||||
}
|
||||
|
||||
func WriteFile(path string, content string) error {
|
||||
return os.WriteFile(path, []byte(content), 0644)
|
||||
}
|
||||
|
||||
// WriteFileAuto writes content to a file, automatically using sudo only if necessary
|
||||
func WriteFileAuto(path string, content string) error {
|
||||
// First check if file exists and is writable
|
||||
if FileExists(path) {
|
||||
// Try to open the file for writing to check permissions
|
||||
file, err := os.OpenFile(path, os.O_WRONLY, 0)
|
||||
if err == nil {
|
||||
// File is writable, close it and write normally
|
||||
file.Close()
|
||||
fmt.Printf("Writing to file %s using normal permissions\n", path)
|
||||
return os.WriteFile(path, []byte(content), 0644)
|
||||
}
|
||||
} else {
|
||||
// Check if parent directory is writable
|
||||
dir := filepath.Dir(path)
|
||||
if IsWritable(dir) {
|
||||
fmt.Printf("Writing to file %s using normal permissions\n", path)
|
||||
return os.WriteFile(path, []byte(content), 0644)
|
||||
}
|
||||
}
|
||||
|
||||
// If we reach here, sudo is needed
|
||||
fmt.Printf("Writing to file %s using sudo\n", path)
|
||||
cmd := exec.Command("sudo", "tee", path)
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get stdin pipe: %w", err)
|
||||
}
|
||||
|
||||
// Start the command
|
||||
if err := cmd.Start(); err != nil {
|
||||
return fmt.Errorf("failed to start sudo command: %w", err)
|
||||
}
|
||||
|
||||
// Write content to stdin
|
||||
if _, err := io.WriteString(stdin, content); err != nil {
|
||||
return fmt.Errorf("failed to write to stdin: %w", err)
|
||||
}
|
||||
stdin.Close()
|
||||
|
||||
// Wait for the command to finish
|
||||
if err := cmd.Wait(); err != nil {
|
||||
return fmt.Errorf("sudo tee command failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CopyFile copies a file from src to dst
|
||||
func CopyFile(src, dst string) error {
|
||||
fmt.Printf("Copying file from %s to %s\n", src, dst)
|
||||
sourceData, err := os.ReadFile(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(dst, sourceData, 0600)
|
||||
}
|
||||
64
client/utils/node.go
Normal file
64
client/utils/node.go
Normal file
@ -0,0 +1,64 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/crypto"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
"source.quilibrium.com/quilibrium/monorepo/node/config"
|
||||
)
|
||||
|
||||
func GetPeerIDFromConfig(cfg *config.Config) peer.ID {
|
||||
peerPrivKey, err := hex.DecodeString(cfg.P2P.PeerPrivKey)
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "error unmarshaling peerkey"))
|
||||
}
|
||||
|
||||
privKey, err := crypto.UnmarshalEd448PrivateKey(peerPrivKey)
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "error unmarshaling peerkey"))
|
||||
}
|
||||
|
||||
pub := privKey.GetPublic()
|
||||
id, err := peer.IDFromPublicKey(pub)
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "error getting peer id"))
|
||||
}
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
func GetPrivKeyFromConfig(cfg *config.Config) (crypto.PrivKey, error) {
|
||||
peerPrivKey, err := hex.DecodeString(cfg.P2P.PeerPrivKey)
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "error unmarshaling peerkey"))
|
||||
}
|
||||
|
||||
privKey, err := crypto.UnmarshalEd448PrivateKey(peerPrivKey)
|
||||
return privKey, err
|
||||
}
|
||||
|
||||
func IsExistingNodeVersion(version string) bool {
|
||||
return FileExists(filepath.Join(NodeDataPath, version))
|
||||
}
|
||||
|
||||
func CheckForSystemd() bool {
|
||||
// Check if systemctl command exists
|
||||
_, err := exec.LookPath("systemctl")
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func LoadNodeConfig(configDirectory string) (*config.Config, error) {
|
||||
NodeConfig, err := config.LoadConfig(configDirectory, "", false)
|
||||
if err != nil {
|
||||
fmt.Printf("invalid config directory: %s\n", configDirectory)
|
||||
os.Exit(1)
|
||||
}
|
||||
return NodeConfig, nil
|
||||
}
|
||||
66
client/utils/system.go
Normal file
66
client/utils/system.go
Normal file
@ -0,0 +1,66 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetSystemInfo determines and validates the OS and architecture
|
||||
func GetSystemInfo() (string, string, error) {
|
||||
osType := runtime.GOOS
|
||||
arch := runtime.GOARCH
|
||||
|
||||
// Check if OS type is supported
|
||||
if osType != "darwin" && osType != "linux" {
|
||||
return "", "", fmt.Errorf("unsupported operating system: %s", osType)
|
||||
}
|
||||
|
||||
// Map Go architecture names to Quilibrium architecture names
|
||||
if arch == "amd64" {
|
||||
arch = "amd64"
|
||||
} else if arch == "arm64" {
|
||||
arch = "arm64"
|
||||
} else {
|
||||
return "", "", fmt.Errorf("unsupported architecture: %s", arch)
|
||||
}
|
||||
|
||||
return osType, arch, nil
|
||||
}
|
||||
|
||||
func GetCurrentSudoUser() (*user.User, error) {
|
||||
if os.Geteuid() != 0 {
|
||||
return user.Current()
|
||||
}
|
||||
|
||||
cmd := exec.Command("sh", "-c", "env | grep SUDO_USER | cut -d= -f2 | cut -d\\n -f1")
|
||||
var out bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
cmd.Stderr = &stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get current sudo user: %v", err)
|
||||
}
|
||||
|
||||
userLookup, err := user.Lookup(strings.TrimSpace(out.String()))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get current sudo user: %v", err)
|
||||
}
|
||||
return userLookup, nil
|
||||
}
|
||||
|
||||
func GetUserQuilibriumDir() string {
|
||||
sudoUser, err := GetCurrentSudoUser()
|
||||
if err != nil {
|
||||
fmt.Println("Error getting current sudo user")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return filepath.Join(sudoUser.HomeDir, ".quilibrium")
|
||||
}
|
||||
55
client/utils/types.go
Normal file
55
client/utils/types.go
Normal file
@ -0,0 +1,55 @@
|
||||
package utils
|
||||
|
||||
type ClientConfig struct {
|
||||
DataDir string `yaml:"dataDir"`
|
||||
SymlinkPath string `yaml:"symlinkPath"`
|
||||
SignatureCheck bool `yaml:"signatureCheck"`
|
||||
}
|
||||
|
||||
type NodeConfig struct {
|
||||
ClientConfig
|
||||
RewardsAddress string `yaml:"rewardsAddress"`
|
||||
AutoUpdateInterval string `yaml:"autoUpdateInterval"`
|
||||
}
|
||||
|
||||
const (
|
||||
DefaultAutoUpdateInterval = "*/10 * * * *"
|
||||
)
|
||||
|
||||
type ReleaseType string
|
||||
|
||||
const (
|
||||
ReleaseTypeQClient ReleaseType = "qclient"
|
||||
ReleaseTypeNode ReleaseType = "node"
|
||||
)
|
||||
|
||||
type BridgedPeerJson struct {
|
||||
Amount string `json:"amount"`
|
||||
Identifier string `json:"identifier"`
|
||||
Variant string `json:"variant"`
|
||||
}
|
||||
|
||||
type FirstRetroJson struct {
|
||||
PeerId string `json:"peerId"`
|
||||
Reward string `json:"reward"`
|
||||
}
|
||||
|
||||
type SecondRetroJson struct {
|
||||
PeerId string `json:"peerId"`
|
||||
Reward string `json:"reward"`
|
||||
JanPresence bool `json:"janPresence"`
|
||||
FebPresence bool `json:"febPresence"`
|
||||
MarPresence bool `json:"marPresence"`
|
||||
AprPresence bool `json:"aprPresence"`
|
||||
MayPresence bool `json:"mayPresence"`
|
||||
}
|
||||
|
||||
type ThirdRetroJson struct {
|
||||
PeerId string `json:"peerId"`
|
||||
Reward string `json:"reward"`
|
||||
}
|
||||
|
||||
type FourthRetroJson struct {
|
||||
PeerId string `json:"peerId"`
|
||||
Reward string `json:"reward"`
|
||||
}
|
||||
37
client/utils/userInputUtils.go
Normal file
37
client/utils/userInputUtils.go
Normal file
@ -0,0 +1,37 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ConfirmSymlinkOverwrite asks the user to confirm overwriting an existing symlink
|
||||
func ConfirmSymlinkOverwrite(path string) bool {
|
||||
fmt.Printf("Symlink already exists at %s. Overwrite? [y/N]: ", path)
|
||||
var response string
|
||||
fmt.Scanln(&response)
|
||||
return strings.ToLower(response) == "y"
|
||||
}
|
||||
|
||||
// CheckAndRequestSudo checks if we have sudo privileges and requests them if needed
|
||||
func CheckAndRequestSudo(reason string) error {
|
||||
// Check if we're already root
|
||||
if os.Geteuid() == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if sudo is available
|
||||
if _, err := exec.LookPath("sudo"); err != nil {
|
||||
return fmt.Errorf("sudo is not available: %w", err)
|
||||
}
|
||||
|
||||
// Request sudo privileges
|
||||
cmd := exec.Command("sudo", "-v")
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to get sudo privileges: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -9,7 +9,6 @@ name = "channel"
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.22.1"
|
||||
hex = "0.4.3"
|
||||
serde_json = "1.0.117"
|
||||
ed448-goldilocks-plus = "0.11.2"
|
||||
rand = "0.8.5"
|
||||
|
||||
@ -23,11 +23,11 @@ use super::super::gmp::mpz::{mp_bitcnt_t, mp_limb_t};
|
||||
use libc::{c_int, c_long, c_ulong, c_void, size_t};
|
||||
// pub use c_ulong;
|
||||
use std::{mem, usize};
|
||||
// We use the unsafe versions to avoid unecessary allocations.
|
||||
// We use the unsafe versions to avoid unnecessary allocations.
|
||||
extern "C" {
|
||||
fn adapted_nudupl(a: *mut Mpz, b: *mut Mpz, c: *mut Mpz, times: c_ulong);
|
||||
}
|
||||
// We use the unsafe versions to avoid unecessary allocations.
|
||||
// We use the unsafe versions to avoid unnecessary allocations.
|
||||
#[link(name = "gmp")]
|
||||
extern "C" {
|
||||
fn __gmpz_gcdext(gcd: *mut Mpz, s: *mut Mpz, t: *mut Mpz, a: *const Mpz, b: *const Mpz);
|
||||
|
||||
@ -8,15 +8,11 @@ crate-type = ["lib", "staticlib"]
|
||||
name = "rpm"
|
||||
|
||||
[dependencies]
|
||||
hex = "0.4.3"
|
||||
serde_json = "1.0.117"
|
||||
uniffi = { version= "0.25", features = ["cli"]}
|
||||
curve25519-dalek = "4.1.3"
|
||||
rand = "0.8.5"
|
||||
num = "0.4.3"
|
||||
lazy_static = "1.5.0"
|
||||
subtle = "2.6.1"
|
||||
rayon = "1.10"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.4", features = ["html_reports"] }
|
||||
|
||||
@ -12,13 +12,13 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
#![deny(warnings)]
|
||||
//! # Rust implementations of class groups and verifyable delay functions
|
||||
//! # Rust implementations of class groups and verifiable delay functions
|
||||
//!
|
||||
//! This repo includes three crates
|
||||
//!
|
||||
//! * `classgroup`, which includes a class group implementation, as well as a
|
||||
//! trait for class groups.
|
||||
//! * `vdf`, which includes a Verifyable Delay Function (VDF) trait, as well as
|
||||
//! * `vdf`, which includes a Verifiable Delay Function (VDF) trait, as well as
|
||||
//! an implementation of that trait.
|
||||
//! * `vdf-cli`, which includes a command-line interface to the `vdf` crate. It
|
||||
//! also includes additional commands, which are deprecated and will later be
|
||||
@ -155,7 +155,7 @@ pub trait VDFParams: Clone + Eq {
|
||||
/// consumers of this trait **MUST NOT** expect this.
|
||||
///
|
||||
/// Instances of this trait are *not* expected to be `Sync`. This allows them
|
||||
/// to reuse allocations (such as scratch memory) accross invocations without
|
||||
/// to reuse allocations (such as scratch memory) across invocations without
|
||||
/// the need for locking. However, they **MUST** be `Send` and `Clone`, so that
|
||||
/// consumers can duplicate them and send them across threads.
|
||||
pub trait VDF: Send + Debug {
|
||||
@ -170,7 +170,7 @@ pub trait VDF: Send + Debug {
|
||||
/// The challenge is an opaque byte string of arbitrary length.
|
||||
/// Implementors **MUST NOT** make any assumptions about its contents,
|
||||
/// and **MUST** produce distinct outputs for distinct challenges
|
||||
/// (except with negiligible probability).
|
||||
/// (except with negligible probability).
|
||||
///
|
||||
/// This can be most easily implemented by using the challenge as part of
|
||||
/// the input of a cryptographic hash function. The VDFs provided in this
|
||||
@ -209,7 +209,7 @@ pub trait VDF: Send + Debug {
|
||||
///
|
||||
/// # Rationale
|
||||
///
|
||||
/// It would be more ideomatic Rust to use the type system to enforce that a
|
||||
/// It would be more idiomatic Rust to use the type system to enforce that a
|
||||
/// difficulty has been validated before use. However, I (Demi) have not
|
||||
/// yet figured out an object-safe way to do so.
|
||||
fn check_difficulty(&self, difficulty: u64) -> Result<(), InvalidIterations>;
|
||||
|
||||
@ -454,7 +454,7 @@ mod test {
|
||||
assert_eq!(calculate_final_t(Iterations(100), 8), 100);
|
||||
}
|
||||
#[test]
|
||||
fn check_assuptions_about_stdlib() {
|
||||
fn check_assumptions_about_stdlib() {
|
||||
assert_eq!(62 - u64::leading_zeros(1024u64), 9);
|
||||
let mut q: Vec<_> = (1..4).step_by(2).collect();
|
||||
assert_eq!(q[..], [1, 3]);
|
||||
|
||||
@ -91,7 +91,7 @@ pub fn approximate_parameters(t: f64) -> (usize, u8, u64) {
|
||||
|
||||
fn u64_to_bytes(q: u64) -> [u8; 8] {
|
||||
if false {
|
||||
// This use of `std::mem::transumte` is correct, but still not justified.
|
||||
// This use of `std::mem::transmute` is correct, but still not justified.
|
||||
unsafe { std::mem::transmute(q.to_be()) }
|
||||
} else {
|
||||
[
|
||||
|
||||
8
verenc/generated/verenc/verenc.c
Normal file
8
verenc/generated/verenc/verenc.c
Normal file
@ -0,0 +1,8 @@
|
||||
#include <verenc.h>
|
||||
|
||||
// This file exists beacause of
|
||||
// https://github.com/golang/go/issues/11263
|
||||
|
||||
void cgo_rust_task_callback_bridge_verenc(RustTaskCallback cb, const void * taskData, int8_t status) {
|
||||
cb(taskData, status);
|
||||
}
|
||||
1055
verenc/generated/verenc/verenc.go
Normal file
1055
verenc/generated/verenc/verenc.go
Normal file
File diff suppressed because it is too large
Load Diff
439
verenc/generated/verenc/verenc.h
Normal file
439
verenc/generated/verenc/verenc.h
Normal file
@ -0,0 +1,439 @@
|
||||
|
||||
|
||||
// This file was autogenerated by some hot garbage in the `uniffi` crate.
|
||||
// Trust me, you don't want to mess with it!
|
||||
|
||||
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// The following structs are used to implement the lowest level
|
||||
// of the FFI, and thus useful to multiple uniffied crates.
|
||||
// We ensure they are declared exactly once, with a header guard, UNIFFI_SHARED_H.
|
||||
#ifdef UNIFFI_SHARED_H
|
||||
// We also try to prevent mixing versions of shared uniffi header structs.
|
||||
// If you add anything to the #else block, you must increment the version suffix in UNIFFI_SHARED_HEADER_V6
|
||||
#ifndef UNIFFI_SHARED_HEADER_V6
|
||||
#error Combining helper code from multiple versions of uniffi is not supported
|
||||
#endif // ndef UNIFFI_SHARED_HEADER_V6
|
||||
#else
|
||||
#define UNIFFI_SHARED_H
|
||||
#define UNIFFI_SHARED_HEADER_V6
|
||||
// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️
|
||||
// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V6 in this file. ⚠️
|
||||
|
||||
typedef struct RustBuffer {
|
||||
int32_t capacity;
|
||||
int32_t len;
|
||||
uint8_t *data;
|
||||
} RustBuffer;
|
||||
|
||||
typedef int32_t (*ForeignCallback)(uint64_t, int32_t, uint8_t *, int32_t, RustBuffer *);
|
||||
|
||||
// Task defined in Rust that Go executes
|
||||
typedef void (*RustTaskCallback)(const void *, int8_t);
|
||||
|
||||
// Callback to execute Rust tasks using a Go routine
|
||||
//
|
||||
// Args:
|
||||
// executor: ForeignExecutor lowered into a uint64_t value
|
||||
// delay: Delay in MS
|
||||
// task: RustTaskCallback to call
|
||||
// task_data: data to pass the task callback
|
||||
typedef int8_t (*ForeignExecutorCallback)(uint64_t, uint32_t, RustTaskCallback, void *);
|
||||
|
||||
typedef struct ForeignBytes {
|
||||
int32_t len;
|
||||
const uint8_t *data;
|
||||
} ForeignBytes;
|
||||
|
||||
// Error definitions
|
||||
typedef struct RustCallStatus {
|
||||
int8_t code;
|
||||
RustBuffer errorBuf;
|
||||
} RustCallStatus;
|
||||
|
||||
// Continuation callback for UniFFI Futures
|
||||
typedef void (*RustFutureContinuation)(void * , int8_t);
|
||||
|
||||
// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️
|
||||
// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V6 in this file. ⚠️
|
||||
#endif // def UNIFFI_SHARED_H
|
||||
|
||||
// Needed because we can't execute the callback directly from go.
|
||||
void cgo_rust_task_callback_bridge_verenc(RustTaskCallback, const void *, int8_t);
|
||||
|
||||
int8_t uniffiForeignExecutorCallbackverenc(uint64_t, uint32_t, RustTaskCallback, void*);
|
||||
|
||||
void uniffiFutureContinuationCallbackverenc(void*, int8_t);
|
||||
|
||||
RustBuffer uniffi_verenc_fn_func_chunk_data_for_verenc(
|
||||
RustBuffer data,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
RustBuffer uniffi_verenc_fn_func_combine_chunked_data(
|
||||
RustBuffer chunks,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
RustBuffer uniffi_verenc_fn_func_new_verenc_proof(
|
||||
RustBuffer data,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
RustBuffer uniffi_verenc_fn_func_new_verenc_proof_encrypt_only(
|
||||
RustBuffer data,
|
||||
RustBuffer encryption_key_bytes,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
RustBuffer uniffi_verenc_fn_func_verenc_compress(
|
||||
RustBuffer proof,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
RustBuffer uniffi_verenc_fn_func_verenc_recover(
|
||||
RustBuffer recovery,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
int8_t uniffi_verenc_fn_func_verenc_verify(
|
||||
RustBuffer proof,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
RustBuffer ffi_verenc_rustbuffer_alloc(
|
||||
int32_t size,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
RustBuffer ffi_verenc_rustbuffer_from_bytes(
|
||||
ForeignBytes bytes,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rustbuffer_free(
|
||||
RustBuffer buf,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
RustBuffer ffi_verenc_rustbuffer_reserve(
|
||||
RustBuffer buf,
|
||||
int32_t additional,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_continuation_callback_set(
|
||||
RustFutureContinuation callback,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_poll_u8(
|
||||
void* handle,
|
||||
void* uniffi_callback,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_cancel_u8(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_free_u8(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
uint8_t ffi_verenc_rust_future_complete_u8(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_poll_i8(
|
||||
void* handle,
|
||||
void* uniffi_callback,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_cancel_i8(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_free_i8(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
int8_t ffi_verenc_rust_future_complete_i8(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_poll_u16(
|
||||
void* handle,
|
||||
void* uniffi_callback,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_cancel_u16(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_free_u16(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
uint16_t ffi_verenc_rust_future_complete_u16(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_poll_i16(
|
||||
void* handle,
|
||||
void* uniffi_callback,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_cancel_i16(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_free_i16(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
int16_t ffi_verenc_rust_future_complete_i16(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_poll_u32(
|
||||
void* handle,
|
||||
void* uniffi_callback,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_cancel_u32(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_free_u32(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
uint32_t ffi_verenc_rust_future_complete_u32(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_poll_i32(
|
||||
void* handle,
|
||||
void* uniffi_callback,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_cancel_i32(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_free_i32(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
int32_t ffi_verenc_rust_future_complete_i32(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_poll_u64(
|
||||
void* handle,
|
||||
void* uniffi_callback,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_cancel_u64(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_free_u64(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
uint64_t ffi_verenc_rust_future_complete_u64(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_poll_i64(
|
||||
void* handle,
|
||||
void* uniffi_callback,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_cancel_i64(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_free_i64(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
int64_t ffi_verenc_rust_future_complete_i64(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_poll_f32(
|
||||
void* handle,
|
||||
void* uniffi_callback,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_cancel_f32(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_free_f32(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
float ffi_verenc_rust_future_complete_f32(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_poll_f64(
|
||||
void* handle,
|
||||
void* uniffi_callback,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_cancel_f64(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_free_f64(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
double ffi_verenc_rust_future_complete_f64(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_poll_pointer(
|
||||
void* handle,
|
||||
void* uniffi_callback,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_cancel_pointer(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_free_pointer(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void* ffi_verenc_rust_future_complete_pointer(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_poll_rust_buffer(
|
||||
void* handle,
|
||||
void* uniffi_callback,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_cancel_rust_buffer(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_free_rust_buffer(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
RustBuffer ffi_verenc_rust_future_complete_rust_buffer(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_poll_void(
|
||||
void* handle,
|
||||
void* uniffi_callback,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_cancel_void(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_free_void(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
void ffi_verenc_rust_future_complete_void(
|
||||
void* handle,
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
uint16_t uniffi_verenc_checksum_func_chunk_data_for_verenc(
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
uint16_t uniffi_verenc_checksum_func_combine_chunked_data(
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
uint16_t uniffi_verenc_checksum_func_new_verenc_proof(
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
uint16_t uniffi_verenc_checksum_func_new_verenc_proof_encrypt_only(
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
uint16_t uniffi_verenc_checksum_func_verenc_compress(
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
uint16_t uniffi_verenc_checksum_func_verenc_recover(
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
uint16_t uniffi_verenc_checksum_func_verenc_verify(
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
uint32_t ffi_verenc_uniffi_contract_version(
|
||||
RustCallStatus* out_status
|
||||
);
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user