diff --git a/.github/workflows/docker-publish.yaml b/.github/workflows/docker-publish.yaml index e3df3aae..be4cd5e0 100644 --- a/.github/workflows/docker-publish.yaml +++ b/.github/workflows/docker-publish.yaml @@ -2,10 +2,12 @@ name: Create and publish a Docker image on: push: - tags: - - '**' branches: - - '**' + - '**' + tags-ignore: + - "v[0-9]+.[0-9]+.[0-9]+" # Push events to matching v*, i.e. v1.0, v20.15.10 + - "v[0-9]+.[0-9]+.[0-9]+-beta[0-9]+" + - "v[0-9]+.[0-9]+.[0-9]+-rc[0-9]+" env: REGISTRY: ghcr.io diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 559cfcb6..7c2523f0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,40 +2,74 @@ name: "Release" on: push: - branches: - - "RC[0-9]/**" tags: - "v[0-9]+.[0-9]+.[0-9]+" # Push events to matching v*, i.e. v1.0, v20.15.10 - "v[0-9]+.[0-9]+.[0-9]+-beta[0-9]+" - "v[0-9]+.[0-9]+.[0-9]+-rc[0-9]+" +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + jobs: - goreleaser: + build-and-push-image: runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: - - name: Checkout + - name: Checkout repository uses: actions/checkout@v3 - with: - fetch-depth: 0 - - uses: actions/setup-go@v4 - with: - go-version: '^1.19' + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 - - run: echo ":rocket::rocket::rocket:" > ../release_notes.md - if: startsWith(github.ref, 'refs/tags/') + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 - - name: Build - uses: goreleaser/goreleaser-action@v4 + - name: Log in to the Container registry + uses: docker/login-action@v2 with: - version: latest - args: build --skip-validate # skip validate skips initial sanity checks in order to be able to fully run + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - - name: Release - uses: goreleaser/goreleaser-action@v4 - if: startsWith(github.ref, 'refs/tags/') + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v4 with: - version: latest - args: release --rm-dist --release-notes=../release_notes.md - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: . + platforms: linux/amd64,linux/arm64 + file: docker/horcrux/Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + draft-release: + needs: build-and-push-image + runs-on: ubuntu-latest + permissions: write-all + steps: + - name: Copy Binary + run: | + docker create -it --entrypoint sh --name amd --platform linux/amd64 ${{ env.REGISTRY }}/${{ github.repository }}:${{ github.ref_name }} + docker create -it --entrypoint sh --name arm --platform linux/arm64 ${{ env.REGISTRY }}/${{ github.repository }}:${{ github.ref_name }} + docker cp amd:/bin/horcrux ./horcrux_linux-amd64 + docker cp arm:/bin/horcrux ./horcrux_linux-arm64 + sha256sum ./horcrux_linux-amd64 > ./horcrux_sha256.txt + sha256sum ./horcrux_linux-arm64 >> ./horcrux_sha256.txt + + - name: Draft Release + uses: softprops/action-gh-release@v1 + with: + draft: true + files: | + horcrux_linux-amd64 + horcrux_linux-arm64 + horcrux_sha256.txt diff --git a/goreleaser.yaml b/goreleaser.yaml deleted file mode 100644 index d79f21b7..00000000 --- a/goreleaser.yaml +++ /dev/null @@ -1,32 +0,0 @@ -project_name: horcrux - -env: - # Require use of Go modules. - - GO111MODULE=on - -builds: - - id: "horcrux" - main: ./cmd/horcrux/main.go - ldflags: - - -X "github.com/strangelove-ventures/horcrux/cmd/horcrux/cmd.Version={{ .Version }}" - - -X "github.com/strangelove-ventures/horcrux/cmd/horcrux/cmd.Commit={{ .Commit }}" - goos: - - darwin - - linux - goarch: - - amd64 - - arm64 - env: - - CGO_ENABLED=0 - -checksum: - name_template: SHA256SUMS-{{.Version}}.txt - algorithm: sha256 - -release: - name_template: "Release {{.Version}}" - -archives: - - files: - - LICENSE.md - - README.md \ No newline at end of file diff --git a/pkg/signer/remote_signer.go b/pkg/signer/remote_signer.go index 68f93bed..f9c2d661 100644 --- a/pkg/signer/remote_signer.go +++ b/pkg/signer/remote_signer.go @@ -18,6 +18,8 @@ import ( cometproto "github.com/cometbft/cometbft/proto/tendermint/types" ) +const connRetrySec = 2 + // PrivValidator is a wrapper for tendermint PrivValidator, // with additional Stop method for safe shutdown. type PrivValidator interface { @@ -73,98 +75,93 @@ func (rs *ReconnRemoteSigner) OnStop() { } func (rs *ReconnRemoteSigner) establishConnection(ctx context.Context) (net.Conn, error) { + ctx, cancel := context.WithTimeout(ctx, connRetrySec*time.Second) + defer cancel() + proto, address := cometnet.ProtocolAndAddress(rs.address) netConn, err := rs.dialer.DialContext(ctx, proto, address) if err != nil { - return nil, fmt.Errorf("dial error: %w", err) } conn, err := cometp2pconn.MakeSecretConnection(netConn, rs.privKey) if err != nil { + netConn.Close() return nil, fmt.Errorf("secret connection error: %w", err) } return conn, nil } -type conWrapper struct { - conn net.Conn -} - -func (rs *ReconnRemoteSigner) attemptConnection(ctx context.Context, cw *conWrapper, connected chan<- bool) { - conn, err := rs.establishConnection(ctx) - if err != nil { - sentryConnectTries.Add(float64(1)) - totalSentryConnectTries.Inc() - - rs.Logger.Error("Error establishing connection", "err", err) - return - } - - cw.conn = conn - - sentryConnectTries.Set(0) - - rs.Logger.Info("Connected to Sentry", "address", rs.address) - connected <- true - -} - // main loop for ReconnRemoteSigner func (rs *ReconnRemoteSigner) loop(ctx context.Context) { - var cw conWrapper + var conn net.Conn for { if !rs.IsRunning() { - if cw.conn != nil { - if err := cw.conn.Close(); err != nil { - rs.Logger.Error("Close", "err", err.Error()+"closing listener failed") - } - } + rs.closeConn(conn) return } - ticker := time.NewTicker(1 * time.Second) - connected := make(chan bool) + retries := 0 + for conn == nil { + var err error + timer := time.NewTimer(connRetrySec * time.Second) + conn, err = rs.establishConnection(ctx) + if err == nil { + sentryConnectTries.Set(0) + timer.Stop() + rs.Logger.Info("Connected to Sentry", "address", rs.address) + break + } - ConnLoop: - for cw.conn == nil { - ctx, cancel := context.WithCancel(ctx) - defer cancel() - go rs.attemptConnection(ctx, &cw, connected) + sentryConnectTries.Add(1) + totalSentryConnectTries.Inc() + retries++ + rs.Logger.Error( + "Error establishing connection, will retry", + "sleep (s)", connRetrySec, + "address", rs.address, + "attempt", retries, + "err", err, + ) select { - case <-connected: - break ConnLoop - case <-ticker.C: - cancel() - rs.Logger.Info("Retrying", "sleep (s)", 1, "address", rs.address) + case <-ctx.Done(): + return + case <-timer.C: + continue } } // since dialing can take time, we check running again if !rs.IsRunning() { - if err := cw.conn.Close(); err != nil { - rs.Logger.Error("Close", "err", err.Error()+"closing listener failed") - } + rs.closeConn(conn) return } - req, err := ReadMsg(cw.conn) + req, err := ReadMsg(conn) if err != nil { - rs.Logger.Error("readMsg", "err", err) - cw.conn.Close() - cw.conn = nil + rs.Logger.Error( + "Failed to read message from connection", + "address", rs.address, + "err", err, + ) + rs.closeConn(conn) + conn = nil continue } // handleRequest handles request errors. We always send back a response res := rs.handleRequest(req) - err = WriteMsg(cw.conn, res) + err = WriteMsg(conn, res) if err != nil { - rs.Logger.Error("writeMsg", "err", err) - cw.conn.Close() - cw.conn = nil + rs.Logger.Error( + "Failed to write message to connection", + "address", rs.address, + "err", err, + ) + rs.closeConn(conn) + conn = nil } } } @@ -410,3 +407,15 @@ func StartRemoteSigners( } return services, err } + +func (rs *ReconnRemoteSigner) closeConn(conn net.Conn) { + if conn == nil { + return + } + if err := conn.Close(); err != nil { + rs.Logger.Error("Failed to close connection to chain node", + "address", rs.address, + "err", err, + ) + } +}