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..73276ce 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,14 +23,14 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: 1.19 + go-version: 1.21 - name: Run GoReleaser uses: goreleaser/goreleaser-action@v5.0.0 with: distribution: goreleaser version: latest - args: release --rm-dist --debug + args: release --clean --debug env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} #- name: Syndicate to GoDocs - # run: make godocs \ No newline at end of file + # run: make godocs diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 8566faa..f513a82 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -51,7 +51,7 @@ 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 diff --git a/.goreleaser.yml b/.goreleaser.yml index 3505765..3d3f9b9 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -18,8 +18,40 @@ changelog: # --------------------------- # Builder # --------------------------- -build: - skip: true +builds: + - env: + - CGO_ENABLED=0 # Required for SQLite + - GO111MODULE=on + #- CGO_CFLAGS=-Wno-error=unused-command-line-argument + main: ./cmd/ + binary: "alert_system" + goarch: + - amd64 + - arm64 + goos: + - darwin + - linux + goarm: + - 6 + - 7 + goamd64: + - v2 + - v3 + mod_timestamp: "{{ .CommitTimestamp }}" + ldflags: + - -s -w -X github.com/bitcoin-sv/{{ .ProjectName }}/cmd.Version={{ .Version }} + +# --------------------------- +# Archives + Checksums +# --------------------------- +archives: + - wrap_in_directory: true + format: tar.gz + name_template: '{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}_{{ .Version }}' + +checksum: + name_template: "{{ .ProjectName }}_checksums.txt" + algorithm: sha256 # --------------------------- # GitHub Release @@ -68,4 +100,4 @@ announce: # Defaults to `{{ .GitURL }}/releases/tag/{{ .Tag }}` # url_template: 'https://github.com/bitcoin-sv/{{ .ProjectName }}/releases/tag/{{ .Tag }}' # Defaults to `{{ .ProjectName }} {{ .Tag }} is out!` - title_template: '{{ .ProjectName }} {{ .Tag }} is out!' + title_template: '{{ .ProjectName }} {{ .Tag }} is out!' \ No newline at end of file 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/Dockerfile b/Dockerfile index 65c7a0f..3bda224 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,6 +15,6 @@ WORKDIR / RUN mkdir /.bitcoin RUN touch /.bitcoin/alert_system_private_key COPY --from=builder /opt/app-root/src/alert-system . -COPY example_settings_local.conf settings_local.conf 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/README.md b/README.md index 22a6fca..bf84d47 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,13 @@ 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 +``` +$ podman run -u root -e P2P_PORT=9908 -e P2P_IP=0.0.0.0 --expose 9908 docker.io/galtbv/alert-system:0.0.2 +``` + ## Documentation View the generated [documentation](https://pkg.go.dev/github.com/bitcoin-sv/alert-system) 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/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 6fe0a9c..60e6cc7 100644 --- a/app/config/config.go +++ b/app/config/config.go @@ -19,7 +19,10 @@ const ( 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 @@ -27,18 +30,24 @@ 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 - DatabasePrefix = "alert_system" // Default database prefix - DefaultAlertSystemProtocolID = "/bitcoin/alert-system/1.0.1" // Default alert system protocol for libp2p syncing - 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 + 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 @@ -46,13 +55,15 @@ type ( // Config is the global configuration settings Config struct { - 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 - 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 + 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 + 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 @@ -61,7 +72,7 @@ type ( 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:"sq_lite"` // Configuration for SQLite + 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) @@ -81,11 +92,13 @@ type ( // 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 + 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 diff --git a/app/config/envs/local.json b/app/config/envs/local.json index c47e939..9a88751 100644 --- a/app/config/envs/local.json +++ b/app/config/envs/local.json @@ -1,6 +1,9 @@ { "alert_webhook_url": "", + "bitcoin_config_path": "", "request_logging": true, + "bitcoin_config_path": "/home/galt/.bitcoin/bitcoin.conf", + "alert_processing_interval": "5m", "web_server": { "idle_timeout": "60s", "port": "3000", @@ -54,14 +57,16 @@ "p2p": { "ip": "0.0.0.0", "port": "9906", - "alert_system_protocol_id": "/bitcoin/alert-system/1.0.1", + "alert_system_protocol_id": "/bitcoin-testnet/alert-system/0.0.1", "bootstrap_peer": "", - "private_key_path": "" + "private_key_path": "", + "peer_discovery_interval": "10m", + "topic_name": "alert_system_testnet" }, "rpc_connections": [ { - "user": "galt", - "password": "galt", + "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..c5f889a --- /dev/null +++ b/app/config/envs/mainnet.json @@ -0,0 +1,70 @@ +{ + "alert_webhook_url": "", + "bitcoin_config_path": "", + "request_logging": true, + "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.1", + "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 index eec24a2..dbf27b5 100644 --- a/app/config/envs/production.json +++ b/app/config/envs/production.json @@ -1,5 +1,6 @@ { "alert_webhook_url": "", + "bitcoin_config_path": "", "request_logging": true, "web_server": { "idle_timeout": "60s", @@ -56,7 +57,8 @@ "port": "9906", "alert_system_protocol_id": "/bitcoin/alert-system/1.0.1", "bootstrap_peer": "", - "private_key_path": "" + "private_key_path": "", + "topic_name": "bitcoin_alert_system" }, "rpc_connections": [ { diff --git a/app/config/envs/stn.json b/app/config/envs/stn.json new file mode 100644 index 0000000..f505a0d --- /dev/null +++ b/app/config/envs/stn.json @@ -0,0 +1,71 @@ +{ + "alert_webhook_url": "", + "bitcoin_config_path": "", + "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 index f275db7..654afa1 100644 --- a/app/config/envs/test.json +++ b/app/config/envs/test.json @@ -1,5 +1,6 @@ { "alert_webhook_url": "https://webhook.url", + "bitcoin_config_path": "", "request_logging": true, "web_server": { "idle_timeout": "60s", @@ -54,7 +55,7 @@ "p2p": { "ip": "192.168.1.1", "port": "8000", - "alert_system_protocol_id": "/bitcoin/alert-system/1.0.1", + "alert_system_protocol_id": "/bitcoin/alert-system/0.0.1", "bootstrap_peer": "", "private_key_path": "/path/to/private/key" }, diff --git a/app/config/envs/testnet.json b/app/config/envs/testnet.json new file mode 100644 index 0000000..7768954 --- /dev/null +++ b/app/config/envs/testnet.json @@ -0,0 +1,71 @@ +{ + "alert_webhook_url": "", + "bitcoin_config_path": "", + "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/load.go b/app/config/load.go index 60042af..0000589 100644 --- a/app/config/load.go +++ b/app/config/load.go @@ -1,11 +1,13 @@ package config import ( + "bufio" "bytes" "context" "errors" "fmt" "io/fs" + "net" "net/http" "os" "strings" @@ -90,6 +92,11 @@ func requireP2P(_appConfig *Config) error { _appConfig.P2P.AlertSystemProtocolID = 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 len(_appConfig.P2P.PrivateKeyPath) == 0 { @@ -98,6 +105,18 @@ func requireP2P(_appConfig *Config) error { } } + // Load bitcoin configuration if specified + if len(_appConfig.BitcoinConfigPath) > 0 { + if err := _appConfig.loadBitcoinConfiguration(); err != nil { + return err + } + } + + // Load the peer discovery interval + if _appConfig.P2P.PeerDiscoveryInterval <= 0 { + _appConfig.P2P.PeerDiscoveryInterval = DefaultPeerDiscoveryInterval + } + // 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 { @@ -200,6 +219,11 @@ func LoadConfigFile() (_appConfig *Config, err error) { Logger: gocore.Log(ApplicationName), } + // 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()) @@ -219,6 +243,73 @@ func (c *Config) createPrivateKeyDirectory() error { return nil } +// 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" + } + + 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) { diff --git a/app/config/load_test.go b/app/config/load_test.go index 61f76d7..39ede04 100644 --- a/app/config/load_test.go +++ b/app/config/load_test.go @@ -29,6 +29,8 @@ func TestLoadConfig_Success(t *testing.T) { 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) 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/p2p/dht.go b/app/p2p/dht.go index 9e9733d..2dd971f 100644 --- a/app/p2p/dht.go +++ b/app/p2p/dht.go @@ -5,11 +5,12 @@ import ( "sync" "time" + "github.com/bitcoin-sv/alert-system/app/config" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/multiformats/go-multiaddr" - "github.com/bitcoin-sv/alert-system/app/config" dht "github.com/libp2p/go-libp2p-kad-dht" - "github.com/libp2p/go-libp2p/core/peer" ) // initDHT will initialize the DHT @@ -43,27 +44,32 @@ func (s *Server) initDHT(ctx context.Context) (*dht.IpfsDHT, error) { } // 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 994586c..41cc88d 100644 --- a/app/p2p/server.go +++ b/app/p2p/server.go @@ -42,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 @@ -81,17 +85,18 @@ func NewServer(o ServerOptions) (*Server, error) { // 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 } @@ -112,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 { @@ -142,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 { @@ -160,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 { @@ -184,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 @@ -237,11 +334,13 @@ 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 @@ -259,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()) @@ -277,10 +378,11 @@ func (s *Server) discoverPeers(ctx context.Context, tn []string, routingDiscover // 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 @@ -289,10 +391,10 @@ func (s *Server) discoverPeers(ctx context.Context, tn []string, routingDiscover continue } - s.config.Services.Log.Debugf("successfully synced messages from peer %s", foundPeer.ID.String()) + 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) } @@ -302,15 +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 alert_system topic") + 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 @@ -376,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 5c841e7..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/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/cmd/main.go b/cmd/main.go index 4c06e14..ec5a963 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -33,10 +33,16 @@ func main() { _appConfig.Services.Log.Fatalf("error creating genesis alert: %s", err.Error()) } + // Ensure that RPC connection is valid + 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()) 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..2038b99 --- /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 | +| datstore.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 | "galt" | RPC username | +| rpc_connections[0].password | "galt" | RPC password | +| rpc_connections[0].host | "http://localhost:8333" | RPC host | diff --git a/go.mod b/go.mod index e0ba050..a55c98c 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.21.3 toolchain go1.21.4 require ( - github.com/99designs/gqlgen v0.17.42 + 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 @@ -15,19 +15,19 @@ require ( 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.5 - github.com/mrz1836/go-api-router v0.7.0 - github.com/mrz1836/go-datastore v0.5.9 - github.com/multiformats/go-multiaddr v0.12.0 + github.com/libsv/go-p2p v0.1.9 + github.com/mrz1836/go-api-router v0.7.1 + github.com/mrz1836/go-datastore v0.5.13 + 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 ( @@ -42,7 +42,7 @@ require ( 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/fsnotify/fsnotify v1.7.0 // indirect github.com/go-logr/logr v1.4.1 // indirect @@ -56,8 +56,8 @@ 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-20231212022811-ec68065c825e // indirect - github.com/google/uuid v1.5.0 // indirect + github.com/google/pprof v0.0.0-20240130152714-0ed6a68c8d9e // 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 @@ -67,7 +67,7 @@ require ( 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 @@ -75,14 +75,15 @@ 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.1 // 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 @@ -105,10 +106,9 @@ require ( 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.19 // 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 @@ -126,22 +126,21 @@ 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.29.0 // 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/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.1 // 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 @@ -154,38 +153,38 @@ require ( 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.22.0 // indirect + go.opentelemetry.io/otel/metric v1.22.0 // indirect + go.opentelemetry.io/otel/trace v1.22.0 // 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.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-20231219180239-dc181d75b848 // indirect + golang.org/x/crypto v0.18.0 // indirect + golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // 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/net v0.20.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.16.1 // 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-20231212172506-995d672761c0 // indirect - google.golang.org/grpc v1.60.1 // 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 ) @@ -194,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 cbed97e..47a9bea 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.42 h1:BVWDOb2VVHQC5k3m6oa0XhDnxltLLrU4so7x/u39Zu4= -github.com/99designs/gqlgen v0.17.42/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= @@ -69,8 +69,8 @@ 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= @@ -78,8 +78,8 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 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.2 h1:tA0guT4GrDuWCAAQp2QZRqf1I0nu4cr8nQJ/sk7x03s= -github.com/galt-tr/go-bn v0.0.2/go.mod h1:U8pPMrGIG/Q0vslgvDrU9fVWskX2kX1+lcmhffF+om4= +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= @@ -147,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-20231212022811-ec68065c825e h1:bwOy7hAFd0C91URzMIEBfr6BAz29yk7Qj0cy6S7DJlU= -github.com/google/pprof v0.0.0-20231212022811-ec68065c825e/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +github.com/google/pprof v0.0.0-20240130152714-0ed6a68c8d9e h1:E+3PBMCXn0ma79O7iCrne0iUpKtZ7rIcZvoz+jNtNtw= +github.com/google/pprof v0.0.0-20240130152714-0ed6a68c8d9e/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.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= -github.com/google/uuid v1.5.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= @@ -179,16 +179,16 @@ 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= @@ -200,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.1 h1:5I9etrGkLrN+2XPCsi6XLlV5DITbSL/xBZdmAxFcXPI= -github.com/jackc/pgx/v5 v5.5.1/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= @@ -217,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= @@ -227,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= @@ -281,8 +283,8 @@ github.com/libsv/go-bk v0.1.6/go.mod h1:khJboDoH18FPUaZlzRFKzlVN84d4YfdmlDtdX4LA 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.5 h1:zbUE1UQ73J7LN/s3gruQBTh3Sz0DSSmj3cWhC1ZQVM0= -github.com/libsv/go-p2p v0.1.5/go.mod h1:9KhX8e+3oEmGiYQSeF/CrHj22YNHqiof3TH77VqcMCs= +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= @@ -291,26 +293,21 @@ github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8 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.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI= -github.com/mattn/go-sqlite3 v1.14.19/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= @@ -331,10 +328,10 @@ 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.9 h1:wNNUNCBCSddOieE5aM06GI5dN8JElbIQrzqEAxnxrPE= -github.com/mrz1836/go-datastore v0.5.9/go.mod h1:tkc466oJtAPNxENZpfjlcerTrCy7kyCzJiVVgbIIguE= +github.com/mrz1836/go-api-router v0.7.1 h1:CD5eRWtpI6uAFTTS6ilostM9GlI0YXg7YJdJQiY8wpo= +github.com/mrz1836/go-api-router v0.7.1/go.mod h1:x9W150CQr7BmHZmDUls1GaNVcarec29HlhQ3dbSmKO4= +github.com/mrz1836/go-datastore v0.5.13 h1:oJrm7lJg0LtaR7HO1BvyeFB4cpJic3YcRu4XDm8CtVs= +github.com/mrz1836/go-datastore v0.5.13/go.mod h1:sRbHLRKhKdC0IR3hRnu2Qgn+OlB2wKYfl+jsiUT8cPo= 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-parameters v0.4.1 h1:8ElvGs8hzk0/hz2t5an2FmS9SPfWozGn2nXUvYwI+Uk= @@ -345,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= @@ -365,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.29.0 h1:Bc1D3DoOkpJs6aIzhOjUp+yIKJ2RfZ+LMQemZOs9t9k= -github.com/newrelic/go-agent/v3 v3.29.0/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= @@ -383,8 +380,8 @@ 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= @@ -398,31 +395,29 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH 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.1 h1:X3AGzUNFs0jVuO3esAGnTfvdgvL4fq655WaOi1snv1Q= -github.com/quic-go/quic-go v0.40.1/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= @@ -498,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= @@ -524,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.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y= +go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= +go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg= +go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= +go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0= +go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= 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= @@ -568,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.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20231219180239-dc181d75b848 h1:+iq7lrkxmFNBM7xx+Rae2W6uyPfhPeDWD+n+JgppptE= -golang.org/x/exp v0.0.0-20231219180239-dc181d75b848/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= +golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA= +golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/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= @@ -606,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.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= 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= @@ -622,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= @@ -648,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.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.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= @@ -659,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= @@ -683,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.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= -golang.org/x/tools v0.16.1/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= @@ -705,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-20231212172506-995d672761c0 h1:/jFB8jK5R3Sq3i/lmeZO0cATSzFfZaJq1J2Euan3XKU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0/go.mod h1:FUoWkonphQm3RhTS+kOEhF8h0iDpm4tdXolVCeZ9KKA= +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= @@ -715,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.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= -google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +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= @@ -748,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 fac1d51..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") @@ -35,7 +39,7 @@ func main() { // Load the configuration and services _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{