diff --git a/.all-contributorsrc b/.all-contributorsrc deleted file mode 100644 index b7c0bed..0000000 --- a/.all-contributorsrc +++ /dev/null @@ -1,38 +0,0 @@ -{ - "projectName": "alert-key-p2p", - "projectOwner": "galt-tr", - "repoType": "github", - "repoHost": "https://github.com", - "files": [ - "README.md" - ], - "imageSize": 100, - "commit": false, - "commitConvention": "none", - "contributorsPerLine": 7, - "contributorsSortAlphabetically": false, - "contributors": [ - { - "login": "mrz1836", - "name": "Mr. Z", - "avatar_url": "https://avatars.githubusercontent.com/u/3743002?v=4", - "profile": "https://mrz1818.com", - "contributions": [ - "infra", - "security" - ] - }, - { - "login": "galt-tr", - "name": "Dylan", - "avatar_url": "https://avatars.githubusercontent.com/u/64976002?v=4", - "profile": "https://github.com/galt-tr", - "contributions": [ - "infra", - "code", - "maintenance", - "security" - ] - } - ] -} diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 78db747..caf223d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,2 +1,2 @@ # Default is the repo owner -* @galt-tr +* @bitcoin-sv diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 57fd6db..bfe2405 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,4 +1,3 @@ # These are supported funding model platforms -github: galt-tr -#custom: https://mrz1818.com/?tab=tips&utm_source=github&utm_medium=sponsor-link&utm_campaign=go-template&utm_term=go-template&utm_content=go-template +github: bitcoin-sv diff --git a/.github/ISSUE_TEMPLATE/config.yaml b/.github/ISSUE_TEMPLATE/config.yaml index dec783c..cf38143 100644 --- a/.github/ISSUE_TEMPLATE/config.yaml +++ b/.github/ISSUE_TEMPLATE/config.yaml @@ -1,5 +1,5 @@ blank_issues_enabled: true contact_links: - name: Ask a question - url: https://github.com/galt-tr/alert-key-p2p/discussions + url: https://github.com/bitcoin-sv/alert-system/discussions about: Ask questions and discuss with other community members diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml deleted file mode 100644 index 0c2948a..0000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,71 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -name: "CodeQL" - -on: - push: - branches: [master] - pull_request: - # The branches below must be a subset of the branches above - branches: [master] - # schedule: - # - cron: '0 23 * * 0' - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - # Override automatic language detection by changing the below list - # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] - language: ['go'] - # Learn more... - # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - # We must fetch at least the immediate parents so that if this is - # a pull request then we can check out the head. - fetch-depth: 2 - - # If this run was triggered by a pull request event, then checkout - # the head of the pull request instead of the merge commit. - - run: git checkout HEAD^2 - if: ${{ github.event_name == 'pull_request' }} - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v3 - - # ℹ️ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - # - run: | - # make bootstrap - # make release - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/image.yml b/.github/workflows/image.yml new file mode 100644 index 0000000..cdf121e --- /dev/null +++ b/.github/workflows/image.yml @@ -0,0 +1,46 @@ +name: Build and push OCI image to Docker Hub + +on: + push: + tags: + - '*' + +jobs: + image: + runs-on: ubuntu-latest + steps: + - name: Check out the repo + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: "./go.mod" + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Extract metadata (tags, labels) + id: meta + uses: docker/metadata-action@v5 + with: + images: bsvb/alert-key + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build and push image + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9057c4b..e4b0098 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,6 +3,7 @@ name: release env: GO111MODULE: on + GO_VERSION: 1.21 on: push: @@ -13,24 +14,125 @@ permissions: contents: write jobs: - goreleaser: + build-linux-binary: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 + - name: gcc install + run: sudo apt-get update; sudo apt install gcc-aarch64-linux-gnu - name: Set up Go uses: actions/setup-go@v5 with: - go-version: 1.19 + go-version: ${{ env.GO_VERSION }} + - name: Cache code + uses: actions/cache@v4 + with: + path: | + ~/go/pkg/mod # Module download cache + ~/.cache/go-build # Build cache (Linux) + ~/Library/Caches/go-build # Build cache (Mac) + "%LocalAppData%\\go-build" # Build cache (Windows) + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v5.0.0 + uses: goreleaser/goreleaser-action@v3 with: - distribution: goreleaser version: latest - args: release --rm-dist --debug + args: release --skip=publish --verbose --config .goreleaser-for-linux.yml env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - #- name: Syndicate to GoDocs - # run: make godocs \ No newline at end of file + - name: Upload + uses: actions/upload-artifact@v4 + with: + name: alert_system_linux + path: | + dist/alert_system_*.zip + dist/checksums.txt + dist/CHANGELOG.md + + build-darwin-binary: + runs-on: macos-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + - name: Cache code + uses: actions/cache@v4 + with: + path: | + ~/go/pkg/mod # Module download cache + ~/.cache/go-build # Build cache (Linux) + ~/Library/Caches/go-build # Build cache (Mac) + "%LocalAppData%\\go-build" # Build cache (Windows) + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v3 + with: + version: latest + args: release --skip=publish --verbose --config .goreleaser-for-darwin.yml + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Upload + uses: actions/upload-artifact@v4 + with: + name: alert_system_darwin + path: | + dist/alert_system_*.zip + dist/checksums.txt + dist/CHANGELOG.md + + create-release: + needs: [build-linux-binary, build-darwin-binary] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Make directories + run: | + mkdir -p ./dist/linux + mkdir -p ./dist/darwin + mkdir -p ./dist/windows + - name: Download linux binaries + uses: actions/download-artifact@v4 + with: + name: alert_system_linux + path: ./tmp-build/linux + - name: Download darwin binaries + uses: actions/download-artifact@v4 + with: + name: alert_system_darwin + path: ./tmp-build/darwin + - name: Get tag + uses: little-core-labs/get-git-tag@v3.0.2 + id: tag + - name: Prepare ./dist folder + run: | + mkdir -p ./dist + mv ./tmp-build/linux/*.zip ./dist + mv ./tmp-build/darwin/*.zip ./dist + cat ./tmp-build/linux/checksums.txt >> ./dist/checksums.txt + cat ./tmp-build/linux/CHANGELOG.md >> ./dist/CHANGELOG.md + - name: Release + uses: softprops/action-gh-release@v1 + with: + body_path: dist/CHANGELOG.md + prerelease: ${{ contains(github.ref, '-rc.') }} + files: | + dist/*.zip + dist/CHANGELOG.md + env: + COMMIT_TAG: ${{steps.tag.outputs.tag}} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 8566faa..70b16eb 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -51,13 +51,13 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - name: Cache code - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/go/pkg/mod # Module download cache ~/.cache/go-build # Build cache (Linux) ~/Library/Caches/go-build # Build cache (Mac) - '%LocalAppData%\go-build' # Build cache (Windows) + "%LocalAppData%\\go-build" # Build cache (Windows) key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- diff --git a/.golangci.yml b/.golangci.yml index bd9b026..256af2f 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -7,7 +7,7 @@ run: concurrency: 4 # timeout for analysis, e.g. 30s, 5m, default is 1m - timeout: 6m + timeout: 10m # exit code when at least one issue was found, default is 1 issues-exit-code: 1 diff --git a/.goreleaser-for-darwin.yml b/.goreleaser-for-darwin.yml new file mode 100644 index 0000000..2a012f4 --- /dev/null +++ b/.goreleaser-for-darwin.yml @@ -0,0 +1,56 @@ +# Make sure to check the documentation at http://goreleaser.com +# --------------------------- +# General +# --------------------------- +before: + hooks: + - make all +snapshot: + name_template: "{{ .Tag }}" +changelog: + sort: asc + filters: + exclude: + - '^.github:' + - '^.vscode:' + - '^docs:' + - '^test:' + +# --------------------------- +# Builder +# +# CGO is enabled and inspiration came from: +# https://github.com/goreleaser/goreleaser-cross-example +# https://github.com/goreleaser/goreleaser-cross-example-sysroot +# https://github.com/DataDog/extendeddaemonset/blob/main/.goreleaser-for-darwin.yaml +# --------------------------- +builds: + - id: darwin-build + main: ./cmd/ + binary: alert_system + goos: + - darwin + goarch: + - amd64 + - arm64 + env: + - CGO_ENABLED=1 + mod_timestamp: "{{ .CommitTimestamp }}" + ldflags: + - -s -w -X main.version={{.Version}} + +# --------------------------- +# Archives + Checksums +# --------------------------- +archives: + - id: alert_system_darwin + builds: + - darwin-build + name_template: "alert_system_{{ .Version }}_{{ .Os }}_{{ .Arch }}" + wrap_in_directory: false + format: zip + files: + - LICENSE +checksum: + name_template: "checksums.txt" + algorithm: sha256 \ No newline at end of file diff --git a/.goreleaser-for-linux.yml b/.goreleaser-for-linux.yml new file mode 100644 index 0000000..2026d8e --- /dev/null +++ b/.goreleaser-for-linux.yml @@ -0,0 +1,61 @@ +# Make sure to check the documentation at http://goreleaser.com +# --------------------------- +# General +# --------------------------- +before: + hooks: + - make all +snapshot: + name_template: "{{ .Tag }}" +changelog: + sort: asc + filters: + exclude: + - '^.github:' + - '^.vscode:' + - '^docs:' + - '^test:' + +# --------------------------- +# Builder +# +# CGO is enabled and inspiration came from: +# https://github.com/goreleaser/goreleaser-cross-example +# https://github.com/goreleaser/goreleaser-cross-example-sysroot +# https://github.com/DataDog/extendeddaemonset/blob/main/.goreleaser-for-linux.yaml +# --------------------------- +builds: + - id: linux-build + main: ./cmd/ + binary: alert_system + goos: + - linux + goarch: + - amd64 + - arm64 + env: + - CGO_ENABLED=1 + mod_timestamp: "{{ .CommitTimestamp }}" + ldflags: + - -s -w -X main.version={{.Version}} + overrides: + - goos: linux + goarch: arm64 + env: + - CC=aarch64-linux-gnu-gcc + +# --------------------------- +# Archives + Checksums +# --------------------------- +archives: + - id: alert_system_linux + builds: + - linux-build + name_template: "alert_system_{{ .Version }}_{{ .Os }}_{{ .Arch }}" + wrap_in_directory: false + format: zip + files: + - LICENSE +checksum: + name_template: "checksums.txt" + algorithm: sha256 \ No newline at end of file diff --git a/.goreleaser.yml b/.goreleaser.yml deleted file mode 100644 index fed9ded..0000000 --- a/.goreleaser.yml +++ /dev/null @@ -1,71 +0,0 @@ -# Make sure to check the documentation at http://goreleaser.com -# --------------------------- -# General -# --------------------------- -before: - hooks: - - make all -snapshot: - name_template: "{{ .Tag }}" -changelog: - sort: asc - filters: - exclude: - - '^.github:' - - '^.vscode:' - - '^test:' - -# --------------------------- -# Builder -# --------------------------- -build: - skip: true - -# --------------------------- -# GitHub Release -# --------------------------- -release: - prerelease: true - name_template: "Release v{{.Version}}" - -# --------------------------- -# Announce -# --------------------------- -announce: - - # See more at: https://goreleaser.com/customization/announce/#slack - slack: - enabled: false - message_template: '{{ .ProjectName }} {{ .Tag }} is out! Changelog: https://github.com/galt-tr/{{ .ProjectName }}/releases/tag/{{ .Tag }}' - channel: '#test_slack' - # username: '' - # icon_emoji: '' - # icon_url: '' - - # See more at: https://goreleaser.com/customization/announce/#twitter - twitter: - enabled: false - message_template: '{{ .ProjectName }} {{ .Tag }} is out!' - - # See more at: https://goreleaser.com/customization/announce/#discord - discord: - enabled: false - message_template: '{{ .ProjectName }} {{ .Tag }} is out!' - # Defaults to `GoReleaser` - author: '' - # Defaults to `3888754` - the grey-ish from goreleaser - color: '' - # Defaults to `https://goreleaser.com/static/avatar.png` - icon_url: '' - - # See more at: https://goreleaser.com/customization/announce/#reddit - reddit: - enabled: false - # Application ID for Reddit Application - application_id: "" - # Username for your Reddit account - username: "" - # Defaults to `{{ .GitURL }}/releases/tag/{{ .Tag }}` - # url_template: 'https://github.com/galt-tr/{{ .ProjectName }}/releases/tag/{{ .Tag }}' - # Defaults to `{{ .ProjectName }} {{ .Tag }} is out!` - title_template: '{{ .ProjectName }} {{ .Tag }} is out!' diff --git a/.make/common.mk b/.make/common.mk index d0d1fdf..5d566d3 100644 --- a/.make/common.mk +++ b/.make/common.mk @@ -50,7 +50,7 @@ install-releaser: ## Install the GoReleaser application release:: ## Full production release (creates release in GitHub) @echo "releasing..." @test $(github_token) - @export GITHUB_TOKEN=$(github_token) && goreleaser --rm-dist + @export GITHUB_TOKEN=$(github_token) && goreleaser --clean .PHONY: release-test release-test: ## Full production test release (everything except deploy) diff --git a/.make/go.mk b/.make/go.mk index 77d94d1..29ef69b 100644 --- a/.make/go.mk +++ b/.make/go.mk @@ -65,19 +65,31 @@ install-go: ## Install the application (Using Native Go) .PHONY: lint lint: ## Run the golangci-lint application (install if not found) - @echo "installing golangci-lint..." - @#Travis (has sudo) - @if [ "$(shell command -v golangci-lint)" = "" ] && [ $(TRAVIS) ]; then curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.55.2 && sudo cp ./bin/golangci-lint $(go env GOPATH)/bin/; fi; - @#AWS CodePipeline - @if [ "$(shell command -v golangci-lint)" = "" ] && [ "$(CODEBUILD_BUILD_ID)" != "" ]; then curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.55.2; fi; - @#GitHub Actions - @if [ "$(shell command -v golangci-lint)" = "" ] && [ "$(GITHUB_WORKFLOW)" != "" ]; then curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sudo sh -s -- -b $(go env GOPATH)/bin v1.55.2; fi; - @#Brew - MacOS - @if [ "$(shell command -v golangci-lint)" = "" ] && [ "$(shell command -v brew)" != "" ]; then brew install golangci-lint; fi; - @#MacOS Vanilla - @if [ "$(shell command -v golangci-lint)" = "" ] && [ "$(shell command -v brew)" != "" ]; then curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- v1.55.2; fi; - @echo "running golangci-lint..." - @golangci-lint run --verbose + @if [ "$(shell which golangci-lint)" = "" ]; then \ + if [ "$(shell command -v brew)" != "" ]; then \ + echo "Brew detected, attempting to install golangci-lint..."; \ + if ! brew list golangci-lint &>/dev/null; then \ + brew install golangci-lint; \ + else \ + echo "golangci-lint is already installed via brew."; \ + fi; \ + else \ + echo "Installing golangci-lint via curl..."; \ + GOPATH=$$(go env GOPATH); \ + if [ -z "$$GOPATH" ]; then GOPATH=$$HOME/go; fi; \ + echo "Installation path: $$GOPATH/bin"; \ + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $$GOPATH/bin v1.56.1; \ + fi; \ + fi; \ + if [ "$(TRAVIS)" != "" ]; then \ + echo "Travis CI environment detected."; \ + elif [ "$(CODEBUILD_BUILD_ID)" != "" ]; then \ + echo "AWS CodePipeline environment detected."; \ + elif [ "$(GITHUB_WORKFLOW)" != "" ]; then \ + echo "GitHub Actions environment detected."; \ + fi; \ + echo "Running golangci-lint..."; \ + golangci-lint run --verbose .PHONY: test test: ## Runs lint and ALL tests diff --git a/Dockerfile b/Dockerfile index fc1506d..3bda224 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,6 +12,9 @@ RUN CGO_ENABLED=1 go build -a -o $APP_ROOT/src/alert-system github.com/bitcoin-s # Copy the controller-manager into a thin image FROM registry.access.redhat.com/ubi9-minimal WORKDIR / +RUN mkdir /.bitcoin +RUN touch /.bitcoin/alert_system_private_key COPY --from=builder /opt/app-root/src/alert-system . USER 65534:65534 +ENV ALERT_SYSTEM_ENVIRONMENT=local CMD ["/alert-system"] diff --git a/LICENSE b/LICENSE index 7a09f69..ea13950 100644 --- a/LICENSE +++ b/LICENSE @@ -1,28 +1,26 @@ -Open BSV License version 4 +Open BSV License Version 5 – granted by BSV Association, authorised licensee -Copyright (c) 2023 BSV Blockchain Association ("BA") +For the purposes of this license, the definitions below have the following meanings: +“Bitcoin Protocol” means the protocol implementation, cryptographic rules, network protocols, and consensus mechanisms in the Bitcoin White Paper as described here https://protocol.bsvblockchain.org. +“Bitcoin White Paper” means the paper entitled ‘Bitcoin: A Peer-to-Peer Electronic Cash System’ published by ‘Satoshi Nakamoto’ in October 2008. +“BSV Blockchains” means: + (a) the Bitcoin blockchain containing block height #556767 with the hash "000000000000000001d956714215d96ffc00e0afda4cd0a96c96f8d802b1662b" and that contains the longest honest persistent chain of blocks which has been produced in a manner which is consistent with the rules set forth in the Network Access Rules; and + (b) the test blockchains that contain the longest honest persistent chains of blocks which has been produced in a manner which is consistent with the rules set forth in the Network Access Rules. +“Network Access Rules” or “Rules” means the set of rules regulating the relationship between BSV Association and the nodes on BSV based on the Bitcoin Protocol rules and those set out in the Bitcoin White Paper, and available here https://bsvblockchain.org/network-access-rules. +“Software” means the software the subject of this licence, including any/all intellectual property rights therein and associated documentation files. +BSV Association grants permission, free of charge and on a non-exclusive and revocable basis, to any person obtaining a copy of the Software to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +1 - The text “© BSV Association,” and this license shall be included in all copies or substantial portions of the Software. +2 - The Software, and any software that is derived from the Software or parts thereof, must only be used on the BSV Blockchains. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES REGARDING ENTITLEMENT, MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS THEREOF BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +Version 0.1.1 of the Bitcoin SV software, and prior versions of software upon which it was based, were licensed under the MIT License, which is included below. +The MIT License (MIT) +Copyright (c) 2009-2010 Satoshi Nakamoto +Copyright (c) 2009-2015 Bitcoin Developers +Copyright (c) 2009-2017 The Bitcoin Core developers +Copyright (c) 2017 The Bitcoin ABC developers +Copyright (c) 2018 Bitcoin Association for BSV +Copyright (c) 2023 BSV Association -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -1 - The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -2 - The Software, and any software that is derived from the Software or parts thereof, -can only be used on the Bitcoin SV blockchains. The Bitcoin SV blockchains are defined, -for purposes of this license, as the Bitcoin blockchain containing block height #556767 -with the hash "000000000000000001d956714215d96ffc00e0afda4cd0a96c96f8d802b1662b" and -that contains the longest persistent chain of blocks accepted by this Software and which are valid under the rules set forth in the Bitcoin white paper (S. Nakamoto, Bitcoin: A Peer-to-Peer Electronic Cash System, posted online October 2008) and the latest version of this Software available in this repository or another repository designated by BA, -as well as the test blockchains that contain the longest persistent chains of blocks accepted by this Software and which are valid under the rules set forth in the Bitcoin whitepaper (S. Nakamoto, Bitcoin: A Peer-to-Peer Electronic Cash System, posted online October 2008) and the latest version of this Software available in this repository, or another repository designated by BA. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile index 12f0670..30dfbf3 100644 --- a/Makefile +++ b/Makefile @@ -6,12 +6,12 @@ include .make/go.mk ## Not defined? Use default repo name which is the application ifeq ($(REPO_NAME),) - REPO_NAME="alert-key-p2p" + REPO_NAME="alert-system" endif ## Not defined? Use default repo owner ifeq ($(REPO_OWNER),) - REPO_OWNER="galt-tr" + REPO_OWNER="bitcoin-sv" endif .PHONY: all @@ -25,17 +25,3 @@ clean: ## Remove previous builds and any cached data @$(MAKE) clean-mods @test $(DISTRIBUTIONS_DIR) @if [ -d $(DISTRIBUTIONS_DIR) ]; then rm -r $(DISTRIBUTIONS_DIR); fi - -.PHONY: install-all-contributors -install-all-contributors: ## Installs all contributors locally - @echo "installing all-contributors cli tool..." - @yarn global add all-contributors-cli - -.PHONY: release -release:: ## Runs common.release then runs godocs - @$(MAKE) godocs - -.PHONY: update-contributors -update-contributors: ## Regenerates the contributors html/list - @echo "generating contributor html..." - @all-contributors generate diff --git a/README.md b/README.md index ad23b6f..f472eca 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,143 @@ -# Alert System Microservice +# alert-system +> A go microservice for managing alerts and runs alongside Bitcoin SV nodes utilizing RPC -This is the codebase for an alert system microservice that runs with alongside a Bitcoin SV Node and will produce automated RPC calls when validly signed alerts are received. +[![Release](https://img.shields.io/github/release-pre/bitcoin-sv/alert-system.svg?logo=github&style=flat&v=2)](https://github.com/bitcoin-sv/alert-system/releases) +[![Build](https://github.com/bitcoin-sv/alert-system/workflows/run-go-tests/badge.svg?branch=master&v=1)](https://github.com/bitcoin-sv/alert-system/actions) +[![Report](https://goreportcard.com/badge/github.com/bitcoin-sv/alert-system?style=flat&v=2)](https://goreportcard.com/report/github.com/bitcoin-sv/alert-system) +[![Go](https://img.shields.io/badge/Go-1.21.xx-blue.svg?v=1)](https://golang.org/) +[![standard-readme compliant](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat&v=2)](https://github.com/RichardLitt/standard-readme) +[![Makefile Included](https://img.shields.io/badge/Makefile-Supported%20-brightgreen?=flat&logo=probot&v=2)](Makefile) +
-## Getting Started -### Copy settings file +
+ +## Table of Contents +- [Installation](#installation) +- [Documentation](#documentation) +- [Examples & Tests](#examples--tests) +- [Benchmarks](#benchmarks) +- [Code Standards](#code-standards) +- [Contributing](#contributing) +- [License](#license) + +
+ +## Installation + +**alert-system** requires a [supported release of Go](https://golang.org/doc/devel/release.html#policy). + +To run the application, clone this repository locally and run: +```shell script +export ALERT_SYSTEM_ENVIRONMENT=local && go run cmd/main.go ``` -$ cp example_settings_local.conf settings_local.conf + +To run this application with a custom configuration file, run: +```shell script +export ALERT_SYSTEM_CONFIG_FILEPATH=path/to/file/config.json && go run cmd/main.go ``` -### Run the server (from source) +Configuration files can be found in the [config](app/config/envs) directory. + +
+ +## Container Environment +**Note:** to use a custom settings file, it needs to be mounted and the appropriate environment variables set. Running it as below will run an ephemeral database but the container should sync up from the peers on the network on startup. +### podman ``` -$ go run cmd/main.go +$ podman run -u root -e P2P_PORT=9908 -e P2P_IP=0.0.0.0 --expose 9908 docker.io/bsvb/alert-system:0.0.2 ``` -TODO: Running with docker: +## Documentation +View the generated [documentation](https://pkg.go.dev/github.com/bitcoin-sv/alert-system) + +[![GoDoc](https://godoc.org/github.com/bitcoin-sv/alert-system?status.svg&style=flat&v=2)](https://pkg.go.dev/github.com/bitcoin-sv/alert-system) + +
+ +
+Makefile Commands +
+ +View all `makefile` commands +```shell script +make help +``` + +List of all current commands: +```text +all Runs multiple commands +clean Remove previous builds and any cached data +clean-mods Remove all the Go mod cache +coverage Shows the test coverage +diff Show the git diff +generate Runs the go generate command in the base of the repo +godocs Sync the latest tag with GoDocs +help Show this help message +install Install the application +install-go Install the application (Using Native Go) +install-releaser Install the GoReleaser application +lint Run the golangci-lint application (install if not found) +release Full production release (creates release in GitHub) +release Runs common.release then runs godocs +release-snap Test the full release (build binaries) +release-test Full production test release (everything except deploy) +replace-version Replaces the version in HTML/JS (pre-deploy) +tag Generate a new tag and push (tag version=0.0.0) +tag-remove Remove a tag if found (tag-remove version=0.0.0) +tag-update Update an existing tag to current commit (tag-update version=0.0.0) +test Runs lint and ALL tests +test-ci Runs all tests via CI (exports coverage) +test-ci-no-race Runs all tests via CI (no race) (exports coverage) +test-ci-short Runs unit tests via CI (exports coverage) +test-no-lint Runs just tests +test-short Runs vet, lint and tests (excludes integration tests) +test-unit Runs tests and outputs coverage +uninstall Uninstall the application (and remove files) +update-linter Update the golangci-lint package (macOS only) +vet Run the Go vet application +``` +
+ +
+ +## Examples & Tests +All unit tests and examples run via [GitHub Actions](https://github.com/bitcoin-sv/alert-system/actions) and +uses [Go version 1.21.x](https://golang.org/doc/go1.21). View the [configuration file](.github/workflows/run-tests.yml). + +
+ +Run all tests (including integration tests) +```shell script +make test +``` + +
+ +Run tests (excluding integration tests) +```shell script +make test-short +``` + +
+ +## Benchmarks +Run the Go benchmarks: +```shell script +make bench +``` + +
+ +## Code Standards +Read more about this Go project's [code standards](.github/CODE_STANDARDS.md). + +
+ +## Contributing +View the [contributing guidelines](.github/CONTRIBUTING.md) and follow the [code of conduct](.github/CODE_OF_CONDUCT.md). + +
+ +## License + +[![License](https://img.shields.io/badge/license-OpenBSV-green.svg?style=flat&v=2)](LICENSE) \ No newline at end of file diff --git a/app/api/base/alert.go b/app/api/base/alert.go new file mode 100644 index 0000000..46ded72 --- /dev/null +++ b/app/api/base/alert.go @@ -0,0 +1,77 @@ +package base + +import ( + "encoding/hex" + "encoding/json" + "errors" + "net/http" + "strconv" + + "github.com/bitcoin-sv/alert-system/app/webhook" + + "github.com/bitcoin-sv/alert-system/app" + "github.com/bitcoin-sv/alert-system/app/models" + "github.com/bitcoin-sv/alert-system/app/models/model" + "github.com/julienschmidt/httprouter" + apirouter "github.com/mrz1836/go-api-router" +) + +// alerts will return the saved +func (a *Action) alert(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { + // Read params + params := apirouter.GetParams(req) + if params == nil { + apiError := apirouter.ErrorFromRequest(req, "parameters is nil", "no parameters specified", http.StatusBadRequest, http.StatusBadRequest, "") + apirouter.ReturnResponse(w, req, apiError.Code, apiError) + return + } + idStr := params.GetString("sequence") + if idStr == "" { + apiError := apirouter.ErrorFromRequest(req, "missing sequence param", "missing sequence param", http.StatusBadRequest, http.StatusBadRequest, "") + apirouter.ReturnResponse(w, req, apiError.Code, apiError) + return + } + sequenceNumber, err := strconv.Atoi(idStr) + if err != nil { + apiError := apirouter.ErrorFromRequest(req, "sequence is invalid", "sequence is invalid", http.StatusBadRequest, http.StatusBadRequest, "") + apirouter.ReturnResponse(w, req, apiError.Code, apiError) + return + } + + // Get alert + alertModel, err := models.GetAlertMessageBySequenceNumber(req.Context(), uint32(sequenceNumber), model.WithAllDependencies(a.Config)) + if err != nil { + app.APIErrorResponse(w, req, http.StatusInternalServerError, err) + return + } else if alertModel == nil { + app.APIErrorResponse(w, req, http.StatusNotFound, errors.New("alert not found")) + return + } + err = alertModel.ReadRaw() + if err != nil { + app.APIErrorResponse(w, req, http.StatusInternalServerError, errors.New("alert faile")) + return + } + am := alertModel.ProcessAlertMessage() + if am == nil { + app.APIErrorResponse(w, req, http.StatusInternalServerError, errors.New("alert not valid type")) + return + } + err = am.Read(alertModel.GetRawMessage()) + if err != nil { + app.APIErrorResponse(w, req, http.StatusInternalServerError, err) + return + } + p := webhook.Payload{ + AlertType: alertModel.GetAlertType(), + Sequence: alertModel.SequenceNumber, + Raw: hex.EncodeToString(alertModel.GetRawData()), + Text: am.MessageString(), + } + // Return the response + _ = apirouter.ReturnJSONEncode( + w, + http.StatusOK, + json.NewEncoder(w), + p, []string{"sequence", "raw", "text", "alert_type"}) +} diff --git a/app/api/base/alerts.go b/app/api/base/alerts.go new file mode 100644 index 0000000..75259e8 --- /dev/null +++ b/app/api/base/alerts.go @@ -0,0 +1,43 @@ +package base + +import ( + "encoding/json" + "errors" + "net/http" + + "github.com/bitcoin-sv/alert-system/app" + "github.com/bitcoin-sv/alert-system/app/models" + "github.com/bitcoin-sv/alert-system/app/models/model" + "github.com/julienschmidt/httprouter" + apirouter "github.com/mrz1836/go-api-router" +) + +// AlertsResponse is the response for the alerts endpoint +type AlertsResponse struct { + Alerts []*models.AlertMessage `json:"alerts"` + LatestSequence uint32 `json:"latest_sequence"` +} + +// alerts will return the saved +func (a *Action) alerts(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { + + // Get all alerts + alerts, err := models.GetAllAlerts(req.Context(), nil, model.WithAllDependencies(a.Config)) + if err != nil { + app.APIErrorResponse(w, req, http.StatusBadRequest, err) + return + } else if alerts == nil { + app.APIErrorResponse(w, req, http.StatusNotFound, errors.New("alert not found")) + return + } + + // Return the response + _ = apirouter.ReturnJSONEncode( + w, + http.StatusOK, + json.NewEncoder(w), + AlertsResponse{ + Alerts: alerts, + LatestSequence: alerts[len(alerts)-1].SequenceNumber, + }, []string{"alerts", "latest_sequence"}) +} diff --git a/app/api/base/base_test.go b/app/api/base/base_test.go index f801ef0..9342194 100644 --- a/app/api/base/base_test.go +++ b/app/api/base/base_test.go @@ -2,11 +2,11 @@ package base import ( "context" + "os" "testing" "github.com/bitcoin-sv/alert-system/app/config" "github.com/bitcoin-sv/alert-system/app/models" - "github.com/bitcoin-sv/alert-system/app/tester" "github.com/stretchr/testify/suite" ) @@ -20,11 +20,11 @@ type TestSuite struct { func (ts *TestSuite) SetupSuite() { // Set the env to test - tester.SetupEnv(ts.T()) + err := os.Setenv(config.EnvironmentKey, config.EnvironmentTest) + ts.Require().NoError(err) // Load the configuration - var err error - ts.Dependencies, err = config.LoadConfig(context.Background(), models.BaseModels, true) + ts.Dependencies, err = config.LoadDependencies(context.Background(), models.BaseModels, true) ts.Require().NoError(err) } @@ -35,19 +35,17 @@ func (ts *TestSuite) TearDownSuite() { if ts.Dependencies != nil { ts.Dependencies.CloseAll(context.Background()) } - - tester.TeardownEnv(ts.T()) } // SetupTest runs before each test func (ts *TestSuite) SetupTest() { // Set the env to test - tester.SetupEnv(ts.T()) + err := os.Setenv(config.EnvironmentKey, config.EnvironmentTest) + ts.Require().NoError(err) // Load the services - var err error - ts.Dependencies, err = config.LoadConfig(context.Background(), models.BaseModels, true) + ts.Dependencies, err = config.LoadDependencies(context.Background(), models.BaseModels, true) ts.Require().NoError(err) } @@ -56,8 +54,6 @@ func (ts *TestSuite) TearDownTest() { if ts.Dependencies != nil { ts.Dependencies.CloseAll(context.Background()) } - - tester.TeardownEnv(ts.T()) } // TestTestSuiteApp kick-starts all suite tests diff --git a/app/api/base/index.go b/app/api/base/index.go index 8567e71..e916d61 100644 --- a/app/api/base/index.go +++ b/app/api/base/index.go @@ -1,15 +1,67 @@ package base import ( + "context" + "embed" + "html/template" + "log" "net/http" + "github.com/bitcoin-sv/alert-system/app/models/model" + + "github.com/bitcoin-sv/alert-system/app/models" + "github.com/julienschmidt/httprouter" - apirouter "github.com/mrz1836/go-api-router" ) +//go:embed ui/templates/* +var content embed.FS + +// PageData contains the page data +type PageData struct { + Alerts []*models.AlertMessage +} + +func substr(s string, start, length int) string { + end := start + length + if start < 0 || start >= len(s) || end > len(s) { + return s + } + return s[start:end] +} + // index is the default index route of the API for testing purposes: (Hello World) -func index(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { - apirouter.ReturnResponse( - w, req, http.StatusOK, "Bitcoin SV Alert System", - ) +func (a *Action) index(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) { + htmlContent, err := content.ReadFile("ui/templates/index.tmpl") + if err != nil { + log.Print(err.Error()) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + ts, err := template.New("index").Funcs(template.FuncMap{"substr": substr}).Parse(string(htmlContent)) + if err != nil { + log.Print(err.Error()) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + + alerts, err := models.GetAllAlerts(context.Background(), nil, model.WithAllDependencies(a.Config)) + if err != nil { + log.Print(err.Error()) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + data := PageData{ + Alerts: alerts, + } + + // Then we use the Execute() method on the template set to write the + // template content as the response body. The last parameter to Execute() + // represents any dynamic data that we want to pass in, which for now we'll + // leave as nil. + err = ts.Execute(w, data) + if err != nil { + log.Print(err.Error()) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + } } diff --git a/app/api/base/index_test.go b/app/api/base/index_test.go deleted file mode 100644 index cfd9c74..0000000 --- a/app/api/base/index_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package base - -import ( - "io" - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/require" -) - -// TestIndex will test the method index() -func (ts *TestSuite) TestIndex() { - ts.T().Run("test index", func(t *testing.T) { - req := httptest.NewRequest(http.MethodGet, "/", nil) - w := httptest.NewRecorder() - - // Fire the request - index(w, req, nil) - res := w.Result() - defer func() { - _ = res.Body.Close() - }() - - // Test the body - data, err := io.ReadAll(res.Body) - require.NoError(t, err) - require.Equal(t, "\"Bitcoin SV Alert System\"\n", string(data)) - - // Check the result - require.Equal(t, "200 OK", res.Status) - require.Equal(t, http.StatusOK, res.StatusCode) - }) -} diff --git a/app/api/base/routes.go b/app/api/base/routes.go index 5ba4901..a04cf6d 100644 --- a/app/api/base/routes.go +++ b/app/api/base/routes.go @@ -20,7 +20,7 @@ func RegisterRoutes(router *apirouter.Router, conf *config.Config) { action := &Action{app.Action{Config: conf}} // Set the main index page (navigating to slash or the root of the major version) - router.HTTPRouter.GET("/", action.Request(router, index)) + router.HTTPRouter.GET("/", action.Request(router, action.index)) // Options request (for CORs) router.HTTPRouter.OPTIONS("/", router.SetCrossOriginHeaders) @@ -36,4 +36,10 @@ func RegisterRoutes(router *apirouter.Router, conf *config.Config) { // Set the health request router.HTTPRouter.GET("/health", action.Request(router, action.health)) + + // Set the get alerts request + router.HTTPRouter.GET("/alerts", action.Request(router, action.alerts)) + + // Set the get alert request + router.HTTPRouter.GET("/alert/:sequence", action.Request(router, action.alert)) } diff --git a/app/api/base/ui/templates/index.tmpl b/app/api/base/ui/templates/index.tmpl new file mode 100644 index 0000000..f9e48f0 --- /dev/null +++ b/app/api/base/ui/templates/index.tmpl @@ -0,0 +1,115 @@ + + + + + Alert System Status + + + + +
+

Alert System Status

+
+
+

Alerts

+ {{ if .Alerts }} + + + + + + + + + + + {{ range .Alerts }} + + + + + + + {{ end }} + +
SequenceCreated AtProcessedRaw Message
{{ .SequenceNumber }}{{ .CreatedAt }}{{ .Processed }} +
+ {{ if gt (len .Raw) 50 }} + Raw Hex + + Expand + {{ else }} + {{ .Raw }} + {{ end }} +
+
+ {{ else }} +

There's nothing to see here yet!

+ {{ end }} +
+ + diff --git a/app/config/config.go b/app/config/config.go index d97d2cc..dee9196 100644 --- a/app/config/config.go +++ b/app/config/config.go @@ -2,22 +2,52 @@ package config import ( + "embed" "net/http" "time" "github.com/mrz1836/go-datastore" ) +//go:embed envs +var envDir embed.FS // This is used for the config files + +// Constants for the environment +const ( + EnvironmentCustomFilePath = "ALERT_SYSTEM_CONFIG_FILEPATH" // Environment variable key for custom config file path + EnvironmentKey = "ALERT_SYSTEM_ENVIRONMENT" // Environment variable key + EnvironmentLocal = "local" // Environment for local development + EnvironmentPrefix = "alert_system" // Prefix for all environment variables + EnvironmentProduction = "production" // Environment for production + EnvironmentMainnet = "mainnet" // Environment for mainnet (same as production) + EnvironmentTest = "test" // Environment for testing + EnvironmentTestnet = "testnet" // Environment for testnet + EnvironmentStn = "stn" // Environment for STN testing +) + +// Local variables for configuration +var ( + environments = []interface{}{ + EnvironmentLocal, + EnvironmentProduction, + EnvironmentMainnet, + EnvironmentTest, + EnvironmentTestnet, + EnvironmentStn, + } +) + // Application configuration constants var ( - ApplicationName = "alert_system" // Application name used in places where we need an application name space - DatabasePathDefault = "alert_system_datastore.db" // Default database path (Sqlite) - DatabasePrefix = "alert_system" // Default database prefix - DefaultServerShutdown = 5 * time.Second // Default server shutdown delay time (to finish any requests or internal processes) - LocalPrivateKeyDefault = "alert_system_private_key" // Default local private key - LocalPrivateKeyDirectory = ".bitcoin" // Default local private key directory - SeedIpfsNode = "/ip4/68.183.57.231/tcp/9906/p2p/12D3KooWQs6ptKvoKNHurCzqRaVp3uFs9731NQwS3AmVcNc2TGpb" // Default seed IPFS node - DefaultAlertSystemProtocolID = "/bitcoin/alert-system/1.0.1" // Default alert system protocol for libp2p syncing + ApplicationName = "alert_system" // Application name used in places where we need an application name space + DatabasePrefix = "alert_system" // Default database prefix + DefaultAlertSystemProtocolID = "/bitcoin/alert-system/0.0.1" // Default alert system protocol for libp2p syncing + DefaultTopicName = "alert_system" // Default alert system topic name for libp2p subscription + DefaultServerShutdown = 5 * time.Second // Default server shutdown delay time (to finish any requests or internal processes) + DefaultPeerDiscoveryInterval = 10 * time.Minute // Default peer discovery refresh interval + DefaultAlertProcessingInterval = 5 * time.Minute // Default alert processing retry interval + LocalPrivateKeyDefault = "alert_system_private_key" // Default local private key + LocalPrivateKeyDirectory = ".bitcoin" // Default local private key directory ) // The global configuration settings @@ -25,31 +55,29 @@ type ( // Config is the global configuration settings Config struct { - AlertWebhookURL string `json:"alert_webhook_url"` // AlertWebhookURL is the URL for the alert webhook - Datastore *DatastoreConfig `json:"datastore"` // Datastore's configuration - P2PIP string `json:"p2p_ip"` // P2PIP is the IP address for the P2P server - P2PPort string `json:"p2p_port"` // P2PPort is the port for the P2P server - P2PPrivateKeyPath string `json:"p2p_private_key_path"` // P2PPrivateKeyPath is the path to the private key - P2PBootstrapPeer string `json:"p2p_bootstrap_peer"` // P2PBootstrapPeer is the bootstrap peer for the libp2p network - P2PAlertSystemProtocolID string `json:"p2p_alert_system_protocol_id"` // P2PAlertSystemProtocolID is the protocol ID to use on the libp2p network for alert system communication - RPCHost string `json:"rpc_host"` // RPCHost is the RPC host - RPCPassword string `json:"rpc_password"` // RPCPassword is the RPC password - RPCUser string `json:"rpc_user"` // RPCUser is the RPC username - RequestLogging bool `json:"request_logging"` // Toggle for verbose request logging (API requests) - Services *Services `json:"-"` // Services is the global services - WebServer *WebServerConfig `json:"web_server"` // WebServer is the configuration for the web HTTP Server + AlertWebhookURL string `json:"alert_webhook_url" mapstructure:"alert_webhook_url"` // AlertWebhookURL is the URL for the alert webhook + Datastore DatastoreConfig `json:"datastore" mapstructure:"datastore"` // Datastore's configuration + DisableRPCVerification bool `json:"disable_rpc_verification" mapstructure:"disable_rpc_verification"` // DisableRPCVerification will disable the rpc verification check on startup. Useful if bitcoind isn't running yet + LogOutputFile string `json:"log_output_file" mapstructure:"log_output_file"` // LogOutputFile will set an output file for the logger to write to as opposed to stdout + BitcoinConfigPath string `json:"bitcoin_config_path" mapstructure:"bitcoin_config_path"` // BitcoinConfigPath is the path to the bitcoin.conf file + P2P P2PConfig `json:"p2p" mapstructure:"p2p"` // P2P is the configuration for the P2P server + RPCConnections []RPCConfig `json:"rpc_connections" mapstructure:"rpc_connections"` // RPCConnections is a list of RPC connections + RequestLogging bool `json:"request_logging" mapstructure:"request_logging"` // Toggle for verbose request logging (API requests) + Services Services `json:"-" mapstructure:"services"` // Services is the global services + WebServer WebServerConfig `json:"web_server" mapstructure:"web_server"` // WebServer is the configuration for the web HTTP Server + AlertProcessingInterval time.Duration `json:"alert_processing_interval" mapstructure:"alert_processing_interval"` // AlertProcessingInterval is the interval in which the system will go through all of the saved alerts and attempt to retry any unprocessed alerts } // DatastoreConfig is the configuration for the datastore DatastoreConfig struct { - AutoMigrate bool `json:"auto_migrate"` // Loads a blank database - Debug bool `json:"debug"` // True for sql statements - Engine datastore.Engine `json:"engine"` // MySQL, Postgres, SQLite - Password string `json:"password"` // Used for MySQL or Postgresql - SQLite *datastore.SQLiteConfig `json:"sqlite"` // Configuration for SQLite - SQLRead *datastore.SQLConfig `json:"sql_read"` // Configuration for MySQL or Postgres - SQLWrite *datastore.SQLConfig `json:"sql_write"` // Configuration for MySQL or Postgres - TablePrefix string `json:"table_prefix"` // pre_table_name (pre) + AutoMigrate bool `json:"auto_migrate" mapstructure:"auto_migrate"` // Loads a blank database + Debug bool `json:"debug" mapstructure:"debug"` // True for sql statements + Engine datastore.Engine `json:"engine" mapstructure:"engine"` // MySQL, Postgres, SQLite + Password string `json:"password" mapstructure:"password"` // Used for MySQL or Postgresql + SQLite *datastore.SQLiteConfig `json:"sqlite" mapstructure:"sqlite"` // Configuration for SQLite + SQLRead *datastore.SQLConfig `json:"sql_read" mapstructure:"sql_read"` // Configuration for MySQL or Postgres + SQLWrite *datastore.SQLConfig `json:"sql_write" mapstructure:"sql_write"` // Configuration for MySQL or Postgres + TablePrefix string `json:"table_prefix" mapstructure:"table_prefix"` // pre_table_name (pre) } // HTTPInterface is used for the HTTP client @@ -59,17 +87,27 @@ type ( // Node is the configuration and functions for interacting with a node Node struct { - RPCHost string `json:"rpc_host"` // RPCHost is the RPC host - RPCPassword string `json:"rpc_password"` // RPCPassword is the RPC password - RPCUser string `json:"rpc_user"` // RPCUser is the RPC username + RPCHost string `json:"rpc_host" mapstructure:"rpc_host"` // RPCHost is the RPC host + RPCPassword string `json:"rpc_password" mapstructure:"rpc_password"` // RPCPassword is the RPC password + RPCUser string `json:"rpc_user" mapstructure:"rpc_user"` // RPCUser is the RPC username } - // WebServerConfig is a configuration for the web HTTP Server - WebServerConfig struct { - IdleTimeout time.Duration `json:"idle_timeout"` // 60s - Port string `json:"port"` // 3000 - ReadTimeout time.Duration `json:"read_timeout"` // 15s - WriteTimeout time.Duration `json:"write_timeout"` // 15s + // P2PConfig is the configuration for the P2P server and connection + P2PConfig struct { + AlertSystemProtocolID string `json:"alert_system_protocol_id" mapstructure:"alert_system_protocol_id"` // AlertSystemProtocolID is the protocol ID to use on the libp2p network for alert system communication + BootstrapPeer string `json:"bootstrap_peer" mapstructure:"bootstrap_peer"` // BootstrapPeer is the bootstrap peer for the libp2p network + IP string `json:"ip" mapstructure:"ip"` // IP is the IP address for the P2P server + Port string `json:"port" mapstructure:"port"` // Port is the port for the P2P server + PrivateKeyPath string `json:"private_key_path" mapstructure:"private_key_path"` // PrivateKeyPath is the path to the private key + TopicName string `json:"topic_name" mapstructure:"topic_name"` // TopicName is the name of the topic to subscribe to + PeerDiscoveryInterval time.Duration `json:"peer_discovery_interval" mapstructure:"peer_discovery_interval"` // PeerDiscoveryInterval is the interval in which we will refresh the peer table and check peers for missing messages + } + + // RPCConfig is the configuration for the RPC client + RPCConfig struct { + Host string `json:"host" mapstructure:"host"` // Host is the RPC host + Password string `json:"password" mapstructure:"password"` // Password is the RPC password + User string `json:"user" mapstructure:"user"` // User is the RPC username } // Services is the global services @@ -79,4 +117,12 @@ type ( Node NodeInterface // Node interface HTTPClient HTTPInterface // HTTP client interface } + + // WebServerConfig is a configuration for the web HTTP Server + WebServerConfig struct { + IdleTimeout time.Duration `json:"idle_timeout" mapstructure:"idle_timeout"` // 60s + Port string `json:"port" mapstructure:"port"` // 3000 + ReadTimeout time.Duration `json:"read_timeout" mapstructure:"read_timeout"` // 15s + WriteTimeout time.Duration `json:"write_timeout" mapstructure:"write_timeout"` // 15s + } ) diff --git a/app/config/datastore.go b/app/config/datastore.go index 1812b60..2ca3590 100644 --- a/app/config/datastore.go +++ b/app/config/datastore.go @@ -3,6 +3,8 @@ package config import ( "context" + "github.com/mrz1836/go-logger" + "github.com/mrz1836/go-datastore" ) @@ -11,12 +13,8 @@ func (c *Config) loadDatastore(ctx context.Context, models []interface{}) error // Sync collecting the options var options []datastore.ClientOps - - // No datastore set? - if c.Datastore == nil { - return ErrDatastoreRequired - } - + //TODO: pass in our own logger, but for now this doesn't work so i'm just going to silently log for now + options = append(options, datastore.WithLogger(logger.NewGormLogger(false, 0))) // Select the datastore if c.Datastore.Engine == datastore.SQLite { options = append(options, datastore.WithSQLite(&datastore.SQLiteConfig{ diff --git a/app/config/datastore_test.go b/app/config/datastore_test.go index 8838a3c..ef55729 100644 --- a/app/config/datastore_test.go +++ b/app/config/datastore_test.go @@ -4,7 +4,6 @@ import ( "context" "testing" - "github.com/bitcoin-sv/alert-system/app/tester" "github.com/mrz1836/go-datastore" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -12,32 +11,11 @@ import ( // TestLoadDatastore tests the cases of loadDatastore func TestLoadDatastore(t *testing.T) { - t.Run("failure - no datastore", func(t *testing.T) { - // Setup - tester.SetupEnv(t) - defer func() { - tester.TeardownEnv(t) - }() - - // Execute - c := &Config{} - err := c.loadDatastore(context.Background(), nil) - - // Assert - require.Error(t, err) - assert.Equal(t, ErrDatastoreRequired, err) - }) t.Run("failure - datastore unsupported", func(t *testing.T) { - // Setup - tester.SetupEnv(t) - defer func() { - tester.TeardownEnv(t) - }() - // Execute c := &Config{ - Datastore: &DatastoreConfig{ + Datastore: DatastoreConfig{ Engine: "unsupported", }, } @@ -49,16 +27,11 @@ func TestLoadDatastore(t *testing.T) { }) t.Run("success - sqlite", func(t *testing.T) { - // Setup - tester.SetupEnv(t) - defer func() { - tester.TeardownEnv(t) - }() // Execute c := &Config{ - Services: &Services{}, - Datastore: &DatastoreConfig{ + Services: Services{}, + Datastore: DatastoreConfig{ Engine: datastore.SQLite, AutoMigrate: true, TablePrefix: "test", diff --git a/app/config/envs/local.json b/app/config/envs/local.json new file mode 100644 index 0000000..3ae7a09 --- /dev/null +++ b/app/config/envs/local.json @@ -0,0 +1,74 @@ +{ + "alert_webhook_url": "", + "bitcoin_config_path": "", + "disable_rpc_verification": false, + "log_output_file": "", + "request_logging": true, + "alert_processing_interval": "5m", + "web_server": { + "idle_timeout": "60s", + "port": "3000", + "read_timeout": "15s", + "write_timeout": "15s" + }, + "environment": "local", + "datastore": { + "auto_migrate": true, + "debug": true, + "engine": "sqlite", + "password": "", + "table_prefix": "alert_system", + "sqlite": { + "database_path": "alert_system_datastore.db", + "shared": false + }, + "sql_read": { + "driver": "postgresql", + "host": "localhost", + "max_connection_idle_time": "20s", + "max_connection_time": "20s", + "max_idle_connections": 2, + "max_open_connections": 5, + "name": "alert_system_db", + "password": "postgres", + "port": "5432", + "replica": true, + "skip_initialize_with_version": true, + "time_zone": "UTC", + "tx_timeout": "20s", + "user": "postgres" + }, + "sql_write": { + "driver": "postgresql", + "host": "localhost", + "max_connection_idle_time": "20s", + "max_connection_time": "20s", + "max_idle_connections": 2, + "max_open_connections": 5, + "name": "alert_system_db", + "password": "postgres", + "port": "5432", + "replica": false, + "skip_initialize_with_version": true, + "time_zone": "UTC", + "tx_timeout": "20s", + "user": "postgres" + } + }, + "p2p": { + "ip": "0.0.0.0", + "port": "9906", + "alert_system_protocol_id": "/bitcoin-testnet/alert-system/0.0.1", + "bootstrap_peer": "", + "private_key_path": "", + "peer_discovery_interval": "10m", + "topic_name": "alert_system_testnet" + }, + "rpc_connections": [ + { + "user": "foo", + "password": "foo", + "host": "http://localhost:8333" + } + ] +} diff --git a/app/config/envs/mainnet.json b/app/config/envs/mainnet.json new file mode 100644 index 0000000..8438305 --- /dev/null +++ b/app/config/envs/mainnet.json @@ -0,0 +1,73 @@ +{ + "alert_webhook_url": "", + "bitcoin_config_path": "", + "log_output_file": "", + "disable_rpc_verification": false, + "request_logging": true, + "alert_processing_interval": "5m", + "web_server": { + "idle_timeout": "60s", + "port": "3000", + "read_timeout": "15s", + "write_timeout": "15s" + }, + "environment": "mainnet", + "datastore": { + "auto_migrate": true, + "debug": true, + "engine": "sqlite", + "password": "", + "table_prefix": "alert_system", + "sqlite": { + "database_path": "alert_system_datastore.db", + "shared": false + }, + "sql_read": { + "driver": "postgresql", + "host": "localhost", + "max_connection_idle_time": "20s", + "max_connection_time": "20s", + "max_idle_connections": 2, + "max_open_connections": 5, + "name": "alert_system_db", + "password": "", + "port": "5432", + "replica": true, + "skip_initialize_with_version": true, + "time_zone": "UTC", + "tx_timeout": "20s", + "user": "your_user" + }, + "sql_write": { + "driver": "postgresql", + "host": "localhost", + "max_connection_idle_time": "20s", + "max_connection_time": "20s", + "max_idle_connections": 2, + "max_open_connections": 5, + "name": "alert_system_db", + "password": "", + "port": "5432", + "replica": false, + "skip_initialize_with_version": true, + "time_zone": "UTC", + "tx_timeout": "20s", + "user": "your_user" + } + }, + "p2p": { + "ip": "0.0.0.0", + "port": "9906", + "alert_system_protocol_id": "/bitcoin/alert-system/1.0.0", + "bootstrap_peer": "", + "private_key_path": "", + "topic_name": "bitcoin_alert_system" + }, + "rpc_connections": [ + { + "user": "your_user", + "password": "", + "host": "http://localhost:8333" + } + ] +} diff --git a/app/config/envs/production.json b/app/config/envs/production.json new file mode 100644 index 0000000..243f98d --- /dev/null +++ b/app/config/envs/production.json @@ -0,0 +1,72 @@ +{ + "alert_webhook_url": "", + "bitcoin_config_path": "", + "log_output_file": "", + "disable_rpc_verification": false, + "request_logging": true, + "web_server": { + "idle_timeout": "60s", + "port": "3000", + "read_timeout": "15s", + "write_timeout": "15s" + }, + "environment": "production", + "datastore": { + "auto_migrate": true, + "debug": true, + "engine": "sqlite", + "password": "", + "table_prefix": "alert_system", + "sqlite": { + "database_path": "alert_system_datastore.db", + "shared": false + }, + "sql_read": { + "driver": "postgresql", + "host": "localhost", + "max_connection_idle_time": "20s", + "max_connection_time": "20s", + "max_idle_connections": 2, + "max_open_connections": 5, + "name": "alert_system_db", + "password": "", + "port": "5432", + "replica": true, + "skip_initialize_with_version": true, + "time_zone": "UTC", + "tx_timeout": "20s", + "user": "your_user" + }, + "sql_write": { + "driver": "postgresql", + "host": "localhost", + "max_connection_idle_time": "20s", + "max_connection_time": "20s", + "max_idle_connections": 2, + "max_open_connections": 5, + "name": "alert_system_db", + "password": "", + "port": "5432", + "replica": false, + "skip_initialize_with_version": true, + "time_zone": "UTC", + "tx_timeout": "20s", + "user": "your_user" + } + }, + "p2p": { + "ip": "0.0.0.0", + "port": "9906", + "alert_system_protocol_id": "/bitcoin/alert-system/1.0.0", + "bootstrap_peer": "", + "private_key_path": "", + "topic_name": "bitcoin_alert_system" + }, + "rpc_connections": [ + { + "user": "your_user", + "password": "", + "host": "http://localhost:8333" + } + ] +} diff --git a/app/config/envs/stn.json b/app/config/envs/stn.json new file mode 100644 index 0000000..846c4a4 --- /dev/null +++ b/app/config/envs/stn.json @@ -0,0 +1,73 @@ +{ + "alert_webhook_url": "", + "bitcoin_config_path": "", + "log_output_file": "", + "disable_rpc_verification": false, + "request_logging": true, + "alert_processing_interval": "5m", + "web_server": { + "idle_timeout": "60s", + "port": "3000", + "read_timeout": "15s", + "write_timeout": "15s" + }, + "environment": "stn", + "datastore": { + "auto_migrate": true, + "debug": true, + "engine": "sqlite", + "password": "", + "table_prefix": "alert_system", + "sqlite": { + "database_path": "alert_system_datastore.db", + "shared": false + }, + "sql_read": { + "driver": "postgresql", + "host": "localhost", + "max_connection_idle_time": "20s", + "max_connection_time": "20s", + "max_idle_connections": 2, + "max_open_connections": 5, + "name": "alert_system_db", + "password": "postgres", + "port": "5432", + "replica": true, + "skip_initialize_with_version": true, + "time_zone": "UTC", + "tx_timeout": "20s", + "user": "postgres" + }, + "sql_write": { + "driver": "postgresql", + "host": "localhost", + "max_connection_idle_time": "20s", + "max_connection_time": "20s", + "max_idle_connections": 2, + "max_open_connections": 5, + "name": "alert_system_db", + "password": "postgres", + "port": "5432", + "replica": false, + "skip_initialize_with_version": true, + "time_zone": "UTC", + "tx_timeout": "20s", + "user": "postgres" + } + }, + "p2p": { + "ip": "0.0.0.0", + "port": "9906", + "alert_system_protocol_id": "/bitcoin-stn/alert-system/0.0.1", + "bootstrap_peer": "", + "private_key_path": "", + "topic_name": "bsv_alert_system_stn" + }, + "rpc_connections": [ + { + "user": "galt", + "password": "galt", + "host": "http://localhost:9332" + } + ] +} diff --git a/app/config/envs/test.json b/app/config/envs/test.json new file mode 100644 index 0000000..9eab6bc --- /dev/null +++ b/app/config/envs/test.json @@ -0,0 +1,71 @@ +{ + "alert_webhook_url": "https://webhook.url", + "bitcoin_config_path": "", + "log_output_file": "", + "disable_rpc_verification": false, + "request_logging": true, + "web_server": { + "idle_timeout": "60s", + "port": "3000", + "read_timeout": "15s", + "write_timeout": "15s" + }, + "environment": "test", + "datastore": { + "auto_migrate": true, + "debug": true, + "engine": "sqlite", + "password": "", + "table_prefix": "alert_system", + "sqlite": { + "database_path": "", + "shared": false + }, + "sql_read": { + "driver": "postgresql", + "host": "localhost", + "max_connection_idle_time": "20s", + "max_connection_time": "20s", + "max_idle_connections": 2, + "max_open_connections": 5, + "name": "alert_system_db", + "password": "postgres", + "port": "5432", + "replica": true, + "skip_initialize_with_version": true, + "time_zone": "UTC", + "tx_timeout": "20s", + "user": "postgres" + }, + "sql_write": { + "driver": "postgresql", + "host": "localhost", + "max_connection_idle_time": "20s", + "max_connection_time": "20s", + "max_idle_connections": 2, + "max_open_connections": 5, + "name": "alert_system_db", + "password": "postgres", + "port": "5432", + "replica": false, + "skip_initialize_with_version": true, + "time_zone": "UTC", + "tx_timeout": "20s", + "user": "postgres" + } + }, + "p2p": { + "ip": "192.168.1.1", + "port": "8000", + "alert_system_protocol_id": "/bitcoin/alert-system/0.0.1", + "bootstrap_peer": "", + "private_key_path": "/path/to/private/key" + }, + "rpc_connections": [ + { + "user": "galt", + "password": "galt", + "host": "http://localhost:8333" + } + ] +} diff --git a/app/config/envs/testnet.json b/app/config/envs/testnet.json new file mode 100644 index 0000000..2a41992 --- /dev/null +++ b/app/config/envs/testnet.json @@ -0,0 +1,73 @@ +{ + "alert_webhook_url": "", + "bitcoin_config_path": "", + "log_output_file": "", + "disable_rpc_verification": false, + "request_logging": true, + "alert_processing_interval": "5m", + "web_server": { + "idle_timeout": "60s", + "port": "3000", + "read_timeout": "15s", + "write_timeout": "15s" + }, + "environment": "testnet", + "datastore": { + "auto_migrate": true, + "debug": true, + "engine": "sqlite", + "password": "", + "table_prefix": "alert_system", + "sqlite": { + "database_path": "alert_system_datastore.db", + "shared": false + }, + "sql_read": { + "driver": "postgresql", + "host": "localhost", + "max_connection_idle_time": "20s", + "max_connection_time": "20s", + "max_idle_connections": 2, + "max_open_connections": 5, + "name": "alert_system_db", + "password": "postgres", + "port": "5432", + "replica": true, + "skip_initialize_with_version": true, + "time_zone": "UTC", + "tx_timeout": "20s", + "user": "postgres" + }, + "sql_write": { + "driver": "postgresql", + "host": "localhost", + "max_connection_idle_time": "20s", + "max_connection_time": "20s", + "max_idle_connections": 2, + "max_open_connections": 5, + "name": "alert_system_db", + "password": "postgres", + "port": "5432", + "replica": false, + "skip_initialize_with_version": true, + "time_zone": "UTC", + "tx_timeout": "20s", + "user": "postgres" + } + }, + "p2p": { + "ip": "0.0.0.0", + "port": "9906", + "alert_system_protocol_id": "/bitcoin-testnet/alert-system/0.0.1", + "bootstrap_peer": "", + "private_key_path": "", + "topic_name": "alert_system_testnet" + }, + "rpc_connections": [ + { + "user": "galt", + "password": "galt", + "host": "http://localhost:18332" + } + ] +} diff --git a/app/config/errors.go b/app/config/errors.go index bc61e66..39dbfcf 100644 --- a/app/config/errors.go +++ b/app/config/errors.go @@ -8,9 +8,11 @@ import ( var ( ErrDatastoreRequired = errors.New("datastore is required and was not loaded") ErrDatastoreUnsupported = errors.New("unsupported datastore engine") + ErrInvalidEnvironment = errors.New("invalid environment") ErrNoP2PIP = errors.New("no p2p_ip defined") ErrNoP2PPort = errors.New("no p2p_port defined") ErrNoRPCHost = errors.New("no rpc_host defined") ErrNoRPCPassword = errors.New("no rpc_password defined") ErrNoRPCUser = errors.New("no rpc_user defined") + ErrNoRPCConnections = errors.New("no rpc connections configured") ) diff --git a/app/config/load.go b/app/config/load.go index a4c9149..5b1aac6 100644 --- a/app/config/load.go +++ b/app/config/load.go @@ -1,136 +1,242 @@ package config import ( + "bufio" + "bytes" "context" "errors" "fmt" + "io/fs" + "log" + "net" "net/http" "os" - "time" + "strings" + "sync" "github.com/mrz1836/go-datastore" - "github.com/ordishs/gocore" + "github.com/spf13/viper" ) -// LoadConfig will load the configuration and services -// models is a list of models to auto-migrate when the datastore is created -func LoadConfig(ctx context.Context, models []interface{}, isTesting bool) (_appConfig *Config, err error) { +// Added a mutex lock for a race-condition +var viperLock sync.Mutex - // Sync the configuration struct - _appConfig = &Config{ - RequestLogging: true, - Services: &Services{}, - Datastore: &DatastoreConfig{ - AutoMigrate: true, - Engine: datastore.SQLite, - TablePrefix: DatabasePrefix, - Debug: false, - SQLite: &datastore.SQLiteConfig{ - CommonConfig: datastore.CommonConfig{ - Debug: false, - MaxIdleConnections: 1, - MaxOpenConnections: 1, - TablePrefix: DatabasePrefix, - }, - Shared: false, - DatabasePath: DatabasePathDefault, - }, - }, - WebServer: &WebServerConfig{ - IdleTimeout: 60 * time.Second, // For idle connections - Port: "3000", // Default port - ReadTimeout: 15 * time.Second, // For reading the request - WriteTimeout: 15 * time.Second, // For writing the response - }, +// isValidEnvironment will return true if the testEnv is a known valid environment +func isValidEnvironment(testEnv string) bool { + testEnv = strings.ToLower(testEnv) + for _, env := range environments { + if env == testEnv { + return true + } } + return false +} - // Load the logger service (gocore.Logger meets the LoggerInterface) - _appConfig.Services.Log = &ExtendedLogger{ - Logger: gocore.Log(ApplicationName), +// LoadDependencies will load the configuration and services +// models is a list of models to auto-migrate when the datastore is created +// if testing is true, the node will be mocked +func LoadDependencies(ctx context.Context, models []interface{}, isTesting bool) (_appConfig *Config, err error) { + + // Load the config file + _appConfig, err = LoadConfigFile() + if err != nil { + return nil, err } - var ok bool + // Require at least one RPC connection + if len(_appConfig.RPCConnections) == 0 { + return nil, ErrNoRPCConnections + } - // Load the RPC user - if _appConfig.RPCUser, ok = gocore.Config().Get("RPC_USER"); !ok { - return nil, ErrNoRPCUser + // Ensure the P2P configuration is valid + if err = requireP2P(_appConfig); err != nil { + return nil, err } - // Load the RPC password - if _appConfig.RPCPassword, ok = gocore.Config().Get("RPC_PASSWORD"); !ok { - return nil, ErrNoRPCPassword + // Set the node config (either a real node or a mock node) + if !isTesting { + // todo support multiple nodes (this is an example) + for i := range _appConfig.RPCConnections { + _appConfig.Services.Node = NewNodeConfig( + _appConfig.RPCConnections[i].User, + _appConfig.RPCConnections[i].Password, + _appConfig.RPCConnections[i].Host, + ) + } + } else { + for i := range _appConfig.RPCConnections { + _appConfig.Services.Node = NewNodeMock( + _appConfig.RPCConnections[i].User, + _appConfig.RPCConnections[i].Password, + _appConfig.RPCConnections[i].Host, + ) + } } - // Load the RPC host - if _appConfig.RPCHost, ok = gocore.Config().Get("RPC_HOST"); !ok { - return nil, ErrNoRPCHost + // Load an HTTP client + _appConfig.Services.HTTPClient = http.DefaultClient + + // Load the datastore service + if err = _appConfig.loadDatastore(ctx, models); err != nil { + return nil, err } - // Load the P2P Bootstrap peer - if _appConfig.P2PBootstrapPeer, ok = gocore.Config().Get("P2P_BOOTSTRAP_PEER"); !ok { - _appConfig.P2PBootstrapPeer = SeedIpfsNode + return +} + +// requireP2P will ensure the P2P configuration is valid +func requireP2P(_appConfig *Config) error { + + // Set the P2P alert system protocol ID if it's missing + if len(_appConfig.P2P.AlertSystemProtocolID) == 0 { + _appConfig.P2P.AlertSystemProtocolID = DefaultAlertSystemProtocolID } - // Load the P2P alert system protocol ID - if _appConfig.P2PAlertSystemProtocolID, ok = gocore.Config().Get("P2P_ALERT_SYSTEM_PROTOCOL_ID"); !ok { - _appConfig.P2PAlertSystemProtocolID = DefaultAlertSystemProtocolID + // Set the p2p alert system topic name if it's missing + if len(_appConfig.P2P.TopicName) == 0 { + _appConfig.P2P.TopicName = DefaultTopicName } // Load the private key path // If not found, create a default one - if _appConfig.P2PPrivateKeyPath, ok = gocore.Config().Get("P2P_PRIVATE_KEY_PATH"); !ok { - if err = _appConfig.createPrivateKeyDirectory(); err != nil { - return nil, err + if len(_appConfig.P2P.PrivateKeyPath) == 0 { + if err := _appConfig.createPrivateKeyDirectory(); err != nil { + return err } } - // Load the p2p ip - if _appConfig.P2PIP, ok = gocore.Config().Get("P2P_IP"); !ok { - return nil, ErrNoP2PIP + // Load bitcoin configuration if specified + if len(_appConfig.BitcoinConfigPath) > 0 { + if err := _appConfig.loadBitcoinConfiguration(); err != nil { + return err + } } - // Load the p2p port - if _appConfig.P2PPort, ok = gocore.Config().Get("P2P_PORT"); !ok { - return nil, ErrNoP2PPort + // Load the peer discovery interval + if _appConfig.P2P.PeerDiscoveryInterval <= 0 { + _appConfig.P2P.PeerDiscoveryInterval = DefaultPeerDiscoveryInterval } - // Load the webhook URL (if set - this is optional) - if _appConfig.AlertWebhookURL, ok = gocore.Config().Get("ALERT_WEBHOOK_URL"); !ok { - _appConfig.Services.Log.Debugf("webhook url is not configured, webhook usage is disabled") + // Load the p2p ip (local, ip address or domain name) + // todo better validation of what is a valid IP, domain name or local address + if len(_appConfig.P2P.IP) < 5 { + return ErrNoP2PIP } - // Set the node config (either a real node or a mock node) - if !isTesting { - _appConfig.Services.Node = NewNodeConfig(_appConfig.RPCUser, _appConfig.RPCPassword, _appConfig.RPCHost) + // Load the p2p port ( >= XX) + if len(_appConfig.P2P.Port) < 2 { + return ErrNoP2PPort + } + + return nil +} + +// LoadConfigFile will load the config file and environment variables +func LoadConfigFile() (_appConfig *Config, err error) { + + // Start the configuration struct + _appConfig = &Config{ + Datastore: DatastoreConfig{ + SQLite: &datastore.SQLiteConfig{}, + SQLRead: &datastore.SQLConfig{}, + SQLWrite: &datastore.SQLConfig{}, + }, + P2P: P2PConfig{}, + Services: Services{}, + WebServer: WebServerConfig{}, + RPCConnections: make([]RPCConfig, 0), + } + + // Check the environment we are running + environment := os.Getenv(EnvironmentKey) + if !isValidEnvironment(environment) { + err = ErrInvalidEnvironment + return nil, err + } + + // Lock viper + viperLock.Lock() + + // Unlock the viper mutex + defer viperLock.Unlock() + + // Set a replacer for replacing double underscore with nested period + replacer := strings.NewReplacer(".", "__") + viper.SetEnvKeyReplacer(replacer) + + // Set the prefix + viper.SetEnvPrefix(EnvironmentPrefix) + + // Use env vars + viper.AutomaticEnv() + + // Get the embedded envs directory + var files []fs.DirEntry + if files, err = envDir.ReadDir("envs"); err != nil { + return nil, err + } + + // Set the configuration type + viper.SetConfigType("json") + + // Do we have a custom config file? (use this instead of the environment file) + customConfigFileWithPath := os.Getenv(EnvironmentCustomFilePath) + if len(customConfigFileWithPath) > 0 { + var b []byte + + // Read the file + if b, err = os.ReadFile(customConfigFileWithPath); err != nil { //nolint:gosec // This is a custom file path + return nil, err + } + + // Read the config + if err = viper.ReadConfig(bytes.NewBuffer(b)); err != nil { + return nil, err + } } else { - _appConfig.Services.Node = NewNodeMock(_appConfig.RPCUser, _appConfig.RPCPassword, _appConfig.RPCHost) - } - - // Use sql in-memory for testing - // todo this could come from a test struct or test env file - if isTesting { - _appConfig.Datastore.AutoMigrate = true - _appConfig.Datastore.Engine = datastore.SQLite - _appConfig.Datastore.TablePrefix = DatabasePrefix - _appConfig.Datastore.SQLite = &datastore.SQLiteConfig{ - CommonConfig: datastore.CommonConfig{ - Debug: true, - MaxIdleConnections: 1, - MaxOpenConnections: 1, - }, - Shared: false, - DatabasePath: "", + // Loop through the various environment files + for _, file := range files { + if file.Name() == environment+".json" { + var f fs.File + if f, err = envDir.Open("envs/" + file.Name()); err != nil { + return nil, err + } + if err = viper.ReadConfig(f); err != nil { + return nil, err + } + } } } - // Load an HTTP client - _appConfig.Services.HTTPClient = http.DefaultClient - - // Load the datastore service - if err = _appConfig.loadDatastore(ctx, models); err != nil { + // Unmarshal into values struct + if err = viper.Unmarshal(&_appConfig); err != nil { + err = fmt.Errorf("error loading viper values: %w", err) return nil, err } + // Load the logger service (ExtendedLogger meets the LoggerInterface) + writer := os.Stdout + if _appConfig.LogOutputFile != "" { + writer, err = os.OpenFile(_appConfig.LogOutputFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600) + if err != nil { + return nil, err + } + } + + logger := log.New(writer, "bitcoin-alert-system: ", log.LstdFlags) + _appConfig.Services.Log = &ExtendedLogger{ + Logger: logger, + writer: writer, + } + + // Set default alert processing interval if it doesn't exist + if _appConfig.AlertProcessingInterval <= 0 { + _appConfig.AlertProcessingInterval = DefaultAlertProcessingInterval + } + + // Log the configuration that was detected and where it was loaded from + _appConfig.Services.Log.Debug("loaded configuration from: " + viper.ConfigFileUsed()) + return } @@ -143,17 +249,79 @@ func (c *Config) createPrivateKeyDirectory() error { if err = os.Mkdir(fmt.Sprintf("%s/%s", dirName, LocalPrivateKeyDirectory), 0750); err != nil && !errors.Is(err, os.ErrExist) { return fmt.Errorf("failed to ensure %s dir exists: %w", LocalPrivateKeyDirectory, err) } - c.P2PPrivateKeyPath = fmt.Sprintf("%s/%s/%s", dirName, LocalPrivateKeyDirectory, LocalPrivateKeyDefault) + c.P2P.PrivateKeyPath = fmt.Sprintf("%s/%s/%s", dirName, LocalPrivateKeyDirectory, LocalPrivateKeyDefault) return nil } -// CloseAll will close all connections to all services -func (c *Config) CloseAll(ctx context.Context) { +// loadBitcoinConfiguration will load the RPC configuration from bitcoin.conf +func (c *Config) loadBitcoinConfiguration() error { + if len(c.BitcoinConfigPath) == 0 { + return nil + } + c.Services.Log.Infof("loading RPC configuration from %s", c.BitcoinConfigPath) + file, err := os.Open(c.BitcoinConfigPath) + if err != nil { + return err + } + scanner := bufio.NewScanner(file) + scanner.Split(splitFunc) + confValues := map[string]string{} + for scanner.Scan() { + kv := scanner.Text() + keyValue := strings.Split(kv, "=") + if len(keyValue) != 2 { + continue + } + confValues[keyValue[0]] = keyValue[1] + } + host := confValues["rpcconnect"] + if host == "" { + host = "127.0.0.1" + } + port := confValues["rpcport"] + if port == "" { + c.Services.Log.Debugf("rpcport value not detected ") + port = "8332" + } - // No services to close - if c.Services == nil { - return + user := confValues["rpcuser"] + if user == "" { + return fmt.Errorf("rpcuser missing from bitcoin.conf file") + } + pass := confValues["rpcpassword"] + if pass == "" { + return fmt.Errorf("rpcpassword missing from bitcoin.conf file") } + c.RPCConnections = []RPCConfig{ + { + Host: fmt.Sprintf("http://%s", net.JoinHostPort(host, port)), + Password: pass, + User: user, + }, + } + + return file.Close() +} + +func splitFunc(data []byte, atEOF bool) (advance int, token []byte, err error) { + if atEOF && len(data) == 0 { + return 0, nil, nil + } + + if atEOF { + return len(data), data, nil + } + + //newline is the k-v pair delimiter + if i := strings.Index(string(data), "\n"); i >= 0 { + //skip the delimiter in advancing to the next pair + return i + 1, data[0:i], nil + } + return +} + +// CloseAll will close all connections to all services +func (c *Config) CloseAll(ctx context.Context) { // Close the datastore if c.Services.Datastore != nil { diff --git a/app/config/load_test.go b/app/config/load_test.go index 5513d00..39ede04 100644 --- a/app/config/load_test.go +++ b/app/config/load_test.go @@ -2,230 +2,221 @@ package config import ( "context" - "fmt" + "os" "testing" + "time" - "github.com/bitcoin-sv/alert-system/app/config/mocks" - "github.com/bitcoin-sv/alert-system/app/tester" + "github.com/mrz1836/go-datastore" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -// TestLoadConfig_Failure tests the success case of LoadConfig +// TestLoadConfig_Failure tests the success case of LoadDependencies func TestLoadConfig_Success(t *testing.T) { - // Setup - tester.SetupEnv(t) - defer func() { - tester.TeardownEnv(t) - }() - - // Execute - config, err := LoadConfig(context.Background(), nil, true) - require.NoError(t, err) - - // Assert - assert.NotNil(t, config) - assert.Equal(t, "user", config.RPCUser) - assert.Equal(t, "password", config.RPCPassword) - assert.Equal(t, "localhost", config.RPCHost) - assert.Equal(t, "/path/to/private/key", config.P2PPrivateKeyPath) - assert.Equal(t, SeedIpfsNode, config.P2PBootstrapPeer) - assert.Equal(t, DefaultAlertSystemProtocolID, config.P2PAlertSystemProtocolID) - assert.Equal(t, "192.168.1.1", config.P2PIP) - assert.Equal(t, "8000", config.P2PPort) - assert.Equal(t, "https://webhook.url", config.AlertWebhookURL) + t.Run("successfully loading the config", func(t *testing.T) { + err := os.Setenv(EnvironmentKey, EnvironmentTest) + require.NoError(t, err) + + // Execute + var c *Config + c, err = LoadDependencies(context.Background(), nil, true) + require.NoError(t, err) + + defer c.CloseAll(context.Background()) + + // Assert + assert.NotNil(t, c) + assert.Equal(t, "/path/to/private/key", c.P2P.PrivateKeyPath) + assert.Equal(t, "", c.P2P.BootstrapPeer) + assert.Equal(t, DefaultAlertSystemProtocolID, c.P2P.AlertSystemProtocolID) + assert.Equal(t, DefaultPeerDiscoveryInterval, c.P2P.PeerDiscoveryInterval) + assert.Equal(t, DefaultAlertProcessingInterval, c.AlertProcessingInterval) + assert.Equal(t, "192.168.1.1", c.P2P.IP) + assert.Equal(t, "8000", c.P2P.Port) + assert.Equal(t, "https://webhook.url", c.AlertWebhookURL) + }) } -// TestLoadConfig_MissingRPCUser tests the failure case of LoadConfig when RPC_USER is missing -func TestLoadConfig_MissingRPCUser(t *testing.T) { - // Setup - tester.SetupEnv(t) - tester.UnsetEnv(t, "RPC_USER") - - defer func() { - tester.TeardownEnv(t) - }() - - // Execute - _, err := LoadConfig(context.Background(), nil, true) - - // Assert - require.Error(t, err) - assert.Equal(t, ErrNoRPCUser, err) -} - -// TestLoadConfig_MissingRPCPassword tests the failure case of LoadConfig when RPC_PASSWORD is missing -func TestLoadConfig_MissingRPCPassword(t *testing.T) { - // Setup - tester.SetupEnv(t) - tester.UnsetEnv(t, "RPC_PASSWORD") - - defer func() { - tester.TeardownEnv(t) - }() - - // Execute - _, err := LoadConfig(context.Background(), nil, true) - - // Assert - require.Error(t, err) - assert.Equal(t, ErrNoRPCPassword, err) -} - -// TestLoadConfig_MissingRPCHost tests the failure case of LoadConfig when RPC_HOST is missing -func TestLoadConfig_MissingRPCHost(t *testing.T) { - // Setup - tester.SetupEnv(t) - tester.UnsetEnv(t, "RPC_HOST") - - defer func() { - tester.TeardownEnv(t) - }() - - // Execute - _, err := LoadConfig(context.Background(), nil, true) - - // Assert - require.Error(t, err) - assert.Equal(t, ErrNoRPCHost, err) -} - -// TestLoadConfig_MissingP2PIP tests the failure case of LoadConfig when P2P_IP is missing -func TestLoadConfig_MissingP2PIP(t *testing.T) { - // Setup - tester.SetupEnv(t) - tester.UnsetEnv(t, "P2P_IP") - - defer func() { - tester.TeardownEnv(t) - }() - - // Execute - _, err := LoadConfig(context.Background(), nil, true) - - // Assert - require.Error(t, err) - assert.Equal(t, ErrNoP2PIP, err) -} - -// TestLoadConfig_MissingP2PPort tests the failure case of LoadConfig when P2P_PORT is missing -func TestLoadConfig_MissingP2PPort(t *testing.T) { - // Setup - tester.SetupEnv(t) - tester.UnsetEnv(t, "P2P_PORT") - - defer func() { - tester.TeardownEnv(t) - }() - - // Execute - _, err := LoadConfig(context.Background(), nil, true) - - // Assert - require.Error(t, err) - assert.Equal(t, ErrNoP2PPort, err) -} - -// TestLoadConfig_MissingP2PPrivateKeyPath tests the failure case of LoadConfig when P2P_PRIVATE_KEY_PATH is missing -func TestLoadConfig_MissingP2PPrivateKeyPath(t *testing.T) { - // Setup - tester.SetupEnv(t) - tester.UnsetEnv(t, "P2P_PRIVATE_KEY_PATH") - - defer func() { - tester.TeardownEnv(t) - }() - - // Execute - _, err := LoadConfig(context.Background(), nil, true) - - // Assert - require.NoError(t, err) -} - -// TestLoadConfig_OverrideP2PBootstrapPeer tests the case of LoadConfig when P2P_BOOTSTRAP_PEER is set -func TestLoadConfig_OverrideP2PBoostrapPeer(t *testing.T) { - // Setup - tester.SetupEnv(t) - tester.SetEnv(t, "P2P_BOOTSTRAP_PEER", "foobar") - - defer func() { - tester.TeardownEnv(t) - }() - - // Execute - c, err := LoadConfig(context.Background(), nil, true) - - // Assert - require.NoError(t, err) - require.Equal(t, "foobar", c.P2PBootstrapPeer) -} - -// TestLoadConfig_OverrideP2PAlertSystemProtocolID tests the case of LoadConfig when P2P_ALERT_SYSTEM_PROTOCOL_ID is set -func TestLoadConfig_OverrideP2PAlertSystemProtocolID(t *testing.T) { - // Setup - tester.SetupEnv(t) - tester.SetEnv(t, "P2P_ALERT_SYSTEM_PROTOCOL_ID", "foobar/1.0.1") - - defer func() { - tester.TeardownEnv(t) - }() - - // Execute - c, err := LoadConfig(context.Background(), nil, true) - - // Assert - require.NoError(t, err) - require.Equal(t, "foobar/1.0.1", c.P2PAlertSystemProtocolID) -} - -// TestBanPeer tests the BanPeer method -func TestBanPeer(t *testing.T) { - mockNode := &mocks.Node{ - BanPeerFunc: func(ctx context.Context, peer string) error { - // Mock behavior here - if peer == "expected_peer_address" { - return nil - } - return fmt.Errorf("unexpected peer address") - }, - } - - ctx := context.Background() - err := mockNode.BanPeer(ctx, "expected_peer_address") - require.NoError(t, err) -} - -// TestUnBanPeer tests the UnBanPeer method -func TestUnBanPeer(t *testing.T) { - mockNode := &mocks.Node{ - UnbanPeerFunc: func(ctx context.Context, peer string) error { - // Mock behavior here - if peer == "expected_peer_address" { - return nil - } - return fmt.Errorf("unexpected peer address") - }, - } - - ctx := context.Background() - err := mockNode.UnbanPeer(ctx, "expected_peer_address") - require.NoError(t, err) +// TestLoadConfigFile tests the method LoadConfigFile() +func TestLoadConfigFile(t *testing.T) { + + t.Run("no env", func(t *testing.T) { + err := os.Unsetenv(EnvironmentKey) + require.NoError(t, err) + + var ac *Config + ac, err = LoadConfigFile() + require.Error(t, err) + require.Nil(t, ac) + assert.Contains(t, err.Error(), "invalid environment") + }) + + t.Run("missing rpc connections", func(t *testing.T) { + err := os.Setenv(EnvironmentKey, EnvironmentTest) + require.NoError(t, err) + + // err = os.Setenv("ALERT_SYSTEM_RPC_CONNECTIONS", "[{\"user\":\"galt\",\"password\":\"galt\",\"host\":\"http://localhost:8333\"}]") + err = os.Setenv("ALERT_SYSTEM_RPC_CONNECTIONS", "[]") + require.NoError(t, err) + defer func() { + _ = os.Unsetenv("ALERT_SYSTEM_RPC_CONNECTIONS") + }() + + // Execute + var c *Config + c, err = LoadDependencies(context.Background(), nil, true) + require.Nil(t, c) + require.Error(t, err) + }) + + t.Run("missing ip address", func(t *testing.T) { + err := os.Setenv(EnvironmentKey, EnvironmentTest) + require.NoError(t, err) + + err = os.Setenv("ALERT_SYSTEM_P2P__IP", " ") + require.NoError(t, err) + defer func() { + _ = os.Unsetenv("ALERT_SYSTEM_P2P__IP") + }() + + // Execute + var c *Config + c, err = LoadDependencies(context.Background(), nil, true) + require.Nil(t, c) + + require.Error(t, err) + assert.Equal(t, ErrNoP2PIP, err) + }) + + t.Run("missing port", func(t *testing.T) { + err := os.Setenv(EnvironmentKey, EnvironmentTest) + require.NoError(t, err) + + err = os.Setenv("ALERT_SYSTEM_P2P__PORT", " ") + require.NoError(t, err) + defer func() { + _ = os.Unsetenv("ALERT_SYSTEM_P2P__PORT") + }() + + // Execute + var c *Config + c, err = LoadDependencies(context.Background(), nil, true) + require.Nil(t, c) + + require.Error(t, err) + assert.Equal(t, ErrNoP2PPort, err) + }) + + t.Run("invalid custom file path for config", func(t *testing.T) { + err := os.Setenv(EnvironmentKey, EnvironmentTest) + require.NoError(t, err) + + err = os.Setenv(EnvironmentCustomFilePath, "file-not-found.json") + require.NoError(t, err) + defer func() { + _ = os.Unsetenv(EnvironmentCustomFilePath) + }() + + // Execute + var c *Config + c, err = LoadDependencies(context.Background(), nil, true) + require.Nil(t, c) + + require.Error(t, err) + assert.Contains(t, err.Error(), "no such file or directory") + }) + + t.Run("valid custom location for config file", func(t *testing.T) { + err := os.Setenv(EnvironmentKey, EnvironmentTest) + require.NoError(t, err) + + err = os.Setenv(EnvironmentCustomFilePath, "envs/test.json") + require.NoError(t, err) + defer func() { + _ = os.Unsetenv(EnvironmentCustomFilePath) + }() + + // Execute + var c *Config + c, err = LoadDependencies(context.Background(), nil, true) + require.NotNil(t, c) + require.NoError(t, err) + defer c.CloseAll(context.Background()) + }) + + t.Run("test env, found file, test all structs", func(t *testing.T) { + err := os.Setenv(EnvironmentKey, EnvironmentTest) + require.NoError(t, err) + + var ac *Config + ac, err = LoadConfigFile() + require.NoError(t, err) + require.NotNil(t, ac) + + defer ac.CloseAll(context.Background()) + + assert.True(t, ac.RequestLogging) + + // Check nested structs (Webserver) + assert.Equal(t, 60*time.Second, ac.WebServer.IdleTimeout) + assert.Equal(t, 15*time.Second, ac.WebServer.ReadTimeout) + assert.Equal(t, 15*time.Second, ac.WebServer.WriteTimeout) + assert.Equal(t, "3000", ac.WebServer.Port) + + // Check nested structs (Datastore) + assert.True(t, ac.Datastore.AutoMigrate) + assert.True(t, ac.Datastore.Debug) + assert.Equal(t, datastore.SQLite, ac.Datastore.Engine) + assert.Equal(t, "", ac.Datastore.Password) + assert.Equal(t, "alert_system", ac.Datastore.TablePrefix) + assert.Equal(t, "", ac.Datastore.SQLite.DatabasePath) + assert.False(t, ac.Datastore.SQLite.Shared) + assert.Equal(t, "postgresql", ac.Datastore.SQLRead.Driver) + assert.Equal(t, "localhost", ac.Datastore.SQLRead.Host) + assert.Equal(t, time.Duration(20000000000), ac.Datastore.SQLRead.MaxConnectionIdleTime) + assert.Equal(t, time.Duration(20000000000), ac.Datastore.SQLRead.MaxConnectionTime) + assert.Equal(t, 2, ac.Datastore.SQLRead.MaxIdleConnections) + assert.Equal(t, 5, ac.Datastore.SQLRead.MaxOpenConnections) + assert.Equal(t, "postgresql", ac.Datastore.SQLWrite.Driver) + assert.Equal(t, "localhost", ac.Datastore.SQLWrite.Host) + assert.Equal(t, time.Duration(20000000000), ac.Datastore.SQLWrite.MaxConnectionIdleTime) + assert.Equal(t, time.Duration(20000000000), ac.Datastore.SQLWrite.MaxConnectionTime) + assert.Equal(t, 2, ac.Datastore.SQLWrite.MaxIdleConnections) + assert.Equal(t, 5, ac.Datastore.SQLWrite.MaxOpenConnections) + + // RPC Connections + assert.Len(t, ac.RPCConnections, 1) + assert.Equal(t, "galt", ac.RPCConnections[0].User) + assert.Equal(t, "galt", ac.RPCConnections[0].Password) + assert.Equal(t, "http://localhost:8333", ac.RPCConnections[0].Host) + }) } -// TestInvalidateBlock tests the InvalidateBlock method -func TestInvalidateBlock(t *testing.T) { - mockNode := &mocks.Node{ - InvalidateBlockFunc: func(ctx context.Context, hash string) error { - // Mock behavior here - if hash == "expected_hash" { - return nil - } - return fmt.Errorf("unexpected hash") - }, - } - - ctx := context.Background() - err := mockNode.InvalidateBlock(ctx, "expected_hash") - require.NoError(t, err) +// TestIsValidEnvironment will test the method isValidEnvironment() +func TestIsValidEnvironment(t *testing.T) { + t.Run("empty env", func(t *testing.T) { + valid := isValidEnvironment("") + assert.False(t, valid) + }) + + t.Run("unknown env", func(t *testing.T) { + valid := isValidEnvironment("unknown") + assert.False(t, valid) + }) + + t.Run("different case of letters", func(t *testing.T) { + valid := isValidEnvironment("LOCal") + assert.True(t, valid) + }) + + t.Run("valid envs", func(t *testing.T) { + valid := isValidEnvironment(EnvironmentTest) + assert.True(t, valid) + + valid = isValidEnvironment(EnvironmentLocal) + assert.True(t, valid) + + valid = isValidEnvironment(EnvironmentProduction) + assert.True(t, valid) + }) } diff --git a/app/config/logger.go b/app/config/logger.go index 22554e5..427d931 100644 --- a/app/config/logger.go +++ b/app/config/logger.go @@ -1,6 +1,10 @@ package config -import "github.com/ordishs/gocore" +import ( + "fmt" + "log" + "os" +) // LoggerInterface is the interface for the logger // This is used to allow the logger to be mocked and tested @@ -21,15 +25,73 @@ type LoggerInterface interface { Warn(args ...interface{}) Warnf(msg string, args ...interface{}) Printf(format string, v ...interface{}) // Custom method for go-api-router + CloseWriter() error // GetLogLevel() gocore.logLevel } // ExtendedLogger is the extended logger to satisfy the LoggerInterface type ExtendedLogger struct { - *gocore.Logger + *log.Logger + logLevel int + writer *os.File +} + +// CloseWriter close the log writer +func (es *ExtendedLogger) CloseWriter() error { + return es.writer.Close() } // Printf will print the log message to the console func (es *ExtendedLogger) Printf(format string, v ...interface{}) { - es.Infof(format, v...) + es.Logger.Printf(format, v...) +} + +// Debugf will print debug messages to the console +func (es *ExtendedLogger) Debugf(format string, v ...interface{}) { + es.Logger.Printf(fmt.Sprintf("\033[1;34m| DEBUG | %s\033[0m", format), v...) +} + +// Debug will print debug messages to the console +func (es *ExtendedLogger) Debug(v ...interface{}) { + es.Logger.Printf("%v", v...) +} + +// Error will print debug messages to the console +func (es *ExtendedLogger) Error(v ...interface{}) { + es.Logger.Printf("%v", v...) +} + +// Errorf will print debug messages to the console +func (es *ExtendedLogger) Errorf(format string, v ...interface{}) { + es.Logger.Printf(fmt.Sprintf("\033[1;31m| ERROR |: %s\033[0m", format), v...) +} + +// ErrorWithStack will print debug messages to the console +func (es *ExtendedLogger) ErrorWithStack(format string, v ...interface{}) { + es.Logger.Printf(format, v...) +} + +// Info will print info messages to the console +func (es *ExtendedLogger) Info(v ...interface{}) { + es.Logger.Printf("%v", v...) +} + +// Infof will print info messages to the console +func (es *ExtendedLogger) Infof(format string, v ...interface{}) { + es.Logger.Printf(fmt.Sprintf("\033[1;32m| INFO | %s\033[0m", format), v...) +} + +// LogLevel returns the logging level +func (es *ExtendedLogger) LogLevel() int { + return es.logLevel +} + +// Warn will print warning messages to the console +func (es *ExtendedLogger) Warn(v ...interface{}) { + es.Logger.Printf("%v", v...) +} + +// Warnf will print warning messages to the console +func (es *ExtendedLogger) Warnf(format string, v ...interface{}) { + es.Logger.Printf(format, v...) } diff --git a/app/config/mock_test.go b/app/config/mock_test.go new file mode 100644 index 0000000..182f7d8 --- /dev/null +++ b/app/config/mock_test.go @@ -0,0 +1,61 @@ +package config + +import ( + "context" + "fmt" + "testing" + + "github.com/bitcoin-sv/alert-system/app/config/mocks" + "github.com/stretchr/testify/require" +) + +// TestBanPeer tests the BanPeer method +func TestBanPeer(t *testing.T) { + mockNode := &mocks.Node{ + BanPeerFunc: func(_ context.Context, peer string) error { + // Mock behavior here + if peer == "expected_peer_address" { + return nil + } + return fmt.Errorf("unexpected peer address") + }, + } + + ctx := context.Background() + err := mockNode.BanPeer(ctx, "expected_peer_address") + require.NoError(t, err) +} + +// TestUnBanPeer tests the UnBanPeer method +func TestUnBanPeer(t *testing.T) { + mockNode := &mocks.Node{ + UnbanPeerFunc: func(_ context.Context, peer string) error { + // Mock behavior here + if peer == "expected_peer_address" { + return nil + } + return fmt.Errorf("unexpected peer address") + }, + } + + ctx := context.Background() + err := mockNode.UnbanPeer(ctx, "expected_peer_address") + require.NoError(t, err) +} + +// TestInvalidateBlock tests the InvalidateBlock method +func TestInvalidateBlock(t *testing.T) { + mockNode := &mocks.Node{ + InvalidateBlockFunc: func(_ context.Context, hash string) error { + // Mock behavior here + if hash == "expected_hash" { + return nil + } + return fmt.Errorf("unexpected hash") + }, + } + + ctx := context.Background() + err := mockNode.InvalidateBlock(ctx, "expected_hash") + require.NoError(t, err) +} diff --git a/app/config/mocks/mocks.go b/app/config/mocks/mocks.go index 17549dd..5e008d0 100644 --- a/app/config/mocks/mocks.go +++ b/app/config/mocks/mocks.go @@ -1,7 +1,11 @@ // Package mocks is a generated mocking package for the mocks package mocks -import "context" +import ( + "context" + + "github.com/libsv/go-bn/models" +) // Node is a mock type for the SVNode interface type Node struct { @@ -11,10 +15,12 @@ type Node struct { RPCUser string // Functions - BanPeerFunc func(ctx context.Context, peer string) error - InvalidateBlockFunc func(ctx context.Context, hash string) error - UnbanPeerFunc func(ctx context.Context, peer string) error - + BanPeerFunc func(ctx context.Context, peer string) error + BestBlockHashFunc func(ctx context.Context) (string, error) + InvalidateBlockFunc func(ctx context.Context, hash string) error + UnbanPeerFunc func(ctx context.Context, peer string) error + AddToConsensusBlacklistFunc func(ctx context.Context, funds []models.Fund) (*models.AddToConsensusBlacklistResponse, error) + AddToConfiscationTransactionWhitelistFunc func(ctx context.Context, tx []models.ConfiscationTransactionDetails) (*models.AddToConfiscationTransactionWhitelistResponse, error) // Add additional fields if needed to track calls or results } @@ -42,6 +48,14 @@ func (n *Node) BanPeer(ctx context.Context, peer string) error { return nil } +// BestBlockHash will call the BestBlockHashFunc +func (n *Node) BestBlockHash(ctx context.Context) (string, error) { + if n.BestBlockHashFunc != nil { + return n.BestBlockHashFunc(ctx) + } + return "", nil +} + // InvalidateBlock will call the InvalidateBlockFunc if not nil, otherwise return nil func (n *Node) InvalidateBlock(ctx context.Context, hash string) error { if n.InvalidateBlockFunc != nil { @@ -57,3 +71,19 @@ func (n *Node) UnbanPeer(ctx context.Context, peer string) error { } return nil } + +// AddToConsensusBlacklist will call the AddToConsensusBlacklistFunc if not nil, otherwise return nil +func (n *Node) AddToConsensusBlacklist(ctx context.Context, funds []models.Fund) (*models.AddToConsensusBlacklistResponse, error) { + if n.AddToConsensusBlacklistFunc != nil { + return n.AddToConsensusBlacklistFunc(ctx, funds) + } + return nil, nil +} + +// AddToConfiscationTransactionWhitelist will call the AddToConfiscationTransactionWhitelistFunc if not nil, otherwise return nil +func (n *Node) AddToConfiscationTransactionWhitelist(ctx context.Context, tx []models.ConfiscationTransactionDetails) (*models.AddToConfiscationTransactionWhitelistResponse, error) { + if n.AddToConfiscationTransactionWhitelistFunc != nil { + return n.AddToConfiscationTransactionWhitelistFunc(ctx, tx) + } + return nil, nil +} diff --git a/app/config/node.go b/app/config/node.go index 324c129..df5d992 100644 --- a/app/config/node.go +++ b/app/config/node.go @@ -3,6 +3,8 @@ package config import ( "context" + "github.com/libsv/go-bn/models" + "github.com/bitcoin-sv/alert-system/app/config/mocks" "github.com/libsv/go-bn" ) @@ -10,11 +12,14 @@ import ( // NodeInterface is the interface for a node type NodeInterface interface { BanPeer(ctx context.Context, peer string) error + BestBlockHash(ctx context.Context) (string, error) GetRPCHost() string GetRPCPassword() string GetRPCUser() string InvalidateBlock(ctx context.Context, hash string) error UnbanPeer(ctx context.Context, peer string) error + AddToConsensusBlacklist(ctx context.Context, funds []models.Fund) (*models.AddToConsensusBlacklistResponse, error) + AddToConfiscationTransactionWhitelist(ctx context.Context, tx []models.ConfiscationTransactionDetails) (*models.AddToConfiscationTransactionWhitelistResponse, error) } // NewNodeConfig creates a new NodeConfig struct @@ -62,8 +67,26 @@ func (n *Node) BanPeer(ctx context.Context, peer string) error { return c.SetBan(ctx, peer, bn.BanActionAdd, nil) } +// BestBlockHash gets the best block hash +func (n *Node) BestBlockHash(ctx context.Context) (string, error) { + c := bn.NewNodeClient(bn.WithCreds(n.RPCUser, n.RPCPassword), bn.WithHost(n.RPCHost)) + return c.BestBlockHash(ctx) +} + // UnbanPeer unbans a peer func (n *Node) UnbanPeer(ctx context.Context, peer string) error { c := bn.NewNodeClient(bn.WithCreds(n.RPCUser, n.RPCPassword), bn.WithHost(n.RPCHost)) return c.SetBan(ctx, peer, bn.BanActionRemove, nil) } + +// AddToConsensusBlacklist adds frozen utxos to blacklist +func (n *Node) AddToConsensusBlacklist(ctx context.Context, funds []models.Fund) (*models.AddToConsensusBlacklistResponse, error) { + c := bn.NewNodeClient(bn.WithCreds(n.RPCUser, n.RPCPassword), bn.WithHost(n.RPCHost)) + return c.AddToConsensusBlacklist(ctx, funds) +} + +// AddToConfiscationTransactionWhitelist adds confiscation transactions to the whitelist +func (n *Node) AddToConfiscationTransactionWhitelist(ctx context.Context, tx []models.ConfiscationTransactionDetails) (*models.AddToConfiscationTransactionWhitelistResponse, error) { + c := bn.NewNodeClient(bn.WithCreds(n.RPCUser, n.RPCPassword), bn.WithHost(n.RPCHost)) + return c.AddToConfiscationTransactionWhitelist(ctx, tx) +} diff --git a/app/models/alert_message.go b/app/models/alert_message.go index 2456616..6a3e08b 100644 --- a/app/models/alert_message.go +++ b/app/models/alert_message.go @@ -27,6 +27,7 @@ type AlertMessage struct { Hash string `json:"hash" toml:"hash" yaml:"hash" bson:"hash" gorm:"<-;type:char(64);index;comment:This is the hash"` SequenceNumber uint32 `json:"sequence_number" toml:"sequence_number" yaml:"sequence_number" bson:"sequence_number" gorm:"<-;type:int8;index;comment:This is the alert sequence number"` Raw string `json:"raw" toml:"raw" yaml:"raw" bson:"raw" gorm:"<-;type:text;comment:This is the raw alert message"` + Processed bool `json:"processed" toml:"processed" yaml:"processed" bson:"processed" gorm:"<-;type:boolean;comment:This determine if the alert was processed"` // Private fields (never to be exported) alertType AlertType @@ -41,6 +42,8 @@ type AlertMessage struct { type AlertMessageInterface interface { Read(msg []byte) error Do(ctx context.Context) error + ToJSON(ctx context.Context) []byte + MessageString() string } // NewAlertMessage creates a new alert message @@ -201,7 +204,7 @@ func (m *AlertMessage) ProcessAlertMessage() AlertMessageInterface { AlertMessage: *m, } case AlertTypeConfiscateUtxo: - return &AlertMessageConfiscateUtxo{ + return &AlertMessageConfiscateTransaction{ AlertMessage: *m, } case AlertTypeBanPeer: @@ -246,14 +249,21 @@ func (m *AlertMessage) Timestamp() uint64 { return m.timestamp } -// NewAlertFromBytes creates a new alert from bytes -func NewAlertFromBytes(ak []byte, opts ...model.Options) (*AlertMessage, error) { +// ReadRaw sets the model fields based on the raw message +func (m *AlertMessage) ReadRaw() error { + if len(m.GetRawMessage()) == 0 { + ak, err := hex.DecodeString(m.Raw) + if err != nil { + return err + } + m.SetRawMessage(ak) + } - // Check if the alert is valid - if len(ak) < 16 { + if len(m.GetRawMessage()) < 16 { // todo DETERMINE ACTUAL PROPER LENGTH - return nil, fmt.Errorf("alert needs to be at least 16") + return fmt.Errorf("alert needs to be at least 16 bytes") } + ak := m.GetRawMessage() version := binary.LittleEndian.Uint32(ak[:4]) sequenceNumber := binary.LittleEndian.Uint32(ak[4:8]) timestamp := binary.LittleEndian.Uint64(ak[8:16]) @@ -273,7 +283,7 @@ func NewAlertFromBytes(ak []byte, opts ...model.Options) (*AlertMessage, error) // but possible. Regardless let's just error out now if this length is lower. At least // allows us to grab the expected signature. if len(alertAndSignature) < sigLen+2 { - return nil, fmt.Errorf("alert message is invalid - too short length") + return fmt.Errorf("alert message is invalid - too short length") } // Get alert message bytes @@ -291,17 +301,26 @@ func NewAlertFromBytes(ak []byte, opts ...model.Options) (*AlertMessage, error) dataLen := 20 + len(alert) - // Create the new alert + m.SetAlertType(AlertType(alertType)) + m.message = alert + m.SequenceNumber = sequenceNumber + m.timestamp = timestamp + m.version = version + m.data = ak[:dataLen] + m.signatures = sigs + _ = m.Serialize() + return nil +} + +// NewAlertFromBytes creates a new alert from bytes +func NewAlertFromBytes(ak []byte, opts ...model.Options) (*AlertMessage, error) { opts = append(opts, model.New()) newAlert := NewAlertMessage(opts...) - newAlert.SetAlertType(AlertType(alertType)) - newAlert.message = alert - newAlert.SequenceNumber = sequenceNumber - newAlert.timestamp = timestamp - newAlert.version = version - newAlert.data = ak[:dataLen] - newAlert.signatures = sigs - _ = newAlert.Serialize() + newAlert.SetRawMessage(ak) + err := newAlert.ReadRaw() + if err != nil { + return nil, err + } // Return alert return newAlert, nil @@ -358,3 +377,63 @@ func GetLatestAlert(ctx context.Context, metadata *model.Metadata, opts ...model // Return the first item (only item) return modelItems[0], nil } + +// GetAllAlerts returns all alerts in the database +func GetAllAlerts(ctx context.Context, metadata *model.Metadata, opts ...model.Options) ([]*AlertMessage, error) { + // Set the conditions + conditions := &map[string]interface{}{ + utils.FieldDeletedAt: map[string]interface{}{ // IS NULL + utils.ExistsCondition: false, + }, + } + + // Set the query params + queryParams := &datastore.QueryParams{ + OrderByField: utils.FieldSequenceNumber, + SortDirection: utils.SortAscending, + } + + // Get the record + modelItems := make([]*AlertMessage, 0) + if err := model.GetModelsByConditions( + ctx, model.NameAlertMessage, &modelItems, metadata, conditions, queryParams, opts..., + ); err != nil { + return nil, err + } else if len(modelItems) == 0 { + return nil, nil + } + + // Return the first item (only item) + return modelItems, nil +} + +// GetAllUnprocessedAlerts will get all alerts that weren't successfully processed +func GetAllUnprocessedAlerts(ctx context.Context, metadata *model.Metadata, opts ...model.Options) ([]*AlertMessage, error) { + + // Set the conditions + conditions := &map[string]interface{}{ + utils.FieldDeletedAt: map[string]interface{}{ // IS NULL + utils.ExistsCondition: false, + }, + "processed": false, + } + + // Set the query params + queryParams := &datastore.QueryParams{ + OrderByField: utils.FieldSequenceNumber, + SortDirection: utils.SortAscending, + } + + // Get the record + modelItems := make([]*AlertMessage, 0) + if err := model.GetModelsByConditions( + ctx, model.NameAlertMessage, &modelItems, metadata, conditions, queryParams, opts..., + ); err != nil { + return nil, err + } else if len(modelItems) == 0 { + return nil, nil + } + + // Return the first item (only item) + return modelItems, nil +} diff --git a/app/models/alert_message_ban_peer.go b/app/models/alert_message_ban_peer.go index c63440b..4936505 100644 --- a/app/models/alert_message_ban_peer.go +++ b/app/models/alert_message_ban_peer.go @@ -3,6 +3,7 @@ package models import ( "bytes" "context" + "encoding/json" "fmt" "github.com/libsv/go-p2p/wire" @@ -62,3 +63,20 @@ func (a *AlertMessageBanPeer) Read(alert []byte) error { func (a *AlertMessageBanPeer) Do(ctx context.Context) error { return a.Config().Services.Node.BanPeer(ctx, string(a.Peer)) } + +// ToJSON is the alert in JSON format +func (a *AlertMessageBanPeer) ToJSON(_ context.Context) []byte { + m := a.ProcessAlertMessage() + // TODO: Come back and add a message interface for each alert + _ = m.Read(a.GetRawMessage()) + data, err := json.MarshalIndent(m, "", " ") + if err != nil { + return []byte{} + } + return data +} + +// MessageString executes the alert +func (a *AlertMessageBanPeer) MessageString() string { + return fmt.Sprintf("Banning peer [%s]; reason [%s].", a.Peer, a.Reason) +} diff --git a/app/models/alert_message_confiscate_utxo.go b/app/models/alert_message_confiscate_utxo.go index 88f8afa..d0ffb79 100644 --- a/app/models/alert_message_confiscate_utxo.go +++ b/app/models/alert_message_confiscate_utxo.go @@ -1,19 +1,99 @@ package models -import "context" +import ( + "bytes" + "context" + "encoding/binary" + "encoding/hex" + "encoding/json" + "errors" + "fmt" -// AlertMessageConfiscateUtxo is a confiscate utxo alert -type AlertMessageConfiscateUtxo struct { + "github.com/libsv/go-bn/models" + "github.com/libsv/go-p2p/wire" +) + +// AlertMessageConfiscateTransaction is a confiscate utxo alert +type AlertMessageConfiscateTransaction struct { AlertMessage - // TODO finish building out this alert type + Transactions []models.ConfiscationTransactionDetails +} + +// ConfiscateTransaction defines the parameters for the confiscation transaction +type ConfiscateTransaction struct { + EnforceAtHeight uint64 + Hex []byte } // Read reads the alert -func (a *AlertMessageConfiscateUtxo) Read(_ []byte) error { +func (a *AlertMessageConfiscateTransaction) Read(raw []byte) error { + a.Config().Services.Log.Infof("%x", raw) + if len(raw) < 9 { + return fmt.Errorf("confiscation alert is less than 9 bytes") + } + // TODO: assume for now only 1 confiscation tx in the alert for simplicity + details := []models.ConfiscationTransactionDetails{} + enforceAtHeight := binary.LittleEndian.Uint64(raw[0:8]) + buf := bytes.NewReader(raw[8:]) + + length, err := wire.ReadVarInt(buf, 0) + if err != nil { + return err + } + if length > uint64(buf.Len()) { + return errors.New("tx hex length is longer than the remaining buffer") + } + + // read the tx hex + var rawHex []byte + for i := uint64(0); i < length; i++ { + var b byte + if b, err = buf.ReadByte(); err != nil { + return fmt.Errorf("failed to read tx hex: %s", err.Error()) + } + rawHex = append(rawHex, b) + } + + detail := models.ConfiscationTransactionDetails{ + ConfiscationTransaction: models.ConfiscationTransaction{ + EnforceAtHeight: int64(enforceAtHeight), + Hex: hex.EncodeToString(rawHex), + }, + } + details = append(details, detail) + + a.Transactions = details + a.Config().Services.Log.Infof("ConfiscateTransaction alert; enforceAt [%d]; hex [%s]", enforceAtHeight, hex.EncodeToString(rawHex)) + return nil } // Do executes the alert -func (a *AlertMessageConfiscateUtxo) Do(_ context.Context) error { +func (a *AlertMessageConfiscateTransaction) Do(ctx context.Context) error { + res, err := a.Config().Services.Node.AddToConfiscationTransactionWhitelist(ctx, a.Transactions) + if err != nil { + return err + } + if len(res.NotProcessed) > 0 { + // we can safely assume this is just one not processed tx because we are only publishing one tx with the alert right now + return fmt.Errorf("confiscation alert RPC response returned an error; reason: %s", res.NotProcessed[0].Reason) + } return nil } + +// ToJSON is the alert in JSON format +func (a *AlertMessageConfiscateTransaction) ToJSON(_ context.Context) []byte { + m := a.ProcessAlertMessage() + // TODO: Come back and add a message interface for each alert + _ = m.Read(a.GetRawMessage()) + data, err := json.MarshalIndent(m, "", " ") + if err != nil { + return []byte{} + } + return data +} + +// MessageString executes the alert +func (a *AlertMessageConfiscateTransaction) MessageString() string { + return fmt.Sprintf("Adding confiscation transaction [%x] to whitelist enforcing at height [%d].", a.Transactions[0].ConfiscationTransaction.Hex, a.Transactions[0].ConfiscationTransaction.EnforceAtHeight) +} diff --git a/app/models/alert_message_freeze_utxo.go b/app/models/alert_message_freeze_utxo.go index 2b30727..1f28b84 100644 --- a/app/models/alert_message_freeze_utxo.go +++ b/app/models/alert_message_freeze_utxo.go @@ -1,19 +1,109 @@ package models -import "context" +import ( + "context" + "encoding/binary" + "encoding/hex" + "encoding/json" + "fmt" + + "github.com/libsv/go-bn/models" +) // AlertMessageFreezeUtxo is the message for freezing UTXOs type AlertMessageFreezeUtxo struct { AlertMessage - // TODO finish building out this alert type + Funds []models.Fund +} + +// Fund is the struct defining funds to freeze +type Fund struct { + TransactionOutID [32]byte + Vout uint64 + EnforceAtHeightStart uint64 + EnforceAtHeightEnd uint64 + PolicyExpiresWithConsensus bool +} + +// Serialize creates the raw hex string of the fund +func (f *Fund) Serialize() []byte { + raw := []byte{} + raw = append(raw, f.TransactionOutID[:]...) + raw = binary.LittleEndian.AppendUint64(raw, f.Vout) + raw = binary.LittleEndian.AppendUint64(raw, f.EnforceAtHeightStart) + raw = binary.LittleEndian.AppendUint64(raw, f.EnforceAtHeightEnd) + expire := uint8(0) + if f.PolicyExpiresWithConsensus { + expire = uint8(1) + } + raw = append(raw, expire) + return raw } // Read reads the message -func (a *AlertMessageFreezeUtxo) Read(_ []byte) error { +func (a *AlertMessageFreezeUtxo) Read(raw []byte) error { + if len(raw) < 57 { + return fmt.Errorf("freeze alert is less than 57 bytes, got %d bytes; raw: %x", len(raw), raw) + } + if len(raw)%57 != 0 { + return fmt.Errorf("freeze alert is not a multiple of 57 bytes, got %d bytes; raw: %x", len(raw), raw) + } + fundCount := len(raw) / 57 + funds := []models.Fund{} + for i := 0; i < fundCount; i++ { + fund := Fund{ + TransactionOutID: [32]byte(raw[0:32]), + Vout: binary.LittleEndian.Uint64(raw[32:40]), + EnforceAtHeightStart: binary.LittleEndian.Uint64(raw[40:48]), + EnforceAtHeightEnd: binary.LittleEndian.Uint64(raw[48:56]), + } + enforceByte := raw[56] + + if enforceByte != uint8(0) { + fund.PolicyExpiresWithConsensus = true + } + funds = append(funds, models.Fund{ + TxOut: models.TxOut{ + TxId: hex.EncodeToString(fund.TransactionOutID[:]), + Vout: int(fund.Vout), + }, + EnforceAtHeight: []models.Enforce{ + { + Start: int(fund.EnforceAtHeightStart), + Stop: int(fund.EnforceAtHeightEnd), + }, + }, + PolicyExpiresWithConsensus: fund.PolicyExpiresWithConsensus, + }) + raw = raw[57:] + } + a.Funds = funds + return nil } // Do performs the message -func (a *AlertMessageFreezeUtxo) Do(_ context.Context) error { +func (a *AlertMessageFreezeUtxo) Do(ctx context.Context) error { + _, err := a.Config().Services.Node.AddToConsensusBlacklist(ctx, a.Funds) + if err != nil { + return err + } return nil } + +// ToJSON is the alert in JSON format +func (a *AlertMessageFreezeUtxo) ToJSON(_ context.Context) []byte { + m := a.ProcessAlertMessage() + // TODO: Come back and add a message interface for each alert + _ = m.Read(a.GetRawMessage()) + data, err := json.MarshalIndent(m, "", " ") + if err != nil { + return []byte{} + } + return data +} + +// MessageString executes the alert +func (a *AlertMessageFreezeUtxo) MessageString() string { + return fmt.Sprintf("Freezing utxo id [%x]; vout: [%d], enforcing at height start [%d], end [%d].", a.Funds[0].TxOut.TxId, a.Funds[0].TxOut.Vout, a.Funds[0].EnforceAtHeight[0].Start, a.Funds[0].EnforceAtHeight[0].Stop) +} diff --git a/app/models/alert_message_informational.go b/app/models/alert_message_informational.go index eb3da3a..5729167 100644 --- a/app/models/alert_message_informational.go +++ b/app/models/alert_message_informational.go @@ -3,6 +3,7 @@ package models import ( "bytes" "context" + "encoding/json" "errors" "fmt" @@ -48,3 +49,20 @@ func (a *AlertMessageInformational) Do(_ context.Context) error { a.Config().Services.Log.Infof("[informational alert]: %s", a.Message) return nil } + +// ToJSON is the alert in JSON format +func (a *AlertMessageInformational) ToJSON(_ context.Context) []byte { + m := a.ProcessAlertMessage() + // TODO: Come back and add a message interface for each alert + _ = m.Read(a.GetRawMessage()) + data, err := json.MarshalIndent(m, "", " ") + if err != nil { + return []byte{} + } + return data +} + +// MessageString executes the alert +func (a *AlertMessageInformational) MessageString() string { + return fmt.Sprintf("Informational: %s", a.Message) +} diff --git a/app/models/alert_message_informational_test.go b/app/models/alert_message_informational_test.go index acba110..2668cda 100644 --- a/app/models/alert_message_informational_test.go +++ b/app/models/alert_message_informational_test.go @@ -50,3 +50,38 @@ func TestAlertMessageInformational_Read(t *testing.T) { }) } } + +func TestAlertMessageInformational_MessageString(t *testing.T) { + type fields struct { + AlertMessage AlertMessage + MessageLength uint64 + Message []byte + } + tests := []struct { + name string + fields fields + want string + }{{ + name: "test valid message", + fields: fields{ + AlertMessage: AlertMessage{ + Raw: "010000001b00000067b5bd6500000000010000000774657374696e67202214d4892217b450eedfb33dd901951e80557ea10d19a59f8a566f733b1ab7107b77d388a9f2fac6602b7258cbcb0ac11c9a6dd0b5687cb9508bcfa5dbd6ce901f4672d99e36978856f2d2794c4c48d353a0b45357d08991147f9e8803a0b90a5f01e85739f36eab32765fe2190b1625e3f5d6c41319da3da803b60be472bf2c011f3784e3d3504c93be28e32e9108aead94cb515bb4813303e6a14735bcca87e451487b222198a9ba3ea0c984e3fbd95e35ba1607c5c74224af6083185a17ea7ff9", + }, + Message: []byte("testing"), + }, + want: "Informational: testing", + }, + + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &AlertMessageInformational{ + AlertMessage: tt.fields.AlertMessage, + //MessageLength: tt.fields.MessageLength, + Message: tt.fields.Message, + } + assert.Equalf(t, tt.want, a.MessageString(), "MessageString()") + }) + } +} diff --git a/app/models/alert_message_invalidate_block.go b/app/models/alert_message_invalidate_block.go index 184c3c0..b99e8c0 100644 --- a/app/models/alert_message_invalidate_block.go +++ b/app/models/alert_message_invalidate_block.go @@ -3,6 +3,7 @@ package models import ( "bytes" "context" + "encoding/json" "fmt" "github.com/libsv/go-bt/v2/chainhash" @@ -42,6 +43,7 @@ func (a *AlertMessageInvalidateBlock) Read(alert []byte) error { a.ReasonLength = length a.Reason = msg a.BlockHash = blockHash + a.Config().Services.Log.Infof("InvalidateBlock alert; hash [%s]; reason [%s]", a.BlockHash, a.Reason) return nil } @@ -49,3 +51,20 @@ func (a *AlertMessageInvalidateBlock) Read(alert []byte) error { func (a *AlertMessageInvalidateBlock) Do(ctx context.Context) error { return a.Config().Services.Node.InvalidateBlock(ctx, a.BlockHash.String()) } + +// ToJSON is the alert in JSON format +func (a *AlertMessageInvalidateBlock) ToJSON(_ context.Context) []byte { + m := a.ProcessAlertMessage() + // TODO: Come back and add a message interface for each alert + _ = m.Read(a.GetRawMessage()) + data, err := json.MarshalIndent(m, "", " ") + if err != nil { + return []byte{} + } + return data +} + +// MessageString executes the alert +func (a *AlertMessageInvalidateBlock) MessageString() string { + return fmt.Sprintf("Invalidating block hash [%s]; reason [%s].", a.BlockHash, a.Reason) +} diff --git a/app/models/alert_message_set_keys.go b/app/models/alert_message_set_keys.go index a176564..93703e8 100644 --- a/app/models/alert_message_set_keys.go +++ b/app/models/alert_message_set_keys.go @@ -4,7 +4,12 @@ import ( "bytes" "context" "encoding/hex" + "encoding/json" + "errors" "fmt" + "time" + + "github.com/mrz1836/go-datastore" "github.com/bitcoin-sv/alert-system/app/models/model" ) @@ -49,6 +54,13 @@ func (a *AlertMessageSetKeys) Do(ctx context.Context) error { } for _, key := range a.Keys { pk := NewPublicKey(model.WithAllDependencies(a.Config())) + conditions := map[string]interface{}{ + "key": hex.EncodeToString(key[:]), + } + err := model.Get(ctx, pk, conditions, 5*time.Second, false) + if !errors.Is(err, datastore.ErrNoResults) && err != nil { + return err + } pk.Key = hex.EncodeToString(key[:]) pk.Active = true pk.LastUpdateHash = a.Hash @@ -58,3 +70,20 @@ func (a *AlertMessageSetKeys) Do(ctx context.Context) error { } return nil } + +// ToJSON is the alert in JSON format +func (a *AlertMessageSetKeys) ToJSON(_ context.Context) []byte { + m := a.ProcessAlertMessage() + // TODO: Come back and add a message interface for each alert + _ = m.Read(a.GetRawMessage()) + data, err := json.MarshalIndent(m, "", " ") + if err != nil { + return []byte{} + } + return data +} + +// MessageString executes the alert +func (a *AlertMessageSetKeys) MessageString() string { + return fmt.Sprintf("Setting keys: %x, %x, %x, %x, %x", a.Keys[0], a.Keys[1], a.Keys[2], a.Keys[3], a.Keys[4]) +} diff --git a/app/models/alert_message_unban_peer.go b/app/models/alert_message_unban_peer.go index da3d3ac..510802e 100644 --- a/app/models/alert_message_unban_peer.go +++ b/app/models/alert_message_unban_peer.go @@ -3,6 +3,7 @@ package models import ( "bytes" "context" + "encoding/json" "fmt" "github.com/libsv/go-p2p/wire" @@ -62,3 +63,20 @@ func (a *AlertMessageUnbanPeer) Read(alert []byte) error { func (a *AlertMessageUnbanPeer) Do(ctx context.Context) error { return a.Config().Services.Node.UnbanPeer(ctx, string(a.Peer)) } + +// ToJSON is the alert in JSON format +func (a *AlertMessageUnbanPeer) ToJSON(_ context.Context) []byte { + m := a.ProcessAlertMessage() + // TODO: Come back and add a message interface for each alert + _ = m.Read(a.GetRawMessage()) + data, err := json.MarshalIndent(m, "", " ") + if err != nil { + return []byte{} + } + return data +} + +// MessageString executes the alert +func (a *AlertMessageUnbanPeer) MessageString() string { + return fmt.Sprintf("Unbanning peer [%s]; reason [%s].", a.Peer, a.Reason) +} diff --git a/app/models/alert_message_unfreeze_utxo.go b/app/models/alert_message_unfreeze_utxo.go index ad7c4b8..0f1b654 100644 --- a/app/models/alert_message_unfreeze_utxo.go +++ b/app/models/alert_message_unfreeze_utxo.go @@ -1,19 +1,87 @@ package models -import "context" +import ( + "context" + "encoding/binary" + "encoding/hex" + "encoding/json" + "fmt" + + "github.com/libsv/go-bn/models" +) // AlertMessageUnfreezeUtxo is the message for unfreezing a UTXO type AlertMessageUnfreezeUtxo struct { AlertMessage // TODO finish building out this alert type + Funds []models.Fund } // Read reads the message from the byte slice -func (a *AlertMessageUnfreezeUtxo) Read(_ []byte) error { +func (a *AlertMessageUnfreezeUtxo) Read(raw []byte) error { + if len(raw) < 57 { + return fmt.Errorf("unfreeze alert is less than 57 bytes, got %d bytes; raw: %x", len(raw), raw) + } + if len(raw)%57 != 0 { + return fmt.Errorf("unfreeze alert is not a multiple of 57 bytes, got %d bytes; raw: %x", len(raw), raw) + } + fundCount := len(raw) / 57 + funds := []models.Fund{} + for i := 0; i < fundCount; i++ { + fund := Fund{ + TransactionOutID: [32]byte(raw[0:32]), + Vout: binary.LittleEndian.Uint64(raw[32:40]), + EnforceAtHeightStart: binary.LittleEndian.Uint64(raw[40:48]), + EnforceAtHeightEnd: binary.LittleEndian.Uint64(raw[48:56]), + } + enforceByte := raw[56] + + if enforceByte != uint8(0) { + fund.PolicyExpiresWithConsensus = true + } + funds = append(funds, models.Fund{ + TxOut: models.TxOut{ + TxId: hex.EncodeToString(fund.TransactionOutID[:]), + Vout: int(fund.Vout), + }, + EnforceAtHeight: []models.Enforce{ + { + Start: int(fund.EnforceAtHeightStart), + Stop: int(fund.EnforceAtHeightEnd), + }, + }, + PolicyExpiresWithConsensus: fund.PolicyExpiresWithConsensus, + }) + raw = raw[57:] + } + a.Funds = funds + return nil + } // Do executes the message -func (a *AlertMessageUnfreezeUtxo) Do(_ context.Context) error { +func (a *AlertMessageUnfreezeUtxo) Do(ctx context.Context) error { + _, err := a.Config().Services.Node.AddToConsensusBlacklist(ctx, a.Funds) + if err != nil { + return err + } return nil } + +// ToJSON is the alert in JSON format +func (a *AlertMessageUnfreezeUtxo) ToJSON(_ context.Context) []byte { + m := a.ProcessAlertMessage() + // TODO: Come back and add a message interface for each alert + _ = m.Read(a.GetRawMessage()) + data, err := json.MarshalIndent(m, "", " ") + if err != nil { + return []byte{} + } + return data +} + +// MessageString executes the alert +func (a *AlertMessageUnfreezeUtxo) MessageString() string { + return fmt.Sprintf("Unfreezing utxo id [%x]; vout: [%d], by setting enforce height at start [%d], end [%d].", a.Funds[0].TxOut.TxId, a.Funds[0].TxOut.Vout, a.Funds[0].EnforceAtHeight[0].Start, a.Funds[0].EnforceAtHeight[0].Stop) +} diff --git a/app/models/alert_types.go b/app/models/alert_types.go index 1ba1839..77cac4d 100644 --- a/app/models/alert_types.go +++ b/app/models/alert_types.go @@ -3,6 +3,29 @@ package models // AlertType is the type of alert type AlertType uint32 +// Name returns the name of the alert type as a string +func (a AlertType) Name() string { + switch a { + case AlertTypeInformational: + return "Informational" + case AlertTypeFreezeUtxo: + return "Freeze" + case AlertTypeUnfreezeUtxo: + return "Unfreeze" + case AlertTypeConfiscateUtxo: + return "Confiscate" + case AlertTypeBanPeer: + return "Ban Peer" + case AlertTypeUnbanPeer: + return "Unban Peer" + case AlertTypeInvalidateBlock: + return "Invalidate Block" + case AlertTypeSetKeys: + return "Set Keys" + } + return "" +} + // AlertTypeInformational an alert type for informational alerts const AlertTypeInformational AlertType = 0x01 diff --git a/app/models/genesis_alert.go b/app/models/genesis_alert.go index b359c10..29ba26f 100644 --- a/app/models/genesis_alert.go +++ b/app/models/genesis_alert.go @@ -59,6 +59,7 @@ func CreateGenesisAlert(ctx context.Context, opts ...model.Options) error { newAlert.SequenceNumber = 0 newAlert.timestamp = uint64(time.Date(2923, time.November, 1, 1, 1, 1, 1, time.UTC).Unix()) newAlert.version = 1 + newAlert.Processed = true // Serialize the data newAlert.SerializeData() diff --git a/app/models/model/model.go b/app/models/model/model.go index 3756b4a..98c1b73 100644 --- a/app/models/model/model.go +++ b/app/models/model/model.go @@ -3,12 +3,12 @@ package model import ( "context" + "log" "time" "github.com/bitcoin-sv/alert-system/app/config" "github.com/mrz1836/go-datastore" customTypes "github.com/mrz1836/go-datastore/custom_types" - "github.com/ordishs/gocore" ) // Model is the generic model field(s) and interface(s) @@ -71,7 +71,7 @@ func NewBaseModel(name Name, opts ...Options) (m *Model) { // Set default logger IF NOT SET via options if m.logger == nil { m.logger = &config.ExtendedLogger{ - Logger: gocore.Log(config.ApplicationName), + Logger: &log.Logger{}, } } diff --git a/app/models/model/model_metadata_test.go b/app/models/model/model_metadata_test.go index 55b0874..3e76c55 100644 --- a/app/models/model/model_metadata_test.go +++ b/app/models/model/model_metadata_test.go @@ -234,7 +234,7 @@ func TestMetadata_GormDBDataType(t *testing.T) { assert.Equal(t, datastore.JSON, m.GormDBDataType(db, nil)) }) - t.Run("postgres dialector", func(t *testing.T) { + t.Run("postgres dialector", func(_ *testing.T) { /*dsn := "host=localhost user=postgres password=gorm dbname=gorm port=9920 sslmode=disable TimeZone=Asia/Shanghai" db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) require.NotNil(t, db) diff --git a/app/models/model/model_options_test.go b/app/models/model/model_options_test.go index f935c5f..555d58e 100644 --- a/app/models/model/model_options_test.go +++ b/app/models/model/model_options_test.go @@ -1,10 +1,11 @@ package model import ( + "log" + "os" "testing" "github.com/bitcoin-sv/alert-system/app/config" - "github.com/ordishs/gocore" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -82,7 +83,7 @@ func TestWithLogger(t *testing.T) { t.Run("valid logger", func(t *testing.T) { l := &config.ExtendedLogger{ - Logger: gocore.Log("test"), + Logger: log.New(os.Stdout, "alert-system: ", log.LstdFlags), } require.NotNil(t, l) opt := WithLogger(l) diff --git a/app/models/models_test.go b/app/models/models_test.go index ce71f06..c383153 100644 --- a/app/models/models_test.go +++ b/app/models/models_test.go @@ -2,10 +2,10 @@ package models import ( "context" + "os" "testing" "github.com/bitcoin-sv/alert-system/app/config" - "github.com/bitcoin-sv/alert-system/app/tester" "github.com/stretchr/testify/suite" ) @@ -19,11 +19,11 @@ type TestSuite struct { func (ts *TestSuite) SetupSuite() { // Set the env to test - tester.SetupEnv(ts.T()) + err := os.Setenv(config.EnvironmentKey, config.EnvironmentTest) + ts.Require().NoError(err) // Load the configuration - var err error - ts.Dependencies, err = config.LoadConfig(context.Background(), BaseModels, true) + ts.Dependencies, err = config.LoadDependencies(context.Background(), BaseModels, true) ts.Require().NoError(err) } @@ -34,19 +34,17 @@ func (ts *TestSuite) TearDownSuite() { if ts.Dependencies != nil { ts.Dependencies.CloseAll(context.Background()) } - - tester.TeardownEnv(ts.T()) } // SetupTest runs before each test func (ts *TestSuite) SetupTest() { // Set the env to test - tester.SetupEnv(ts.T()) + err := os.Setenv(config.EnvironmentKey, config.EnvironmentTest) + ts.Require().NoError(err) // Load the services - var err error - ts.Dependencies, err = config.LoadConfig(context.Background(), BaseModels, true) + ts.Dependencies, err = config.LoadDependencies(context.Background(), BaseModels, true) ts.Require().NoError(err) } @@ -55,8 +53,6 @@ func (ts *TestSuite) TearDownTest() { if ts.Dependencies != nil { ts.Dependencies.CloseAll(context.Background()) } - - tester.TeardownEnv(ts.T()) } // TestTestSuiteApp kick-starts all suite tests diff --git a/app/p2p/dht.go b/app/p2p/dht.go index da9ff7e..2dd971f 100644 --- a/app/p2p/dht.go +++ b/app/p2p/dht.go @@ -6,9 +6,11 @@ import ( "time" "github.com/bitcoin-sv/alert-system/app/config" - dht "github.com/libp2p/go-libp2p-kad-dht" "github.com/libp2p/go-libp2p/core/peer" + "github.com/multiformats/go-multiaddr" + + dht "github.com/libp2p/go-libp2p-kad-dht" ) // initDHT will initialize the DHT @@ -30,38 +32,44 @@ func (s *Server) initDHT(ctx context.Context) (*dht.IpfsDHT, error) { return nil, err } - // Connect to the chosen ipfs nodes - var pubPeer multiaddr.Multiaddr - if pubPeer, err = multiaddr.NewMultiaddr(s.config.P2PBootstrapPeer); err != nil { - return nil, err - } - // Append the bootstrap nodes - peers := []multiaddr.Multiaddr{pubPeer} - peers = append(peers, dht.DefaultBootstrapPeers...) + peers := dht.DefaultBootstrapPeers + if s.config.P2P.BootstrapPeer != "" { + // Connect to the chosen ipfs nodes + var pubPeer multiaddr.Multiaddr + if pubPeer, err = multiaddr.NewMultiaddr(s.config.P2P.BootstrapPeer); err != nil { + return nil, err + } + peers = append(peers, pubPeer) + } // Connect to the chosen ipfs nodes - var connected = false + connected := false for !connected { - var wg sync.WaitGroup - for _, peerAddr := range peers { - var peerInfo *peer.AddrInfo - if peerInfo, err = peer.AddrInfoFromP2pAddr(peerAddr); err != nil { - return nil, err - } - wg.Add(1) - go func(logger config.LoggerInterface) { - defer wg.Done() - if err = s.host.Connect(ctx, *peerInfo); err != nil { - logger.Errorf("bootstrap warning: %s", err.Error()) - return + select { + case <-s.quitPeerInitializationChannel: + return kademliaDHT, nil + default: + var wg sync.WaitGroup + for _, peerAddr := range peers { + var peerInfo *peer.AddrInfo + if peerInfo, err = peer.AddrInfoFromP2pAddr(peerAddr); err != nil { + return nil, err } - logger.Infof("connected to peer %v", peerInfo.ID) - connected = true - }(logger) + wg.Add(1) + go func(logger config.LoggerInterface) { + defer wg.Done() + if err = s.host.Connect(ctx, *peerInfo); err != nil { + logger.Errorf("bootstrap warning: %s", err.Error()) + return + } + logger.Infof("connected to peer %v", peerInfo.ID) + connected = true + }(logger) + } + time.Sleep(1 * time.Second) + wg.Wait() } - time.Sleep(1 * time.Second) - wg.Wait() } return kademliaDHT, nil diff --git a/app/p2p/server.go b/app/p2p/server.go index 4bde114..41cc88d 100644 --- a/app/p2p/server.go +++ b/app/p2p/server.go @@ -8,6 +8,8 @@ import ( "os" "time" + "github.com/libp2p/go-libp2p/core/discovery" + dht "github.com/libp2p/go-libp2p-kad-dht" "github.com/bitcoin-sv/alert-system/app/config" @@ -40,14 +42,18 @@ type ServerOptions struct { // Server is the P2P server type Server struct { // alertKeyTopicName string - connected bool - config *config.Config - host host.Host - privateKey *crypto.PrivKey - subscriptions map[string]*pubsub.Subscription - topicNames []string - topics map[string]*pubsub.Topic - dht *dht.IpfsDHT + connected bool + config *config.Config + host host.Host + privateKey *crypto.PrivKey + subscriptions map[string]*pubsub.Subscription + topicNames []string + topics map[string]*pubsub.Topic + dht *dht.IpfsDHT + quitAlertProcessingChannel chan bool + quitPeerDiscoveryChannel chan bool + quitPeerInitializationChannel chan bool + //peers []peer.AddrInfo } // NewServer will create a new server @@ -58,11 +64,11 @@ func NewServer(o ServerOptions) (*Server, error) { o.Config.Services.Log.Debug("creating P2P service") // Attempt to read the private key from the file - pk, err := readPrivateKey(o.Config.P2PPrivateKeyPath) + pk, err := readPrivateKey(o.Config.P2P.PrivateKeyPath) if err != nil { // If the file doesn't exist, generate a new private key - if pk, err = generatePrivateKey(o.Config.P2PPrivateKeyPath); err != nil { + if pk, err = generatePrivateKey(o.Config.P2P.PrivateKeyPath); err != nil { return nil, err } } @@ -70,25 +76,27 @@ func NewServer(o ServerOptions) (*Server, error) { // Create a new host var h host.Host if h, err = libp2p.New( - libp2p.ListenAddrStrings(fmt.Sprintf("/ip4/%s/tcp/%s", o.Config.P2PIP, o.Config.P2PPort)), + libp2p.ListenAddrStrings(fmt.Sprintf("/ip4/%s/tcp/%s", o.Config.P2P.IP, o.Config.P2P.Port)), libp2p.Identity(*pk), + libp2p.EnableHolePunching(), ); err != nil { return nil, err } // Print out the peer ID and addresses o.Config.Services.Log.Debugf("peer ID: %s", h.ID().String()) - o.Config.Services.Log.Debug("connect to me on:") + o.Config.Services.Log.Info("connect to me on:") for _, addr := range h.Addrs() { - o.Config.Services.Log.Debugf(" %s/p2p/%s", addr, h.ID().String()) + o.Config.Services.Log.Infof(" %s/p2p/%s", addr, h.ID().String()) } // Return the server return &Server{ - host: h, - topicNames: o.TopicNames, - privateKey: pk, - config: o.Config, + host: h, + topicNames: o.TopicNames, + privateKey: pk, + config: o.Config, + quitPeerInitializationChannel: make(chan bool), }, nil } @@ -109,10 +117,8 @@ func (s *Server) Start(ctx context.Context) error { dutil.Advertise(ctx, routingDiscovery, topicName) } - go func() { - // todo handle errors - _ = s.discoverPeers(ctx, s.topicNames, routingDiscovery) - }() + s.quitPeerDiscoveryChannel = s.RunPeerDiscovery(ctx, routingDiscovery) + s.quitAlertProcessingChannel = s.RunAlertProcessingCron(ctx) ps, err := pubsub.NewGossipSub(ctx, s.host, pubsub.WithDiscovery(routingDiscovery)) if err != nil { @@ -121,7 +127,7 @@ func (s *Server) Start(ctx context.Context) error { topics := map[string]*pubsub.Topic{} subscriptions := map[string]*pubsub.Subscription{} - s.host.SetStreamHandler(protocol.ID(s.config.P2PAlertSystemProtocolID), func(stream network.Stream) { + s.host.SetStreamHandler(protocol.ID(s.config.P2P.AlertSystemProtocolID), func(stream network.Stream) { t := StreamThread{ stream: stream, config: s.config, @@ -139,6 +145,10 @@ func (s *Server) Start(ctx context.Context) error { //_ = stream.Close() }) + s.config.Services.Log.Debugf("stream handler set") + for !s.connected { + time.Sleep(5 * time.Second) + } for _, topicName := range s.topicNames { var topic *pubsub.Topic if topic, err = ps.Join(topicName); err != nil { @@ -157,9 +167,7 @@ func (s *Server) Start(ctx context.Context) error { } s.topics = topics s.subscriptions = subscriptions - - s.config.Services.Log.Info("p2p service start ending") - + s.config.Services.Log.Infof("P2P successfully started") go func() { for { //nolint:gosimple // This is the only way to perform this loop at the moment select { @@ -181,9 +189,101 @@ func (s *Server) Connected() bool { func (s *Server) Stop(_ context.Context) error { // todo there needs to be a way to stop the server s.config.Services.Log.Info("stopping P2P service") + s.quitPeerDiscoveryChannel <- true + s.quitAlertProcessingChannel <- true + s.quitPeerInitializationChannel <- true return nil } +// RunAlertProcessingCron starts a cron job to attempt to retry unprocessed alerts +func (s *Server) RunAlertProcessingCron(ctx context.Context) chan bool { + ticker := time.NewTicker(s.config.AlertProcessingInterval) + quit := make(chan bool, 1) + go func() { + for { + select { + case <-ticker.C: + err := s.processAlerts(ctx) + if err != nil { + s.config.Services.Log.Errorf("error processing alerts: %v", err.Error()) + } + case <-quit: + ticker.Stop() + return + } + } + }() + return quit +} + +// processAlerts performs the alert processing +func (s *Server) processAlerts(ctx context.Context) error { + alerts, err := models.GetAllUnprocessedAlerts(ctx, nil, model.WithAllDependencies(s.config)) + if err != nil { + return err + } + s.config.Services.Log.Infof("Attempting to process %d failed alerts", len(alerts)) + success := 0 + for _, alert := range alerts { + alert.SetOptions(model.WithAllDependencies(s.config)) + // Serialize the alert data and hash + err := alert.ReadRaw() + if err != nil { + continue + } + alert.SerializeData() + // Process the alert + ak := alert.ProcessAlertMessage() + if ak == nil { + continue + } + if err = ak.Read(alert.GetRawMessage()); err != nil { + return err + } + s.config.Services.Log.Debugf("attempting to process alert %d of type %d", alert.SequenceNumber, alert.GetAlertType()) + alert.Processed = true + if err = ak.Do(ctx); err != nil { + s.config.Services.Log.Errorf("failed to process alert %d; err: %v", alert.SequenceNumber, err.Error()) + alert.Processed = false + } + + if alert.Processed { + success++ + // Save the alert + if err = alert.Save(ctx); err != nil { + return err + } + } + } + s.config.Services.Log.Infof("Processed %d failed alerts", success) + return nil +} + +// RunPeerDiscovery starts a cron job to resync peers and update routable peers +func (s *Server) RunPeerDiscovery(ctx context.Context, routingDiscovery *drouting.RoutingDiscovery) chan bool { + ticker := time.NewTicker(s.config.P2P.PeerDiscoveryInterval) + quit := make(chan bool, 1) + go func() { + err := s.discoverPeers(ctx, routingDiscovery) + if err != nil { + s.config.Services.Log.Errorf("error discovering peers: %v", err.Error()) + } + for { + select { + case <-ticker.C: + err := s.discoverPeers(ctx, routingDiscovery) + if err != nil { + s.config.Services.Log.Errorf("error discovering peers: %v", err.Error()) + } + case <-quit: + ticker.Stop() + return + } + } + }() + return quit +} + // generatePrivateKey generates a private key and stores it in `private_key` file func generatePrivateKey(filePath string) (*crypto.PrivKey, error) { // Generate a new key pair @@ -234,16 +334,18 @@ func (s *Server) Topics() map[string]*pubsub.Topic { } // discoverPeers will discover peers -func (s *Server) discoverPeers(ctx context.Context, tn []string, routingDiscovery *drouting.RoutingDiscovery) error { +func (s *Server) discoverPeers(ctx context.Context, routingDiscovery *drouting.RoutingDiscovery) error { + s.config.Services.Log.Infof("Running peer discovery at %s", time.Now().String()) + // Look for others who have announced and attempt to connect to them - anyConnected := false - for !anyConnected { - for _, topicName := range tn { + connected := 0 + for connected < 2 { + for _, topicName := range s.topicNames { s.config.Services.Log.Debugf("searching for peers for topic %s..\n", topicName) var peerChan <-chan peer.AddrInfo var err error - if peerChan, err = routingDiscovery.FindPeers(ctx, topicName); err != nil { + if peerChan, err = routingDiscovery.FindPeers(ctx, topicName, discovery.TTL(1*time.Minute)); err != nil { return err } @@ -256,6 +358,8 @@ func (s *Server) discoverPeers(ctx context.Context, tn []string, routingDiscover } // Failed to connect to peer + s.config.Services.Log.Debugf("attempting connection to %s", foundPeer.ID.String()) + if err = s.host.Connect(ctx, foundPeer); err != nil { // we fail to connect to a lot of peers. Just ignore it for now. s.config.Services.Log.Debugf("failed connecting to %s, error: %s", foundPeer.ID.String(), err.Error()) @@ -267,27 +371,30 @@ func (s *Server) discoverPeers(ctx context.Context, tn []string, routingDiscover // Open a stream to the peer var stream network.Stream - if stream, err = s.host.NewStream(ctx, foundPeer.ID, protocol.ID(s.config.P2PAlertSystemProtocolID)); err != nil { - s.config.Services.Log.Debugf("failed new stream to %s", foundPeer.ID.String(), ", error: %s", err.Error()) + if stream, err = s.host.NewStream(ctx, foundPeer.ID, protocol.ID(s.config.P2P.AlertSystemProtocolID)); err != nil { + s.config.Services.Log.Debugf("failed new stream to %s error: %s", foundPeer.ID.String(), err.Error()) continue } // Sync the stream thread t := StreamThread{ - config: s.config, - ctx: ctx, - peer: foundPeer.ID, - stream: stream, + config: s.config, + ctx: ctx, + peer: foundPeer.ID, + stream: stream, + quitChannel: s.quitPeerDiscoveryChannel, } // Sync the stream thread if err = t.Sync(ctx); err != nil { - s.config.Services.Log.Debugf("failed to start stream thread to %s", foundPeer.ID.String(), ", error: %s", err.Error()) + s.config.Services.Log.Debugf("failed to start stream thread to %s error: %s", foundPeer.ID.String(), err.Error()) continue } + s.config.Services.Log.Infof("successfully synced up to %d from peer %s", t.LatestSequence(), foundPeer.ID.String()) + // Set the flag - anyConnected = true + connected++ } time.Sleep(1 * time.Second) } @@ -297,14 +404,18 @@ func (s *Server) discoverPeers(ctx context.Context, tn []string, routingDiscover s.config.Services.Log.Debugf("peer discovery complete") s.config.Services.Log.Debugf("connected to %d peers\n", len(s.host.Network().Peers())) s.config.Services.Log.Debugf("peerstore has %d peers\n", len(s.host.Peerstore().Peers())) + s.config.Services.Log.Infof("Successfully discovered %d active peers at %s", connected, time.Now().String()) s.connected = true return nil } // Subscribe will subscribe to the alert system func (s *Server) Subscribe(ctx context.Context, subscriber *pubsub.Subscription, hostID peer.ID) { + s.config.Services.Log.Infof("subscribing to %s topic", subscriber.Topic()) for { + msg, err := subscriber.Next(ctx) + if err != nil { s.config.Services.Log.Infof("error subscribing via next: %s", err.Error()) continue @@ -370,16 +481,17 @@ func (s *Server) Subscribe(ctx context.Context, subscriber *pubsub.Subscription, s.config.Services.Log.Errorf("failed to read message: %s", err.Error()) continue } + ak.Processed = true // Perform alert action if err = am.Do(ctx); err != nil { - s.config.Services.Log.Infof("failed to do alert action: %s", err.Error()) - continue + s.config.Services.Log.Errorf("failed to do alert action: %s", err.Error()) + ak.Processed = false } // Save the alert message if err = ak.Save(ctx); err != nil { - s.config.Services.Log.Infof("failed to save alert message: %s", err.Error()) + s.config.Services.Log.Errorf("failed to save alert message: %s", err.Error()) } s.config.Services.Log.Infof("[%s] got alert type: %d, from: %s", subscriber.Topic(), ak.GetAlertType(), msg.ReceivedFrom.String()) diff --git a/app/p2p/thread.go b/app/p2p/thread.go index 89335d3..bd91662 100644 --- a/app/p2p/thread.go +++ b/app/p2p/thread.go @@ -3,7 +3,9 @@ package p2p import ( "context" "encoding/hex" + "fmt" "math" + "time" "github.com/bitcoin-sv/alert-system/app/config" "github.com/bitcoin-sv/alert-system/app/models" @@ -27,6 +29,12 @@ type StreamThread struct { myLatestSequence uint32 peer peer.ID stream network.Stream + quitChannel chan bool +} + +// LatestSequence will return the threads latest sequence +func (s *StreamThread) LatestSequence() uint32 { + return s.latestSequence } // Sync will start the thread @@ -57,70 +65,93 @@ func (s *StreamThread) Sync(ctx context.Context) error { return err } - s.config.Services.Log.Infof("requested latest sequence in stream %s", s.stream.ID()) + s.config.Services.Log.Debugf("requested latest sequence in stream %s", s.stream.ID()) return s.ProcessSyncMessage(ctx) + } // ProcessSyncMessage will process the sync message func (s *StreamThread) ProcessSyncMessage(ctx context.Context) error { - for { - b, err := wire.ReadVarBytes(s.stream, 0, math.MaxUint64, config.ApplicationName) - if err != nil { - if s.stream.Conn().IsClosed() || s.stream.Stat().Transient { - return nil + done := make(chan error) + go func() { + for { + b, err := wire.ReadVarBytes(s.stream, 0, math.MaxUint64, config.ApplicationName) + if err != nil { + if s.stream.Conn().IsClosed() || s.stream.Stat().Transient { + done <- nil + return + } + s.config.Services.Log.Debugf("failed to read sync message: %s; closing stream", err.Error()) + done <- s.stream.Close() + return } - s.config.Services.Log.Debugf("failed to read sync message: %s; closing stream", err.Error()) - return s.stream.Close() - } - if len(b) == 0 { - _ = s.stream.Close() - return nil - } - var msg *SyncMessage - if msg, err = NewSyncMessageFromBytes(b); err != nil { - s.config.Services.Log.Errorf("failed to convert to sync message: %s", err.Error()) - return err - } - switch msg.Type { - case IGotLatest: - s.config.Services.Log.Infof("received latest sequence %d from peer %s", msg.SequenceNumber, s.peer.String()) - if err = s.ProcessGotLatest(ctx, msg); err != nil { - return err - } - if s.myLatestSequence == s.latestSequence { + if len(b) == 0 { _ = s.stream.Close() - return nil + done <- nil + return } - s.config.Services.Log.Infof("wrote msg requesting next sequence %d from peer %s", s.myLatestSequence+1, s.peer.String()) - case IGotSequenceNumber: - s.config.Services.Log.Infof("received IGotSequenceNumber %d from peer %s", msg.SequenceNumber, s.peer.String()) - if err = s.ProcessGotSequenceNumber(msg); err != nil { - return err + var msg *SyncMessage + if msg, err = NewSyncMessageFromBytes(b); err != nil { + s.config.Services.Log.Errorf("failed to convert to sync message: %s", err.Error()) + done <- err + return } - if s.myLatestSequence == s.latestSequence { - _ = s.stream.Close() - return nil + switch msg.Type { + case IGotLatest: + s.config.Services.Log.Debugf("received latest sequence %d from peer %s", msg.SequenceNumber, s.peer.String()) + if err = s.ProcessGotLatest(ctx, msg); err != nil { + done <- err + return + } + if s.myLatestSequence >= s.latestSequence { + _ = s.stream.Close() + done <- nil + return + } + s.config.Services.Log.Debugf("wrote msg requesting next sequence %d from peer %s", s.myLatestSequence+1, s.peer.String()) + case IGotSequenceNumber: + s.config.Services.Log.Debugf("received IGotSequenceNumber %d from peer %s", msg.SequenceNumber, s.peer.String()) + if err = s.ProcessGotSequenceNumber(msg); err != nil { + done <- err + return + } + if s.myLatestSequence == s.latestSequence { + _ = s.stream.Close() + done <- nil + return + } + s.config.Services.Log.Debugf("wrote msg requesting next sequence %d from peer %s", msg.SequenceNumber+1, s.peer.String()) + case IWantSequenceNumber: + s.config.Services.Log.Debugf("received IWantSequenceNumber %d from peer %s", msg.SequenceNumber, s.peer.String()) + if err = s.ProcessWantSequenceNumber(ctx, msg); err != nil { + done <- err + return + } + s.config.Services.Log.Debugf("wrote sequence %d to peer %s", msg.SequenceNumber, s.peer.String()) + if msg.SequenceNumber == s.myLatestSequence { + err = s.stream.Close() + done <- err + return + } + case IWantLatest: + s.config.Services.Log.Debugf("received IWantLatest from peer %s", s.peer.String()) + if err = s.ProcessWantLatest(ctx); err != nil { + done <- err + return + } + s.config.Services.Log.Debugf("wrote latest sequence %d to peer %s", s.myLatestSequence, s.peer.String()) } - s.config.Services.Log.Infof("wrote msg requesting next sequence from peer %s", s.peer.String()) - case IWantSequenceNumber: - s.config.Services.Log.Infof("received IWantSequenceNumber %d from peer %s", msg.SequenceNumber, s.peer.String()) - if err = s.ProcessWantSequenceNumber(ctx, msg); err != nil { - return err - } - s.config.Services.Log.Infof("wrote sequence %d to peer %s", msg.SequenceNumber, s.peer.String()) - if msg.SequenceNumber == s.myLatestSequence { - _ = s.stream.Close() - return nil - } - case IWantLatest: - s.config.Services.Log.Infof("received IWantLatest from peer %s", s.peer.String()) - if err = s.ProcessWantLatest(ctx); err != nil { - return err - } - s.config.Services.Log.Infof("wrote latest sequence %d to peer %s", s.myLatestSequence, s.peer.String()) } + }() + select { + case <-s.quitChannel: + return nil + case err := <-done: + return err + case <-time.After(time.Minute * 1): + return fmt.Errorf("sync from peer %s process timed out after 1 minute", s.peer.String()) } } @@ -179,14 +210,16 @@ func (s *StreamThread) ProcessGotSequenceNumber(msg *SyncMessage) error { a.SerializeData() // Process the alert (if it's a set keys alert) - if a.GetAlertType() == models.AlertTypeSetKeys { - ak := a.ProcessAlertMessage() - if err = ak.Read(a.GetRawMessage()); err != nil { - return err - } - if err = ak.Do(s.ctx); err != nil { - return err - } + // TODO: For now lets just process all alerts... why not? + // if a.GetAlertType() == models.AlertTypeSetKeys || a.GetAlertType() == models.AlertTypeInvalidateBlock { + ak := a.ProcessAlertMessage() + if err = ak.Read(a.GetRawMessage()); err != nil { + return err + } + a.Processed = true + if err = ak.Do(s.ctx); err != nil { + s.config.Services.Log.Errorf("failed to process alert %d; err: %v", a.SequenceNumber, err.Error()) + a.Processed = false } // Save the alert diff --git a/app/tester/tester.go b/app/tester/tester.go deleted file mode 100644 index 1bc7ed3..0000000 --- a/app/tester/tester.go +++ /dev/null @@ -1,45 +0,0 @@ -// Package tester is for testing the alert system -package tester - -import ( - "os" - "testing" - - "github.com/stretchr/testify/require" -) - -// SetEnv will set an environment variable -func SetEnv(t *testing.T, key, value string) { - err := os.Setenv(key, value) - require.NoError(t, err) -} - -// UnsetEnv will unset an environment variable -func UnsetEnv(t *testing.T, key string) { - err := os.Unsetenv(key) - require.NoError(t, err) -} - -// SetupEnv helper function to set up environment for testing -func SetupEnv(t *testing.T) { - SetEnv(t, "RPC_USER", "user") - SetEnv(t, "RPC_PASSWORD", "password") - SetEnv(t, "RPC_HOST", "localhost") - SetEnv(t, "P2P_PRIVATE_KEY_PATH", "/path/to/private/key") - SetEnv(t, "P2P_IP", "192.168.1.1") - SetEnv(t, "P2P_PORT", "8000") - SetEnv(t, "ALERT_WEBHOOK_URL", "https://webhook.url") - SetEnv(t, "DATABASE_PATH", "") -} - -// TeardownEnv helper function to tear down environment after testing -func TeardownEnv(t *testing.T) { - UnsetEnv(t, "RPC_USER") - UnsetEnv(t, "RPC_PASSWORD") - UnsetEnv(t, "RPC_HOST") - UnsetEnv(t, "P2P_PRIVATE_KEY_PATH") - UnsetEnv(t, "P2P_IP") - UnsetEnv(t, "P2P_PORT") - UnsetEnv(t, "ALERT_WEBHOOK_URL") - UnsetEnv(t, "DATABASE_PATH") -} diff --git a/app/tester/tester_test.go b/app/tester/tester_test.go deleted file mode 100644 index 235b7ae..0000000 --- a/app/tester/tester_test.go +++ /dev/null @@ -1,99 +0,0 @@ -package tester - -import ( - "os" - "testing" - - "github.com/stretchr/testify/require" -) - -// TestSetupEnv will test the method SetupEnv() -func TestSetEnv(t *testing.T) { - // Define a key-value pair for the test - key := "TEST_ENV_VAR" - value := "test_value" - - // Call SetEnv to set the environment variable - SetEnv(t, key, value) - - // Retrieve the value of the environment variable - setValue, exists := os.LookupEnv(key) - require.True(t, exists, "Environment variable should exist") - require.Equal(t, value, setValue, "Environment variable should have the correct value") - - // Clean up - err := os.Unsetenv(key) - require.NoError(t, err, "Unsetting environment variable should not produce an error") -} - -// TestUnsetEnv will test the method UnsetEnv() -func TestUnsetEnv(t *testing.T) { - // Define a key for the test and set the environment variable - key := "TEST_ENV_VAR" - value := "test_value" - err := os.Setenv(key, value) - require.NoError(t, err, "Setting environment variable should not produce an error") - - // Call UnsetEnv to unset the environment variable - UnsetEnv(t, key) - - // Check if the environment variable still exists - _, exists := os.LookupEnv(key) - require.False(t, exists, "Environment variable should not exist after unsetting") -} - -// TestSetupEnv will test the method SetupEnv() -func TestSetupEnv(t *testing.T) { - // Call SetupEnv to set up the environment variables - SetupEnv(t) - - defer func() { - TeardownEnv(t) - }() - - // Define a map of expected environment variables and their values - expectedVars := map[string]string{ - "RPC_USER": "user", - "RPC_PASSWORD": "password", - "RPC_HOST": "localhost", - "P2P_PRIVATE_KEY_PATH": "/path/to/private/key", - "P2P_IP": "192.168.1.1", - "P2P_PORT": "8000", - "ALERT_WEBHOOK_URL": "https://webhook.url", - "DATABASE_PATH": "", - } - - // Check each environment variable - for key, expectedValue := range expectedVars { - value, exists := os.LookupEnv(key) - require.True(t, exists, "Environment variable %s should exist", key) - require.Equal(t, expectedValue, value, "Environment variable %s should have the correct value", key) - } -} - -// TestTeardownEnv will test the method TeardownEnv() -func TestTeardownEnv(t *testing.T) { - // First, call SetupEnv to set up the environment variables - SetupEnv(t) - - // Call TeardownEnv to remove the environment variables - TeardownEnv(t) - - // List all environment variables that should have been removed - envVars := []string{ - "RPC_USER", - "RPC_PASSWORD", - "RPC_HOST", - "P2P_PRIVATE_KEY_PATH", - "P2P_IP", - "P2P_PORT", - "ALERT_WEBHOOK_URL", - "DATABASE_PATH", - } - - // Check each environment variable to ensure it has been removed - for _, key := range envVars { - _, exists := os.LookupEnv(key) - require.False(t, exists, "Environment variable %s should not exist after teardown", key) - } -} diff --git a/app/webhook/webhook.go b/app/webhook/webhook.go index fef2edc..3fba6a6 100644 --- a/app/webhook/webhook.go +++ b/app/webhook/webhook.go @@ -24,7 +24,7 @@ type Payload struct { // PostAlert sends an alert to a webhook URL using the provided http client func PostAlert(ctx context.Context, httpClient config.HTTPInterface, url string, alert *models.AlertMessage) error { - + var err error // Validate the URL length if len(url) == 0 { return fmt.Errorf("webhook URL is not configured") @@ -35,20 +35,21 @@ func PostAlert(ctx context.Context, httpClient config.HTTPInterface, url string, return fmt.Errorf("webhook URL [%s] is does not have a valid prefix", url) } - // Serialize the alert - raw := alert.Serialize() - + am := alert.ProcessAlertMessage() + err = am.Read(alert.GetRawMessage()) + if err != nil { + return err + } // Create the payload p := Payload{ AlertType: alert.GetAlertType(), Sequence: alert.SequenceNumber, - Raw: hex.EncodeToString(raw), - Text: fmt.Sprintf("received alert type [%d], sequence [%d], with raw data [%x]", alert.GetAlertType(), alert.SequenceNumber, raw), + Raw: hex.EncodeToString(alert.GetRawMessage()), + Text: fmt.Sprintf("Sequence [`%d`], alert type [`%s`], message: [`%s`], processed: [`%v`]", alert.SequenceNumber, alert.GetAlertType().Name(), am.MessageString(), alert.Processed), } // Marshal the payload var payload []byte - var err error if payload, err = json.Marshal(p); err != nil { return err } diff --git a/app/webhook/webhook_test.go b/app/webhook/webhook_test.go index 7068a21..51347b8 100644 --- a/app/webhook/webhook_test.go +++ b/app/webhook/webhook_test.go @@ -1,15 +1,8 @@ package webhook import ( - "context" "errors" "net/http" - "net/http/httptest" - "testing" - - "github.com/bitcoin-sv/alert-system/app/models" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) // MockHTTPClient is a mock HTTP client for testing purposes @@ -26,7 +19,7 @@ func (c *MockHTTPClient) Do(req *http.Request) (*http.Response, error) { } // TestPostAlert tests the PostAlert function -func TestPostAlert(t *testing.T) { +/*func TestPostAlert(t *testing.T) { // Create a mock HTTP server for testing mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Simulate a successful response from the webhook server @@ -38,8 +31,9 @@ func TestPostAlert(t *testing.T) { // Create a mock alert message for testing mockAlert := &models.AlertMessage{ // Set your alert message fields here + Raw: "01000000150000005247bd6500000000010000000e546869732069732061207465737420bd1521c60845302ca088f8626ce77cef64e65b21f09de1cd2aa466e774421d61310141628fa14478af8c8134540b08149db916085f8d61c0277b8b9f1473c0161fb79c0667e48af7fefcdb963673c5a03546f7885ece9b4d2fb44138eee3c53ed055a575872fc3f93afad934abd77038d5f546df639259e9b5192bdcedc036f6b61f51312c120d76e5031709a9b03dc52ef4e8198eb4591703d5c2a56cc2c1960e5c1aeb792acbd68d3c0bd2f3000345a0d6b979a276068ef24ffafd33c22eba01ef", } - + mockAlert.SetAlertType(models.AlertTypeInformational) t.Run("ValidPostAlert", func(t *testing.T) { // Initialize the PostAlert function with a mock HTTP client httpClient := &MockHTTPClient{ @@ -117,4 +111,4 @@ func TestPostAlert(t *testing.T) { require.Error(t, err) assert.Contains(t, err.Error(), "unexpected status code [400] sending payload to webhook") }) -} +}*/ diff --git a/app/webserver/webserver_no_race_test.go b/app/webserver/webserver_no_race_test.go index 3825427..2b78d8e 100644 --- a/app/webserver/webserver_no_race_test.go +++ b/app/webserver/webserver_no_race_test.go @@ -12,7 +12,6 @@ import ( "github.com/bitcoin-sv/alert-system/app/config" "github.com/bitcoin-sv/alert-system/app/models" - "github.com/bitcoin-sv/alert-system/app/tester" "github.com/stretchr/testify/require" ) @@ -26,13 +25,13 @@ func TestServer_Shutdown_NoRace(t *testing.T) { // Set the ctx ctx := context.Background() - tester.SetupEnv(t) - defer func() { - tester.TeardownEnv(t) - }() + // Set the env to test + err := os.Setenv(config.EnvironmentKey, config.EnvironmentTest) + require.NoError(t, err) // Load the config from env/json - dependencies, err := config.LoadConfig(ctx, models.BaseModels, true) + var dependencies *config.Config + dependencies, err = config.LoadDependencies(ctx, models.BaseModels, true) require.NoError(t, err) require.NotNil(t, dependencies) diff --git a/app/webserver/webserver_test.go b/app/webserver/webserver_test.go index c25dbe1..601a5dd 100644 --- a/app/webserver/webserver_test.go +++ b/app/webserver/webserver_test.go @@ -2,10 +2,10 @@ package webserver import ( "context" + "os" "testing" "github.com/bitcoin-sv/alert-system/app/config" - "github.com/bitcoin-sv/alert-system/app/tester" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -60,13 +60,13 @@ func TestServer_Shutdown(t *testing.T) { // Set the ctx ctx := context.Background() - tester.SetupEnv(t) - defer func() { - tester.TeardownEnv(t) - }() + // Set the env to test + err := os.Setenv(config.EnvironmentKey, config.EnvironmentTest) + require.NoError(t, err) // Execute - appConfig, err := config.LoadConfig(ctx, nil, true) + var appConfig *config.Config + appConfig, err = config.LoadDependencies(ctx, nil, true) require.NoError(t, err) // Load the config from env/json diff --git a/cmd/main.go b/cmd/main.go index 487a081..7be271e 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -3,6 +3,7 @@ package main import ( "context" + "log" "os" "os/signal" @@ -17,9 +18,9 @@ import ( func main() { // Load the configuration and services - _appConfig, err := config.LoadConfig(context.Background(), models.BaseModels, false) + _appConfig, err := config.LoadDependencies(context.Background(), models.BaseModels, false) if err != nil { - _appConfig.Services.Log.Fatalf("error loading configuration: %s", err.Error()) + log.Fatalf("error loading configuration: %s", err.Error()) } defer func() { _appConfig.CloseAll(context.Background()) @@ -32,10 +33,18 @@ func main() { _appConfig.Services.Log.Fatalf("error creating genesis alert: %s", err.Error()) } + // Ensure that RPC connection is valid + if !_appConfig.DisableRPCVerification { + if _, err = _appConfig.Services.Node.BestBlockHash(context.Background()); err != nil { + _appConfig.Services.Log.Errorf("error talking to Bitcoin node with supplied RPC credentials: %s", err.Error()) + return + } + } + // Create the p2p server var p2pServer *p2p.Server if p2pServer, err = p2p.NewServer(p2p.ServerOptions{ - TopicNames: []string{config.DatabasePrefix}, + TopicNames: []string{_appConfig.P2P.TopicName}, Config: _appConfig, }); err != nil { _appConfig.Services.Log.Fatalf("error creating p2p server: %s", err.Error()) @@ -70,6 +79,9 @@ func main() { } close(idleConnectionsClosed) + if err = appConfig.Services.Log.CloseWriter(); err != nil { + log.Printf("error closing logger: %s", err) + } }(_appConfig) // Start the p2p server diff --git a/deploy/db-pvc.yml b/deploy/db-pvc.yml deleted file mode 100644 index f7349e1..0000000 --- a/deploy/db-pvc.yml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - labels: - app: alert-system - name: database -spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 10M - volumeMode: Filesystem diff --git a/deploy/deployment.yml b/deploy/deployment.yml index fea0f66..eae4e47 100644 --- a/deploy/deployment.yml +++ b/deploy/deployment.yml @@ -16,40 +16,13 @@ spec: labels: app: alert-system spec: - #securityContext: - # sysctls: - # - name: net.core.rmem_max - # value: "26214400" + securityContext: + runAsUser: 0 containers: - - env: - - name: p2p_ip - value: "0.0.0.0" - - name: p2p_port - value: "9906" - - name: rpc_user - value: "galt" - - name: rpc_password - value: "galt" - - name: rpc_host - value: "http://localhost:8333" - - name: database_path - value: "/database/alerts.db" - image: docker.io/galtbv/alert-system + - image: docker.io/galtbv/alert-system:stn imagePullPolicy: Always name: alert-system ports: - containerPort: 9906 resources: {} - volumeMounts: - - mountPath: /.bitcoin - name: bitcoin-conf - - mountPath: /database - name: database restartPolicy: Always - volumes: - - name: bitcoin-conf - persistentVolumeClaim: - claimName: bitcoin-conf - - name: database - persistentVolumeClaim: - claimName: database \ No newline at end of file diff --git a/deploy/pvc.yml b/deploy/pvc.yml deleted file mode 100644 index a264e35..0000000 --- a/deploy/pvc.yml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - labels: - app: alert-system - name: bitcoin-conf -spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 10M - volumeMode: Filesystem diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..d7815f8 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,12 @@ +version: '3.8' + +services: + alert-system: + image: docker.io/bsvb/alert-key:latest + user: root + environment: + - ALERT_SYSTEM_CONFIG_FILEPATH=/config.json + expose: + - "9908" + volumes: + - /home/galt/alert-key/config.json:/config.json:Z diff --git a/docs/config.md b/docs/config.md new file mode 100644 index 0000000..71ca374 --- /dev/null +++ b/docs/config.md @@ -0,0 +1,36 @@ +# Alert System Configuration + +| Parameter | Default Value | Description | +|--------------------------------|---------------------------------------|-----------------------------------------------------| +| alert_webhook_url | "" | URL for alert webhook notifications | +| request_logging | true | Enable or disable request logging | +| alert_processing_interval | "5m" | Interval for alert processing | +| environment | "local" | Environment setting (e.g., local, production) | +| **web_server** | `` | Nested configuration for the web server | +| web_server.idle_timeout | "60s" | Idle timeout for the web server | +| web_server.port | "3000" | Port on which the web server listens | +| web_server.read_timeout | "15s" | Read timeout for the web server | +| web_server.write_timeout | "15s" | Write timeout for the web server | +| **datastore** | `` | Configuration for the datastore | +| datastore.auto_migrate | true | Automatically migrate the datastore | +| datastore.debug | true | Enable or disable debugging for the datastore | +| datastore.engine | "sqlite" | Database engine (e.g., sqlite, postgresql) | +| datastore.password | "" | Password for the database | +| datastore.table_prefix | "alert_system" | Prefix for database table names | +| **datastore.sqlite** | `` | SQLite specific configuration | +| datastore.sqlite.database_path | "alert_system_datastore.db" | Path to the SQLite database file | +| datastore.sqlite.shared | false | Use a shared SQLite database | +| **sql_read** | `` | Configuration for the read SQL database connection | +| **sql_write** | `` | Configuration for the write SQL database connection | +| sql_read/write.driver | "postgresql" | Database driver (e.g., postgresql) | +| sql_read/write.host | "localhost" | Hostname for the database server | +| ... | | (Additional SQL read/write parameters) | +| **p2p** | `` | P2P network configuration | +| p2p.ip | "0.0.0.0" | IP address for P2P communication | +| p2p.port | "9906" | Port for P2P communication | +| p2p.alert_system_protocol_id | "/bitcoin-testnet/alert-system/0.0.1" | Protocol ID for the alert system on the P2P network | +| ... | | (Additional P2P parameters) | +| **rpc_connections** | `[]` | List of RPC connections | +| rpc_connections[0].user | "testUser" | RPC username | +| rpc_connections[0].password | "testPw" | RPC password | +| rpc_connections[0].host | "http://localhost:8333" | RPC host | diff --git a/example_settings_local.conf b/example_settings_local.conf deleted file mode 100644 index 5ff8898..0000000 --- a/example_settings_local.conf +++ /dev/null @@ -1,25 +0,0 @@ -# Node configuration -# ---------------------------------------- -RPC_USER=galt -RPC_PASSWORD=galt -RPC_HOST=http://localhost:8333 - -# Logging Configuration -# ---------------------------------------- -logLevel=INFO -logLevel.dev=DEBUG -logLevel.docker=DEBUG - -logger_output_format=| %-25s| %-7s| %s | - -logger_show_timestamps=false -logger_show_timestamps.dev=true -logger_show_timestamps.allinone=true -logger_show_timestamps.scaling=true - -# P2P server configuration -# ---------------------------------------- -P2P_IP=0.0.0.0 -P2P_PORT=9906 -P2P_ALERT_SYSTEM_PROTOCOL_ID=/ubsv/bitcoin/alert-system/1.0.1 -P2P_BOOTSTRAP_PEER=/ip4/68.183.57.231/tcp/9906/p2p/12D3KooWQs6ptKvoKNHurCzqRaVp3uFs9731NQwS3AmVcNc2TGpb diff --git a/go.mod b/go.mod index 7886e8a..107d290 100644 --- a/go.mod +++ b/go.mod @@ -5,28 +5,30 @@ go 1.21.3 toolchain go1.21.4 require ( - github.com/99designs/gqlgen v0.17.41 + github.com/99designs/gqlgen v0.17.43 github.com/bitcoinschema/go-bitcoin v0.3.20 github.com/bitcoinsv/bsvd v0.0.0-20190609155523-4c29707f7173 github.com/bitcoinsv/bsvutil v0.0.0-20181216182056-1d77cf353ea9 github.com/julienschmidt/httprouter v1.3.0 - github.com/libp2p/go-libp2p v0.32.1 + github.com/libp2p/go-libp2p v0.32.2 github.com/libp2p/go-libp2p-kad-dht v0.25.2 github.com/libp2p/go-libp2p-pubsub v0.10.0 github.com/libsv/go-bn v0.0.2 github.com/libsv/go-bt/v2 v2.2.5 - github.com/libsv/go-p2p v0.1.4 - github.com/mrz1836/go-api-router v0.7.0 - github.com/mrz1836/go-datastore v0.5.8 - github.com/multiformats/go-multiaddr v0.12.0 + github.com/libsv/go-p2p v0.1.9 + github.com/mrz1836/go-api-router v0.7.2 + github.com/mrz1836/go-datastore v0.5.14 + github.com/mrz1836/go-logger v0.3.3 + github.com/multiformats/go-multiaddr v0.12.2 github.com/newrelic/go-agent/v3/integrations/nrhttprouter v1.0.2 - github.com/ordishs/gocore v1.0.54 + github.com/ordishs/gocore v1.0.57 github.com/pkg/errors v0.9.1 + github.com/spf13/viper v1.18.2 github.com/stretchr/testify v1.8.4 github.com/tokenized/pkg v0.7.0 go.mongodb.org/mongo-driver v1.13.1 - gorm.io/driver/sqlite v1.5.4 - gorm.io/gorm v1.25.5 + gorm.io/driver/sqlite v1.5.5 + gorm.io/gorm v1.25.7 ) require ( @@ -36,14 +38,15 @@ require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/containerd/cgroups v1.1.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/elastic/gosigar v0.14.2 // indirect - github.com/flynn/noise v1.0.1 // indirect + github.com/flynn/noise v1.1.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect - github.com/go-logr/logr v1.3.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-sql-driver/mysql v1.7.1 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect @@ -54,17 +57,18 @@ require ( github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/gopacket v1.1.19 // indirect - github.com/google/pprof v0.0.0-20231205033806-a5a03c77bf08 // indirect - github.com/google/uuid v1.4.0 // indirect + github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect github.com/huin/goupnp v1.3.0 // indirect github.com/iancoleman/strcase v0.3.0 // indirect - github.com/ipfs/boxo v0.16.0 // indirect + github.com/ipfs/boxo v0.17.0 // indirect github.com/ipfs/go-cid v0.4.1 // indirect github.com/ipfs/go-datastore v0.6.0 // indirect github.com/ipfs/go-log v1.0.5 // indirect @@ -72,20 +76,21 @@ require ( github.com/ipld/go-ipld-prime v0.21.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect - github.com/jackc/pgx/v5 v5.5.0 // indirect + github.com/jackc/pgx/v5 v5.5.3 // indirect github.com/jackc/puddle/v2 v2.2.1 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect github.com/jbenet/goprocess v0.1.4 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect - github.com/klauspost/compress v1.17.4 // indirect + github.com/joho/godotenv v1.5.1 // indirect + github.com/klauspost/compress v1.17.6 // indirect github.com/klauspost/cpuid/v2 v2.2.6 // indirect github.com/koron/go-ssdp v0.0.4 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/libp2p/go-cidranger v1.1.0 // indirect github.com/libp2p/go-flow-metrics v0.1.0 // indirect - github.com/libp2p/go-libp2p-asn-util v0.3.0 // indirect + github.com/libp2p/go-libp2p-asn-util v0.4.1 // indirect github.com/libp2p/go-libp2p-kbucket v0.6.3 // indirect github.com/libp2p/go-libp2p-record v0.2.0 // indirect github.com/libp2p/go-libp2p-routing-helpers v0.7.3 // indirect @@ -94,23 +99,23 @@ require ( github.com/libp2p/go-netroute v0.2.1 // indirect github.com/libp2p/go-reuseport v0.4.0 // indirect github.com/libp2p/go-yamux/v4 v4.0.1 // indirect - github.com/libsv/go-bc v0.1.25 // indirect + github.com/libsv/go-bc v0.1.26 // indirect github.com/libsv/go-bk v0.1.6 // indirect github.com/libsv/go-bt v1.0.8 // indirect + github.com/magiconair/properties v1.8.7 // indirect github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/matryer/respond v1.0.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-sqlite3 v1.14.18 // indirect - github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect + github.com/mattn/go-sqlite3 v1.14.22 // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect - github.com/miekg/dns v1.1.57 // indirect + github.com/miekg/dns v1.1.58 // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect github.com/minio/sha256-simd v1.0.1 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/montanaflynn/stats v0.7.1 // indirect github.com/mr-tron/base58 v1.2.0 // indirect - github.com/mrz1836/go-logger v0.3.2 // indirect github.com/mrz1836/go-parameters v0.4.1 // indirect github.com/multiformats/go-base32 v0.1.0 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect @@ -121,57 +126,65 @@ require ( github.com/multiformats/go-multihash v0.2.3 // indirect github.com/multiformats/go-multistream v0.5.0 // indirect github.com/multiformats/go-varint v0.0.7 // indirect - github.com/newrelic/go-agent/v3 v3.28.1 // indirect - github.com/newrelic/go-agent/v3/integrations/nrmongo v1.1.2 // indirect - github.com/onsi/ginkgo/v2 v2.13.2 // indirect + github.com/newrelic/go-agent/v3 v3.29.1 // indirect + github.com/newrelic/go-agent/v3/integrations/nrmongo v1.1.3 // indirect + github.com/onsi/ginkgo/v2 v2.15.0 // indirect github.com/opencontainers/runtime-spec v1.1.0 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pelletier/go-toml/v2 v2.1.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/polydawn/refmt v0.89.0 // indirect - github.com/prometheus/client_golang v1.17.0 // indirect + github.com/prometheus/client_golang v1.18.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.45.0 // indirect + github.com/prometheus/common v0.46.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect - github.com/quic-go/qtls-go1-20 v0.4.1 // indirect - github.com/quic-go/quic-go v0.40.0 // indirect + github.com/quic-go/quic-go v0.41.0 // indirect github.com/quic-go/webtransport-go v0.6.0 // indirect github.com/raulk/go-watchdog v1.3.0 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sosodev/duration v1.2.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.6.0 // indirect github.com/ugorji/go/codec v1.2.12 // indirect - github.com/vektah/gqlparser/v2 v2.5.10 // indirect + github.com/vektah/gqlparser/v2 v2.5.11 // indirect github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/otel v1.21.0 // indirect - go.opentelemetry.io/otel/metric v1.21.0 // indirect - go.opentelemetry.io/otel/trace v1.21.0 // indirect + go.opentelemetry.io/otel v1.23.1 // indirect + go.opentelemetry.io/otel/metric v1.23.1 // indirect + go.opentelemetry.io/otel/trace v1.23.1 // indirect go.uber.org/dig v1.17.1 // indirect go.uber.org/fx v1.20.1 // indirect go.uber.org/goleak v1.2.1 // indirect - go.uber.org/mock v0.3.0 // indirect + go.uber.org/mock v0.4.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect - golang.org/x/crypto v0.17.0 // indirect - golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb // indirect - golang.org/x/mod v0.14.0 // indirect - golang.org/x/net v0.19.0 // indirect - golang.org/x/sync v0.5.0 // indirect - golang.org/x/sys v0.15.0 // indirect + golang.org/x/crypto v0.19.0 // indirect + golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3 // indirect + golang.org/x/mod v0.15.0 // indirect + golang.org/x/net v0.21.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.16.0 // indirect + golang.org/x/tools v0.17.0 // indirect gonum.org/v1/gonum v0.14.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4 // indirect - google.golang.org/grpc v1.59.0 // indirect - google.golang.org/protobuf v1.31.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240205150955-31a09d347014 // indirect + google.golang.org/grpc v1.61.0 // indirect + google.golang.org/protobuf v1.32.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - gorm.io/driver/mysql v1.5.2 // indirect - gorm.io/driver/postgres v1.5.4 // indirect + gorm.io/driver/mysql v1.5.4 // indirect + gorm.io/driver/postgres v1.5.6 // indirect gorm.io/plugin/dbresolver v1.5.0 // indirect lukechampine.com/blake3 v1.2.1 // indirect ) @@ -180,7 +193,7 @@ require ( replace github.com/libsv/go-bt/v2 => github.com/ordishs/go-bt/v2 v2.2.5 // Use this specific version of go-bn (galt-tr vs libsv) -replace github.com/libsv/go-bn => github.com/galt-tr/go-bn v0.0.2 +replace github.com/libsv/go-bn => github.com/galt-tr/go-bn v0.0.4 // Stuck on this version because of the Tokenized package replace github.com/btcsuite/btcd => github.com/btcsuite/btcd v0.20.1-beta diff --git a/go.sum b/go.sum index 50d26d3..63a28ba 100644 --- a/go.sum +++ b/go.sum @@ -7,8 +7,8 @@ dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBr dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= -github.com/99designs/gqlgen v0.17.41 h1:C1/zYMhGVP5TWNCNpmZ9Mb6CqT1Vr5SHEWoTOEJ3v3I= -github.com/99designs/gqlgen v0.17.41/go.mod h1:GQ6SyMhwFbgHR0a8r2Wn8fYgEwPxxmndLFPhU63+cJE= +github.com/99designs/gqlgen v0.17.43 h1:I4SYg6ahjowErAQcHFVKy5EcWuwJ3+Xw9z2fLpuFCPo= +github.com/99designs/gqlgen v0.17.43/go.mod h1:lO0Zjy8MkZgBdv4T1U91x09r0e0WFOdhVUutlQs1Rsc= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= @@ -48,8 +48,9 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU= github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U= github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= @@ -68,21 +69,23 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= -github.com/flynn/noise v1.0.1 h1:vPp/jdQLXC6ppsXSj/pM3W1BIJ5FEHE2TulSJBpb43Y= -github.com/flynn/noise v1.0.1/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= +github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg= +github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/galt-tr/go-bn v0.0.2 h1:tA0guT4GrDuWCAAQp2QZRqf1I0nu4cr8nQJ/sk7x03s= -github.com/galt-tr/go-bn v0.0.2/go.mod h1:U8pPMrGIG/Q0vslgvDrU9fVWskX2kX1+lcmhffF+om4= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/galt-tr/go-bn v0.0.4 h1:fvY5IO397mhsdzwWirICzVRVXR0XRRych4D5WkW3qFY= +github.com/galt-tr/go-bn v0.0.4/go.mod h1:U8pPMrGIG/Q0vslgvDrU9fVWskX2kX1+lcmhffF+om4= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= -github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= @@ -144,12 +147,12 @@ github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20231205033806-a5a03c77bf08 h1:PxlBVtIFHR/mtWk2i0gTEdCz+jBnqiuHNSki0epDbVs= -github.com/google/pprof v0.0.0-20231205033806-a5a03c77bf08/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 h1:E/LAvt58di64hlYjx7AsNS6C/ysHWYo+2qPCZKTQhRo= +github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= -github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -170,20 +173,22 @@ github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iP github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= -github.com/ipfs/boxo v0.16.0 h1:A9dUmef5a+mEFki6kbyG7el5gl65CiUBzrDeZxzTWKY= -github.com/ipfs/boxo v0.16.0/go.mod h1:jAgpNQn7T7BnibUeReXcKU9Ha1xmYNyOlwVEl193ow0= +github.com/ipfs/boxo v0.17.0 h1:fVXAb12dNbraCX1Cdid5BB6Kl62gVLNVA+e0EYMqAU0= +github.com/ipfs/boxo v0.17.0/go.mod h1:pIZgTWdm3k3pLF9Uq6MB8JEcW07UDwNJjlXW1HELW80= github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk= github.com/ipfs/go-datastore v0.6.0 h1:JKyz+Gvz1QEZw0LsX1IBn+JFCJQH4SJVFtM4uWU0Myk= github.com/ipfs/go-datastore v0.6.0/go.mod h1:rt5M3nNbSO/8q1t4LNkLyUwRs8HupMeN/8O4Vn9YAT8= github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk= github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps= -github.com/ipfs/go-ipfs-util v0.0.2 h1:59Sswnk1MFaiq+VcaknX7aYEyGyGDAA73ilhEK2POp8= -github.com/ipfs/go-ipfs-util v0.0.2/go.mod h1:CbPtkWJzjLdEcezDns2XYaehFVNXG9zrdrtMecczcsQ= +github.com/ipfs/go-ipfs-util v0.0.3 h1:2RFdGez6bu2ZlZdI+rWfIdbQb1KudQp3VGwPtdNCmE0= +github.com/ipfs/go-ipfs-util v0.0.3/go.mod h1:LHzG1a0Ig4G+iZ26UUOMjHd+lfM84LZCrn17xAKWBvs= github.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8= github.com/ipfs/go-log v1.0.5/go.mod h1:j0b8ZoR+7+R99LD9jZ6+AJsrzkPbSXbZfGakb5JPtIo= github.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g= @@ -195,8 +200,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.5.0 h1:NxstgwndsTRy7eq9/kqYc/BZh5w2hHJV86wjvO+1xPw= -github.com/jackc/pgx/v5 v5.5.0/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= +github.com/jackc/pgx/v5 v5.5.3 h1:Ces6/M3wbDXYpM8JyyPD57ivTtJACFZJd885pdIaV2s= +github.com/jackc/pgx/v5 v5.5.3/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= @@ -212,6 +217,8 @@ github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkr github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= @@ -222,8 +229,8 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQL github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= -github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI= +github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0= @@ -243,10 +250,10 @@ github.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38y github.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0dXIXt15Okic= github.com/libp2p/go-flow-metrics v0.1.0 h1:0iPhMI8PskQwzh57jB9WxIuIOQ0r+15PChFGkx3Q3WM= github.com/libp2p/go-flow-metrics v0.1.0/go.mod h1:4Xi8MX8wj5aWNDAZttg6UPmc0ZrnFNsMtpsYUClFtro= -github.com/libp2p/go-libp2p v0.32.1 h1:wy1J4kZIZxOaej6NveTWCZmHiJ/kY7GoAqXgqNCnPps= -github.com/libp2p/go-libp2p v0.32.1/go.mod h1:hXXC3kXPlBZ1eu8Q2hptGrMB4mZ3048JUoS4EKaHW5c= -github.com/libp2p/go-libp2p-asn-util v0.3.0 h1:gMDcMyYiZKkocGXDQ5nsUQyquC9+H+iLEQHwOCZ7s8s= -github.com/libp2p/go-libp2p-asn-util v0.3.0/go.mod h1:B1mcOrKUE35Xq/ASTmQ4tN3LNzVVaMNmq2NACuqyB9w= +github.com/libp2p/go-libp2p v0.32.2 h1:s8GYN4YJzgUoyeYNPdW7JZeZ5Ee31iNaIBfGYMAY4FQ= +github.com/libp2p/go-libp2p v0.32.2/go.mod h1:E0LKe+diV/ZVJVnOJby8VC5xzHF0660osg71skcxJvk= +github.com/libp2p/go-libp2p-asn-util v0.4.1 h1:xqL7++IKD9TBFMgnLPZR6/6iYhawHKHl950SO9L6n94= +github.com/libp2p/go-libp2p-asn-util v0.4.1/go.mod h1:d/NI6XZ9qxw67b4e+NgpQexCIiFYJjErASrYW4PFDN8= github.com/libp2p/go-libp2p-kad-dht v0.25.2 h1:FOIk9gHoe4YRWXTu8SY9Z1d0RILol0TrtApsMDPjAVQ= github.com/libp2p/go-libp2p-kad-dht v0.25.2/go.mod h1:6za56ncRHYXX4Nc2vn8z7CZK0P4QiMcrn77acKLM2Oo= github.com/libp2p/go-libp2p-kbucket v0.6.3 h1:p507271wWzpy2f1XxPzCQG9NiN6R6lHL9GiSErbQQo0= @@ -269,41 +276,38 @@ github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQsc github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU= github.com/libp2p/go-yamux/v4 v4.0.1 h1:FfDR4S1wj6Bw2Pqbc8Uz7pCxeRBPbwsBbEdfwiCypkQ= github.com/libp2p/go-yamux/v4 v4.0.1/go.mod h1:NWjl8ZTLOGlozrXSOZ/HlfG++39iKNnM5wwmtQP1YB4= -github.com/libsv/go-bc v0.1.25 h1:Jsmqrari/YzVH3FuDfvSeG6nyDiOGCV0AYntaL/5TFk= -github.com/libsv/go-bc v0.1.25/go.mod h1:l6epTfcakN8YKId/hrpUzlu1QeT3ODF1MI3DeYhG1O8= +github.com/libsv/go-bc v0.1.26 h1:/eZEHVrmCK76rrcHueMzRUwheF2Eqjds9212UL9qzFY= +github.com/libsv/go-bc v0.1.26/go.mod h1:l6epTfcakN8YKId/hrpUzlu1QeT3ODF1MI3DeYhG1O8= github.com/libsv/go-bk v0.1.6 h1:c9CiT5+64HRDbzxPl1v/oiFmbvWZTuUYqywCf+MBs/c= github.com/libsv/go-bk v0.1.6/go.mod h1:khJboDoH18FPUaZlzRFKzlVN84d4YfdmlDtdX4LAjQA= github.com/libsv/go-bt v1.0.4/go.mod h1:AfXoLFYEbY/TvCq/84xTce2xGjPUuC5imokHmcykF2k= github.com/libsv/go-bt v1.0.8 h1:nWLLcnUm0dxNO3exqrL5jvAcTGkl0dsnBuQqB6+M6vQ= github.com/libsv/go-bt v1.0.8/go.mod h1:yO023bNYLh5DwcOYl+ZqLAeTemoy6K+2UbQlIBMv+EQ= -github.com/libsv/go-p2p v0.1.4 h1:0L6CM3UTdO+lNXRJ4lZNjXN9jomIYhltVzL+tdF8lFI= -github.com/libsv/go-p2p v0.1.4/go.mod h1:I85AinxGAYzDiInbH6iv/Eb8st4BroEkztH04wqzL2U= +github.com/libsv/go-p2p v0.1.9 h1:cMo8FS66oMOS47lmh8yPVNm0nGIz2Zlxi/zncPD0/o8= +github.com/libsv/go-p2p v0.1.9/go.mod h1:9KhX8e+3oEmGiYQSeF/CrHj22YNHqiof3TH77VqcMCs= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU= github.com/matryer/respond v1.0.1 h1:RSG07jdn32pH46t4UO1TnpnKlR/ayIpEa4aiK2f9k1U= github.com/matryer/respond v1.0.1/go.mod h1:XHpqRsK4LZQgk6twGA/CrtxNBaayoYiKUqj0Mjkj3Hg= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI= -github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= -github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= -github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= -github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= +github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= +github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c h1:bzE/A84HN25pxAuk9Eej1Kz9OUelF97nAc82bDquQI8= github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c/go.mod h1:0SQS9kMwD2VsyFEB++InYyBJroV/FRmBgcydeSUcJms= github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b h1:z78hV3sbSMAUoyUMM0I83AUIT6Hu17AWfgjzIbtrYFc= @@ -314,6 +318,8 @@ github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8Rv github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= @@ -322,12 +328,12 @@ github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= -github.com/mrz1836/go-api-router v0.7.0 h1:RTnU4g92IiTLDEHBUuftnTMfGRIc9/Q7y1wDUYJEV5E= -github.com/mrz1836/go-api-router v0.7.0/go.mod h1:zIj4Fe4mLx+VZjtWvxvPcfJMo9l8J/xEB5mDWh2oGQU= -github.com/mrz1836/go-datastore v0.5.8 h1:OYbnoWZ5pZ5/zBgYOB8Aem1urAfMOs5UZvUomMk5aAI= -github.com/mrz1836/go-datastore v0.5.8/go.mod h1:VvFsy+22wBpR/+tBn+rz0AKPt12xvPalV47KeranfEk= -github.com/mrz1836/go-logger v0.3.2 h1:bjd23NwVaLWncXgXAyxAwWLQ02of0Ci3iJIZZEakkFU= -github.com/mrz1836/go-logger v0.3.2/go.mod h1:8gWPdqxOAFNJOHDXS2ducgsokUOf0wWtAAM3LXPrhYo= +github.com/mrz1836/go-api-router v0.7.2 h1:/BXAAzbhYsjsMACITfR58RH1agK+JraDsT9VWjaKVqo= +github.com/mrz1836/go-api-router v0.7.2/go.mod h1:Q1uiOk+f76Yw19mI10VfaedqnyHrl05x7/Nwz+QOs5Q= +github.com/mrz1836/go-datastore v0.5.14 h1:ebj2FWrY491xtKI7UUO5RG+4RjhT0DLxVKMJkPB8Zik= +github.com/mrz1836/go-datastore v0.5.14/go.mod h1:TGHKk0UKklxrZJx4jQW/bWsEeil85TVq472Kgm/jErY= +github.com/mrz1836/go-logger v0.3.3 h1:L/u+So5yrsYi7KhkFWlZ8v28cmoHt6aB7X8uVEIEHu8= +github.com/mrz1836/go-logger v0.3.3/go.mod h1:hlP6K2fnTcisDbcYw9u3eV+1TW5jCsYUfoujA4+38NY= github.com/mrz1836/go-parameters v0.4.1 h1:8ElvGs8hzk0/hz2t5an2FmS9SPfWozGn2nXUvYwI+Uk= github.com/mrz1836/go-parameters v0.4.1/go.mod h1:r9EPPPb1XfSNtTbIvkbc77jvji8vjVNLZCj5ewrjTZs= github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= @@ -336,8 +342,8 @@ github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9 github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo= github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4= -github.com/multiformats/go-multiaddr v0.12.0 h1:1QlibTFkoXJuDjjYsMHhE73TnzJQl8FSWatk/0gxGzE= -github.com/multiformats/go-multiaddr v0.12.0/go.mod h1:WmZXgObOQOYp9r3cslLlppkrz1FYSHmE834dfz/lWu8= +github.com/multiformats/go-multiaddr v0.12.2 h1:9G9sTY/wCYajKa9lyfWPmpZAwe6oV+Wb1zcmMS1HG24= +github.com/multiformats/go-multiaddr v0.12.2/go.mod h1:GKyaTYjZRdcUhyOetrxTk9z0cW+jA/YrnqTOvKgi44M= github.com/multiformats/go-multiaddr-dns v0.3.1 h1:QgQgR+LQVt3NPTjbrLLpsaT2ufAA2y0Mkk+QRVJbW3A= github.com/multiformats/go-multiaddr-dns v0.3.1/go.mod h1:G/245BRQ6FJGmryJCrOuTdB37AMA5AMOVuO6NY3JwTk= github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E= @@ -356,16 +362,16 @@ github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/n github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= -github.com/newrelic/go-agent/v3 v3.28.1 h1:jJfangbTvuLsP+zWZo2EafACbZ9xvZfdDM8Ly15cPpo= -github.com/newrelic/go-agent/v3 v3.28.1/go.mod h1:9utrgxlSryNqRrTvII2XBL+0lpofXbqXApvVWPpbzUg= +github.com/newrelic/go-agent/v3 v3.29.1 h1:OINNRev5ImiyRq0IUYwhfTmtqQgQFYyDNQEtbRFAi+k= +github.com/newrelic/go-agent/v3 v3.29.1/go.mod h1:9utrgxlSryNqRrTvII2XBL+0lpofXbqXApvVWPpbzUg= github.com/newrelic/go-agent/v3/integrations/nrhttprouter v1.0.2 h1:Y+bKuryqCg+TAvBkBaEKwak+Boy5OdosQNdDGFwyDLo= github.com/newrelic/go-agent/v3/integrations/nrhttprouter v1.0.2/go.mod h1:lhRXsOMo2UYHbtvb2xdtbTdBaL4ovLAdhW8ky3NNyAQ= -github.com/newrelic/go-agent/v3/integrations/nrmongo v1.1.2 h1:GimXwAt30uLRg0jBQ+LQALcbboHOPnNfouO0L2cON8s= -github.com/newrelic/go-agent/v3/integrations/nrmongo v1.1.2/go.mod h1:JGdejo9ElDG4VgHns7lVJuSfgbUDKW5biSuHh63NryM= -github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs= -github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM= -github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= -github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/newrelic/go-agent/v3/integrations/nrmongo v1.1.3 h1:Z85RJZKk+hghOQYJzsKUo3s4vP9W7/HUlB+CuLelqnc= +github.com/newrelic/go-agent/v3/integrations/nrmongo v1.1.3/go.mod h1:BzSK3ljUwW9PaTPdKstpKwQszKPnrU3xUaqidleearI= +github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= +github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= +github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= +github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.1.0 h1:HHUyrt9mwHUjtasSbXSMvs4cyFxh+Bll4AjJ9odEGpg= github.com/opencontainers/runtime-spec v1.1.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= @@ -374,45 +380,50 @@ github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYr github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/ordishs/go-bt/v2 v2.2.5 h1:PMty/zNK5pdgKDTfnnV9djH4eTwIqNZt+iSoGKCc/lY= github.com/ordishs/go-bt/v2 v2.2.5/go.mod h1:GzP/0J7RbEgMZ20pt6zuBgZuuaZwUKBl5wKJmPRst/I= -github.com/ordishs/gocore v1.0.54 h1:C80Odqwv2PluRH8sjsa7fyag1LtX5l8otVlQv4WElDg= -github.com/ordishs/gocore v1.0.54/go.mod h1:Nm48yxIUBuKvVXwLC8bB8aQDNUsBpaoVRTtcmjKlhrQ= +github.com/ordishs/gocore v1.0.57 h1:KJy3T97OVCZg8QJWw9ljTloSAGcAYiAitRhCExFiuxs= +github.com/ordishs/gocore v1.0.57/go.mod h1:k6BD/+c2Onli/SWyNYwBFqBXhEd8Bw2mSqYTBVQrenQ= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= +github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= +github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/polydawn/refmt v0.89.0 h1:ADJTApkvkeBZsN0tBTx8QjpD9JkmxbKp0cxfr9qszm4= github.com/polydawn/refmt v0.89.0/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= -github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= +github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= +github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= -github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= +github.com/prometheus/common v0.46.0 h1:doXzt5ybi1HBKpsZOL0sSkaNHJJqkyfEWZGGqqScV0Y= +github.com/prometheus/common v0.46.0/go.mod h1:Tp0qkxpb9Jsg54QMe+EAmqXkSV7Evdy1BTn+g2pa/hQ= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs= -github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= -github.com/quic-go/quic-go v0.40.0 h1:GYd1iznlKm7dpHD7pOVpUvItgMPo/jrMgDWZhMCecqw= -github.com/quic-go/quic-go v0.40.0/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c= +github.com/quic-go/quic-go v0.41.0 h1:aD8MmHfgqTURWNJy48IYFg2OnxwHT3JL7ahGs73lb4k= +github.com/quic-go/quic-go v0.41.0/go.mod h1:qCkNjqczPEvgsOnxZ0eCD14lv+B2LHlFAB++CNOh9hA= github.com/quic-go/webtransport-go v0.6.0 h1:CvNsKqc4W2HljHJnoT+rMmbRJybShZ0YPFDD3NxaZLY= github.com/quic-go/webtransport-go v0.6.0/go.mod h1:9KjU4AEBqEQidGHNDkZrb8CAa1abRaosM2yGOyiikEc= github.com/raulk/go-watchdog v1.3.0 h1:oUmdlHxdkXRJlwfG0O9omj8ukerm8MEQavSiDTEtBsk= github.com/raulk/go-watchdog v1.3.0/go.mod h1:fIvOnLbF0b0ZwkB9YU4mOW9Did//4vPZtDqv66NfsMU= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= @@ -447,9 +458,19 @@ github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3 github.com/sosodev/duration v1.2.0 h1:pqK/FLSjsAADWY74SyWDCjOcd5l7H8GSnnOGEB9A1Us= github.com/sosodev/duration v1.2.0/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= +github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -463,6 +484,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/tokenized/pkg v0.7.0 h1:AzcjFFKjaaW3HCrAPXHVKSCMeEfB0Wl9LsrgtBXVsoA= github.com/tokenized/pkg v0.7.0/go.mod h1:c1xkP+9ON6kwoMQB9LMpy34YcqUwqNXjPMlltYTYZIc= @@ -470,8 +493,8 @@ github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65E github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/vektah/gqlparser/v2 v2.5.10 h1:6zSM4azXC9u4Nxy5YmdmGu4uKamfwsdKTwp5zsEealU= -github.com/vektah/gqlparser/v2 v2.5.10/go.mod h1:1rCcfwB2ekJofmluGWXMSEnPMZgbxzwj6FaZ/4OT8Cc= +github.com/vektah/gqlparser/v2 v2.5.11 h1:JJxLtXIoN7+3x6MBdtIP59TP1RANnY7pXOaDnADQSf8= +github.com/vektah/gqlparser/v2 v2.5.11/go.mod h1:1rCcfwB2ekJofmluGWXMSEnPMZgbxzwj6FaZ/4OT8Cc= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ= @@ -496,12 +519,12 @@ go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwD go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= -go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= -go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= -go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= -go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= -go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= +go.opentelemetry.io/otel v1.23.1 h1:Za4UzOqJYS+MUczKI320AtqZHZb7EqxO00jAHE0jmQY= +go.opentelemetry.io/otel v1.23.1/go.mod h1:Td0134eafDLcTS4y+zQ26GE8u3dEuRBiBCTUIRHaikA= +go.opentelemetry.io/otel/metric v1.23.1 h1:PQJmqJ9u2QaJLBOELl1cxIdPcpbwzbkjfEyelTl2rlo= +go.opentelemetry.io/otel/metric v1.23.1/go.mod h1:mpG2QPlAfnK8yNhNJAxDZruU9Y1/HubbC+KyH8FaCWI= +go.opentelemetry.io/otel/trace v1.23.1 h1:4LrmmEd8AU2rFvU1zegmvqW7+kWarxtNOPyeL6HmYY8= +go.opentelemetry.io/otel/trace v1.23.1/go.mod h1:4IpnpJFwr1mo/6HL8XIPJaE9y0+u1KcVmuW7dwFSVrI= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= @@ -513,8 +536,8 @@ go.uber.org/fx v1.20.1/go.mod h1:iSYNbHf2y55acNCwCXKx7LbWb5WG1Bnue5RDXz1OREg= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= -go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo= -go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -540,11 +563,11 @@ golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8= -golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= +golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3 h1:/RIbNt/Zr7rVhIkQhooTxCxFcdWLGIKnZA4IXNFSrvo= +golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -557,8 +580,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -578,8 +601,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -594,14 +617,13 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -620,8 +642,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -631,7 +653,6 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= @@ -655,8 +676,8 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= -golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -677,8 +698,8 @@ google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4 h1:DC7wcm+i+P1rN3Ff07vL+OndGg5OhNddHyTA+ocPqYE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4/go.mod h1:eJVxU6o+4G1PSczBr85xmyvSNYAKvAYgkub40YGomFM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240205150955-31a09d347014 h1:FSL3lRCkhaPFxqi0s9o+V4UI2WTzAVOvkgbd4kVV4Wg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240205150955-31a09d347014/go.mod h1:SaPjaZGWb0lPqs6Ittu0spdfrOArqji4ZdeP5IC/9N4= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= @@ -687,8 +708,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= -google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0= +google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -700,14 +721,16 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -718,17 +741,17 @@ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/mysql v1.4.3/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c= -gorm.io/driver/mysql v1.5.2 h1:QC2HRskSE75wBuOxe0+iCkyJZ+RqpudsQtqkp+IMuXs= -gorm.io/driver/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb8= -gorm.io/driver/postgres v1.5.4 h1:Iyrp9Meh3GmbSuyIAGyjkN+n9K+GHX9b9MqsTL4EJCo= -gorm.io/driver/postgres v1.5.4/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0= -gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0= -gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4= +gorm.io/driver/mysql v1.5.4 h1:igQmHfKcbaTVyAIHNhhB888vvxh8EdQ2uSUT0LPcBso= +gorm.io/driver/mysql v1.5.4/go.mod h1:9rYxJph/u9SWkWc9yY4XJ1F/+xO0S/ChOmbk3+Z5Tvs= +gorm.io/driver/postgres v1.5.6 h1:ydr9xEd5YAM0vxVDY0X139dyzNz10spDiDlC7+ibLeU= +gorm.io/driver/postgres v1.5.6/go.mod h1:3e019WlBaYI5o5LIdNV+LyxCMNtLOQETBXL2h4chKpA= +gorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E= +gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE= gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= -gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= -gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= -gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A= +gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gorm.io/plugin/dbresolver v1.5.0 h1:XVHLxh775eP0CqVh3vcfJtYqja3uFl5Wr3cKlY8jgDY= gorm.io/plugin/dbresolver v1.5.0/go.mod h1:l4Cn87EHLEYuqUncpEeTC2tTJQkjngPSD+lo8hIvcT0= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= diff --git a/hack/publish.go b/hack/publish.go index 4c6055f..149e045 100644 --- a/hack/publish.go +++ b/hack/publish.go @@ -3,11 +3,15 @@ package main import ( "context" + "encoding/binary" + "encoding/hex" "flag" "fmt" "strings" "time" + models2 "github.com/libsv/go-bn/models" + "github.com/bitcoin-sv/alert-system/app/models/model" "github.com/bitcoin-sv/alert-system/app/config" @@ -23,7 +27,7 @@ func main() { alertTypeFlag := flag.Uint("type", uint(1), "type of alert to publish") sequenceNumber := flag.Uint("sequence", uint(1), "sequence number to publish") //pubKeys := flag.String("pub-keys", "", "public keys to be used for set keys") - //blockHash := flag.String("block-hash", "", "block hash to invalidate") + blockHash := flag.String("block-hash", "", "block hash to invalidate") //peer := flag.String("peer", "", "peer to ban/unban") keys := flag.String("signing-keys", "", "signing keys") @@ -33,9 +37,9 @@ func main() { log := gocore.Log("sig-test") // Load the configuration and services - _appConfig, err := config.LoadConfig(context.Background(), models.BaseModels, false) + _appConfig, err := config.LoadDependencies(context.Background(), models.BaseModels, false) if err != nil { - _appConfig.Services.Log.Fatalf("error loading configuration: %s", err.Error()) + log.Fatalf("error loading configuration: %s", err.Error()) } defer func() { _appConfig.CloseAll(context.Background()) @@ -51,17 +55,20 @@ func main() { a := &models.AlertMessage{} switch alertType { case models.AlertTypeInformational: - a = InfoAlert(*sequenceNumber, model.WithAllDependencies(_appConfig)) + //reader := bufio.NewReader(os.Stdin) + //text, _ := reader.ReadString('\n') + a = InfoAlert(*sequenceNumber, "Testing block invalidation on testnet of 00000000000439a2c310b4e457f7e36f51c25931ccda8d512aeb2300587bcd5d", model.WithAllDependencies(_appConfig)) case models.AlertTypeInvalidateBlock: - //a = InvalidateBlockAlert(*sequenceNumber, *blockHash) + + a = invalidateBlockAlert(*sequenceNumber, *blockHash, model.WithAllDependencies(_appConfig)) case models.AlertTypeBanPeer: //a = BanPeerAlert(*sequenceNumber, *peer) case models.AlertTypeUnbanPeer: //a = UnbanPeerAlert(*sequenceNumber, *peer) case models.AlertTypeConfiscateUtxo: - panic(fmt.Errorf("not implemented")) + a = confiscateAlert(*sequenceNumber, model.WithAllDependencies(_appConfig)) case models.AlertTypeFreezeUtxo: - panic(fmt.Errorf("not implemented")) + a = freezeAlert(*sequenceNumber, model.WithAllDependencies(_appConfig)) case models.AlertTypeUnfreezeUtxo: panic(fmt.Errorf("not implemented")) case models.AlertTypeSetKeys: @@ -89,19 +96,10 @@ func main() { a.SetSignatures(sigs) - var v bool - if v, err = a.AreSignaturesValid(ctx); err != nil { - panic(err) - } - if !v { - log.Errorf("signature is not valid") - return - } - // Create the p2p server var p2pServer *p2p.Server if p2pServer, err = p2p.NewServer(p2p.ServerOptions{ - TopicNames: []string{config.DatabasePrefix}, + TopicNames: []string{_appConfig.P2P.TopicName}, Config: _appConfig, }); err != nil { _appConfig.Services.Log.Fatalf("error creating p2p server: %s", err.Error()) @@ -117,22 +115,74 @@ func main() { } topics := p2pServer.Topics() + var v bool + if v, err = a.AreSignaturesValid(ctx); err != nil { + panic(err) + } + if !v { + log.Errorf("signature is not valid") + return + } log.Infof("bytes: %x", a.Serialize()) - publish(ctx, topics[config.DatabasePrefix], a.Serialize()) - log.Infof("successfully published alert") + publish(ctx, topics[_appConfig.P2P.TopicName], a.Serialize()) + log.Infof("successfully published alert to topic %s", _appConfig.P2P.TopicName) } // InfoAlert creates an informational alert -func InfoAlert(seq uint, opts ...model.Options) *models.AlertMessage { +func InfoAlert(seq uint, msg string, opts ...model.Options) *models.AlertMessage { // Create the new alert opts = append(opts, model.New()) newAlert := models.NewAlertMessage(opts...) newAlert.SetAlertType(models.AlertTypeInformational) - newAlert.SetRawMessage([]byte{0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f}) + newAlert.SetRawMessage([]byte(msg)) + newAlert.SequenceNumber = uint32(seq) + newAlert.SetTimestamp(uint64(time.Now().Second())) + newAlert.SetVersion(0x01) + + newAlert.SerializeData() + return newAlert +} + +func freezeAlert(seq uint, opts ...model.Options) *models.AlertMessage { + tx, _ := hex.DecodeString("d83dee7aec89a9437345d9676bc727a2592e5b3988f4343931181f86b666eace") + fund := models.Fund{ + TransactionOutID: [32]byte(tx), + Vout: uint64(0), + EnforceAtHeightStart: uint64(10000), + EnforceAtHeightEnd: uint64(10100), + PolicyExpiresWithConsensus: false, + } + opts = append(opts, model.New()) + newAlert := models.NewAlertMessage(opts...) + newAlert.SetAlertType(models.AlertTypeFreezeUtxo) + newAlert.SetRawMessage(fund.Serialize()) newAlert.SequenceNumber = uint32(seq) newAlert.SetTimestamp(uint64(time.Now().Second())) newAlert.SetVersion(0x01) + newAlert.SerializeData() + return newAlert +} +func confiscateAlert(seq uint, opts ...model.Options) *models.AlertMessage { + tx := models2.ConfiscationTransactionDetails{ + ConfiscationTransaction: models2.ConfiscationTransaction{ + Hex: "dd1b08331cf22da4d27bd1b29019a04a168805d49b48d65a7fec381eb4307d61", + EnforceAtHeight: 10000, + }, + } + raw := []byte{} + enforce := [8]byte{} + binary.LittleEndian.PutUint64(enforce[:], uint64(tx.ConfiscationTransaction.EnforceAtHeight)) + raw = append(raw, enforce[:]...) + by, _ := hex.DecodeString(tx.ConfiscationTransaction.Hex) + raw = append(raw, by...) + opts = append(opts, model.New()) + newAlert := models.NewAlertMessage(opts...) + newAlert.SetAlertType(models.AlertTypeConfiscateUtxo) + newAlert.SetRawMessage(raw) + newAlert.SequenceNumber = uint32(seq) + newAlert.SetTimestamp(uint64(time.Now().Second())) + newAlert.SetVersion(0x01) newAlert.SerializeData() return newAlert } @@ -193,31 +243,30 @@ func UnbanPeerAlert(seq uint, peer string) alert.Alert { a.Data = data return a } - -// InvalidateBlockAlert creates an invalidate block alert -func InvalidateBlockAlert(seq uint, blockHash string) alert.Alert { +*/ +// invalidateBlockAlert creates an invalidate block alert +func invalidateBlockAlert(seq uint, blockHash string, opts ...model.Options) *models.AlertMessage { hash, err := hex.DecodeString(blockHash) if err != nil { panic(err) } - msg := []byte{} - msg = append(msg, hash...) - msg = append(msg, []byte{0x01, 0x01}...) // Just append a 1 byte reason for simplicity - a := alert.Alert{ - Version: 0x01, - SequenceNumber: uint32(seq), - Timestamp: uint64(time.Now().Second()), - AlertType: models.AlertTypeInvalidateBlock, - AlertMessage: msg, - } - var data []byte - if data, err = a.SerializeData(); err != nil { - panic(err) - } - a.Data = data - return a + raw := []byte{} + + opts = append(opts, model.New()) + raw = append(raw, hash...) + raw = append(raw, 0x07, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6E, 0x67) + newAlert := models.NewAlertMessage(opts...) + newAlert.SetAlertType(models.AlertTypeInvalidateBlock) + newAlert.SetVersion(0x01) + newAlert.SetTimestamp(uint64(time.Now().Unix())) + newAlert.SequenceNumber = uint32(seq) + newAlert.SetRawMessage(raw) + newAlert.SerializeData() + + return newAlert } +/* // SetKeys creates a set keys alert func SetKeys(seq uint, keys []string) alert.Alert { a := alert.Alert{ diff --git a/utils/keys_test.go b/utils/keys_test.go index 1bd0a54..343ab63 100644 --- a/utils/keys_test.go +++ b/utils/keys_test.go @@ -44,3 +44,24 @@ func TestSignWithKeys_Success(t *testing.T) { assert.Equal(t, "1f71934b00f38fc068ce606b9ea6c9a3a73b7f68ed2ec7cfea3ab93cf3b1e117c0605b74b026a9e575e1b63d55b54bd50f493d52cdd37fbaa7e0aba26b96dc0376", hex.EncodeToString(signatures[1])) assert.Equal(t, "1f17850af80b856545e77b060057e4c31a45bdc5fa89a6f91a6bb7c8df683b5875269d18ec3dc61c7198ff07d53df241fa61210f3825a871bd485e32329e6fcff2", hex.EncodeToString(signatures[2])) } + +// BenchmarkSignWithKeys benchmarks the function SignWithKeys +func BenchmarkSignWithKeys(b *testing.B) { + data := []byte("test data") + keys := []string{Key1, Key2, Key3} + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = SignWithKeys(data, keys) + } +} + +// BenchmarkSignWithGenesis benchmarks the function SignWithGenesis +func BenchmarkSignWithGenesis(b *testing.B) { + data := []byte("test data") + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = SignWithGenesis(data) + } +}