diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5d5dcc30..b47d3aa3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -37,6 +37,9 @@ concurrency: env: GO_VERSION: "1.22.x" PNPM_VERSION: "9.4.x" + GO_LDFLAGS: >- + -X 'github.com/hemilabs/heminetwork/version.Brand=Hemi Labs' + -X github.com/hemilabs/heminetwork/version.PreRelease= jobs: # Run tests @@ -112,7 +115,8 @@ jobs: GOARCH: "${{ matrix.goarch }}" CGO_ENABLED: 0 # Disable CGO. GOGC: off # Disable GC during build, faster but uses more RAM. - run: make GOCACHE="$(go env GOCACHE)" archive + GO_LDFLAGS: "${{ env.GO_LDFLAGS }}" + run: make GOCACHE="$(go env GOCACHE)" GO_LDFLAGS="$GO_LDFLAGS" archive - name: "Upload artifacts" uses: actions/upload-artifact@v4 @@ -182,6 +186,7 @@ jobs: VERSION=${{ needs.prepare.outputs.version }} VCS_REF=${{ github.sha }} BUILD_DATE=${{ steps.prepare.outputs.build_date }} + GO_LDFLAGS=${{ env.GO_LDFLAGS }} tags: | hemilabs/${{ matrix.service }}:latest hemilabs/${{ matrix.service }}:${{ needs.prepare.outputs.tag }} diff --git a/Makefile b/Makefile index 3f93c2e1..5a342ef9 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,7 @@ PROJECTPATH = $(abspath $(dir $(realpath $(firstword $(MAKEFILE_LIST))))) export GOBIN=$(PROJECTPATH)/bin export GOCACHE=$(PROJECTPATH)/.gocache export GOPKG=$(PROJECTPATH)/pkg +GO_LDFLAGS="" DIST=$(PROJECTPATH)/dist ifeq ($(GOOS),windows) @@ -49,7 +50,7 @@ go-deps: go mod verify $(cmds): - go build -trimpath -o $(GOBIN)/$@$(BIN_EXT) ./cmd/$@ + go build -trimpath -ldflags "$(GO_LDFLAGS)" -o $(GOBIN)/$@$(BIN_EXT) ./cmd/$@ build: go build ./... @@ -105,4 +106,3 @@ sources: dist checksums: dist cd $(DIST) && shasum -a 256 * > $(project)_$(version)_checksums.txt - diff --git a/api/auth/secp256k1.go b/api/auth/secp256k1.go index 6cf87912..9767a068 100644 --- a/api/auth/secp256k1.go +++ b/api/auth/secp256k1.go @@ -16,6 +16,7 @@ import ( dcrecdsa "github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa" "github.com/hemilabs/heminetwork/api/protocol" + "github.com/hemilabs/heminetwork/version" ) const ( @@ -25,19 +26,24 @@ const ( CmdSecp256k1HelloChallengeAccepted = "secp256k1-hello-challenge-accepted" ) -// Hello is a client->server command that sends the client ECDSA public key. +// Secp256k1Hello is a client->server command that sends the client Secp256k1 +// public key. type Secp256k1Hello struct { - PublicKey string `json:"publickey"` // Client compressed public key + // UserAgent is the client user agent. + UserAgent string `json:"userAgent,omitempty"` + + // PublicKey is the client compressed public key. + PublicKey string `json:"publickey"` } -// HelloChallenge is a server->client command that challenges the the client to -// sign the hash of the provided message. +// Secp256k1HelloChallenge is a server->client command that challenges the +// client to sign the hash of the provided message. type Secp256k1HelloChallenge struct { Message string `json:"message"` } -// Secp256k1HelloChallengeAccepted returns the signature of the HelloChallenge.Message -// hash. +// Secp256k1HelloChallengeAccepted is a client->server command containing the +// signature of the Secp256k1HelloChallenge.Message hash. type Secp256k1HelloChallengeAccepted struct { Signature string `json:"signature"` } @@ -106,7 +112,8 @@ type Secp256k1Auth struct { privKey *dcrsecpk256k1.PrivateKey // client private key pubKey *dcrsecpk256k1.PublicKey // client public key - remotePubKey *dcrsecpk256k1.PublicKey // server side remote key (client) + remoteUserAgent string // client user agent + remotePubKey *dcrsecpk256k1.PublicKey // server side remote key (client) } func NewSecp256k1AuthClient(privKey *dcrsecpk256k1.PrivateKey) (*Secp256k1Auth, error) { @@ -117,7 +124,11 @@ func NewSecp256k1AuthServer() (*Secp256k1Auth, error) { return &Secp256k1Auth{}, nil } -func (s Secp256k1Auth) RemotePublicKey() *dcrsecpk256k1.PublicKey { +func (s *Secp256k1Auth) RemoteUserAgent() string { + return s.remoteUserAgent +} + +func (s *Secp256k1Auth) RemotePublicKey() *dcrsecpk256k1.PublicKey { pub := *s.remotePubKey return &pub } @@ -136,8 +147,11 @@ func (s *Secp256k1Auth) HandshakeClient(ctx context.Context, conn protocol.APICo defer log.Tracef("HandshakeClient exit") pubKey := hex.EncodeToString(s.pubKey.SerializeCompressed()) - id := "Hello: " + pubKey - err := protocol.Write(ctx, conn, s, id, Secp256k1Hello{PublicKey: pubKey}) + id := "Hello:" + pubKey + err := protocol.Write(ctx, conn, s, id, Secp256k1Hello{ + UserAgent: version.UserAgent(), + PublicKey: pubKey, + }) if err != nil { return err } diff --git a/api/protocol/dial.go b/api/protocol/dial.go new file mode 100644 index 00000000..3cb0be95 --- /dev/null +++ b/api/protocol/dial.go @@ -0,0 +1,15 @@ +// Copyright (c) 2024 Hemi Labs, Inc. +// Use of this source code is governed by the MIT License, +// which can be found in the LICENSE file. + +//go:build !js && !wasm + +package protocol + +import "github.com/coder/websocket" + +func newDialOptions(opts ConnOptions) *websocket.DialOptions { + return &websocket.DialOptions{ + HTTPHeader: opts.Headers, + } +} diff --git a/api/protocol/dial_wasm.go b/api/protocol/dial_wasm.go new file mode 100644 index 00000000..0c6409c3 --- /dev/null +++ b/api/protocol/dial_wasm.go @@ -0,0 +1,16 @@ +// Copyright (c) 2024 Hemi Labs, Inc. +// Use of this source code is governed by the MIT License, +// which can be found in the LICENSE file. + +//go:build js && wasm + +package protocol + +import "github.com/coder/websocket" + +func newDialOptions(_ ConnOptions) *websocket.DialOptions { + // HTTPHeader is not supported in WASM, due to the JavaScript WebSocket API + // not supporting setting HTTP headers for the handshake/initial HTTP + // request. + return nil +} diff --git a/api/protocol/protocol.go b/api/protocol/protocol.go index 7b98ac2b..dc252ad5 100644 --- a/api/protocol/protocol.go +++ b/api/protocol/protocol.go @@ -12,6 +12,7 @@ import ( "errors" "fmt" "io" + "net/http" "net/url" "reflect" "sync" @@ -380,6 +381,9 @@ type ConnOptions struct { // Authenticator is the connection authenticator. Authenticator Authenticator + + // Headers are the HTTP headers included in the WebSocket handshake request. + Headers http.Header } // defaultConnReadLimit is the default connection read limit. @@ -430,7 +434,7 @@ func (ac *Conn) Connect(ctx context.Context) error { // package. // Note that we cannot have DialOptions on a WASM websocket log.Tracef("Connect: dialing %v", ac.serverURL) - conn, _, err := websocket.Dial(connectCtx, ac.serverURL, nil) + conn, _, err := websocket.Dial(connectCtx, ac.serverURL, newDialOptions(ac.opts)) if err != nil { return fmt.Errorf("dial server: %w", err) } diff --git a/cmd/bfgd/bfgd.go b/cmd/bfgd/bfgd.go index 3abaccde..9c33073b 100644 --- a/cmd/bfgd/bfgd.go +++ b/cmd/bfgd/bfgd.go @@ -27,7 +27,7 @@ const ( var ( log = loggo.GetLogger(daemonName) - welcome = fmt.Sprintf("Hemi Bitcoin Finality Governor: v%v", version.String()) + welcome string cfg = bfg.NewDefaultConfig() cm = config.CfgMap{ @@ -129,7 +129,7 @@ func _main() error { } loggo.ConfigureLoggers(cfg.LogLevel) - log.Infof("%v", welcome) + log.Infof(welcome) pc := config.PrintableConfig(cm) for k := range pc { @@ -152,6 +152,11 @@ func _main() error { return nil } +func init() { + version.Component = "bfgd" + welcome = "Hemi Bitcoin Finality Governor " + version.BuildInfo() +} + func main() { if len(os.Args) != 1 { fmt.Fprintf(os.Stderr, "%v\n", welcome) diff --git a/cmd/bssd/bssd.go b/cmd/bssd/bssd.go index 4d214a08..575bb2fc 100644 --- a/cmd/bssd/bssd.go +++ b/cmd/bssd/bssd.go @@ -28,7 +28,7 @@ const ( var ( log = loggo.GetLogger(daemonName) - welcome = fmt.Sprintf("Hemi Bitcoin Secure Sequencer: v%s", version.String()) + welcome string cfg = bss.NewDefaultConfig() cm = config.CfgMap{ @@ -115,6 +115,11 @@ func _main() error { return nil } +func init() { + version.Component = "bssd" + welcome = "Hemi Bitcoin Secure Sequencer " + version.BuildInfo() +} + func main() { if len(os.Args) != 1 { fmt.Fprintf(os.Stderr, "%v\n", welcome) diff --git a/cmd/btctool/btctool.go b/cmd/btctool/btctool.go index d8ec9133..ab07b98a 100644 --- a/cmd/btctool/btctool.go +++ b/cmd/btctool/btctool.go @@ -32,6 +32,7 @@ import ( "github.com/hemilabs/heminetwork/cmd/btctool/blockstream" "github.com/hemilabs/heminetwork/cmd/btctool/btctool" "github.com/hemilabs/heminetwork/database/tbcd" + "github.com/hemilabs/heminetwork/version" ) var log = loggo.GetLogger("bdf") @@ -488,6 +489,7 @@ func addressToScript(addr string) (btcutil.Address, error) { } func init() { + version.Component = "btctool" } func _main() error { diff --git a/cmd/extool/extool.go b/cmd/extool/extool.go index 78edaab4..07676875 100644 --- a/cmd/extool/extool.go +++ b/cmd/extool/extool.go @@ -20,6 +20,10 @@ import ( "github.com/hemilabs/heminetwork/version" ) +func init() { + version.Component = "extool" +} + func main() { ver := flag.Bool("v", false, "version") flag.Parse() diff --git a/cmd/hemictl/hemictl.go b/cmd/hemictl/hemictl.go index 59b630e5..ccf46e9f 100644 --- a/cmd/hemictl/hemictl.go +++ b/cmd/hemictl/hemictl.go @@ -52,7 +52,7 @@ const ( var ( log = loggo.GetLogger(daemonName) - welcome = fmt.Sprintf("Hemi Network Controller: v%v", version.String()) + welcome string bssURL string logLevel string @@ -785,6 +785,9 @@ var ( ) func init() { + version.Component = "hemictl" + welcome = "Hemi Network Controller " + version.BuildInfo() + // merge all command maps for k, v := range bssapi.APICommands() { allCommands[string(k)] = v diff --git a/cmd/keygen/keygen.go b/cmd/keygen/keygen.go index 0975eeee..41c69e94 100644 --- a/cmd/keygen/keygen.go +++ b/cmd/keygen/keygen.go @@ -25,7 +25,7 @@ var ( secp256k1KeyPair = flag.Bool("secp256k1", false, "Generate a secp256k1 key pair") jsonFormat = flag.Bool("json", false, "print output as JSON") - welcome = fmt.Sprintf("key generator: v%v", version.String()) + welcome string ) func usage() { @@ -34,6 +34,11 @@ func usage() { flag.PrintDefaults() } +func init() { + version.Component = "keygen" + welcome = "Key Generator " + version.BuildInfo() +} + func _main() error { var btcChainParams *btcchaincfg.Params switch *net { diff --git a/cmd/popmd/popmd.go b/cmd/popmd/popmd.go index a532cee5..01fcd610 100644 --- a/cmd/popmd/popmd.go +++ b/cmd/popmd/popmd.go @@ -26,7 +26,7 @@ const ( var ( log = loggo.GetLogger(daemonName) - welcome = fmt.Sprintf("Hemi Proof of Proof miner: v%v", version.String()) + welcome string cfg = popm.NewDefaultConfig() cm = config.CfgMap{ @@ -131,6 +131,11 @@ func _main() error { return nil } +func init() { + version.Component = "popmd" + welcome = "Hemi Proof-of-Proof Miner " + version.BuildInfo() +} + func main() { if len(os.Args) != 1 { fmt.Fprintf(os.Stderr, "%v\n", welcome) diff --git a/cmd/tbcd/README.md b/cmd/tbcd/README.md index c199ca02..6b961aae 100644 --- a/cmd/tbcd/README.md +++ b/cmd/tbcd/README.md @@ -110,4 +110,3 @@ The `tbcd` daemon runs an RPC server that listens on the address provided by the The RPC protocol is **WebSocket-based** and **uses a standard request/response model.** [Read more about the RPC protocol and available commands](../../api/tbcapi/README.md). - diff --git a/cmd/tbcd/tbcd.go b/cmd/tbcd/tbcd.go index 45ffedf5..fcf128b6 100644 --- a/cmd/tbcd/tbcd.go +++ b/cmd/tbcd/tbcd.go @@ -29,7 +29,7 @@ const ( var ( log = loggo.GetLogger(daemonName) - welcome = fmt.Sprintf("Hemi Tiny Bitcoin Daemon: v%v", version.String()) + welcome string cfg = tbc.NewDefaultConfig() cm = config.CfgMap{ @@ -134,6 +134,11 @@ func HandleSignals(ctx context.Context, cancel context.CancelFunc, callback func os.Exit(2) } +func init() { + version.Component = "tbcd" + welcome = "Hemi Tiny Bitcoin Daemon " + version.BuildInfo() +} + func _main() error { // Parse configuration from environment if err := config.Parse(cm); err != nil { diff --git a/docker/bfgd/Dockerfile b/docker/bfgd/Dockerfile index 1087862c..aff38699 100644 --- a/docker/bfgd/Dockerfile +++ b/docker/bfgd/Dockerfile @@ -5,6 +5,8 @@ # Build stage FROM golang:1.22.6-alpine3.20@sha256:1a478681b671001b7f029f94b5016aed984a23ad99c707f6a0ab6563860ae2f3 AS builder +ARG GO_LDFLAGS + # Add ca-certificates, timezone data, make and git RUN apk --no-cache add --update ca-certificates tzdata make git @@ -18,7 +20,7 @@ WORKDIR /build/ COPY . . RUN make deps -RUN GOOS=$(go env GOOS) GOARCH=$(go env GOARCH) CGO_ENABLED=0 GOGC=off make bfgd +RUN GOOS=$(go env GOOS) GOARCH=$(go env GOARCH) CGO_ENABLED=0 GOGC=off make GO_LDFLAGS="$GO_LDFLAGS" bfgd # Run stage FROM scratch diff --git a/docker/bssd/Dockerfile b/docker/bssd/Dockerfile index 690a638b..6c901312 100644 --- a/docker/bssd/Dockerfile +++ b/docker/bssd/Dockerfile @@ -5,6 +5,8 @@ # Build stage FROM golang:1.22.6-alpine3.20@sha256:1a478681b671001b7f029f94b5016aed984a23ad99c707f6a0ab6563860ae2f3 AS builder +ARG GO_LDFLAGS + # Add ca-certificates, timezone data, make and git RUN apk --no-cache add --update ca-certificates tzdata make git @@ -18,7 +20,7 @@ WORKDIR /build/ COPY . . RUN make deps -RUN GOOS=$(go env GOOS) GOARCH=$(go env GOARCH) CGO_ENABLED=0 GOGC=off make bssd +RUN GOOS=$(go env GOOS) GOARCH=$(go env GOARCH) CGO_ENABLED=0 GOGC=off make GO_LDFLAGS="$GO_LDFLAGS" bssd # Run stage FROM scratch diff --git a/docker/popmd/Dockerfile b/docker/popmd/Dockerfile index 4ed86109..7e231998 100644 --- a/docker/popmd/Dockerfile +++ b/docker/popmd/Dockerfile @@ -5,6 +5,8 @@ # Build stage FROM golang:1.22.6-alpine3.20@sha256:1a478681b671001b7f029f94b5016aed984a23ad99c707f6a0ab6563860ae2f3 AS builder +ARG GO_LDFLAGS + # Add ca-certificates, timezone data, make and git RUN apk --no-cache add --update ca-certificates tzdata make git @@ -18,7 +20,7 @@ WORKDIR /build/ COPY . . RUN make deps -RUN GOOS=$(go env GOOS) GOARCH=$(go env GOARCH) CGO_ENABLED=0 GOGC=off make popmd +RUN GOOS=$(go env GOOS) GOARCH=$(go env GOARCH) CGO_ENABLED=0 GOGC=off make GO_LDFLAGS="$GO_LDFLAGS" popmd # Run stage FROM scratch diff --git a/e2e/monitor/README.md b/e2e/monitor/README.md index c8e914ac..45a9592a 100644 --- a/e2e/monitor/README.md +++ b/e2e/monitor/README.md @@ -46,4 +46,4 @@ after these milliseconds, values will be read and dumped. ``` $ HEMI_E2E_DUMP_JSON_AFTER_MS=10000 go run ./... {"bitcoin_block_count":3011,"pop_tx_count":20,"first_batcher_publication_hash":"0x2b86a72b48668b7a35dcab99166f9330c884c50d1b19847c3c0569a0d0806465,21","last_batcher_publication_hash":"0x5ec52eeba46c300e98546de25991c1862ef8dd11c3ee3357ee2a717517e2fe8c,192","batcher_publication_count":34,"pop_miner_hemi_balance":"14000000000000000000"} -``` \ No newline at end of file +``` diff --git a/service/bfg/bfg.go b/service/bfg/bfg.go index 688518f7..bb69c3c4 100644 --- a/service/bfg/bfg.go +++ b/service/bfg/bfg.go @@ -1009,10 +1009,16 @@ func (s *Server) handleWebsocketPublic(w http.ResponseWriter, r *http.Request) { log.Errorf("Handshake Server failed for %v: %s", bws.addr, err) return } + publicKey := authenticator.RemotePublicKey().SerializeCompressed() publicKeyEncoded := hex.EncodeToString(publicKey) log.Tracef("successful handshake with public key: %s", publicKeyEncoded) + userAgent := r.UserAgent() + if ua := authenticator.RemoteUserAgent(); ua != "" { + userAgent = ua + } + if s.cfg.PublicKeyAuth { log.Tracef("will enforce auth") @@ -1056,8 +1062,8 @@ func (s *Server) handleWebsocketPublic(w http.ResponseWriter, r *http.Request) { bws.wg.Add(1) go s.handleWebsocketPublicRead(r.Context(), bws) - log.Infof("Authenticated session %s from %s public key %x", - bws.sessionId, r.RemoteAddr, bws.publicKey) + log.Infof("Authenticated session %s from %s public key %x (%s)", + bws.sessionId, r.RemoteAddr, bws.publicKey, userAgent) bws.wg.Wait() log.Infof("Terminated session %s from %s public key %x", bws.sessionId, r.RemoteAddr, bws.publicKey) diff --git a/service/popm/popm.go b/service/popm/popm.go index 47d1df6a..df42e512 100644 --- a/service/popm/popm.go +++ b/service/popm/popm.go @@ -14,6 +14,7 @@ import ( "errors" "fmt" "math/big" + "net/http" "slices" "strings" "sync" @@ -37,6 +38,7 @@ import ( "github.com/hemilabs/heminetwork/hemi" "github.com/hemilabs/heminetwork/hemi/pop" "github.com/hemilabs/heminetwork/service/pprof" + "github.com/hemilabs/heminetwork/version" ) // XXX we should debate if we can make pop miner fully transient. It feels like @@ -809,8 +811,12 @@ func (m *Miner) connectBFG(pctx context.Context) error { return err } + headers := http.Header{} + headers.Add("User-Agent", version.UserAgent()) + conn, err = protocol.NewConn(m.cfg.BFGWSURL, &protocol.ConnOptions{ Authenticator: authenticator, + Headers: headers, }) if err != nil { return err diff --git a/version/useragent.go b/version/useragent.go new file mode 100644 index 00000000..9aa374d8 --- /dev/null +++ b/version/useragent.go @@ -0,0 +1,88 @@ +// Copyright (c) 2024 Hemi Labs, Inc. +// Use of this source code is governed by the MIT License, +// which can be found in the LICENSE file. + +package version + +import ( + "runtime" + "strings" +) + +// srcUrl is the URL to the source code for this project. +const srcUrl = "https://github.com/hemilabs/heminetwork" + +var ( + // Brand is an identifier that is used to identify the organisation or + // entity that built the binary. + // + // Official binaries built by Hemi Labs, Inc. use the brand "Hemi Labs". + // If you are building your own specialised version of our binaries, please + // set this to something that uniquely identifies who you are. + // + // This helps us see when people are building on our work, and making their + // own specialised versions of our packages. We cannot wait to see what you + // are able to create! + // + // This should be set at link-time using: + // + // -ldflags "-X 'github.com/hemilabs/heminetwork/version.Brand=my brand'" + Brand string + + // Component is an identifier for the binary. + // + // This should be set in an init function in the main package: + // + // func init() { + // version.Component = "bfgd" + // } + Component string +) + +// userAgent an HTTP User-Agent header value that should be used when making +// HTTP requests. +// +// The User-Agent value contains the component name (e.g. bfgd), version, brand, +// operating system name (GOOS), system architecture, and source code URL. +var userAgent = createUserAgent(Component, String(), Brand, + runtime.GOOS+"/"+runtime.GOARCH, "+"+srcUrl) + +// UserAgent returns an HTTP User-Agent header value that should be used when +// making HTTP requests. +func UserAgent() string { + return userAgent +} + +// createUserAgent creates a RFC9110-compliant User-Agent header value. +// https://www.rfc-editor.org/rfc/rfc9110#name-user-agent +func createUserAgent(product, version string, comments ...string) string { + if product == "" { + product = "heminetwork" + } + + var out strings.Builder + out.WriteString(product) + if version != "" { + out.WriteRune('/') + out.WriteString(version) + } + + var cmts []string + for _, comment := range comments { + if c := strings.TrimSpace(comment); c != "" { + cmts = append(cmts, c) + } + } + if len(cmts) > 0 { + out.WriteString(" (") + for i, c := range cmts { + out.WriteString(c) + if i < len(cmts)-1 { + out.WriteString("; ") + } + } + out.WriteString(")") + } + + return out.String() +} diff --git a/version/useragent_test.go b/version/useragent_test.go new file mode 100644 index 00000000..9f036e28 --- /dev/null +++ b/version/useragent_test.go @@ -0,0 +1,73 @@ +// Copyright (c) 2024 Hemi Labs, Inc. +// Use of this source code is governed by the MIT License, +// which can be found in the LICENSE file. + +package version + +import "testing" + +func TestCreateUserAgent(t *testing.T) { + tests := []struct { + name string + product string + version string + comments []string + want string + }{ + { + name: "empty comment", + version: "0.0.0", + comments: []string{"test", ""}, + want: "heminetwork/0.0.0 (test)", + }, + { + name: "empty version", + product: "test", + version: "", + want: "test", + }, + { + name: "two empty comments", + product: "test", + version: "0.0.0", + comments: []string{"", ""}, + want: "test/0.0.0", + }, + { + name: "test comment", + product: "bfgd", + version: "1.0.0", + comments: []string{"test"}, + want: "bfgd/1.0.0 (test)", + }, + { + name: "empty comments with one test comment", + product: "bssd", + version: "1.0.0", + comments: []string{"", "test", ""}, + want: "bssd/1.0.0 (test)", + }, + { + name: "whitespace comments with one test comment", + product: "bssd", + version: "1.0.0", + comments: []string{" ", "test", " "}, + want: "bssd/1.0.0 (test)", + }, + { + name: "realistic", + product: "popmd", + version: "0.1.0", + comments: []string{"Hemi Labs", "linux/amd64", "+https://github.com/hemilabs/heminetwork"}, + want: "popmd/0.1.0 (Hemi Labs; linux/amd64; +https://github.com/hemilabs/heminetwork)", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := createUserAgent(tt.product, tt.version, tt.comments...); got != tt.want { + t.Errorf("createUserAgent(%q, %q, %q) = %v, want %v", + tt.product, tt.version, tt.comments, got, tt.want) + } + }) + } +} diff --git a/version/version.go b/version/version.go index e2b54ba0..3132383e 100644 --- a/version/version.go +++ b/version/version.go @@ -1,5 +1,6 @@ // Copyright (c) 2013-2014 The btcsuite developers // Copyright (c) 2015-2021 The Decred developers +// Copyright (c) 2024 Hemi Labs, Inc. // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -8,31 +9,32 @@ package version import ( "bytes" "fmt" + "runtime" "strings" ) // semverAlphabet is an alphabet of all characters allowed in semver prerelease -// or build metadata identifiers, and the . separator. +// or build metadata identifiers, and the `.` separator. const semverAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-." // Constants defining the application version number. const ( Major = 0 - Minor = 1 + Minor = 3 Patch = 0 ) // Integer is an integer encoding of the major.minor.patch version. const Integer = 1000000*Major + 10000*Minor + 100*Patch -// PreRelease contains the prerelease name of the application. It is a variable +// PreRelease contains the prerelease name of the application. It is a variable, // so it can be modified at link time (e.g. -// `-ldflags "-X decred.org/dcrwallet/v4/version.PreRelease=rc1"`). +// `-ldflags "-X github.com/hemilabs/heminetwork/version.PreRelease=rc1"`). // It must only contain characters from the semantic version alphabet. var PreRelease = "pre" -// BuildMetadata defines additional build metadata. It is modified at link time -// for official releases. It must only contain characters from the semantic +// BuildMetadata defines additional build metadata. It is modified at link time +// for official releases. It must only contain characters from the semantic // version alphabet. var BuildMetadata = "" @@ -48,18 +50,18 @@ func String() string { // Start with the major, minor, and path versions. version := fmt.Sprintf("%d.%d.%d", Major, Minor, Patch) - // Append pre-release version if there is one. The hyphen called for + // Append pre-release version if there is one. The hyphen called for // by the semantic versioning spec is automatically appended and should - // not be contained in the pre-release string. The pre-release version + // not be contained in the pre-release string. The pre-release version // is not appended if it contains invalid characters. preRelease := normalizeVerString(PreRelease) if preRelease != "" { version = version + "-" + preRelease } - // Append build metadata if there is any. The plus called for + // Append build metadata if there is any. The plus called for // by the semantic versioning spec is automatically appended and should - // not be contained in the build metadata string. The build metadata + // not be contained in the build metadata string. The build metadata // string is not appended if it contains invalid characters. buildMetadata := normalizeVerString(BuildMetadata) if buildMetadata != "" { @@ -71,7 +73,7 @@ func String() string { // normalizeVerString returns the passed string stripped of all characters which // are not valid according to the semantic versioning guidelines for pre-release -// version and build metadata strings. In particular they MUST only contain +// version and build metadata strings. In particular, they MUST only contain // characters in semanticAlphabet. func normalizeVerString(str string) string { var buf bytes.Buffer @@ -87,3 +89,20 @@ func normalizeVerString(str string) string { } return buf.String() } + +// BuildInfo returns a string containing information about the build. +func BuildInfo() string { + var out strings.Builder + out.WriteString(fmt.Sprintf("v%s (", String())) + if Brand != "" { + out.WriteString(Brand) + out.WriteString(", ") + } + if Component != "" { + out.WriteString(Component) + out.WriteString(", ") + } + out.WriteString(fmt.Sprintf("%s %s/%s)", + runtime.Version(), runtime.GOOS, runtime.GOARCH)) + return out.String() +} diff --git a/web/popminer/popminer.go b/web/popminer/popminer.go index 3089ad38..87ccc6f5 100644 --- a/web/popminer/popminer.go +++ b/web/popminer/popminer.go @@ -11,13 +11,13 @@ import ( "errors" "os" "path/filepath" - "runtime" "sync" "syscall/js" "github.com/juju/loggo" "github.com/hemilabs/heminetwork/service/popm" + versionPkg "github.com/hemilabs/heminetwork/version" ) var ( @@ -112,6 +112,7 @@ func (m *Miner) shutdown() error { func init() { loggo.ConfigureLoggers(logLevel) + versionPkg.Component = "popm-web" } func main() { @@ -121,11 +122,7 @@ func main() { // Create event listeners map svc.listeners = make(map[EventType][]js.Value) - // Enable function dispatcher - log.Infof("=== Start of Day ===") - log.Infof("%v version %v compiled with go version %v %v/%v revision %v", - filepath.Base(os.Args[0]), version, runtime.Version(), - runtime.GOOS, runtime.GOARCH, gitCommit) + log.Infof("%s %s", filepath.Base(os.Args[0]), versionPkg.BuildInfo()) log.Infof("Logging level: %v", logLevel) // Set global variable