From 3157f2d023c3729b43505355165e97069626bda4 Mon Sep 17 00:00:00 2001 From: ice-cronus <105345303+ice-cronus@users.noreply.github.com> Date: Wed, 4 Sep 2024 10:19:48 +0300 Subject: [PATCH 01/18] feat(NIP-96-4e73e94,nip-98-ae0fd96): storage management endpoints, auth, integration with distributed storage --- README.md | 3 +- cmd/subzero/subzero.go | 31 ++- database/command/command.go | 12 +- go.mod | 71 ++++--- go.sum | 209 +++++++++++------- server/http/.testdata/image.jpg | Bin 0 -> 415939 bytes server/http/.testdata/image2.png | Bin 0 -> 182744 bytes server/http/auth_nip98.go | 90 ++++++++ server/http/nip11_test.go | 26 ++- server/http/nip96.json | 15 ++ server/http/storage_nip96.go | 291 +++++++++++++++++++++++++ server/http/storage_nip_96_test.go | 328 +++++++++++++++++++++++++++++ server/server.go | 11 +- server/ws/fixture/contract.go | 16 +- server/ws/fixture/fixture.go | 19 +- server/ws/internal/contract.go | 6 +- server/ws/internal/ws.go | 6 +- server/ws/ws_test.go | 5 +- storage/download.go | 139 ++++++++++++ storage/fixture/fixture.go | 80 +++++++ storage/global.go | 171 +++++++++++++++ storage/storage.go | 182 ++++++++++++++++ storage/upload.go | 175 +++++++++++++++ 23 files changed, 1747 insertions(+), 139 deletions(-) create mode 100644 server/http/.testdata/image.jpg create mode 100644 server/http/.testdata/image2.png create mode 100644 server/http/auth_nip98.go create mode 100644 server/http/nip96.json create mode 100644 server/http/storage_nip96.go create mode 100644 server/http/storage_nip_96_test.go create mode 100644 storage/download.go create mode 100644 storage/fixture/fixture.go create mode 100644 storage/global.go create mode 100644 storage/storage.go create mode 100644 storage/upload.go diff --git a/README.md b/README.md index 687793f..5b27f1a 100644 --- a/README.md +++ b/README.md @@ -28,4 +28,5 @@ NIPs | latest commit hash implemented | comments [90](https://github.com/nostr-protocol/nips/blob/master/90.md) | | [92](https://github.com/nostr-protocol/nips/blob/master/92.md) | | [94](https://github.com/nostr-protocol/nips/blob/master/94.md) | | -[96](https://github.com/nostr-protocol/nips/blob/master/96.md) | | \ No newline at end of file +[96](https://github.com/nostr-protocol/nips/blob/master/96.md) | [4e73e94d417f16fa3451e58ef921cb3b512c6f8e](https://github.com/ice-blockchain/subzero/commit/130bac5adedf6563fe8d8e869f7e46b4cfb414e0)| +[98](https://github.com/nostr-protocol/nips/blob/master/98.md) | [ae0fd96907d0767f07fb54ca1de9f197c600cb27](https://github.com/ice-blockchain/subzero/commit/130bac5adedf6563fe8d8e869f7e46b4cfb414e0)| \ No newline at end of file diff --git a/cmd/subzero/subzero.go b/cmd/subzero/subzero.go index 0602aaf..ea0e66b 100644 --- a/cmd/subzero/subzero.go +++ b/cmd/subzero/subzero.go @@ -4,7 +4,10 @@ package main import ( "context" + "crypto/ed25519" + "crypto/rand" "log" + "net" "github.com/cockroachdb/errors" "github.com/spf13/cobra" @@ -14,6 +17,7 @@ import ( "github.com/ice-blockchain/subzero/model" "github.com/ice-blockchain/subzero/server" wsserver "github.com/ice-blockchain/subzero/server/ws" + "github.com/ice-blockchain/subzero/storage" ) var ( @@ -22,6 +26,11 @@ var ( cert string key string databasePath string + externalIP string + adnlPort uint16 + storageRootDir string + globalConfigUrl string + adnlNodeKey []byte subzero = &cobra.Command{ Use: "subzero", Short: "subzero", @@ -35,7 +44,7 @@ var ( log.Print("using database at ", databasePath) } query.MustInit(databasePath) - + storage.MustInit(ctx, adnlNodeKey, globalConfigUrl, storageRootDir, net.ParseIP(externalIP), int(adnlPort)) server.ListenAndServe(ctx, cancel, &server.Config{ CertPath: cert, KeyPath: key, @@ -50,6 +59,17 @@ var ( subzero.Flags().StringVar(&key, "key", "", "path to tls certificate for the http/ws server (TLS)") subzero.Flags().Uint16Var(&port, "port", 0, "port to communicate with clients (http/websocket)") subzero.Flags().IntVar(&minLeadingZeroBits, "minLeadingZeroBits", 0, "min leading zero bits according NIP-13") + subzero.Flags().StringVar(&externalIP, "adnl-external-ip", "", "external ip for storage service") + subzero.Flags().Uint16Var(&adnlPort, "adnl-port", 0, "port to open adnl-gateway for storage service") + subzero.Flags().StringVar(&storageRootDir, "storage-root", "./.uploads", "root storage directory") + subzero.Flags().StringVar(&globalConfigUrl, "global-config-url", storage.DefaultConfigUrl, "global config for ION storage") + subzero.Flags().BytesHexVar(&adnlNodeKey, "adnl-node-key", func() []byte { + _, nodeKey, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + log.Panic(errorx.Wrapf(err, "failed to generate node key")) + } + return nodeKey + }(), "adnl node key in hex") if err := subzero.MarkFlagRequired("cert"); err != nil { log.Print(err) } @@ -59,6 +79,15 @@ var ( if err := subzero.MarkFlagRequired("port"); err != nil { log.Print(err) } + if err := subzero.MarkFlagRequired("adnl-external-ip"); err != nil { + log.Print(err) + } + if err := subzero.MarkFlagRequired("adnl-port"); err != nil { + log.Print(err) + } + if err := subzero.MarkFlagRequired("storage-root"); err != nil { + log.Print(err) + } } ) diff --git a/database/command/command.go b/database/command/command.go index 54e9395..e8ad78c 100644 --- a/database/command/command.go +++ b/database/command/command.go @@ -5,11 +5,19 @@ package command import ( "context" + "github.com/gookit/goutil/errorx" + "github.com/hashicorp/go-multierror" + "github.com/ice-blockchain/subzero/model" + "github.com/ice-blockchain/subzero/storage" ) func AcceptEvent(ctx context.Context, event *model.Event) error { + var mErr *multierror.Error //TODO impl consensus - - return nil + // After consensus pass it to storage + if sErr := storage.AcceptEvent(ctx, event); sErr != nil { + mErr = multierror.Append(mErr, errorx.Withf(sErr, "failed to process NIP-94 event")) + } + return mErr.ErrorOrNil() } diff --git a/go.mod b/go.mod index 4fff9cf..d1ea63f 100644 --- a/go.mod +++ b/go.mod @@ -1,47 +1,54 @@ module github.com/ice-blockchain/subzero -go 1.23.2 +go 1.23.0 -replace filippo.io/mkcert => github.com/kixelated/mkcert v1.4.4-days - -replace github.com/quic-go/quic-go v0.48.0 => github.com/quic-go/quic-go v0.47.0 +replace ( + filippo.io/mkcert => github.com/kixelated/mkcert v1.4.4-days + github.com/xssnick/tonutils-storage => github.com/ice-cronus/tonutils-storage v0.0.0-20240923131001-35b67f016acf +) require ( - github.com/cockroachdb/errors v1.11.3 + github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8 + github.com/fsnotify/fsnotify v1.4.7 github.com/gin-gonic/gin v1.10.0 github.com/gobwas/httphead v0.1.0 github.com/gobwas/ws v1.4.0 github.com/google/uuid v1.6.0 + github.com/gookit/goutil v0.6.16 github.com/hashicorp/go-multierror v1.1.1 github.com/ice-blockchain/go/src v0.0.0-20240529122316-8d9458949bdd github.com/jamiealquiza/tachymeter v2.0.0+incompatible github.com/jmoiron/sqlx v1.4.0 - github.com/mattn/go-sqlite3 v1.14.24 - github.com/mxschmitt/golang-combinations v1.2.0 - github.com/nbd-wtf/go-nostr v0.39.1 - github.com/quic-go/quic-go v0.48.0 + github.com/mattn/go-sqlite3 v1.14.23 + github.com/nbd-wtf/go-nostr v0.38.1 + github.com/pkg/errors v0.9.1 + github.com/quic-go/quic-go v0.47.0 github.com/quic-go/webtransport-go v0.8.0 - github.com/schollz/progressbar/v3 v3.16.1 + github.com/schollz/progressbar/v3 v3.16.0 github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.9.0 + github.com/syndtr/goleveldb v1.0.0 + github.com/xssnick/tonutils-go v1.10.1 + github.com/xssnick/tonutils-storage v0.6.5 go.uber.org/goleak v1.3.0 + golang.org/x/net v0.29.0 pgregory.net/rand v1.0.2 ) require ( + atomicgo.dev/cursor v0.1.1 // indirect + atomicgo.dev/keyboard v0.2.9 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect github.com/bytedance/sonic v1.12.3 // indirect github.com/bytedance/sonic/loader v0.2.0 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect - github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect - github.com/cockroachdb/redact v1.1.5 // indirect + github.com/containerd/console v1.0.3 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/decred/dcrd/crypto/blake256 v1.1.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.6 // indirect - github.com/getsentry/sentry-go v0.29.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.5 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect @@ -49,46 +56,50 @@ require ( github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gobwas/pool v0.2.1 // indirect github.com/goccy/go-json v0.10.3 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/pprof v0.0.0-20241017200806-017d972448fc // indirect + github.com/google/pprof v0.0.0-20241001023024-f4c0cfd0cf1d // indirect + github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect + github.com/gookit/color v1.5.4 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/kevinms/leakybucket-go v0.0.0-20200115003610-082473db97ca // indirect github.com/klauspost/cpuid/v2 v2.2.8 // indirect - github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/lithammer/fuzzysearch v1.1.5 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae // indirect github.com/onsi/ginkgo/v2 v2.20.2 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/pterm/pterm v0.12.59 // indirect github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect github.com/quic-go/qpack v0.5.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/rogpeppe/go-internal v1.13.1 // indirect + github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/tidwall/gjson v1.18.0 // indirect + github.com/tidwall/gjson v1.17.3 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect - go.uber.org/mock v0.5.0 // indirect - golang.org/x/arch v0.11.0 // indirect - golang.org/x/crypto v0.28.0 // indirect - golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + go.uber.org/mock v0.4.0 // indirect + golang.org/x/arch v0.10.0 // indirect + golang.org/x/crypto v0.27.0 // indirect + golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect golang.org/x/mod v0.21.0 // indirect - golang.org/x/net v0.30.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/term v0.25.0 // indirect - golang.org/x/text v0.19.0 // indirect - golang.org/x/tools v0.26.0 // indirect - google.golang.org/protobuf v1.35.1 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/term v0.24.0 // indirect + golang.org/x/text v0.18.0 // indirect + golang.org/x/tools v0.25.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 26e7d7a..625d7a6 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,21 @@ +atomicgo.dev/assert v0.0.2 h1:FiKeMiZSgRrZsPo9qn/7vmr7mCsh5SZyXY4YGYiYwrg= +atomicgo.dev/assert v0.0.2/go.mod h1:ut4NcI3QDdJtlmAxQULOmA13Gz6e2DWbSAS8RUOmNYQ= +atomicgo.dev/cursor v0.1.1 h1:0t9sxQomCTRh5ug+hAMCs59x/UmC9QL6Ci5uosINKD4= +atomicgo.dev/cursor v0.1.1/go.mod h1:Lr4ZJB3U7DfPPOkbH7/6TOtJ4vFGHlgj1nc+n900IpU= +atomicgo.dev/keyboard v0.2.9 h1:tOsIid3nlPLZ3lwgG8KZMp/SFmr7P0ssEN5JUsm78K8= +atomicgo.dev/keyboard v0.2.9/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs= +github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8= +github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII= +github.com/MarvinJWendt/testza v0.2.10/go.mod h1:pd+VWsoGUiFtq+hRKSU1Bktnn+DMCSrDrXDpX2bG66k= +github.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzXjB69adAhzZkI= +github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/2oUqKc6bF2c= +github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE= +github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi+zB4= +github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc4n3EjyIYvEY= +github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk= github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ= github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= @@ -15,14 +31,12 @@ github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/ github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= -github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I= -github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8= -github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= -github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= -github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= -github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8 h1:Z9lwXumT5ACSmJ7WGnFl+OMLLjpz5uR2fyz7dC255FI= +github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8/go.mod h1:4abs/jPXcmJzYoYGF91JF9Uq9s/KL5n1jvFDix8KcqY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -33,16 +47,14 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnN github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= -github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc= -github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc= -github.com/getsentry/sentry-go v0.29.1 h1:DyZuChN8Hz3ARxGVV8ePaNXh1dQ7d76AiB117xcREwA= -github.com/getsentry/sentry-go v0.29.1/go.mod h1:x3AtIzN01d6SiWkderzaH28Tm0lgkafpJ5Bm3li39O0= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4= +github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= -github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= -github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= @@ -65,22 +77,33 @@ github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs= github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20241017200806-017d972448fc h1:NGyrhhFhwvRAZg02jnYVg3GBQy0qGBKmFQJwaPmpmxs= -github.com/google/pprof v0.0.0-20241017200806-017d972448fc/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/pprof v0.0.0-20241001023024-f4c0cfd0cf1d h1:Jaz2JzpQaQXyET0AjLBXShrthbpqMkhGiEfkcQAiAUs= +github.com/google/pprof v0.0.0-20241001023024-f4c0cfd0cf1d/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= 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/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= +github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo= +github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= +github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= +github.com/gookit/goutil v0.6.16 h1:9fRMCF4X9abdRD5+2HhBS/GwafjBlTUBjRtA5dgkvuw= +github.com/gookit/goutil v0.6.16/go.mod h1:op2q8AoPDFSiY2+qkHxcBWQMYxOLQ1GbLXqe7vrwscI= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ice-blockchain/go/src v0.0.0-20240529122316-8d9458949bdd h1:SY8V7ujSDgEYEdtMk26DEaiq8qvCTEZ7XlojacrWZj4= github.com/ice-blockchain/go/src v0.0.0-20240529122316-8d9458949bdd/go.mod h1:pVzkmaIwkLaJ5cKPHPtuKyxaXiTsa/V7kbbgLmsr4Hw= +github.com/ice-cronus/tonutils-storage v0.0.0-20240923131001-35b67f016acf h1:YeCFhrgx+O3v+rjEaUGR4t0U6QQTqtsnIXdDtYVk3Kw= +github.com/ice-cronus/tonutils-storage v0.0.0-20240923131001-35b67f016acf/go.mod h1:BtcgpubknSLs2wRLLTnOAzB7MRac8S8IN2RgqPHDeqs= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jamiealquiza/tachymeter v2.0.0+incompatible h1:mGiF1DGo8l6vnGT8FXNNcIXht/YmjzfraiUprXYwJ6g= @@ -91,29 +114,37 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -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/kevinms/leakybucket-go v0.0.0-20200115003610-082473db97ca h1:qNtd6alRqd3qOdPrKXMZImV192ngQ0WSh1briEO33Tk= +github.com/kevinms/leakybucket-go v0.0.0-20200115003610-082473db97ca/go.mod h1:ph+C5vpnCcQvKBwJwKLTK3JLNGnBXYlG7m7JjoC/zYA= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= +github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lithammer/fuzzysearch v1.1.5 h1:Ag7aKU08wp0R9QCfF4GoGST9HbmAIeLP7xwMrOBEp1c= +github.com/lithammer/fuzzysearch v1.1.5/go.mod h1:1R1LRNk7yKid1BaQkmuLQaHruxcC4HmAH30Dh61Ih1Q= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 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-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= -github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/go-sqlite3 v1.14.23 h1:gbShiuAP1W5j9UOksQ06aiiqPMxYecovVGwmTxWtuw0= +github.com/mattn/go-sqlite3 v1.14.23/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -121,24 +152,34 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/mxschmitt/golang-combinations v1.2.0 h1:V5E7MncIK8Yr1SL/SpdqMuSquFsfoIs5auI7Y3n8z14= -github.com/mxschmitt/golang-combinations v1.2.0/go.mod h1:RCm5eR03B+JrBOMRDLsKZWShluXdrHu+qwhPEJ0miBM= -github.com/nbd-wtf/go-nostr v0.39.1 h1:sz9GxRzMxofil9w5l0eyNs+3nsyQlHU3BZf0Cc79vpA= -github.com/nbd-wtf/go-nostr v0.39.1/go.mod h1:UTFitPKazxHbGlFMh0QvB8MpOyuTViBU4Zf05Z1aUdk= +github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae h1:7smdlrfdcZic4VfsGKD2ulWL804a4GVphr4s7WZxGiY= +github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae/go.mod h1:hVoHR2EVESiICEMbg137etN/Lx+lSrHPTD39Z/uE+2s= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/nbd-wtf/go-nostr v0.38.1 h1:D0moEtIpjhWs2zbgeRyokA4TOLzBdumtpL1/O7/frww= +github.com/nbd-wtf/go-nostr v0.38.1/go.mod h1:TGKGj00BmJRXvRe0LlpDN3KKbELhhPXgBwUEhzu3Oq0= github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= -github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= -github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI= +github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg= +github.com/pterm/pterm v0.12.30/go.mod h1:MOqLIyMOgmTDz9yorcYbcw+HsgoZo3BQfg2wtl3HEFE= +github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEejaWgXU= +github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE= +github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8= +github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s= +github.com/pterm/pterm v0.12.59 h1:VBStvXiZL+6L+nNYjlXsD/035RJF5crqOvgqAm/Rvns= +github.com/pterm/pterm v0.12.59/go.mod h1:Lt90KhnId704siiQtMZiLS7UfoC7TRUM1HufzdM0kjk= github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4= github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= @@ -147,14 +188,18 @@ github.com/quic-go/quic-go v0.47.0 h1:yXs3v7r2bm1wmPTYNLKAAJTHMYkPEsfYJmTazXrCZ7 github.com/quic-go/quic-go v0.47.0/go.mod h1:3bCapYsJvXGZcipOHuu7plYtaV6tnF+z7wIFsU0WK9E= github.com/quic-go/webtransport-go v0.8.0 h1:HxSrwun11U+LlmwpgM1kEqIqH90IT4N8auv/cD7QFJg= github.com/quic-go/webtransport-go v0.8.0/go.mod h1:N99tjprW432Ut5ONql/aUhSLT0YVSlwHohQsuac9WaM= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +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/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/schollz/progressbar/v3 v3.16.1 h1:RnF1neWZFzLCoGx8yp1yF7SDl4AzNDI5y4I0aUJRrZQ= -github.com/schollz/progressbar/v3 v3.16.1/go.mod h1:I2ILR76gz5VXqYMIY/LdLecvMHDPVcQm3W/MSKi1TME= +github.com/schollz/progressbar/v3 v3.16.0 h1:+MbBim/cE9DqDb8UXRfLJ6RZdyDkXG1BDy/sWc5s0Mc= +github.com/schollz/progressbar/v3 v3.16.0/go.mod h1:lLiKjKJ9/yzc9Q8jk+sVLfxWxgXKsktvUf6TO+4Y2nw= +github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 h1:aQKxg3+2p+IFXXg97McgDGT5zcMrQoi0EICZs8Pgchs= +github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3/go.mod h1:9/etS5gpQq9BJsJMWg1wpLbfuSnkm8dPF6FdW2JXVhA= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -163,14 +208,18 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= -github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= +github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= +github.com/tidwall/gjson v1.17.3 h1:bwWLZU7icoKRG+C+0PNwIKC6FCJO/Q3p2pZvuP0jN94= +github.com/tidwall/gjson v1.17.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= @@ -182,67 +231,69 @@ 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/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8= github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/xssnick/tonutils-go v1.10.1 h1:xsSm6Is/KkJH3E+14J+CMzy4DNVOEFKlrPjZRFz74bo= +github.com/xssnick/tonutils-go v1.10.1/go.mod h1:p1l1Bxdv9sz6x2jfbuGQUGJn6g5cqg7xsTp8rBHFoJY= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= -go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= -golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4= -golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= -golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= -golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +golang.org/x/arch v0.10.0 h1:S3huipmSclq3PJMNe76NGwkBR504WFkQ5dhzWzP8ZW8= +golang.org/x/arch v0.10.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/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.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= -golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= +golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= -golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= -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= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= +golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= diff --git a/server/http/.testdata/image.jpg b/server/http/.testdata/image.jpg new file mode 100644 index 0000000000000000000000000000000000000000..75dff0b052d77f5d592a08ef7110d9c843341c78 GIT binary patch literal 415939 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

G3RT}T~OfV+^rfJ3n&EUP- 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

Cx5;xUR& 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ɍM-Z>|Ke9vyXcs076C#dv6 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 z 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 literal 0 HcmV?d00001 diff --git a/server/http/.testdata/image2.png b/server/http/.testdata/image2.png new file mode 100644 index 0000000000000000000000000000000000000000..2e2748457489a247e2b517364517b061b2a25254 GIT binary patch literal 182744 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Ξ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 tokenExpirationWindow) { + return nil, errorx.New("expired token") + } + if urlTag := event.Tags.GetFirst([]string{"u"}); urlTag != nil && len(*urlTag) > 1 { + url := (*urlTag)[1] + fullReqUrl := fmt.Sprintf("https://%v%v", gCtx.Request.Host, gCtx.Request.RequestURI) + if url != fullReqUrl { + return nil, errorx.Newf("invalid token: url mismatch token>%v url>%v", url, fullReqUrl) + } + } else { + return nil, errorx.Newf("invalid token: malformed u tag %v", urlTag) + } + if methodTag := event.Tags.GetFirst([]string{"method"}); methodTag != nil && len(*methodTag) > 1 { + method := (*methodTag)[1] + if method != gCtx.Request.Method { + return nil, errorx.Newf("invalid token: method mismatch token>%v url>%v", method, gCtx.Request.Method) + } + } else { + return nil, errorx.Newf("invalid token: malformed method tag %v", methodTag) + } + expectedHash := "" + if payloadTag := event.Tags.GetFirst([]string{"payload"}); payloadTag != nil && len(*payloadTag) > 1 { + expectedHash = (*payloadTag)[1] + } + return &nostrToken{ev: event, expectedHash: expectedHash}, nil +} +func (t *nostrToken) PubKey() string { + return t.ev.PubKey +} +func (t *nostrToken) ExpectedHash() string { + return t.expectedHash +} diff --git a/server/http/nip11_test.go b/server/http/nip11_test.go index a1b10f2..fa7f4ed 100644 --- a/server/http/nip11_test.go +++ b/server/http/nip11_test.go @@ -10,10 +10,13 @@ import ( "testing" "time" + "github.com/gin-gonic/gin" "github.com/nbd-wtf/go-nostr/nip11" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "golang.org/x/net/http2" + "github.com/ice-blockchain/subzero/database/query" wsserver "github.com/ice-blockchain/subzero/server/ws" "github.com/ice-blockchain/subzero/server/ws/fixture" ) @@ -22,6 +25,7 @@ const ( testDeadline = 30 * time.Second certPath = "%v/../ws/fixture/.testdata/localhost.crt" keyPath = "%v/../ws/fixture/.testdata/localhost.key" + storageRoot = "../../.test-uploads" ) var pubsubServer *fixture.MockService @@ -29,18 +33,30 @@ var pubsubServer *fixture.MockService func TestMain(m *testing.M) { serverCtx, serverCancel := context.WithTimeout(context.Background(), 10*time.Minute) defer serverCancel() + initServer(serverCtx, serverCancel, 9997, storageRoot) + http.DefaultClient.Transport = &http2.Transport{TLSClientConfig: fixture.LocalhostTLS()} + m.Run() + serverCancel() +} + +func initServer(serverCtx context.Context, serverCancel context.CancelFunc, port uint16, storageRoot string) { wd, _ := os.Getwd() certFilePath := fmt.Sprintf(certPath, wd) keyFilePath := fmt.Sprintf(keyPath, wd) + query.MustInit() + initStorage(serverCtx, storageRoot) + uploader := NewUploadHandler(serverCtx) pubsubServer = fixture.NewTestServer(serverCtx, serverCancel, &wsserver.Config{ CertPath: certFilePath, KeyPath: keyFilePath, - Port: 9997, - }, nil, NewNIP11Handler()) - http.DefaultClient.Transport = &http.Transport{TLSClientConfig: fixture.LocalhostTLS()} + Port: port, + }, nil, NewNIP11Handler(), map[string]gin.HandlerFunc{ + "POST /media": uploader.Upload(), + "GET /media": uploader.ListFiles(), + "GET /media/:file": uploader.Download(), + "DELETE /media/:file": uploader.Delete(), + }) time.Sleep(100 * time.Millisecond) - m.Run() - serverCancel() } func TestNIP11(t *testing.T) { diff --git a/server/http/nip96.json b/server/http/nip96.json new file mode 100644 index 0000000..d0777fd --- /dev/null +++ b/server/http/nip96.json @@ -0,0 +1,15 @@ +{ + "api_url": "/media", + "download_url": "/media", + "delegated_to_url": "", + "content_types": ["image/webp", "video/mpeg4", "*/*"], + "plans": { + "free": { + "name": "Free Tier", + "is_nip98_required": true, + "max_byte_size": 104857600, + "file_expiration": [0, 0], + "media_transformations": {} + } + } +} diff --git a/server/http/storage_nip96.go b/server/http/storage_nip96.go new file mode 100644 index 0000000..d26d5d6 --- /dev/null +++ b/server/http/storage_nip96.go @@ -0,0 +1,291 @@ +package http + +import ( + "context" + "crypto/sha256" + _ "embed" + "encoding/hex" + "errors" + "io" + "log" + "mime/multipart" + "net/http" + "os" + "path/filepath" + "strconv" + "strings" + "time" + + gomime "github.com/cubewise-code/go-mime" + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.com/gookit/goutil/errorx" + "github.com/nbd-wtf/go-nostr" + "github.com/nbd-wtf/go-nostr/nip96" + + "github.com/ice-blockchain/subzero/storage" +) + +type ( + Uploader interface { + Upload() gin.HandlerFunc + NIP96Info() gin.HandlerFunc + Download() gin.HandlerFunc + Delete() gin.HandlerFunc + ListFiles() gin.HandlerFunc + } +) + +//go:embed nip96.json +var nip96Info string + +type storageHandler struct { + storageClient storage.StorageClient + auth AuthClient +} + +const mediaEndpointTimeout = 60 * time.Second +const maxUploadSize = 100 * 1024 * 1024 + +type ( + fileUpload struct { + File *multipart.FileHeader `form:"file" formMultipart:"file" swaggerignore:"true"` + // Optional. + Caption string `form:"caption" formMultipart:"caption"` + // Optional. + Expiration string `form:"expiration" formMultipart:"expiration" swaggerignore:"expiration"` + Size uint64 `form:"size" formMultipart:"size"` + Alt string `form:"alt" formMultipart:"alt"` + MediaType string `form:"media_type" formMultipart:"media_type"` + ContentType string `form:"content_type" formMultipart:"content_type"` + NoTransform string `form:"no_transform" formMultipart:"no_transform"` + } + fileUploadResponse = nip96.UploadResponse + listedFiles struct { + Total uint32 `json:"total"` + Page uint32 `json:"page"` + Files []struct { + Tags nostr.Tags `json:"tags"` + Content string `json:"content"` + } + } +) + +func (storageHandler) NIP96Info() gin.HandlerFunc { + return func(c *gin.Context) { + c.Data(http.StatusOK, "application/json", []byte(nip96Info)) + } +} + +func (s *storageHandler) Upload() gin.HandlerFunc { + return func(gCtx *gin.Context) { + ctx, cancel := context.WithTimeout(gCtx, mediaEndpointTimeout) + defer cancel() + authHeader := strings.TrimPrefix(gCtx.GetHeader("Authorization"), "Nostr ") + token, authErr := s.auth.VerifyToken(gCtx, authHeader) + if authErr != nil { + log.Printf("ERROR: endpoint authentification failed: %v", errorx.With(authErr, "endpoint authentification failed")) + gCtx.JSON(http.StatusUnauthorized, uploadErr("Unauthorized")) + return + } + //token := &nostrToken{ev: nostr.Event{PubKey: "test"}} + var upload fileUpload + if err := gCtx.ShouldBindWith(&upload, binding.FormMultipart); err != nil { + log.Printf("ERROR: failed to bind multipart form: %v", errorx.With(err, "failed to bind multipart form")) + gCtx.JSON(http.StatusBadRequest, uploadErr("invalid multipart data")) + return + } + if upload.Size > maxUploadSize { + gCtx.JSON(http.StatusRequestEntityTooLarge, uploadErr("file too large")) + return + } + if upload.File == nil { + gCtx.JSON(http.StatusBadRequest, uploadErr("file required")) + return + } + if upload.ContentType == "" { + upload.ContentType = gomime.TypeByExtension(filepath.Ext(upload.File.Filename)) + } + + storagePath, fileType := s.storageClient.BuildUserPath(token.PubKey(), upload.ContentType) + uploadingFilePath := filepath.Join(storagePath, fileType, upload.File.Filename) + relativePath := filepath.Join(fileType, upload.File.Filename) + if err := os.MkdirAll(filepath.Dir(uploadingFilePath), 0o755); err != nil { + log.Printf("ERROR: %v", errorx.With(err, "failed to open temp file while processing upload")) + gCtx.JSON(http.StatusInternalServerError, uploadErr("failed to open temporary file")) + return + } + fileUploadTo, err := os.Create(uploadingFilePath) + if err != nil { + log.Printf("ERROR: %v", errorx.With(err, "failed to open temp file while processing upload")) + gCtx.JSON(http.StatusInternalServerError, uploadErr("failed to open temporary file")) + return + } + defer fileUploadTo.Close() + mpFile, err := upload.File.Open() + if err != nil { + log.Printf("ERROR: %v", errorx.With(err, "failed to open upload file")) + gCtx.JSON(http.StatusInternalServerError, uploadErr("failed to open upload file")) + return + } + defer mpFile.Close() + hashCalc := sha256.New() + if _, err = io.Copy(fileUploadTo, io.TeeReader(mpFile, hashCalc)); err != nil { + log.Printf("ERROR: %v", errorx.With(err, "failed to copy temp file while processing upload")) + gCtx.JSON(http.StatusBadRequest, uploadErr("failed to store temporary file")) + return + } + if err = fileUploadTo.Sync(); err != nil { + log.Printf("ERROR: %v", errorx.With(err, "failed to copy temp file while processing upload")) + gCtx.JSON(http.StatusBadRequest, uploadErr("failed to store temporary file")) + return + } + hash := hashCalc.Sum(nil) + hashHex := hex.EncodeToString(hash) + if hashHex != token.ExpectedHash() { + log.Printf("ERROR: endpoint authentification failed: %v", errorx.Errorf("payload hash mismatch actual>%v token>%v", hashHex, token.ExpectedHash())) + gCtx.JSON(http.StatusUnauthorized, uploadErr("Unauthorized")) + os.Remove(uploadingFilePath) + return + } + bagID, url, err := s.storageClient.StartUpload(ctx, token.PubKey(), relativePath, hex.EncodeToString(hash), &storage.FileMeta{ + Hash: hash, + Caption: upload.Caption, + Alt: upload.Alt, + }) + + if err != nil { + log.Printf("ERROR: failed to upload file: %v", errorx.With(err, "failed to upload file to ion storage")) + gCtx.JSON(http.StatusInternalServerError, uploadErr("oops, error occured!")) + return + } + + gCtx.JSON(200, fileUploadResponse{ + Status: "success", + Message: "Upload successful.", + Nip94Event: struct { + Tags nostr.Tags `json:"tags"` + Content string `json:"content"` + }{ + Tags: nostr.Tags{ + nostr.Tag{"url", url}, + nostr.Tag{"ox", hashHex}, + nostr.Tag{"x", hashHex}, + nostr.Tag{"m", upload.ContentType}, + nostr.Tag{"i", bagID}, + nostr.Tag{"alt", upload.Alt}, + nostr.Tag{"size", strconv.FormatUint(uint64(upload.File.Size), 10)}, + }, + Content: upload.Caption, + }, + }) + return + } +} +func (s *storageHandler) Download() gin.HandlerFunc { + return func(gCtx *gin.Context) { + authHeader := strings.TrimPrefix(gCtx.GetHeader("Authorization"), "Nostr ") + token, authErr := s.auth.VerifyToken(gCtx, authHeader) + if authErr != nil { + log.Printf("ERROR: endpoint authentification failed: %v", errorx.With(authErr, "endpoint authentification failed")) + gCtx.JSON(http.StatusUnauthorized, uploadErr("Unauthorized")) + return + } + file := gCtx.Param("file") + if strings.TrimSpace(file) == "" { + gCtx.JSON(http.StatusUnprocessableEntity, uploadErr("filename is required")) + return + } + url, err := s.storageClient.DownloadUrl(token.PubKey(), file) + if err != nil { + if errors.Is(err, storage.ErrNotFound) { + gCtx.Status(http.StatusNotFound) + return + } + log.Printf("ERROR: %v", errorx.With(err, "failed to build download url")) + gCtx.JSON(http.StatusInternalServerError, uploadErr("oops, error occured!")) + return + } + gCtx.Redirect(http.StatusFound, url) + } +} +func (s *storageHandler) Delete() gin.HandlerFunc { + return func(gCtx *gin.Context) { + authHeader := strings.TrimPrefix(gCtx.GetHeader("Authorization"), "Nostr ") + token, authErr := s.auth.VerifyToken(gCtx, authHeader) + if authErr != nil { + log.Printf("ERROR: endpoint authentification failed: %v", errorx.With(authErr, "endpoint authentification failed")) + gCtx.JSON(http.StatusUnauthorized, uploadErr("Unauthorized")) + return + } + file := gCtx.Param("file") + if strings.TrimSpace(file) == "" { + gCtx.JSON(http.StatusUnprocessableEntity, uploadErr("filehash is required")) + return + } + if err := s.storageClient.Delete(token.PubKey(), file); err != nil { + log.Printf("ERROR: %v", errorx.With(err, "failed to delete file")) + if errors.Is(err, storage.ErrNotFound) { + gCtx.JSON(http.StatusForbidden, uploadErr("user do not own file")) + return + } + gCtx.JSON(http.StatusInternalServerError, uploadErr("oops, error occured!")) + return + } + gCtx.JSON(http.StatusOK, map[string]any{"status": "success", "message": "deleted"}) + } +} + +func (s *storageHandler) ListFiles() gin.HandlerFunc { + return func(gCtx *gin.Context) { + authHeader := strings.TrimPrefix(gCtx.GetHeader("Authorization"), "Nostr ") + token, authErr := s.auth.VerifyToken(gCtx, authHeader) + if authErr != nil { + log.Printf("ERROR: endpoint authentification failed: %v", errorx.With(authErr, "endpoint authentification failed")) + gCtx.JSON(http.StatusUnauthorized, uploadErr("Unauthorized")) + return + } + var params struct { + Page uint32 `form:"page"` + Count uint32 `form:"count"` + } + if err := gCtx.ShouldBindWith(¶ms, binding.Query); err != nil { + log.Printf("ERROR: failed to bind data : %v", errorx.With(err, "failed to bind data")) + gCtx.JSON(http.StatusBadRequest, uploadErr("invalid data")) + return + } + if params.Count == 0 { + params.Count = 10 + } + total, filesList, err := s.storageClient.ListFiles(token.PubKey(), params.Page, params.Count) + if err != nil { + log.Printf("ERROR: %v", errorx.Withf(err, "failed to list files for user %v", token.PubKey())) + gCtx.JSON(http.StatusInternalServerError, uploadErr("oops, error occured!")) + return + } + res := &listedFiles{ + Total: total, + Page: params.Page, + Files: []struct { + Tags nostr.Tags `json:"tags"` + Content string `json:"content"` + }{}, + } + for _, f := range filesList { + res.Files = append(res.Files, struct { + Tags nostr.Tags `json:"tags"` + Content string `json:"content"` + }{Tags: f.ToTags(), Content: f.Content}) + } + gCtx.JSON(http.StatusOK, res) + } +} + +func uploadErr(message string) any { + return map[string]any{"status": "error", "message": message} +} + +func NewUploadHandler(ctx context.Context) Uploader { + s := &storageHandler{storageClient: storage.Client(), auth: NewAuth()} + return s +} diff --git a/server/http/storage_nip_96_test.go b/server/http/storage_nip_96_test.go new file mode 100644 index 0000000..f159893 --- /dev/null +++ b/server/http/storage_nip_96_test.go @@ -0,0 +1,328 @@ +package http + +import ( + "context" + "crypto/ed25519" + "crypto/rand" + "embed" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "log" + "math/big" + "net" + "net/http" + "os" + "path/filepath" + "strconv" + "sync" + "testing" + "time" + + gomime "github.com/cubewise-code/go-mime" + "github.com/gookit/goutil/errorx" + "github.com/nbd-wtf/go-nostr" + "github.com/nbd-wtf/go-nostr/nip94" + "github.com/nbd-wtf/go-nostr/nip96" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/ice-blockchain/subzero/database/query" + "github.com/ice-blockchain/subzero/model" + "github.com/ice-blockchain/subzero/storage" + storagefixture "github.com/ice-blockchain/subzero/storage/fixture" +) + +//go:embed .testdata +var testdata embed.FS + +func TestNIP96(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + defer func() { + require.NoError(t, os.RemoveAll("./../../.test-uploads")) + require.NoError(t, os.RemoveAll("./../../.test-uploads2")) + }() + user1 := nostr.GeneratePrivateKey() + var tagsToBroadcast nostr.Tags + t.Run("files are uploaded, response is ok", func(t *testing.T) { + var wg sync.WaitGroup + wg.Add(2) + var responses chan *nip96.UploadResponse + responses = make(chan *nip96.UploadResponse, 2) + go upload(t, ctx, user1, ".testdata/image2.png", "profile.png", "ice profile pic", func(resp *nip96.UploadResponse) { + responses <- resp + wg.Done() + }) + go upload(t, ctx, user1, ".testdata/image.jpg", "ice.jpg", "ice logo", func(resp *nip96.UploadResponse) { + responses <- resp + wg.Done() + }) + wg.Wait() + close(responses) + for resp := range responses { + if resp.Nip94Event.Content == "ice logo" { + tagsToBroadcast = resp.Nip94Event.Tags + } + + verifyFile(t, resp.Nip94Event.Content, resp.Nip94Event.Tags) + } + }) + const newStorageRoot = "./../../.test-uploads2" + var nip94EventToSign *model.Event + t.Run("nip-94 event is broadcasted, it causes download to other node", func(t *testing.T) { + nip94EventToSign = &model.Event{nostr.Event{ + CreatedAt: nostr.Timestamp(time.Now().Unix()), + Kind: nostr.KindFileMetadata, + Tags: tagsToBroadcast, + }} + require.NoError(t, nip94EventToSign.Sign(user1)) + // Simulate another storage node where we broadcast event/bag, and it needs to download it. + initStorage(ctx, newStorageRoot) + require.NoError(t, query.AcceptEvent(ctx, nip94EventToSign)) + require.NoError(t, storage.AcceptEvent(ctx, nip94EventToSign)) + pk, err := nostr.GetPublicKey(user1) + require.NoError(t, err) + downloadedProfileHash, err := storagefixture.WaitForFile(ctx, newStorageRoot, filepath.Join(newStorageRoot, pk, "image/profile.png"), int64(182744)) + require.NoError(t, err) + require.Equal(t, "b2b8cf9202b45dad7e137516bcf44b915ce30b39c3b294629a9b6b8fa1585292", downloadedProfileHash) + downloadedLogoHash, err := storagefixture.WaitForFile(ctx, newStorageRoot, filepath.Join(newStorageRoot, pk, "image/ice.jpg"), int64(415939)) + require.NoError(t, err) + require.Equal(t, "777d453395088530ce8de776fe54c3e5ace548381007b743e067844858962218", downloadedLogoHash) + }) + + t.Run("download endpoint redirects to same download url over ton storage", func(t *testing.T) { + expected := nip94.ParseFileMetadata(nostr.Event{Tags: expectedResponse("ice logo").Nip94Event.Tags}) + _, location := download(t, ctx, user1, "777d453395088530ce8de776fe54c3e5ace548381007b743e067844858962218") + require.Regexp(t, fmt.Sprintf("^http://[0-9a-fA-F]{64}.bag/%v", expected.Summary), location) + + expected = nip94.ParseFileMetadata(nostr.Event{Tags: expectedResponse("ice profile pic").Nip94Event.Tags}) + _, location = download(t, ctx, user1, "b2b8cf9202b45dad7e137516bcf44b915ce30b39c3b294629a9b6b8fa1585292") + require.Regexp(t, fmt.Sprintf("^http://[0-9a-fA-F]{64}.bag/%v", expected.Summary), location) + status, _ := download(t, ctx, user1, "non_valid_hash") + require.Equal(t, http.StatusNotFound, status) + }) + t.Run("list files responds with up to all files for the user when total is less than page", func(t *testing.T) { + files := list(t, ctx, user1, 0, 0) + assert.Equal(t, uint32(2), files.Total) + assert.Len(t, files.Files, 2) + for _, f := range files.Files { + verifyFile(t, f.Content, f.Tags) + } + }) + t.Run("list files with pagination", func(t *testing.T) { + files := list(t, ctx, user1, 0, 1) + assert.Equal(t, uint32(2), files.Total) + assert.Len(t, files.Files, 1) + uniqFiles := map[string]struct{}{} + for _, f := range files.Files { + verifyFile(t, f.Content, f.Tags) + uniqFiles[f.Content] = struct{}{} + } + files = list(t, ctx, user1, 1, 1) + assert.Equal(t, uint32(2), files.Total) + assert.Len(t, files.Files, 1) + for _, f := range files.Files { + verifyFile(t, f.Content, f.Tags) + _, presentedBefore := uniqFiles[f.Content] + require.False(t, presentedBefore) + } + }) + t.Run("delete file", func(t *testing.T) { + status := deleteFile(t, ctx, user1, "777d453395088530ce8de776fe54c3e5ace548381007b743e067844858962218") + require.Equal(t, http.StatusOK, status) + pk, err := nostr.GetPublicKey(user1) + require.NoError(t, err) + require.NoFileExists(t, filepath.Join(storageRoot, pk, "image/ice.jpg")) + deletionEventToSign := &model.Event{nostr.Event{ + CreatedAt: nostr.Timestamp(time.Now().Unix()), + Kind: nostr.KindDeletion, + Tags: nostr.Tags{ + nostr.Tag{"e", nip94EventToSign.ID}, + nostr.Tag{"k", strconv.FormatInt(int64(nostr.KindFileMetadata), 10)}, + }, + }} + require.NoError(t, deletionEventToSign.Sign(user1)) + require.NoError(t, storage.AcceptEvent(ctx, deletionEventToSign)) + require.NoFileExists(t, filepath.Join(newStorageRoot, pk, "image/ice.jpg")) + }) + +} + +func verifyFile(t *testing.T, content string, tags nostr.Tags) { + t.Helper() + md := nip94.ParseFileMetadata(nostr.Event{Tags: tags}) + expected := nip94.ParseFileMetadata(nostr.Event{Tags: expectedResponse(content).Nip94Event.Tags}) + url := md.URL + bagID := md.TorrentInfoHash + expectedFileName := expected.Summary + expected.Summary = "" + md.URL = "" + md.TorrentInfoHash = "" + require.Equal(t, expected, md) + require.Contains(t, url, fmt.Sprintf("http://%v.bag/%v", bagID, expectedFileName)) + require.Regexp(t, fmt.Sprintf("^http://[0-9a-fA-F]{64}.bag/%v", expectedFileName), url) + require.Regexp(t, "^[0-9a-fA-F]{64}$", bagID) +} + +func upload(t *testing.T, ctx context.Context, sk, path, filename, caption string, result func(resp *nip96.UploadResponse)) { + t.Helper() + img, _ := testdata.Open(path) + defer img.Close() + resp, err := nip96.Upload(ctx, nip96.UploadRequest{ + Host: "https://localhost:9997/media", + File: img, + Filename: filename, + Caption: caption, + ContentType: gomime.TypeByExtension(filepath.Ext(path)), + SK: sk, + SignPayload: true, + }) + require.NoError(t, err) + require.NotNil(t, resp) + result(resp) +} + +func download(t *testing.T, ctx context.Context, sk, fileHash string) (status int, locationUrl string) { + t.Helper() + http.DefaultClient.CheckRedirect = func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + } + defer func() { + http.DefaultClient.CheckRedirect = func(req *http.Request, via []*http.Request) error { return nil } + }() + resp := authorizedReq(t, ctx, sk, "GET", fmt.Sprintf("https://localhost:9997/media/%v", fileHash)) + if resp.StatusCode == http.StatusFound { + require.Equal(t, http.StatusFound, resp.StatusCode) + locationUrl = resp.Header.Get("location") + require.NotEmpty(t, locationUrl) + return resp.StatusCode, locationUrl + } + return resp.StatusCode, "" +} + +func list(t *testing.T, ctx context.Context, sk string, page, limit uint32) *listedFiles { + resp := authorizedReq(t, ctx, sk, "GET", fmt.Sprintf("https://localhost:9997/media?page=%v&count=%v", page, limit)) + require.Equal(t, http.StatusOK, resp.StatusCode) + var files listedFiles + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + require.NoError(t, json.Unmarshal(body, &files)) + return &files +} + +func deleteFile(t *testing.T, ctx context.Context, sk string, fileHash string) int { + resp := authorizedReq(t, ctx, sk, "DELETE", fmt.Sprintf("https://localhost:9997/media/%v", fileHash)) + if resp.StatusCode == http.StatusOK { + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + var respBody struct { + Message string `json:"message"` + Status string `json:"status"` + } + require.NoError(t, json.Unmarshal(body, &respBody)) + require.Equal(t, "success", respBody.Status) + require.Equal(t, "deleted", respBody.Message) + return resp.StatusCode + } + return resp.StatusCode +} + +func authorizedReq(t *testing.T, ctx context.Context, sk, method, url string) *http.Response { + uploadReq, err := http.NewRequest(method, url, nil) + require.NoError(t, err) + auth, err := generateAuthHeader(sk, url, method) + require.NoError(t, err) + uploadReq.Header.Set("Authorization", auth) + resp, err := http.DefaultClient.Do(uploadReq.WithContext(ctx)) + require.NoError(t, err) + require.NotNil(t, resp) + return resp +} + +func expectedResponse(caption string) *nip96.UploadResponse { + expectedResponses := map[string]*nip96.UploadResponse{ + "ice profile pic": &nip96.UploadResponse{ + Status: "success", + Message: "Upload successful.", + ProcessingURL: "", + Nip94Event: struct { + Tags nostr.Tags `json:"tags"` + Content string `json:"content"` + }{ + Tags: nostr.Tags{ + nostr.Tag{"summary", "image/profile.png"}, + nostr.Tag{"ox", "b2b8cf9202b45dad7e137516bcf44b915ce30b39c3b294629a9b6b8fa1585292"}, + nostr.Tag{"x", "b2b8cf9202b45dad7e137516bcf44b915ce30b39c3b294629a9b6b8fa1585292"}, + nostr.Tag{"m", "image/png"}, + nostr.Tag{"size", "182744"}, + }, + Content: "ice profile pic", + }, + }, + "ice logo": &nip96.UploadResponse{ + Status: "success", + Message: "Upload successful.", + ProcessingURL: "", + Nip94Event: struct { + Tags nostr.Tags `json:"tags"` + Content string `json:"content"` + }{ + Tags: nostr.Tags{ + nostr.Tag{"summary", "image/ice.jpg"}, + nostr.Tag{"ox", "777d453395088530ce8de776fe54c3e5ace548381007b743e067844858962218"}, + nostr.Tag{"x", "777d453395088530ce8de776fe54c3e5ace548381007b743e067844858962218"}, + nostr.Tag{"m", "image/png"}, + nostr.Tag{"size", "415939"}, + }, + Content: "ice profile pic", + }, + }, + } + return expectedResponses[caption] +} + +func initStorage(ctx context.Context, path string) { + transportOverride := http.DefaultClient.Transport + http.DefaultClient.Transport = http.DefaultTransport + _, nodeKey, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + log.Panic(errorx.Wrapf(err, "failed to generate node key")) + } + storagePort, err := rand.Int(rand.Reader, big.NewInt(63500)) + if err != nil { + log.Panic(errorx.Wrapf(err, "failed to generate port number")) + } + wd, _ := os.Getwd() + rootStorage := filepath.Join(wd, path) + storage.MustInit(ctx, nodeKey, storage.DefaultConfigUrl, rootStorage, net.ParseIP("127.0.0.1"), int(storagePort.Int64())+1024) + http.DefaultClient.Transport = transportOverride +} + +func generateAuthHeader(sk, url, method string) (string, error) { + pk, err := nostr.GetPublicKey(sk) + if err != nil { + return "", fmt.Errorf("nostr.GetPublicKey: %w", err) + } + + event := nostr.Event{ + Kind: nostrHttpAuthKind, + PubKey: pk, + CreatedAt: nostr.Now(), + Tags: nostr.Tags{ + nostr.Tag{"u", url}, + nostr.Tag{"method", method}, + }, + } + event.Sign(sk) + + b, err := json.Marshal(event) + if err != nil { + return "", fmt.Errorf("json.Marshal: %w", err) + } + + payload := base64.StdEncoding.EncodeToString(b) + + return fmt.Sprintf("Nostr %s", payload), nil +} diff --git a/server/server.go b/server/server.go index 0138acf..ba291f1 100644 --- a/server/server.go +++ b/server/server.go @@ -18,6 +18,13 @@ func ListenAndServe(ctx context.Context, cancel context.CancelFunc, config *wsse wsserver.New(config, &router{}).ListenAndServe(ctx, cancel) } -func (r *router) RegisterRoutes(wsroutes *wsserver.Router) { - wsroutes.Any("/", wsserver.WithWS(wsserver.NewHandler(), httpserver.NewNIP11Handler())) +func (r *router) RegisterRoutes(ctx context.Context, wsroutes wsserver.Router) { + uploader := httpserver.NewUploadHandler(ctx) + wsroutes.Any("/", wsserver.WithWS(wsserver.NewHandler(), httpserver.NewNIP11Handler())). + POST("/media", uploader.Upload()). + GET("/media", uploader.ListFiles()). + GET("/media/:file", uploader.Download()). + DELETE("/media/:file", uploader.Delete()). + GET("/.well-known/nostr/nip96.json", uploader.NIP96Info()) //. + //GET("/self.json", uploader.SelfDHT()) } diff --git a/server/ws/fixture/contract.go b/server/ws/fixture/contract.go index ca209c2..4e4e104 100644 --- a/server/ws/fixture/contract.go +++ b/server/ws/fixture/contract.go @@ -12,6 +12,8 @@ import ( "sync/atomic" stdlibtime "time" + "github.com/gin-gonic/gin" + h2ec "github.com/ice-blockchain/go/src/net/http" "github.com/ice-blockchain/subzero/server/ws/internal" "github.com/ice-blockchain/subzero/server/ws/internal/adapters" @@ -27,12 +29,14 @@ var ( type ( MockService struct { - server internal.Server - handlersMx sync.Mutex - Handlers map[adapters.WSWriter]struct{} - processingFunc func(ctx context.Context, writer adapters.WSWriter, in []byte, cfg *config.Config) - nip11Handler http.Handler - ReaderExited atomic.Uint64 + server internal.Server + + handlersMx sync.Mutex + Handlers map[adapters.WSWriter]struct{} + processingFunc func(ctx context.Context, writer adapters.WSWriter, in []byte, cfg *config.Config) + nip11Handler http.Handler + extraHttpHandlers map[string]gin.HandlerFunc + ReaderExited atomic.Uint64 } Client interface { Received diff --git a/server/ws/fixture/fixture.go b/server/ws/fixture/fixture.go index 8df8a3e..2ce5acb 100644 --- a/server/ws/fixture/fixture.go +++ b/server/ws/fixture/fixture.go @@ -5,14 +5,17 @@ package fixture import ( "context" "net/http" + "strings" + + "github.com/gin-gonic/gin" "github.com/ice-blockchain/subzero/server/ws/internal" "github.com/ice-blockchain/subzero/server/ws/internal/adapters" "github.com/ice-blockchain/subzero/server/ws/internal/config" ) -func NewTestServer(ctx context.Context, cancel context.CancelFunc, cfg *config.Config, processingFunc func(ctx context.Context, w adapters.WSWriter, in []byte, cfg *config.Config), nonWsHandler http.Handler) *MockService { - service := newMockService(processingFunc, nonWsHandler) +func NewTestServer(ctx context.Context, cancel context.CancelFunc, cfg *config.Config, processingFunc func(ctx context.Context, w adapters.WSWriter, in []byte, cfg *config.Config), nip11 http.Handler, extraHttpHandlers map[string]gin.HandlerFunc) *MockService { + service := newMockService(processingFunc, nip11, extraHttpHandlers) server := internal.NewWSServer(service, cfg) service.server = server go service.server.ListenAndServe(ctx, cancel) @@ -20,8 +23,8 @@ func NewTestServer(ctx context.Context, cancel context.CancelFunc, cfg *config.C return service } -func newMockService(processingFunc func(ctx context.Context, w adapters.WSWriter, in []byte, cfg *config.Config), nip11Handler http.Handler) *MockService { - return &MockService{processingFunc: processingFunc, Handlers: make(map[adapters.WSWriter]struct{}), nip11Handler: nip11Handler} +func newMockService(processingFunc func(ctx context.Context, w adapters.WSWriter, in []byte, cfg *config.Config), nip11Handler http.Handler, extraHttpHandlers map[string]gin.HandlerFunc) *MockService { + return &MockService{processingFunc: processingFunc, Handlers: make(map[adapters.WSWriter]struct{}), nip11Handler: nip11Handler, extraHttpHandlers: extraHttpHandlers} } func (m *MockService) Reset() { @@ -51,8 +54,14 @@ func (m *MockService) Read(ctx context.Context, w internal.WS, cfg *config.Confi } } -func (m *MockService) RegisterRoutes(r *internal.Router) { +func (m *MockService) RegisterRoutes(ctx context.Context, r internal.Router) { + for route, handler := range m.extraHttpHandlers { + parts := strings.Split(route, " ") + method, path := parts[0], parts[1] + r = r.Handle(method, path, handler) + } r.Any("/", internal.WithWS(m, m.nip11Handler)) + } func (m *MockService) Close(ctx context.Context) error { diff --git a/server/ws/internal/contract.go b/server/ws/internal/contract.go index 6be3996..2142e7a 100644 --- a/server/ws/internal/contract.go +++ b/server/ws/internal/contract.go @@ -15,13 +15,13 @@ import ( ) type ( - Router = gin.Engine + Router = gin.IRoutes Server interface { // ListenAndServe starts everything and blocks indefinitely. ListenAndServe(ctx context.Context, cancel context.CancelFunc) } RegisterRoutes interface { - RegisterRoutes(router *Router) + RegisterRoutes(ctx context.Context, router Router) } WSHandler = adapters.WSHandler @@ -31,7 +31,7 @@ type ( Srv struct { H3Server http3.Server H2Server http2.Server - router *Router + router *gin.Engine cfg *config.Config quit chan<- os.Signal routesSetup RegisterRoutes diff --git a/server/ws/internal/ws.go b/server/ws/internal/ws.go index c413cbb..6b3a273 100644 --- a/server/ws/internal/ws.go +++ b/server/ws/internal/ws.go @@ -33,15 +33,15 @@ func NewWSServer(router RegisterRoutes, cfg *config.Config) Server { s.router.UseRawPath = true s.H3Server = http3.New(s.cfg, s.router) s.H2Server = http2.New(s.cfg, s.router) - s.setupRouter() return s } -func (s *Srv) setupRouter() { - s.routesSetup.RegisterRoutes(s.router) +func (s *Srv) setupRouter(ctx context.Context) { + s.routesSetup.RegisterRoutes(ctx, s.router) } func (s *Srv) ListenAndServe(ctx context.Context, cancel context.CancelFunc) { + s.setupRouter(ctx) ctx = withServer(ctx, s) go s.startServer(ctx, s.H3Server) go s.startServer(ctx, s.H2Server) diff --git a/server/ws/ws_test.go b/server/ws/ws_test.go index abfa44c..f8b39ec 100644 --- a/server/ws/ws_test.go +++ b/server/ws/ws_test.go @@ -12,6 +12,7 @@ import ( stdlibtime "time" "github.com/cockroachdb/errors" + "github.com/gin-gonic/gin" "github.com/gobwas/ws" "github.com/google/uuid" "github.com/stretchr/testify/assert" @@ -50,14 +51,14 @@ func TestMain(m *testing.M) { CertPath: certFilePath, KeyPath: keyFilePath, Port: 9999, - }, echoFunc, nil) + }, echoFunc, nil, map[string]gin.HandlerFunc{}) hdl = new(handler) pubsubServer = fixture.NewTestServer(serverCtx, serverCancel, &Config{ CertPath: certFilePath, KeyPath: keyFilePath, Port: 9998, NIP13MinLeadingZeroBits: NIP13MinLeadingZeroBits, - }, hdl.Handle, nil) + }, hdl.Handle, nil, map[string]gin.HandlerFunc{}) m.Run() serverCancel() } diff --git a/storage/download.go b/storage/download.go new file mode 100644 index 0000000..4aa56f8 --- /dev/null +++ b/storage/download.go @@ -0,0 +1,139 @@ +package storage + +import ( + "context" + "encoding/base64" + "encoding/hex" + "encoding/json" + "log" + "net/url" + + "github.com/gookit/goutil/errorx" + "github.com/xssnick/tonutils-storage/storage" + + "github.com/ice-blockchain/subzero/model" +) + +func (c *client) DownloadUrl(userPubkey string, fileHash string) (string, error) { + bag, err := c.bagByUser(userPubkey) + if err != nil { + return "", errorx.Withf(err, "failed to get bagID for the user %v", userPubkey) + } + bs, err := c.buildBootstrapNodeInfo(bag) + if err != nil { + return "", errorx.Withf(err, "failed to build bootstap for bag %v", hex.EncodeToString(bag.BagID)) + } + file, err := c.detectFile(bag, fileHash) + if err != nil { + return "", errorx.Withf(err, "failed to detect file %v in bag %v", fileHash, hex.EncodeToString(bag.BagID)) + } + return c.buildUrl(hex.EncodeToString(bag.BagID), file, []*Bootstrap{bs}) +} + +func acceptNewBag(ctx context.Context, event *model.Event) error { + infohash := "" + var fileUrl *url.URL + var err error + if iTag := event.Tags.GetFirst([]string{"i"}); iTag != nil && len(*iTag) > 1 { + infohash = (*iTag)[1] + } else { + return errorx.Newf("malformed i tag %v", iTag) + } + if urlTag := event.Tags.GetFirst([]string{"url"}); urlTag != nil && len(*urlTag) > 1 { + fileUrl, err = url.Parse((*urlTag)[1]) + if err != nil { + return errorx.Withf(err, "malformed url in url tag %v", *urlTag) + } + } else { + return errorx.Newf("malformed url tag %v", urlTag) + } + bootstrap := fileUrl.Query().Get("bootstrap") + if err = globalClient.newBagIDPromoted(ctx, event.PubKey, infohash, &bootstrap); err != nil { + return errorx.Withf(err, "failed to promote new bag ID %v for user %v", infohash, event.PubKey) + } + return nil +} + +func (c *client) newBagIDPromoted(ctx context.Context, user, bagID string, bootstap *string) error { + existingBagForUser, err := c.bagByUser(user) + if err != nil { + return errorx.Withf(err, "failed to find existing bag for user %s", user) + } + if existingBagForUser != nil { + if err = c.progressStorage.RemoveTorrent(existingBagForUser, false); err != nil { + return errorx.Withf(err, "failed to replace bag for user %s", user) + } + } + if err = c.download(ctx, bagID, user, bootstap); err != nil { + return errorx.Withf(err, "failed to download new bag ID %v for user %v", bagID, user) + } + return nil +} + +func (c *client) download(ctx context.Context, bagID, user string, bootstrap *string) (err error) { + bag, err := hex.DecodeString(bagID) + if err != nil { + return errorx.Withf(err, "invalid bagID %v", bagID) + } + if len(bag) != 32 { + return errorx.Withf(err, "invalid bagID %v, should be len 32", bagID) + } + + tor := c.progressStorage.GetTorrent(bag) + if tor == nil { + tor = storage.NewTorrent(c.rootStoragePath, c.progressStorage, c.conn) + tor.BagID = bag + if err = tor.Start(true, true, false); err != nil { + return errorx.Withf(err, "failed to start new torrent %v", bagID) + } + if bootstrap != nil && *bootstrap != "" { + if err = c.connectToBootstrap(ctx, tor, *bootstrap); err != nil { + log.Printf("failed to connect to bootstrap node, waiting for DHT: %v", err) + } + } + if err = c.saveTorrent(tor, user); err != nil { + return errorx.Withf(err, "failed to store new torrent %v", bagID) + } + } else { + if err = tor.Start(true, true, false); err != nil { + return errorx.Withf(err, "failed to start existing torrent %v", bagID) + } + } + return nil +} + +func (c *client) connectToBootstrap(ctx context.Context, torrent *storage.Torrent, bootstrap string) error { + b64, err := base64.StdEncoding.DecodeString(bootstrap) + if err != nil { + return errorx.Withf(err, "failed to decode bootstrap %v", bootstrap) + } + var bootstraps []Bootstrap + if err = json.Unmarshal(b64, &bootstraps); err != nil { + return errorx.Withf(err, "failed to decode bootstrap %v", string(b64)) + } + for _, bs := range bootstraps { + pk := bs.Overlay.ID.(map[string]any)["Key"].(string) + var pubKey []byte + pubKey, err = base64.StdEncoding.DecodeString(pk) + if err != nil { + return errorx.Withf(err, "failed to decode bootstrap %v, invalid pubkey %v", string(b64), string(pk)) + } + if err = c.server.ConnectToNode(ctx, torrent, bs.Overlay, bs.DHT.AddrList, pubKey); err != nil { + return errorx.Withf(err, "failed to connect to bootstrap node %#v", bs.DHT.AddrList.Addresses[0]) + } + } + return nil +} + +func (c *client) saveTorrent(tr *storage.Torrent, userPubKey string) error { + if err := c.progressStorage.SetTorrent(tr); err != nil { + return errorx.With(err, "failed to save torrent into storage") + } + k := make([]byte, 5+64) + copy(k, "desc:") + copy(k[5:], userPubKey) + if err := c.db.Put(k, tr.BagID, nil); err != nil { + return errorx.With(err, "failed to save userID:bag mapping") + } + return nil +} diff --git a/storage/fixture/fixture.go b/storage/fixture/fixture.go new file mode 100644 index 0000000..0423118 --- /dev/null +++ b/storage/fixture/fixture.go @@ -0,0 +1,80 @@ +package fixture + +import ( + "context" + "crypto/sha256" + "encoding/hex" + "errors" + "io" + "os" + "time" + + "github.com/fsnotify/fsnotify" + "github.com/gookit/goutil/errorx" +) + +func WaitForFile(ctx context.Context, watchPath, expectedPath string, expectedSize int64) (hash string, err error) { + skipWatch := false + fileInfo, err := os.Stat(expectedPath) + if err == nil && fileInfo.Size() == expectedSize { + skipWatch = true + } + if !skipWatch { + if err = watchFile(ctx, watchPath, expectedPath, expectedSize); err != nil { + return "", errorx.Withf(err, "failed to monitor file %v", expectedPath) + } + } + f, err := os.Open(expectedPath) + if err != nil { + return "", errorx.Withf(err, "failed to open %v to check hash", expectedPath) + } + defer f.Close() + hashCalc := sha256.New() + if _, err = io.Copy(hashCalc, f); err != nil { + return "", errorx.Withf(err, "failed to calc hash of %v", expectedPath) + } + return hex.EncodeToString(hashCalc.Sum(nil)), nil +} + +func watchFile(ctx context.Context, monitorPath, expectedPath string, expectedSize int64) error { + watcher, err := fsnotify.NewWatcher() + if err != nil { + return errorx.Withf(err, "failed to create fsnotify watcher %s", expectedPath) + } + defer watcher.Close() + err = watcher.Add(monitorPath) + if err != nil { + return errorx.Withf(err, "failed to monitor expectedPath %s", expectedPath) + } +loop: + for { + select { + case event := <-watcher.Events: + if event.Op == fsnotify.Write && event.Name == expectedPath { + fileInfo, err := os.Stat(expectedPath) + if err != nil { + return errorx.Withf(err, "failed to stat file %s", expectedPath) + } + if fileInfo.Size() == expectedSize { + break loop + } + } + case <-time.After(1 * time.Second): + fileInfo, err := os.Stat(expectedPath) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + continue loop + } + return errorx.Withf(err, "failed to stat file %s", expectedPath) + } + if fileInfo.Size() == expectedSize { + break loop + } + case err = <-watcher.Errors: + return errorx.Withf(err, "got error from fsnotify") + case <-ctx.Done(): + return errors.New("timeout") + } + } + return nil +} diff --git a/storage/global.go b/storage/global.go new file mode 100644 index 0000000..9ab7aca --- /dev/null +++ b/storage/global.go @@ -0,0 +1,171 @@ +package storage + +import ( + "context" + "crypto/ed25519" + "encoding/hex" + "errors" + "fmt" + "log" + "net" + "net/url" + "os" + "path/filepath" + "strconv" + "sync" + "time" + + "github.com/gookit/goutil/errorx" + "github.com/nbd-wtf/go-nostr" + "github.com/syndtr/goleveldb/leveldb" + ldbstorage "github.com/syndtr/goleveldb/leveldb/storage" + "github.com/xssnick/tonutils-go/adnl" + "github.com/xssnick/tonutils-go/adnl/dht" + "github.com/xssnick/tonutils-go/liteclient" + "github.com/xssnick/tonutils-storage/db" + "github.com/xssnick/tonutils-storage/storage" + + "github.com/ice-blockchain/subzero/database/query" + "github.com/ice-blockchain/subzero/model" +) + +var globalClient *client + +const DefaultConfigUrl = "https://ton.org/global.config.json" + +func Client() StorageClient { + return globalClient +} + +func AcceptEvent(ctx context.Context, event *model.Event) error { + if !(event.Kind == nostr.KindFileMetadata || event.Kind == nostr.KindDeletion) { + return nil + } + if event.Kind == nostr.KindDeletion { + matchKTag := false + if kTag := event.Tags.GetFirst([]string{"k"}); kTag != nil && len(*kTag) > 1 { + if kTag.Value() == strconv.FormatInt(int64(nostr.KindFileMetadata), 10) { + matchKTag = true + } + } + if matchKTag { + return acceptDeletion(ctx, event) + } + return nil + } + return acceptNewBag(ctx, event) +} +func acceptDeletion(ctx context.Context, event *model.Event) error { + refs, err := model.ParseEventReference(event.Tags) + if err != nil { + return errorx.Withf(err, "failed to detect events for delete") + } + filters := model.Filters{} + for _, r := range refs { + filters = append(filters, r.Filter()) + } + events := query.GetStoredEvents(ctx, &model.Subscription{Filters: filters}) + var originalEvent *model.Event + for fileEvent, err := range events { + if err != nil { + return errorx.Withf(err, "failed to query referenced deletion file event") + } + if fileEvent.Kind != nostr.KindFileMetadata { + return errorx.Errorf("event mismatch: event %v is %v not file metadata (%v)", fileEvent.ID, fileEvent.Kind, nostr.KindFileMetadata) + } + if fileEvent.PubKey != event.PubKey { + return errorx.Errorf("user mismatch: event %v is signed by %v not %v", fileEvent.ID, fileEvent.PubKey, event.PubKey) + } + originalEvent = fileEvent + break + } + if originalEvent == nil { + return nil + } + fileHash := "" + if xTag := originalEvent.Tags.GetFirst([]string{"x"}); xTag != nil && len(*xTag) > 1 { + fileHash = xTag.Value() + } else { + return errorx.Errorf("malformed x tag in event %v", originalEvent.ID) + } + bag, err := globalClient.bagByUser(event.PubKey) + if err != nil { + return errorx.Withf(err, "failed to get bagID for the user %v", event.PubKey) + } + file, err := globalClient.detectFile(bag, fileHash) + if err != nil { + return errorx.Withf(err, "failed to detect file %v in bag %v", fileHash, hex.EncodeToString(bag.BagID)) + } + userRoot, _ := globalClient.BuildUserPath(event.PubKey, "") + if err := os.Remove(filepath.Join(userRoot, file)); err != nil && !errors.Is(err, os.ErrNotExist) { + return errorx.Withf(err, "failed to delete file %v", file) + } + if _, _, err := globalClient.StartUpload(ctx, event.PubKey, file, fileHash, nil); err != nil { + return errorx.Withf(err, "failed to rebuild bag with deleted file") + } + return nil +} + +func MustInit(ctx context.Context, nodeKey ed25519.PrivateKey, tonConfigUrl, rootStorage string, externalAddress net.IP, port int) { + storage.Logger = log.Println + var lsCfg *liteclient.GlobalConfig + u, err := url.Parse(tonConfigUrl) + if err != nil { + log.Panic(errorx.Wrapf(err, "invalid ton config url: %v", tonConfigUrl)) + } + if u.Scheme == "file" { + lsCfg, err = liteclient.GetConfigFromFile(u.Path) + if err != nil { + log.Panic(errorx.Wrapf(err, "failed to load ton network config from file: %v", u.Path)) + } + } else { + downloadConfigCtx, cancelDownloadConfig := context.WithTimeout(ctx, 30*time.Second) + defer cancelDownloadConfig() + lsCfg, err = liteclient.GetConfigFromUrl(downloadConfigCtx, tonConfigUrl) + if err != nil { + log.Panic(errorx.Wrapf(err, "failed to load ton network config from url: %v", u.String())) + } + } + + gate := adnl.NewGateway(nodeKey) + gate.SetExternalIP(externalAddress) + if err = gate.StartServer(fmt.Sprintf(":%v", port)); err != nil { + log.Panic(errorx.Wrapf(err, "failed to start adnl gateway")) + } + dhtGate := adnl.NewGateway(nodeKey) + if err = dhtGate.StartClient(); err != nil { + log.Panic(errorx.Wrapf(err, "failed to start dht")) + } + + dhtClient, err := dht.NewClientFromConfig(dhtGate, lsCfg) + if err != nil { + log.Panic(errorx.Wrapf(err, "failed to create dht client")) + } + srv := storage.NewServer(dhtClient, gate, nodeKey, true) + conn := storage.NewConnector(srv) + fStorage, err := ldbstorage.OpenFile(filepath.Join(rootStorage, "db"), false) + if err != nil { + log.Panic(errorx.Wrapf(err, "failed to open leveldb storage %v", filepath.Join(rootStorage, "db"))) + } + progressDb, err := leveldb.Open(fStorage, nil) + if err != nil { + log.Panic(errorx.Wrapf(err, "failed to open leveldb")) + } + globalClient = &client{ + conn: conn, + db: progressDb, + server: srv, + gateway: gate, + dht: dhtClient, + events: make(chan db.Event), + rootStoragePath: rootStorage, + newFiles: make(map[string]map[string]*FileMeta), + newFilesMx: &sync.RWMutex{}, + } + progressStorage, err := db.NewStorage(progressDb, conn, true, globalClient.events) + if err != nil { + log.Panic(errorx.Wrapf(err, "failed to open storage")) + } + globalClient.progressStorage = progressStorage + globalClient.server.SetStorage(progressStorage) +} diff --git a/storage/storage.go b/storage/storage.go new file mode 100644 index 0000000..ca471c4 --- /dev/null +++ b/storage/storage.go @@ -0,0 +1,182 @@ +package storage + +import ( + "context" + "encoding/hex" + "encoding/json" + "errors" + "io" + "os" + "path/filepath" + "strconv" + "strings" + "sync" + + gomime "github.com/cubewise-code/go-mime" + "github.com/gookit/goutil/errorx" + "github.com/hashicorp/go-multierror" + "github.com/nbd-wtf/go-nostr/nip94" + "github.com/syndtr/goleveldb/leveldb" + "github.com/xssnick/tonutils-go/adnl" + "github.com/xssnick/tonutils-go/adnl/dht" + "github.com/xssnick/tonutils-go/adnl/overlay" + "github.com/xssnick/tonutils-storage/db" + "github.com/xssnick/tonutils-storage/storage" +) + +type ( + StorageClient interface { + io.Closer + StartUpload(ctx context.Context, userPubkey, relativePathToFileForUrl, fileHash string, newFile *FileMeta) (bagID, url string, err error) + BuildUserPath(userPubKey, contentType string) (string, string) + DownloadUrl(userPubkey, fileSha256 string) (string, error) + ListFiles(userPubkey string, page, count uint32) (totalFiles uint32, files []*nip94.FileMetadata, err error) + Delete(userPubkey string, fileSha256 string) error + } + Bootstrap struct { + Overlay *overlay.Node + DHT *dht.Node + } + headerData struct { + User string `json:"u"` + FileMetadata map[string]*FileMeta `json:"f"` + FileHash map[string]string `json:"fh"` + } + FileMeta struct { + Hash []byte `json:"h"` + Caption string `json:"c"` + Alt string `json:"a"` + } + client struct { + conn *storage.Connector + db *leveldb.DB + server *storage.Server + progressStorage *db.Storage + gateway *adnl.Gateway + dht *dht.Client + events chan db.Event + rootStoragePath string + newFiles map[string]map[string]*FileMeta + newFilesMx *sync.RWMutex + } +) + +var ErrNotFound = storage.ErrFileNotExist + +func (c *client) fileMeta(bag *storage.Torrent) (*headerData, error) { + var desc headerData + hData := bag.Header.Data + if len(hData) == 0 { + hData = []byte("{}") + } + if err := json.Unmarshal(hData, &desc); err != nil { + return nil, errorx.With(err, "failed to unmarshal bag header data") + } + return &desc, nil +} + +func (c *client) detectFile(bag *storage.Torrent, fileHash string) (string, error) { + metadata, err := c.fileMeta(bag) + if err != nil { + return "", errorx.Withf(err, "failed to parse bag header data %v", hex.EncodeToString(bag.BagID)) + } + name := metadata.FileHash[fileHash] + f, err := bag.GetFileOffsets(name) + if err != nil { + return "", errorx.Withf(err, "failed to locate file %v in bag %v", name, hex.EncodeToString(bag.BagID)) + } + return f.Name, nil +} + +func (c *client) bagByUser(userPubKey string) (*storage.Torrent, error) { + k := make([]byte, 5+64) + copy(k, "desc:") + copy(k[5:], userPubKey) + bagID, err := c.db.Get(k, nil) + if err != nil && !errors.Is(err, leveldb.ErrNotFound) { + return nil, errorx.With(err, "failed to read userID:bag mapping") + } + tr := c.progressStorage.GetTorrent(bagID) + + return tr, nil +} + +func (c *client) BuildUserPath(userPubKey string, contentType string) (userStorage string, uploadPath string) { + spl := strings.Split(contentType, "/") + return filepath.Join(c.rootStoragePath, userPubKey), spl[0] +} + +func (c *client) ListFiles(userPubKey string, page, limit uint32) (total uint32, res []*nip94.FileMetadata, err error) { + bag, err := c.bagByUser(userPubKey) + if err != nil { + return 0, nil, errorx.Withf(err, "failed to get bagID for the user %v", userPubKey) + } + metadata, err := c.fileMeta(bag) + if err != nil { + return 0, nil, errorx.Withf(err, "failed to parse bag header data %v", hex.EncodeToString(bag.BagID)) + } + startOffset := page * limit + if startOffset >= bag.Header.FilesCount { + return bag.Header.FilesCount, []*nip94.FileMetadata{}, nil + } + endOffset := page*limit + limit + if endOffset >= bag.Header.FilesCount { + endOffset = bag.Header.FilesCount + } + res = make([]*nip94.FileMetadata, 0, limit) + bs, err := c.buildBootstrapNodeInfo(bag) + if err != nil { + return 0, nil, errorx.Withf(err, "failed to build bootstap for bag %v", hex.EncodeToString(bag.BagID)) + } + files, err := bag.ListFiles() + if err != nil { + return 0, nil, errorx.Withf(err, "failed to parse bag info for files %v", hex.EncodeToString(bag.BagID)) + } + for i, f := range files[startOffset:endOffset] { + idx := page*limit + uint32(i) + fileInfo, _ := bag.GetFileOffsets(f) + md := metadata.FileMetadata[fileInfo.Name] + url, _ := c.buildUrl(hex.EncodeToString(bag.BagID), f, []*Bootstrap{bs}) + res = append(res, &nip94.FileMetadata{ + Size: strconv.FormatUint(uint64(fileInfo.Size), 10), + Summary: md.Alt, + URL: url, + M: gomime.TypeByExtension(filepath.Ext(files[idx])), + X: hex.EncodeToString(md.Hash), + OX: hex.EncodeToString(md.Hash), + TorrentInfoHash: hex.EncodeToString(bag.BagID), + Content: md.Caption, + }) + } + return bag.Header.FilesCount, res, nil +} + +func (c *client) Delete(userPubKey, fileHash string) error { + bag, err := c.bagByUser(userPubKey) + if err != nil { + return errorx.Withf(err, "failed to get bagID for the user %v", userPubKey) + } + file, err := c.detectFile(bag, fileHash) + if err != nil { + return errorx.Withf(err, "failed to detect file %v in bag %v", fileHash, hex.EncodeToString(bag.BagID)) + } + userPath, _ := c.BuildUserPath(userPubKey, "") + err = os.Remove(filepath.Join(userPath, file)) + if err != nil { + return errorx.Withf(err, "failed to remove file %v (%v)", fileHash, filepath.Join(userPath, file)) + } + return nil +} + +func (c *client) Close() error { + var err *multierror.Error + c.server.Stop() + c.dht.Close() + if gClose := c.gateway.Close(); gClose != nil { + err = multierror.Append(err, errorx.Withf(gClose, "failed to stop gateway")) + } + if dErr := c.db.Close(); dErr != nil { + err = multierror.Append(err, errorx.Withf(dErr, "failed to close db")) + } + return err.ErrorOrNil() +} diff --git a/storage/upload.go b/storage/upload.go new file mode 100644 index 0000000..6947926 --- /dev/null +++ b/storage/upload.go @@ -0,0 +1,175 @@ +package storage + +import ( + "context" + "crypto/ed25519" + "encoding/base64" + "encoding/hex" + "encoding/json" + "fmt" + "sync" + "time" + + "github.com/gookit/goutil/errorx" + "github.com/xssnick/tonutils-go/adnl" + "github.com/xssnick/tonutils-go/adnl/dht" + "github.com/xssnick/tonutils-go/adnl/overlay" + "github.com/xssnick/tonutils-go/tl" + "github.com/xssnick/tonutils-storage/storage" +) + +func (c *client) StartUpload(ctx context.Context, userPubkey, relativePathToFileForUrl, hash string, newFile *FileMeta) (bagID, url string, err error) { + existingBagForUser, err := c.bagByUser(userPubkey) + if err != nil { + return "", "", errorx.Withf(err, "failed to find existing bag for user %s", userPubkey) + } + var existingHD headerData + if existingBagForUser != nil { + if len(existingBagForUser.Header.Data) > 0 { + if err = json.Unmarshal(existingBagForUser.Header.Data, &existingHD); err != nil { + return "", "", errorx.Withf(err, "corrupted header metadata for bag %v", hex.EncodeToString(existingBagForUser.BagID)) + } + } + if err = c.progressStorage.RemoveTorrent(existingBagForUser, false); err != nil { + return "", "", errorx.Withf(err, "failed to replace bag for user %s", userPubkey) + } + } + var bs []*Bootstrap + bagID, bs, err = c.upload(ctx, userPubkey, relativePathToFileForUrl, hash, newFile, &existingHD) + if err != nil { + return "", "", errorx.Withf(err, "failed to start upload of %v", relativePathToFileForUrl) + } + url, err = c.buildUrl(bagID, relativePathToFileForUrl, bs) + if err != nil { + return "", "", errorx.Withf(err, "failed to build url for %v (bag %v)", relativePathToFileForUrl, bagID) + } + return bagID, url, err +} + +func (c *client) upload(ctx context.Context, user, relativePath, hash string, fileMeta *FileMeta, headerMetadata *headerData) (bagID string, bootstrap []*Bootstrap, err error) { + if fileMeta != nil { + c.newFilesMx.Lock() + if userNewFiles, hasNewFiles := c.newFiles[user]; !hasNewFiles || userNewFiles == nil { + c.newFiles[user] = make(map[string]*FileMeta) + } + c.newFiles[user][relativePath] = fileMeta + c.newFilesMx.Unlock() + } + rootUserPath, _ := c.BuildUserPath(user, "") + refs, err := c.progressStorage.GetAllFilesRefsInDir(rootUserPath) + if err != nil { + return "", nil, errorx.With(err, "failed to detect shareable files") + } + headerMD := &headerData{ + User: user, + FileMetadata: headerMetadata.FileMetadata, + FileHash: headerMetadata.FileHash, + } + if headerMD.FileHash == nil { + headerMD.FileHash = make(map[string]string) + } + if headerMD.FileMetadata == nil { + headerMD.FileMetadata = make(map[string]*FileMeta) + } + if fileMeta != nil { + headerMD.FileMetadata[relativePath] = fileMeta + headerMD.FileHash[hex.EncodeToString(fileMeta.Hash)] = relativePath + } else { + delete(headerMD.FileMetadata, relativePath) + delete(headerMD.FileHash, hash) + } + + c.newFilesMx.RLock() + for key, value := range c.newFiles[user] { + headerMD.FileMetadata[key] = value + headerMD.FileHash[hex.EncodeToString(value.Hash)] = key + } + c.newFilesMx.RUnlock() + var headerMDSerialized []byte + headerMDSerialized, err = json.Marshal(headerMD) + if err != nil { + return "", nil, errorx.With(err, "failed to put file hashes") + } + header := &storage.TorrentHeader{ + DirNameSize: uint32(len(user)), + DirName: []byte(user), + Data: headerMDSerialized, + TotalDataSize: uint64(len(headerMDSerialized)), + } + var wg sync.WaitGroup + wg.Add(1) + tr, err := storage.CreateTorrentWithInitialHeader(ctx, c.rootStoragePath, user, header, c.progressStorage, c.conn, refs, func(done uint64, max uint64) { + if done == max { + wg.Done() + } + }) + if err != nil { + return "", nil, errorx.With(err, "failed to initialize bag") + } + err = tr.Start(true, true, false) + if err != nil { + return "", nil, errorx.With(err, "failed to start bag upload") + } + wg.Wait() + err = c.saveUploadTorrent(tr, user) + if err != nil { + return "", nil, errorx.With(err, "failed to save updated bag") + } + bootstrapNode, err := c.buildBootstrapNodeInfo(tr) + if err != nil { + return "", nil, errorx.With(err, "failed to build bootstrap node info") + } + return hex.EncodeToString(tr.BagID), []*Bootstrap{bootstrapNode}, nil +} + +func (c *client) buildBootstrapNodeInfo(tr *storage.Torrent) (*Bootstrap, error) { + key := c.server.GetADNLPrivateKey() + overlayNode, err := overlay.NewNode(tr.BagID, key) + if err != nil { + return nil, errorx.With(err, "failed to build overlay node") + } + addr := c.gateway.GetAddressList() + + dNode := dht.Node{ + ID: adnl.PublicKeyED25519{Key: key.Public().(ed25519.PublicKey)}, + AddrList: &addr, + Version: int32(time.Now().Unix()), + Signature: nil, + } + + toVerify, err := tl.Serialize(dNode, true) + if err != nil { + return nil, errorx.Withf(err, "failed to sign dht bootstrap, serialize failure") + } + dNode.Signature = ed25519.Sign(key, toVerify) + + return &Bootstrap{ + Overlay: overlayNode, + DHT: &dNode, + }, nil +} + +func (c *client) buildUrl(bagID, relativePath string, bs []*Bootstrap) (string, error) { + b, err := json.Marshal(bs) + if err != nil { + return "", errorx.Withf(err, "failed to marshal %#v", bs) + } + bootstrap := base64.StdEncoding.EncodeToString(b) + url := fmt.Sprintf("http://%v.bag/%v?bootstrap=%v", bagID, relativePath, bootstrap) + + return url, nil +} + +func (c *client) saveUploadTorrent(tr *storage.Torrent, userPubKey string) error { + if err := c.saveTorrent(tr, userPubKey); err != nil { + return errorx.With(err, "failed to save upload torrent into storage") + } + c.newFilesMx.Lock() + for k := range c.newFiles[userPubKey] { + if _, err := tr.GetFileOffsets(k); err == nil { + delete(c.newFiles[userPubKey], k) + } + } + c.newFilesMx.Unlock() + return nil +} From f24637ea23af4695098c16dce819c3fd5105f0fb Mon Sep 17 00:00:00 2001 From: ice-cronus <105345303+ice-cronus@users.noreply.github.com> Date: Tue, 24 Sep 2024 13:39:07 +0300 Subject: [PATCH 02/18] fix starting upload from node after downloading bag header, more meta for uploaded files, fix tests --- go.mod | 2 +- go.sum | 4 +- server/http/storage_nip96.go | 25 +++++++---- server/http/storage_nip_96_test.go | 67 ++++++++++++++++++++---------- storage/download.go | 21 +++++++++- storage/fixture/fixture.go | 17 ++++++-- storage/fs.go | 36 ++++++++++++++++ storage/storage.go | 12 ++++-- storage/upload.go | 4 +- 9 files changed, 145 insertions(+), 43 deletions(-) create mode 100644 storage/fs.go diff --git a/go.mod b/go.mod index d1ea63f..85a494b 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.23.0 replace ( filippo.io/mkcert => github.com/kixelated/mkcert v1.4.4-days - github.com/xssnick/tonutils-storage => github.com/ice-cronus/tonutils-storage v0.0.0-20240923131001-35b67f016acf + github.com/xssnick/tonutils-storage => github.com/ice-cronus/tonutils-storage v0.0.0-20240924150703-eb2225c54ef2 ) require ( diff --git a/go.sum b/go.sum index 625d7a6..74dd012 100644 --- a/go.sum +++ b/go.sum @@ -102,8 +102,8 @@ github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ice-blockchain/go/src v0.0.0-20240529122316-8d9458949bdd h1:SY8V7ujSDgEYEdtMk26DEaiq8qvCTEZ7XlojacrWZj4= github.com/ice-blockchain/go/src v0.0.0-20240529122316-8d9458949bdd/go.mod h1:pVzkmaIwkLaJ5cKPHPtuKyxaXiTsa/V7kbbgLmsr4Hw= -github.com/ice-cronus/tonutils-storage v0.0.0-20240923131001-35b67f016acf h1:YeCFhrgx+O3v+rjEaUGR4t0U6QQTqtsnIXdDtYVk3Kw= -github.com/ice-cronus/tonutils-storage v0.0.0-20240923131001-35b67f016acf/go.mod h1:BtcgpubknSLs2wRLLTnOAzB7MRac8S8IN2RgqPHDeqs= +github.com/ice-cronus/tonutils-storage v0.0.0-20240924150703-eb2225c54ef2 h1:ZAmeOwaDTYHEVp17aL7zoMFU2Mj+vYgNiFzmWGAv5WY= +github.com/ice-cronus/tonutils-storage v0.0.0-20240924150703-eb2225c54ef2/go.mod h1:kDD8rSR3Fcp7tTNZ1C/eHxq/JSYpXBJdHU3amOZoTcg= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jamiealquiza/tachymeter v2.0.0+incompatible h1:mGiF1DGo8l6vnGT8FXNNcIXht/YmjzfraiUprXYwJ6g= diff --git a/server/http/storage_nip96.go b/server/http/storage_nip96.go index d26d5d6..bd2c7b7 100644 --- a/server/http/storage_nip96.go +++ b/server/http/storage_nip96.go @@ -20,10 +20,8 @@ import ( "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" "github.com/gookit/goutil/errorx" - "github.com/nbd-wtf/go-nostr" - "github.com/nbd-wtf/go-nostr/nip96" - "github.com/ice-blockchain/subzero/storage" + "github.com/nbd-wtf/go-nostr" ) type ( @@ -60,8 +58,16 @@ type ( ContentType string `form:"content_type" formMultipart:"content_type"` NoTransform string `form:"no_transform" formMultipart:"no_transform"` } - fileUploadResponse = nip96.UploadResponse - listedFiles struct { + fileUploadResponse struct { + Status string `json:"status"` + Message string `json:"message"` + ProcessingURL string `json:"processing_url"` + Nip94Event struct { + Tags nostr.Tags `json:"tags"` + Content string `json:"content"` + } `json:"nip94_event"` + } + listedFiles struct { Total uint32 `json:"total"` Page uint32 `json:"page"` Files []struct { @@ -148,10 +154,12 @@ func (s *storageHandler) Upload() gin.HandlerFunc { os.Remove(uploadingFilePath) return } + now := time.Now().In(time.UTC) bagID, url, err := s.storageClient.StartUpload(ctx, token.PubKey(), relativePath, hex.EncodeToString(hash), &storage.FileMeta{ - Hash: hash, - Caption: upload.Caption, - Alt: upload.Alt, + Hash: hash, + Caption: upload.Caption, + Alt: upload.Alt, + CreatedAt: uint64(now.UnixNano()), }) if err != nil { @@ -175,6 +183,7 @@ func (s *storageHandler) Upload() gin.HandlerFunc { nostr.Tag{"i", bagID}, nostr.Tag{"alt", upload.Alt}, nostr.Tag{"size", strconv.FormatUint(uint64(upload.File.Size), 10)}, + nostr.Tag{"createdAt", strconv.FormatUint(uint64(now.UnixNano()), 10)}, }, Content: upload.Caption, }, diff --git a/server/http/storage_nip_96_test.go b/server/http/storage_nip_96_test.go index f159893..005ccf3 100644 --- a/server/http/storage_nip_96_test.go +++ b/server/http/storage_nip_96_test.go @@ -16,7 +16,6 @@ import ( "os" "path/filepath" "strconv" - "sync" "testing" "time" @@ -46,26 +45,30 @@ func TestNIP96(t *testing.T) { }() user1 := nostr.GeneratePrivateKey() var tagsToBroadcast nostr.Tags + var contentToBroadcast string t.Run("files are uploaded, response is ok", func(t *testing.T) { - var wg sync.WaitGroup - wg.Add(2) var responses chan *nip96.UploadResponse responses = make(chan *nip96.UploadResponse, 2) - go upload(t, ctx, user1, ".testdata/image2.png", "profile.png", "ice profile pic", func(resp *nip96.UploadResponse) { + upload(t, ctx, user1, ".testdata/image2.png", "profile.png", "ice profile pic", func(resp *nip96.UploadResponse) { responses <- resp - wg.Done() }) - go upload(t, ctx, user1, ".testdata/image.jpg", "ice.jpg", "ice logo", func(resp *nip96.UploadResponse) { + upload(t, ctx, user1, ".testdata/image.jpg", "ice.jpg", "ice logo", func(resp *nip96.UploadResponse) { responses <- resp - wg.Done() }) - wg.Wait() close(responses) + var latest uint64 for resp := range responses { - if resp.Nip94Event.Content == "ice logo" { - tagsToBroadcast = resp.Nip94Event.Tags + var createdAt uint64 + if createdAtTag := resp.Nip94Event.Tags.GetFirst([]string{"createdAt"}); createdAtTag != nil && len(*createdAtTag) > 1 { + var err error + createdAt, err = strconv.ParseUint(createdAtTag.Value(), 10, 64) + require.NoError(t, err) + if createdAt > latest { + latest = createdAt + tagsToBroadcast = resp.Nip94Event.Tags + contentToBroadcast = resp.Nip94Event.Content + } } - verifyFile(t, resp.Nip94Event.Content, resp.Nip94Event.Tags) } }) @@ -76,6 +79,7 @@ func TestNIP96(t *testing.T) { CreatedAt: nostr.Timestamp(time.Now().Unix()), Kind: nostr.KindFileMetadata, Tags: tagsToBroadcast, + Content: contentToBroadcast, }} require.NoError(t, nip94EventToSign.Sign(user1)) // Simulate another storage node where we broadcast event/bag, and it needs to download it. @@ -84,23 +88,25 @@ func TestNIP96(t *testing.T) { require.NoError(t, storage.AcceptEvent(ctx, nip94EventToSign)) pk, err := nostr.GetPublicKey(user1) require.NoError(t, err) - downloadedProfileHash, err := storagefixture.WaitForFile(ctx, newStorageRoot, filepath.Join(newStorageRoot, pk, "image/profile.png"), int64(182744)) + downloadedProfileHash, err := storagefixture.WaitForFile(ctx, newStorageRoot, filepath.Join(newStorageRoot, pk, "image/profile.png"), "b2b8cf9202b45dad7e137516bcf44b915ce30b39c3b294629a9b6b8fa1585292", int64(182744)) require.NoError(t, err) require.Equal(t, "b2b8cf9202b45dad7e137516bcf44b915ce30b39c3b294629a9b6b8fa1585292", downloadedProfileHash) - downloadedLogoHash, err := storagefixture.WaitForFile(ctx, newStorageRoot, filepath.Join(newStorageRoot, pk, "image/ice.jpg"), int64(415939)) + downloadedLogoHash, err := storagefixture.WaitForFile(ctx, newStorageRoot, filepath.Join(newStorageRoot, pk, "image/ice.jpg"), "777d453395088530ce8de776fe54c3e5ace548381007b743e067844858962218", int64(415939)) require.NoError(t, err) require.Equal(t, "777d453395088530ce8de776fe54c3e5ace548381007b743e067844858962218", downloadedLogoHash) }) t.Run("download endpoint redirects to same download url over ton storage", func(t *testing.T) { expected := nip94.ParseFileMetadata(nostr.Event{Tags: expectedResponse("ice logo").Nip94Event.Tags}) - _, location := download(t, ctx, user1, "777d453395088530ce8de776fe54c3e5ace548381007b743e067844858962218") + status, location := download(t, ctx, user1, "777d453395088530ce8de776fe54c3e5ace548381007b743e067844858962218") + require.Equal(t, http.StatusFound, status) require.Regexp(t, fmt.Sprintf("^http://[0-9a-fA-F]{64}.bag/%v", expected.Summary), location) expected = nip94.ParseFileMetadata(nostr.Event{Tags: expectedResponse("ice profile pic").Nip94Event.Tags}) - _, location = download(t, ctx, user1, "b2b8cf9202b45dad7e137516bcf44b915ce30b39c3b294629a9b6b8fa1585292") + status, location = download(t, ctx, user1, "b2b8cf9202b45dad7e137516bcf44b915ce30b39c3b294629a9b6b8fa1585292") + require.Equal(t, http.StatusFound, status) require.Regexp(t, fmt.Sprintf("^http://[0-9a-fA-F]{64}.bag/%v", expected.Summary), location) - status, _ := download(t, ctx, user1, "non_valid_hash") + status, _ = download(t, ctx, user1, "non_valid_hash") require.Equal(t, http.StatusNotFound, status) }) t.Run("list files responds with up to all files for the user when total is less than page", func(t *testing.T) { @@ -130,11 +136,18 @@ func TestNIP96(t *testing.T) { } }) t.Run("delete file", func(t *testing.T) { - status := deleteFile(t, ctx, user1, "777d453395088530ce8de776fe54c3e5ace548381007b743e067844858962218") + fileHash := "" + if xTag := nip94EventToSign.Tags.GetFirst([]string{"x"}); xTag != nil && len(*xTag) > 1 { + fileHash = xTag.Value() + } else { + t.Fatalf("malformed x tag in nip94 event %v", nip94EventToSign.ID) + } + status := deleteFile(t, ctx, user1, fileHash) + fileName := nip94.ParseFileMetadata(nostr.Event{Tags: expectedResponse(nip94EventToSign.Content).Nip94Event.Tags}).Summary require.Equal(t, http.StatusOK, status) pk, err := nostr.GetPublicKey(user1) require.NoError(t, err) - require.NoFileExists(t, filepath.Join(storageRoot, pk, "image/ice.jpg")) + require.NoFileExists(t, filepath.Join(storageRoot, pk, fileName)) deletionEventToSign := &model.Event{nostr.Event{ CreatedAt: nostr.Timestamp(time.Now().Unix()), Kind: nostr.KindDeletion, @@ -145,7 +158,7 @@ func TestNIP96(t *testing.T) { }} require.NoError(t, deletionEventToSign.Sign(user1)) require.NoError(t, storage.AcceptEvent(ctx, deletionEventToSign)) - require.NoFileExists(t, filepath.Join(newStorageRoot, pk, "image/ice.jpg")) + require.NoFileExists(t, filepath.Join(newStorageRoot, pk, fileName)) }) } @@ -181,6 +194,9 @@ func upload(t *testing.T, ctx context.Context, sk, path, filename, caption strin }) require.NoError(t, err) require.NotNil(t, resp) + jResp, err := json.Marshal(resp) + require.NoError(t, err) + log.Println(filename, string(jResp)) result(resp) } @@ -203,6 +219,7 @@ func download(t *testing.T, ctx context.Context, sk, fileHash string) (status in } func list(t *testing.T, ctx context.Context, sk string, page, limit uint32) *listedFiles { + t.Helper() resp := authorizedReq(t, ctx, sk, "GET", fmt.Sprintf("https://localhost:9997/media?page=%v&count=%v", page, limit)) require.Equal(t, http.StatusOK, resp.StatusCode) var files listedFiles @@ -213,6 +230,7 @@ func list(t *testing.T, ctx context.Context, sk string, page, limit uint32) *lis } func deleteFile(t *testing.T, ctx context.Context, sk string, fileHash string) int { + t.Helper() resp := authorizedReq(t, ctx, sk, "DELETE", fmt.Sprintf("https://localhost:9997/media/%v", fileHash)) if resp.StatusCode == http.StatusOK { body, err := io.ReadAll(resp.Body) @@ -230,9 +248,10 @@ func deleteFile(t *testing.T, ctx context.Context, sk string, fileHash string) i } func authorizedReq(t *testing.T, ctx context.Context, sk, method, url string) *http.Response { + t.Helper() uploadReq, err := http.NewRequest(method, url, nil) require.NoError(t, err) - auth, err := generateAuthHeader(sk, url, method) + auth, err := generateAuthHeader(t, sk, url, method) require.NoError(t, err) uploadReq.Header.Set("Authorization", auth) resp, err := http.DefaultClient.Do(uploadReq.WithContext(ctx)) @@ -296,11 +315,13 @@ func initStorage(ctx context.Context, path string) { } wd, _ := os.Getwd() rootStorage := filepath.Join(wd, path) - storage.MustInit(ctx, nodeKey, storage.DefaultConfigUrl, rootStorage, net.ParseIP("127.0.0.1"), int(storagePort.Int64())+1024) + port := int(storagePort.Int64()) + 1024 + storage.MustInit(ctx, nodeKey, storage.DefaultConfigUrl, rootStorage, net.ParseIP("127.0.0.1"), port) http.DefaultClient.Transport = transportOverride } -func generateAuthHeader(sk, url, method string) (string, error) { +func generateAuthHeader(t *testing.T, sk, url, method string) (string, error) { + t.Helper() pk, err := nostr.GetPublicKey(sk) if err != nil { return "", fmt.Errorf("nostr.GetPublicKey: %w", err) @@ -315,7 +336,7 @@ func generateAuthHeader(sk, url, method string) (string, error) { nostr.Tag{"method", method}, }, } - event.Sign(sk) + require.NoError(t, event.Sign(sk)) b, err := json.Marshal(event) if err != nil { diff --git a/storage/download.go b/storage/download.go index 4aa56f8..d645e2c 100644 --- a/storage/download.go +++ b/storage/download.go @@ -7,6 +7,7 @@ import ( "encoding/json" "log" "net/url" + "time" "github.com/gookit/goutil/errorx" "github.com/xssnick/tonutils-storage/storage" @@ -60,6 +61,7 @@ func (c *client) newBagIDPromoted(ctx context.Context, user, bagID string, boots return errorx.Withf(err, "failed to find existing bag for user %s", user) } if existingBagForUser != nil { + existingBagForUser.Stop() if err = c.progressStorage.RemoveTorrent(existingBagForUser, false); err != nil { return errorx.Withf(err, "failed to replace bag for user %s", user) } @@ -83,7 +85,7 @@ func (c *client) download(ctx context.Context, bagID, user string, bootstrap *st if tor == nil { tor = storage.NewTorrent(c.rootStoragePath, c.progressStorage, c.conn) tor.BagID = bag - if err = tor.Start(true, true, false); err != nil { + if err = tor.Start(false, true, false); err != nil { return errorx.Withf(err, "failed to start new torrent %v", bagID) } if bootstrap != nil && *bootstrap != "" { @@ -91,6 +93,7 @@ func (c *client) download(ctx context.Context, bagID, user string, bootstrap *st log.Printf("failed to connect to bootstrap node, waiting for DHT: %v", err) } } + go c.startUploadAfterDownloadingHeader(tor) if err = c.saveTorrent(tor, user); err != nil { return errorx.Withf(err, "failed to store new torrent %v", bagID) } @@ -137,3 +140,19 @@ func (c *client) saveTorrent(tr *storage.Torrent, userPubKey string) error { } return nil } + +func (c *client) startUploadAfterDownloadingHeader(tor *storage.Torrent) { + for { + select { + case <-time.After(1 * time.Second): + if _, isUplActive := tor.IsActive(); isUplActive { + return + } + if tor.Header == nil { + continue + } + tor.Start(true, true, false) + return + } + } +} diff --git a/storage/fixture/fixture.go b/storage/fixture/fixture.go index 0423118..5654118 100644 --- a/storage/fixture/fixture.go +++ b/storage/fixture/fixture.go @@ -13,7 +13,10 @@ import ( "github.com/gookit/goutil/errorx" ) -func WaitForFile(ctx context.Context, watchPath, expectedPath string, expectedSize int64) (hash string, err error) { +func WaitForFile(ctx context.Context, watchPath, expectedPath, expectedHash string, expectedSize int64) (hash string, err error) { + if ctx.Err() != nil { + return "", ctx.Err() + } skipWatch := false fileInfo, err := os.Stat(expectedPath) if err == nil && fileInfo.Size() == expectedSize { @@ -30,10 +33,18 @@ func WaitForFile(ctx context.Context, watchPath, expectedPath string, expectedSi } defer f.Close() hashCalc := sha256.New() - if _, err = io.Copy(hashCalc, f); err != nil { + var n int64 + if n, err = io.Copy(hashCalc, f); err != nil { return "", errorx.Withf(err, "failed to calc hash of %v", expectedPath) } - return hex.EncodeToString(hashCalc.Sum(nil)), nil + if n != expectedSize { + return WaitForFile(ctx, watchPath, expectedPath, expectedHash, expectedSize) + } + hash = hex.EncodeToString(hashCalc.Sum(nil)) + if hash != expectedHash { + return WaitForFile(ctx, watchPath, expectedPath, expectedHash, expectedSize) + } + return hash, nil } func watchFile(ctx context.Context, monitorPath, expectedPath string, expectedSize int64) error { diff --git a/storage/fs.go b/storage/fs.go new file mode 100644 index 0000000..d7567fa --- /dev/null +++ b/storage/fs.go @@ -0,0 +1,36 @@ +package storage + +import ( + "github.com/xssnick/tonutils-storage/storage" + "io" + "os" +) + +func init() { + storage.Fs = newFS() +} + +type fs struct{} +type fd struct { + f *os.File +} + +func (f *fd) Get() io.ReaderAt { + return f.f +} + +func newFS() storage.FSController { + return &fs{} +} + +func (f *fs) Acquire(path string) (storage.FDesc, error) { + file, err := os.Open(path) + if err != nil { + return nil, err + } + return &fd{file}, nil +} + +func (fs *fs) Free(f storage.FDesc) { + f.(*fd).f.Close() +} diff --git a/storage/storage.go b/storage/storage.go index ca471c4..e6aa957 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -43,9 +43,10 @@ type ( FileHash map[string]string `json:"fh"` } FileMeta struct { - Hash []byte `json:"h"` - Caption string `json:"c"` - Alt string `json:"a"` + Hash []byte `json:"h"` + Caption string `json:"c"` + Alt string `json:"a"` + CreatedAt uint64 `json:"cAt"` } client struct { conn *storage.Connector @@ -135,7 +136,10 @@ func (c *client) ListFiles(userPubKey string, page, limit uint32) (total uint32, for i, f := range files[startOffset:endOffset] { idx := page*limit + uint32(i) fileInfo, _ := bag.GetFileOffsets(f) - md := metadata.FileMetadata[fileInfo.Name] + md, hasMD := metadata.FileMetadata[fileInfo.Name] + if !hasMD { + continue + } url, _ := c.buildUrl(hex.EncodeToString(bag.BagID), f, []*Bootstrap{bs}) res = append(res, &nip94.FileMetadata{ Size: strconv.FormatUint(uint64(fileInfo.Size), 10), diff --git a/storage/upload.go b/storage/upload.go index 6947926..b3ad7dc 100644 --- a/storage/upload.go +++ b/storage/upload.go @@ -7,6 +7,7 @@ import ( "encoding/hex" "encoding/json" "fmt" + "log" "sync" "time" @@ -30,6 +31,7 @@ func (c *client) StartUpload(ctx context.Context, userPubkey, relativePathToFile return "", "", errorx.Withf(err, "corrupted header metadata for bag %v", hex.EncodeToString(existingBagForUser.BagID)) } } + existingBagForUser.Stop() if err = c.progressStorage.RemoveTorrent(existingBagForUser, false); err != nil { return "", "", errorx.Withf(err, "failed to replace bag for user %s", userPubkey) } @@ -78,7 +80,6 @@ func (c *client) upload(ctx context.Context, user, relativePath, hash string, fi delete(headerMD.FileMetadata, relativePath) delete(headerMD.FileHash, hash) } - c.newFilesMx.RLock() for key, value := range c.newFiles[user] { headerMD.FileMetadata[key] = value @@ -90,6 +91,7 @@ func (c *client) upload(ctx context.Context, user, relativePath, hash string, fi if err != nil { return "", nil, errorx.With(err, "failed to put file hashes") } + log.Println("UPLOAD META FOR ", relativePath, string(headerMDSerialized)) header := &storage.TorrentHeader{ DirNameSize: uint32(len(user)), DirName: []byte(user), From f2c1fb0dd69b9c8fbd5608d3a6d9337bc0781095 Mon Sep 17 00:00:00 2001 From: ice-cronus <105345303+ice-cronus@users.noreply.github.com> Date: Mon, 30 Sep 2024 17:00:49 +0300 Subject: [PATCH 03/18] statistics about uploads --- cmd/subzero/subzero.go | 4 +- go.mod | 7 ++ go.sum | 88 +++++++++++++ server/http/storage_nip96.go | 1 - server/http/storage_nip_96_test.go | 3 +- server/ws/internal/config/contract.go | 1 + server/ws/internal/http3/server.go | 2 +- storage/global.go | 4 +- storage/statistics/metadata/generic.go | 21 ++++ storage/statistics/metadata/image.go | 42 +++++++ storage/statistics/metadata/metadata.go | 51 ++++++++ storage/statistics/metadata/video.go | 120 ++++++++++++++++++ storage/statistics/statistics.go | 160 ++++++++++++++++++++++++ storage/storage.go | 5 + storage/upload.go | 36 ++++-- 15 files changed, 527 insertions(+), 18 deletions(-) create mode 100644 storage/statistics/metadata/generic.go create mode 100644 storage/statistics/metadata/image.go create mode 100644 storage/statistics/metadata/metadata.go create mode 100644 storage/statistics/metadata/video.go create mode 100644 storage/statistics/statistics.go diff --git a/cmd/subzero/subzero.go b/cmd/subzero/subzero.go index ea0e66b..95a09b1 100644 --- a/cmd/subzero/subzero.go +++ b/cmd/subzero/subzero.go @@ -31,6 +31,7 @@ var ( storageRootDir string globalConfigUrl string adnlNodeKey []byte + debug bool subzero = &cobra.Command{ Use: "subzero", Short: "subzero", @@ -44,7 +45,7 @@ var ( log.Print("using database at ", databasePath) } query.MustInit(databasePath) - storage.MustInit(ctx, adnlNodeKey, globalConfigUrl, storageRootDir, net.ParseIP(externalIP), int(adnlPort)) + storage.MustInit(ctx, adnlNodeKey, globalConfigUrl, storageRootDir, net.ParseIP(externalIP), int(adnlPort), debug) server.ListenAndServe(ctx, cancel, &server.Config{ CertPath: cert, KeyPath: key, @@ -70,6 +71,7 @@ var ( } return nodeKey }(), "adnl node key in hex") + subzero.Flags().BoolVar(&debug, "debug", false, "enable debugging info") if err := subzero.MarkFlagRequired("cert"); err != nil { log.Print(err) } diff --git a/go.mod b/go.mod index 85a494b..3be55a4 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ replace ( require ( github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8 + github.com/davidbyttow/govips/v2 v2.15.0 github.com/fsnotify/fsnotify v1.4.7 github.com/gin-gonic/gin v1.10.0 github.com/gobwas/httphead v0.1.0 @@ -24,10 +25,12 @@ require ( github.com/pkg/errors v0.9.1 github.com/quic-go/quic-go v0.47.0 github.com/quic-go/webtransport-go v0.8.0 + github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 github.com/schollz/progressbar/v3 v3.16.0 github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.9.0 github.com/syndtr/goleveldb v1.0.0 + github.com/u2takey/ffmpeg-go v0.5.0 github.com/xssnick/tonutils-go v1.10.1 github.com/xssnick/tonutils-storage v0.6.5 go.uber.org/goleak v1.3.0 @@ -38,6 +41,7 @@ require ( require ( atomicgo.dev/cursor v0.1.1 // indirect atomicgo.dev/keyboard v0.2.9 // indirect + github.com/aws/aws-sdk-go v1.55.5 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect github.com/bytedance/sonic v1.12.3 // indirect @@ -61,6 +65,7 @@ require ( github.com/gookit/color v1.5.4 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kevinms/leakybucket-go v0.0.0-20200115003610-082473db97ca // indirect @@ -88,12 +93,14 @@ require ( github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/u2takey/go-utils v0.3.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect go.uber.org/mock v0.4.0 // indirect golang.org/x/arch v0.10.0 // indirect golang.org/x/crypto v0.27.0 // indirect golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect + golang.org/x/image v0.20.0 // indirect golang.org/x/mod v0.21.0 // indirect golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.25.0 // indirect diff --git a/go.sum b/go.sum index 74dd012..83e37e7 100644 --- a/go.sum +++ b/go.sum @@ -16,6 +16,9 @@ github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYew github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi+zB4= github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc4n3EjyIYvEY= github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk= +github.com/aws/aws-sdk-go v1.38.20/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= +github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= +github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ= github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= @@ -41,10 +44,13 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davidbyttow/govips/v2 v2.15.0 h1:h3lF+rQElBzGXbQSSPqmE3XGySPhcQo2x3t5l/dZ+pU= +github.com/davidbyttow/govips/v2 v2.15.0/go.mod h1:3OQCHj0nf5Mnrplh5VlNvmx3IhJXyxbAoTJZPflUjmM= github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8= github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= @@ -55,6 +61,7 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= @@ -77,6 +84,7 @@ github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs= github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -85,6 +93,7 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20241001023024-f4c0cfd0cf1d h1:Jaz2JzpQaQXyET0AjLBXShrthbpqMkhGiEfkcQAiAUs= github.com/google/pprof v0.0.0-20241001023024-f4c0cfd0cf1d/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/uuid v1.1.1/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/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= @@ -98,6 +107,7 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ice-blockchain/go/src v0.0.0-20240529122316-8d9458949bdd h1:SY8V7ujSDgEYEdtMk26DEaiq8qvCTEZ7XlojacrWZj4= @@ -108,14 +118,21 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jamiealquiza/tachymeter v2.0.0+incompatible h1:mGiF1DGo8l6vnGT8FXNNcIXht/YmjzfraiUprXYwJ6g= github.com/jamiealquiza/tachymeter v2.0.0+incompatible/go.mod h1:Ayf6zPZKEnLsc3winWEXJRkTBhdHo58HODAu1oFJkYU= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kevinms/leakybucket-go v0.0.0-20200115003610-082473db97ca h1:qNtd6alRqd3qOdPrKXMZImV192ngQ0WSh1briEO33Tk= github.com/kevinms/leakybucket-go v0.0.0-20200115003610-082473db97ca/go.mod h1:ph+C5vpnCcQvKBwJwKLTK3JLNGnBXYlG7m7JjoC/zYA= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= @@ -150,8 +167,13 @@ github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/nbd-wtf/go-nostr v0.37.2 h1:42rriFqqz07EdydERwYeQnewl+Rah1Gq46I+Wh0KYYg= +github.com/nbd-wtf/go-nostr v0.37.2/go.mod h1:TGKGj00BmJRXvRe0LlpDN3KKbELhhPXgBwUEhzu3Oq0= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae h1:7smdlrfdcZic4VfsGKD2ulWL804a4GVphr4s7WZxGiY= github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae/go.mod h1:hVoHR2EVESiICEMbg137etN/Lx+lSrHPTD39Z/uE+2s= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -164,8 +186,10 @@ github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5co github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/panjf2000/ants/v2 v2.4.2/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OIhFxRNFr4A= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -188,6 +212,8 @@ github.com/quic-go/quic-go v0.47.0 h1:yXs3v7r2bm1wmPTYNLKAAJTHMYkPEsfYJmTazXrCZ7 github.com/quic-go/quic-go v0.47.0/go.mod h1:3bCapYsJvXGZcipOHuu7plYtaV6tnF+z7wIFsU0WK9E= github.com/quic-go/webtransport-go v0.8.0 h1:HxSrwun11U+LlmwpgM1kEqIqH90IT4N8auv/cD7QFJg= github.com/quic-go/webtransport-go v0.8.0/go.mod h1:N99tjprW432Ut5ONql/aUhSLT0YVSlwHohQsuac9WaM= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -200,6 +226,7 @@ github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 h1:aQKxg3+2p+IFXXg97McgDGT5zcMrQoi0EICZs8Pgchs= github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3/go.mod h1:9/etS5gpQq9BJsJMWg1wpLbfuSnkm8dPF6FdW2JXVhA= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -209,6 +236,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -227,6 +255,10 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/u2takey/ffmpeg-go v0.5.0 h1:r7d86XuL7uLWJ5mzSeQ03uvjfIhiJYvsRAJFCW4uklU= +github.com/u2takey/ffmpeg-go v0.5.0/go.mod h1:ruZWkvC1FEiUNjmROowOAps3ZcWxEiOpFoHCvk97kGc= +github.com/u2takey/go-utils v0.3.1 h1:TaQTgmEZZeDHQFYfd+AdUT1cT4QJgJn/XVPELhHw4ys= +github.com/u2takey/go-utils v0.3.1/go.mod h1:6e+v5vEZ/6gu12w/DC2ixZdZtCrNokVxD0JUklcqdCs= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8= @@ -236,53 +268,105 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/xssnick/tonutils-go v1.10.1 h1:xsSm6Is/KkJH3E+14J+CMzy4DNVOEFKlrPjZRFz74bo= github.com/xssnick/tonutils-go v1.10.1/go.mod h1:p1l1Bxdv9sz6x2jfbuGQUGJn6g5cqg7xsTp8rBHFoJY= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +gocv.io/x/gocv v0.25.0/go.mod h1:Rar2PS6DV+T4FL+PM535EImD/h13hGVaHhnCu1xarBs= golang.org/x/arch v0.10.0 h1:S3huipmSclq3PJMNe76NGwkBR504WFkQ5dhzWzP8ZW8= golang.org/x/arch v0.10.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= +golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.10.0/go.mod h1:jtrku+n79PfroUbvDdeUWMAI+heR786BofxrbiSF+J0= +golang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw= +golang.org/x/image v0.20.0/go.mod h1:0a88To4CYVBAHp5FXJm8o7QbUl37Vd85ply1vyD8auM= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/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.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= @@ -292,6 +376,9 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= @@ -301,3 +388,4 @@ pgregory.net/rand v1.0.2 h1:ASEbkvwOmY/UPF2evJPBJ8XZg71xdKWYdByqKapI7Vw= pgregory.net/rand v1.0.2/go.mod h1:EyNx8APnDE3Svi8sWgUZ5lOiz60cNZUPPBTyzOUpPl4= pgregory.net/rapid v0.4.8 h1:d+5SGZWUbJPbl3ss6tmPFqnNeQR6VDOFly+eTjwPiEw= pgregory.net/rapid v0.4.8/go.mod h1:Z5PbWqjvWR1I3UGjvboUuan4fe4ZYEYNLNQLExzCoUs= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/server/http/storage_nip96.go b/server/http/storage_nip96.go index bd2c7b7..ae8bf8d 100644 --- a/server/http/storage_nip96.go +++ b/server/http/storage_nip96.go @@ -94,7 +94,6 @@ func (s *storageHandler) Upload() gin.HandlerFunc { gCtx.JSON(http.StatusUnauthorized, uploadErr("Unauthorized")) return } - //token := &nostrToken{ev: nostr.Event{PubKey: "test"}} var upload fileUpload if err := gCtx.ShouldBindWith(&upload, binding.FormMultipart); err != nil { log.Printf("ERROR: failed to bind multipart form: %v", errorx.With(err, "failed to bind multipart form")) diff --git a/server/http/storage_nip_96_test.go b/server/http/storage_nip_96_test.go index 005ccf3..e16c9d4 100644 --- a/server/http/storage_nip_96_test.go +++ b/server/http/storage_nip_96_test.go @@ -40,6 +40,7 @@ func TestNIP96(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() defer func() { + require.NoError(t, storage.Client().Close()) require.NoError(t, os.RemoveAll("./../../.test-uploads")) require.NoError(t, os.RemoveAll("./../../.test-uploads2")) }() @@ -316,7 +317,7 @@ func initStorage(ctx context.Context, path string) { wd, _ := os.Getwd() rootStorage := filepath.Join(wd, path) port := int(storagePort.Int64()) + 1024 - storage.MustInit(ctx, nodeKey, storage.DefaultConfigUrl, rootStorage, net.ParseIP("127.0.0.1"), port) + storage.MustInit(ctx, nodeKey, storage.DefaultConfigUrl, rootStorage, net.ParseIP("127.0.0.1"), port, true) http.DefaultClient.Transport = transportOverride } diff --git a/server/ws/internal/config/contract.go b/server/ws/internal/config/contract.go index c276703..738c359 100644 --- a/server/ws/internal/config/contract.go +++ b/server/ws/internal/config/contract.go @@ -12,5 +12,6 @@ type ( Port uint16 `yaml:"port"` WriteTimeout stdlibtime.Duration `yaml:"writeTimeout"` ReadTimeout stdlibtime.Duration `yaml:"readTimeout"` + Debug bool `yaml:"debug"` } ) diff --git a/server/ws/internal/http3/server.go b/server/ws/internal/http3/server.go index 357691b..1a2f579 100644 --- a/server/ws/internal/http3/server.go +++ b/server/ws/internal/http3/server.go @@ -42,7 +42,7 @@ func (s *srv) ListenAndServeTLS(ctx context.Context, certFile, keyFile string) e }, }, } - if /*s.cfg.Development*/ true { + if s.cfg.Debug { noCors := func(_ *http.Request) bool { return true } diff --git a/storage/global.go b/storage/global.go index 9ab7aca..491c0fe 100644 --- a/storage/global.go +++ b/storage/global.go @@ -6,6 +6,7 @@ import ( "encoding/hex" "errors" "fmt" + "github.com/ice-blockchain/subzero/storage/statistics" "log" "net" "net/url" @@ -106,7 +107,7 @@ func acceptDeletion(ctx context.Context, event *model.Event) error { return nil } -func MustInit(ctx context.Context, nodeKey ed25519.PrivateKey, tonConfigUrl, rootStorage string, externalAddress net.IP, port int) { +func MustInit(ctx context.Context, nodeKey ed25519.PrivateKey, tonConfigUrl, rootStorage string, externalAddress net.IP, port int, debug bool) { storage.Logger = log.Println var lsCfg *liteclient.GlobalConfig u, err := url.Parse(tonConfigUrl) @@ -161,6 +162,7 @@ func MustInit(ctx context.Context, nodeKey ed25519.PrivateKey, tonConfigUrl, roo rootStoragePath: rootStorage, newFiles: make(map[string]map[string]*FileMeta), newFilesMx: &sync.RWMutex{}, + stats: statistics.NewStatistics(rootStorage, debug), } progressStorage, err := db.NewStorage(progressDb, conn, true, globalClient.events) if err != nil { diff --git a/storage/statistics/metadata/generic.go b/storage/statistics/metadata/generic.go new file mode 100644 index 0000000..58e3634 --- /dev/null +++ b/storage/statistics/metadata/generic.go @@ -0,0 +1,21 @@ +package metadata + +import "path/filepath" + +type genericMetaExtractor struct{} + +func newGenericExtractor() Extractor { + return &genericMetaExtractor{} +} + +func (*genericMetaExtractor) Extract(filePath, _ string, size uint64) (*Metadata, error) { + ext := filepath.Ext(filePath) + return &Metadata{ + Ext: ext, + Size: size, + }, nil +} + +func (*genericMetaExtractor) Close() error { + return nil +} diff --git a/storage/statistics/metadata/image.go b/storage/statistics/metadata/image.go new file mode 100644 index 0000000..4906f3e --- /dev/null +++ b/storage/statistics/metadata/image.go @@ -0,0 +1,42 @@ +package metadata + +import ( + "github.com/davidbyttow/govips/v2/vips" + "github.com/gookit/goutil/errorx" + + "path/filepath" +) + +type imageMetaExtractor struct{} + +type ImageMetadata struct { + Width int + Height int +} + +func newImageExtractor() Extractor { + vips.Startup(nil) + return &imageMetaExtractor{} +} + +func (i *imageMetaExtractor) Extract(filePath, _ string, size uint64) (*Metadata, error) { + ext := filepath.Ext(filePath) + im, err := vips.LoadImageFromFile(filePath, nil) + if err != nil { + return nil, errorx.Withf(err, "failed to load image %v", filePath) + } + defer im.Close() + return &Metadata{ + Ext: ext, + Size: size, + TypeMeta: &ImageMetadata{ + Width: im.Width(), + Height: im.Height(), + }, + }, nil +} + +func (*imageMetaExtractor) Close() error { + vips.Shutdown() + return nil +} diff --git a/storage/statistics/metadata/metadata.go b/storage/statistics/metadata/metadata.go new file mode 100644 index 0000000..9844f28 --- /dev/null +++ b/storage/statistics/metadata/metadata.go @@ -0,0 +1,51 @@ +package metadata + +import ( + "github.com/gookit/goutil/errorx" + "github.com/hashicorp/go-multierror" + "io" + "strings" +) + +type Extractor interface { + io.Closer + Extract(filePath, contentType string, size uint64) (*Metadata, error) +} +type Metadata struct { + Ext string + Size uint64 + TypeMeta any +} +type extractor struct { + extractorsByFileType map[string]Extractor + generic *genericMetaExtractor +} + +func NewExtractor() Extractor { + return &extractor{ + extractorsByFileType: map[string]Extractor{ + "video": newVideoExtractor(), + "image": newImageExtractor(), + }, + } +} +func (e *extractor) Extract(filePath, contentType string, size uint64) (*Metadata, error) { + fileType := strings.Split(contentType, "/")[0] + if ext, hasExtractor := e.extractorsByFileType[fileType]; hasExtractor { + return ext.Extract(filePath, contentType, size) + } + return e.generic.Extract(filePath, contentType, size) +} +func (e *extractor) Close() error { + var mErr *multierror.Error + for k, ex := range e.extractorsByFileType { + if clErr := ex.Close(); clErr != nil { + mErr = multierror.Append(mErr, errorx.Withf(clErr, "failed to close %v meta extractor", k)) + } + + } + if err := e.generic.Close(); err != nil { + mErr = multierror.Append(mErr, errorx.Withf(err, "failed to close generic meta extractor")) + } + return mErr.ErrorOrNil() +} diff --git a/storage/statistics/metadata/video.go b/storage/statistics/metadata/video.go new file mode 100644 index 0000000..791996a --- /dev/null +++ b/storage/statistics/metadata/video.go @@ -0,0 +1,120 @@ +package metadata + +import ( + "encoding/json" + "github.com/gookit/goutil/errorx" + ffmpeg "github.com/u2takey/ffmpeg-go" + "log" + "path/filepath" + "strings" +) + +type videoMetaExtractor struct{} +type ( + VideoMetadata struct { + Format *VideoFormat `json:"format"` + Streams []*VideoStream `json:"streams"` + } + VideoFormat struct { + Filename string `json:"filename"` + NbStreams int `json:"nb_streams"` + NbPrograms int `json:"nb_programs"` + FormatName string `json:"format_name"` + FormatLongName string `json:"format_long_name"` + StartTime string `json:"start_time"` + Duration string `json:"duration"` + Size string `json:"size"` + BitRate string `json:"bit_rate"` + ProbeScore int `json:"probe_score"` + Tags struct { + MajorBrand string `json:"major_brand"` + MinorVersion string `json:"minor_version"` + CompatibleBrands string `json:"compatible_brands"` + Encoder string `json:"encoder"` + LocationEng string `json:"location-eng"` + Location string `json:"location"` + } `json:"tags"` + } + VideoStream struct { + Index int `json:"index"` + CodecName string `json:"codec_name"` + CodecLongName string `json:"codec_long_name"` + Profile string `json:"profile"` + CodecType string `json:"codec_type"` + CodecTimeBase string `json:"codec_time_base"` + CodecTagString string `json:"codec_tag_string"` + CodecTag string `json:"codec_tag"` + SampleFmt string `json:"sample_fmt"` + SampleRate string `json:"sample_rate"` + Channels int `json:"channels"` + ChannelLayout string `json:"channel_layout"` + BitsPerSample int `json:"bits_per_sample"` + RFrameRate string `json:"r_frame_rate"` + AvgFrameRate string `json:"avg_frame_rate"` + TimeBase string `json:"time_base"` + StartPts int `json:"start_pts"` + StartTime string `json:"start_time"` + DurationTs int `json:"duration_ts"` + Duration string `json:"duration"` + BitRate string `json:"bit_rate"` + MaxBitRate string `json:"max_bit_rate"` + NbFrames string `json:"nb_frames"` + Width int `json:"width"` + Height int `json:"height"` + Disposition struct { + Default int `json:"default"` + Dub int `json:"dub"` + Original int `json:"original"` + Comment int `json:"comment"` + Lyrics int `json:"lyrics"` + Karaoke int `json:"karaoke"` + Forced int `json:"forced"` + HearingImpaired int `json:"hearing_impaired"` + VisualImpaired int `json:"visual_impaired"` + CleanEffects int `json:"clean_effects"` + AttachedPic int `json:"attached_pic"` + TimedThumbnails int `json:"timed_thumbnails"` + } `json:"disposition"` + Tags struct { + Language string `json:"language"` + HandlerName string `json:"handler_name"` + } `json:"tags"` + } +) + +func (v *videoMetaExtractor) Close() error { + return nil +} + +func (v *videoMetaExtractor) Extract(filePath, _ string, size uint64) (*Metadata, error) { + res, err := ffmpeg.Probe(filePath) + if err != nil { + return nil, errorx.Withf(err, "failed to fetch video metadata from %s", filePath) + } + var md VideoMetadata + if err = json.Unmarshal([]byte(res), &md); err != nil { + return nil, errorx.Withf(err, "failed to unmarshal video metadata from %s", filePath) + } + ext := filepath.Ext(filePath) + return &Metadata{ + Ext: ext, + Size: size, + TypeMeta: &md, + }, nil +} + +func newVideoExtractor() Extractor { + res, err := ffmpeg.Probe("") + if err != nil || !strings.Contains(res, "You have to specify one input file") { + if err == nil { + err = errorx.New(res) + } + if strings.Contains(err.Error(), "ffprobe version ") { + err = nil + } + if err != nil { + log.Panic(errorx.Withf(err, "failed to call ffprobe, is ffmpeg installed?")) + } + } + return &videoMetaExtractor{} +} diff --git a/storage/statistics/statistics.go b/storage/statistics/statistics.go new file mode 100644 index 0000000..e3377b0 --- /dev/null +++ b/storage/statistics/statistics.go @@ -0,0 +1,160 @@ +package statistics + +import ( + "fmt" + "github.com/gookit/goutil/errorx" + "github.com/ice-blockchain/subzero/storage/statistics/metadata" + "github.com/rcrowley/go-metrics" + "io" + "log" + "os" + "path/filepath" + "strconv" + "time" +) + +type ( + Statistics interface { + io.Closer + ProcessFile(filePath, contentType string, size uint64) + } + statistics struct { + metaExtractor metadata.Extractor + metrics metrics.Registry + rootStorageDir string + } + noopStats struct{} +) + +func (n *noopStats) Close() error { + return nil +} + +func (n *noopStats) ProcessFile(filePath, contentType string, size uint64) { +} + +const ( + imageWidth = "imageWidth" + imageHeight = "imageHeight" + fileSize = "fileSize" + duration = "duration" + videoWidth = "videoWidth" + videoHeight = "videoHeight" + videoBitrate = "videoBitrate" + audioBitrate = "audioBitrate" +) + +func NewStatistics(rootStorageDir string, debug bool) Statistics { + if !debug { + return &noopStats{} + } + s := &statistics{ + metaExtractor: metadata.NewExtractor(), + metrics: metrics.NewRegistry(), + rootStorageDir: rootStorageDir, + } + if err := s.metrics.Register(imageWidth, metrics.NewHistogram(metrics.NewExpDecaySample(10000, 0.15))); err != nil { + log.Panic(errorx.Withf(err, "failed to register metric %v", imageWidth)) + } + if err := s.metrics.Register(imageHeight, metrics.NewHistogram(metrics.NewExpDecaySample(10000, 0.15))); err != nil { + log.Panic(errorx.Withf(err, "failed to register metric %v", imageHeight)) + } + if err := s.metrics.Register(videoWidth, metrics.NewHistogram(metrics.NewExpDecaySample(10000, 0.15))); err != nil { + log.Panic(errorx.Withf(err, "failed to register metric %v", videoWidth)) + } + if err := s.metrics.Register(videoHeight, metrics.NewHistogram(metrics.NewExpDecaySample(10000, 0.15))); err != nil { + log.Panic(errorx.Withf(err, "failed to register metric %v", videoHeight)) + } + if err := s.metrics.Register(duration, metrics.NewHistogram(metrics.NewExpDecaySample(10000, 0.15))); err != nil { + log.Panic(errorx.Withf(err, "failed to register metric %v", duration)) + } + if err := s.metrics.Register(videoBitrate, metrics.NewHistogram(metrics.NewExpDecaySample(10000, 0.15))); err != nil { + log.Panic(errorx.Withf(err, "failed to register metric %v", videoBitrate)) + } + if err := s.metrics.Register(audioBitrate, metrics.NewHistogram(metrics.NewExpDecaySample(10000, 0.15))); err != nil { + log.Panic(errorx.Withf(err, "failed to register metric %v", audioBitrate)) + } + if err := s.metrics.Register(fileSize, metrics.NewHistogram(metrics.NewExpDecaySample(10000, 0.15))); err != nil { + log.Panic(errorx.Withf(err, "failed to register metric %v", fileSize)) + } + go func() { + for _ = range time.Tick(60 * time.Second) { + s.writeJSON() + } + }() + return s +} + +func (s *statistics) writeJSON() { + statsFile, err := os.OpenFile(filepath.Join(s.rootStorageDir, "stats.json"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755) + if err != nil { + log.Printf("ERROR: %v", errorx.Withf(err, "failed to open file for stats collection")) + } + defer func() { + statsFile.Sync() + statsFile.Close() + }() + metrics.WriteJSONOnce(s.metrics, statsFile) + +} + +func (s *statistics) Close() error { + s.writeJSON() + if err := s.metaExtractor.Close(); err != nil { + return errorx.Withf(err, "failed to close metadata extractors") + } + return nil + +} +func (s *statistics) ProcessFile(filePath, contentType string, size uint64) { + go func() { + md, err := s.metaExtractor.Extract(filePath, contentType, size) + if err != nil { + log.Printf("Error extracting metadata for file stats: %v\n", err) + } + s.registerMetadataStats(md) + }() +} + +func (s *statistics) registerMetadataStats(md *metadata.Metadata) { + s.registerFileStats(md) + switch typedMD := md.TypeMeta.(type) { + case *metadata.ImageMetadata: + s.registerImageStats(md, typedMD) + case *metadata.VideoMetadata: + s.registerVideoStats(md, typedMD) + } + +} +func (s *statistics) registerImageStats(md *metadata.Metadata, imageMetadata *metadata.ImageMetadata) { + s.metrics.Get(imageWidth).(metrics.Histogram).Update(int64(imageMetadata.Width)) + s.metrics.Get(imageHeight).(metrics.Histogram).Update(int64(imageMetadata.Height)) +} +func (s *statistics) registerVideoStats(md *metadata.Metadata, videoMetadata *metadata.VideoMetadata) { + for _, stream := range videoMetadata.Streams { + switch stream.CodecType { + case "video": + s.metrics.Get(videoWidth).(metrics.Histogram).Update(int64(stream.Width)) + s.metrics.Get(videoHeight).(metrics.Histogram).Update(int64(stream.Height)) + dur, err := strconv.ParseFloat(stream.Duration, 64) + if err == nil { + s.metrics.Get(duration).(metrics.Histogram).Update(int64(dur)) + } + bitrate, err := strconv.ParseInt(stream.BitRate, 10, 64) + if err == nil { + s.metrics.Get(videoBitrate).(metrics.Histogram).Update(bitrate) + } + s.metrics.GetOrRegister(fmt.Sprintf("videoCodec/%v/%v", stream.CodecName, stream.CodecTagString), metrics.NewCounter()).(metrics.Counter).Inc(1) + case "audio": + bitrate, err := strconv.ParseInt(stream.BitRate, 10, 64) + if err == nil { + s.metrics.Get(audioBitrate).(metrics.Histogram).Update(bitrate) + } + s.metrics.GetOrRegister(fmt.Sprintf("audioCodec/%v/%v", stream.CodecName, stream.CodecTagString), metrics.NewCounter()).(metrics.Counter).Inc(1) + } + } +} +func (s *statistics) registerFileStats(md *metadata.Metadata) { + s.metrics.Get(fileSize).(metrics.Histogram).Update(int64(md.Size)) + s.metrics.GetOrRegister("ext/"+md.Ext, metrics.NewCounter()).(metrics.Counter).Inc(1) +} diff --git a/storage/storage.go b/storage/storage.go index e6aa957..07b9c7b 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "encoding/json" "errors" + "github.com/ice-blockchain/subzero/storage/statistics" "io" "os" "path/filepath" @@ -59,6 +60,7 @@ type ( rootStoragePath string newFiles map[string]map[string]*FileMeta newFilesMx *sync.RWMutex + stats statistics.Statistics } ) @@ -179,6 +181,9 @@ func (c *client) Close() error { if gClose := c.gateway.Close(); gClose != nil { err = multierror.Append(err, errorx.Withf(gClose, "failed to stop gateway")) } + if sClose := c.stats.Close(); sClose != nil { + err = multierror.Append(err, errorx.Withf(sClose, "failed to close stats file")) + } if dErr := c.db.Close(); dErr != nil { err = multierror.Append(err, errorx.Withf(dErr, "failed to close db")) } diff --git a/storage/upload.go b/storage/upload.go index b3ad7dc..0b7552e 100644 --- a/storage/upload.go +++ b/storage/upload.go @@ -7,7 +7,9 @@ import ( "encoding/hex" "encoding/json" "fmt" + gomime "github.com/cubewise-code/go-mime" "log" + "path/filepath" "sync" "time" @@ -31,16 +33,24 @@ func (c *client) StartUpload(ctx context.Context, userPubkey, relativePathToFile return "", "", errorx.Withf(err, "corrupted header metadata for bag %v", hex.EncodeToString(existingBagForUser.BagID)) } } - existingBagForUser.Stop() - if err = c.progressStorage.RemoveTorrent(existingBagForUser, false); err != nil { - return "", "", errorx.Withf(err, "failed to replace bag for user %s", userPubkey) - } } + var bs []*Bootstrap - bagID, bs, err = c.upload(ctx, userPubkey, relativePathToFileForUrl, hash, newFile, &existingHD) + var bag *storage.Torrent + bag, bs, err = c.upload(ctx, userPubkey, relativePathToFileForUrl, hash, newFile, &existingHD) if err != nil { return "", "", errorx.Withf(err, "failed to start upload of %v", relativePathToFileForUrl) } + bagID = hex.EncodeToString(bag.BagID) + if newFile != nil { + uplFile, err := bag.GetFileOffsets(relativePathToFileForUrl) + if err != nil { + return "", "", errorx.Withf(err, "failed to get just created file from new bag") + } + fullFilePath := filepath.Join(c.rootStoragePath, userPubkey, relativePathToFileForUrl) + go c.stats.ProcessFile(fullFilePath, gomime.TypeByExtension(filepath.Ext(fullFilePath)), uplFile.Size) + } + url, err = c.buildUrl(bagID, relativePathToFileForUrl, bs) if err != nil { return "", "", errorx.Withf(err, "failed to build url for %v (bag %v)", relativePathToFileForUrl, bagID) @@ -48,7 +58,7 @@ func (c *client) StartUpload(ctx context.Context, userPubkey, relativePathToFile return bagID, url, err } -func (c *client) upload(ctx context.Context, user, relativePath, hash string, fileMeta *FileMeta, headerMetadata *headerData) (bagID string, bootstrap []*Bootstrap, err error) { +func (c *client) upload(ctx context.Context, user, relativePath, hash string, fileMeta *FileMeta, headerMetadata *headerData) (torrent *storage.Torrent, bootstrap []*Bootstrap, err error) { if fileMeta != nil { c.newFilesMx.Lock() if userNewFiles, hasNewFiles := c.newFiles[user]; !hasNewFiles || userNewFiles == nil { @@ -60,7 +70,7 @@ func (c *client) upload(ctx context.Context, user, relativePath, hash string, fi rootUserPath, _ := c.BuildUserPath(user, "") refs, err := c.progressStorage.GetAllFilesRefsInDir(rootUserPath) if err != nil { - return "", nil, errorx.With(err, "failed to detect shareable files") + return nil, nil, errorx.With(err, "failed to detect shareable files") } headerMD := &headerData{ User: user, @@ -89,7 +99,7 @@ func (c *client) upload(ctx context.Context, user, relativePath, hash string, fi var headerMDSerialized []byte headerMDSerialized, err = json.Marshal(headerMD) if err != nil { - return "", nil, errorx.With(err, "failed to put file hashes") + return nil, nil, errorx.With(err, "failed to put file hashes") } log.Println("UPLOAD META FOR ", relativePath, string(headerMDSerialized)) header := &storage.TorrentHeader{ @@ -106,22 +116,22 @@ func (c *client) upload(ctx context.Context, user, relativePath, hash string, fi } }) if err != nil { - return "", nil, errorx.With(err, "failed to initialize bag") + return nil, nil, errorx.With(err, "failed to initialize bag") } err = tr.Start(true, true, false) if err != nil { - return "", nil, errorx.With(err, "failed to start bag upload") + return nil, nil, errorx.With(err, "failed to start bag upload") } wg.Wait() err = c.saveUploadTorrent(tr, user) if err != nil { - return "", nil, errorx.With(err, "failed to save updated bag") + return nil, nil, errorx.With(err, "failed to save updated bag") } bootstrapNode, err := c.buildBootstrapNodeInfo(tr) if err != nil { - return "", nil, errorx.With(err, "failed to build bootstrap node info") + return nil, nil, errorx.With(err, "failed to build bootstrap node info") } - return hex.EncodeToString(tr.BagID), []*Bootstrap{bootstrapNode}, nil + return tr, []*Bootstrap{bootstrapNode}, nil } func (c *client) buildBootstrapNodeInfo(tr *storage.Torrent) (*Bootstrap, error) { From 4d7d7871a8a73f7ec5332380d418214544374f7d Mon Sep 17 00:00:00 2001 From: ice-cronus <105345303+ice-cronus@users.noreply.github.com> Date: Tue, 1 Oct 2024 18:51:28 +0300 Subject: [PATCH 04/18] add licensing --- .github/workflows/CICD.yaml | 4 +++ go.mod | 21 +++++++------ go.sum | 42 ++++++++++++++----------- server/http/auth_nip98.go | 5 +-- server/http/storage_nip96.go | 5 ++- server/http/storage_nip_96_test.go | 2 ++ server/server.go | 3 +- storage/download.go | 2 ++ storage/fixture/fixture.go | 2 ++ storage/fs.go | 5 ++- storage/global.go | 4 ++- storage/statistics/metadata/generic.go | 2 ++ storage/statistics/metadata/image.go | 6 ++-- storage/statistics/metadata/metadata.go | 7 +++-- storage/statistics/metadata/video.go | 7 +++-- storage/statistics/statistics.go | 10 ++++-- storage/storage.go | 5 ++- storage/upload.go | 4 ++- 18 files changed, 90 insertions(+), 46 deletions(-) diff --git a/.github/workflows/CICD.yaml b/.github/workflows/CICD.yaml index 985d852..ed0824f 100644 --- a/.github/workflows/CICD.yaml +++ b/.github/workflows/CICD.yaml @@ -158,6 +158,8 @@ jobs: uses: actions/setup-go@v5 with: go-version: ${{ steps.findLatestGoVersion.outputs.latestGoVersion }} + - name: Install libvips and ffmpeg for meta extraction + run: sudo apt-get install -y libvips-dev ffmpeg - name: Build all run: make build-all@ci/cd - name: Slack Notification For Failure/Cancellation @@ -193,6 +195,8 @@ jobs: uses: actions/setup-go@v5 with: go-version: ${{ steps.findLatestGoVersion.outputs.latestGoVersion }} + - name: Install libvips and ffmpeg for meta extraction + run: sudo apt-get install -y libvips-dev ffmpeg - name: Test ${{ matrix.package }} #TODO enable coverage run: | diff --git a/go.mod b/go.mod index 3be55a4..b01aef0 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,8 @@ module github.com/ice-blockchain/subzero go 1.23.0 +toolchain go1.23.1 + replace ( filippo.io/mkcert => github.com/kixelated/mkcert v1.4.4-days github.com/xssnick/tonutils-storage => github.com/ice-cronus/tonutils-storage v0.0.0-20240924150703-eb2225c54ef2 @@ -10,7 +12,7 @@ replace ( require ( github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8 github.com/davidbyttow/govips/v2 v2.15.0 - github.com/fsnotify/fsnotify v1.4.7 + github.com/fsnotify/fsnotify v1.7.0 github.com/gin-gonic/gin v1.10.0 github.com/gobwas/httphead v0.1.0 github.com/gobwas/ws v1.4.0 @@ -31,7 +33,7 @@ require ( github.com/stretchr/testify v1.9.0 github.com/syndtr/goleveldb v1.0.0 github.com/u2takey/ffmpeg-go v0.5.0 - github.com/xssnick/tonutils-go v1.10.1 + github.com/xssnick/tonutils-go v1.10.2 github.com/xssnick/tonutils-storage v0.6.5 go.uber.org/goleak v1.3.0 golang.org/x/net v0.29.0 @@ -39,8 +41,9 @@ require ( ) require ( - atomicgo.dev/cursor v0.1.1 // indirect + atomicgo.dev/cursor v0.2.0 // indirect atomicgo.dev/keyboard v0.2.9 // indirect + atomicgo.dev/schedule v0.1.0 // indirect github.com/aws/aws-sdk-go v1.55.5 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect @@ -48,7 +51,7 @@ require ( github.com/bytedance/sonic/loader v0.2.0 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect - github.com/containerd/console v1.0.3 // indirect + github.com/containerd/console v1.0.4 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/decred/dcrd/crypto/blake256 v1.1.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect @@ -60,8 +63,8 @@ require ( github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gobwas/pool v0.2.1 // indirect github.com/goccy/go-json v0.10.3 // indirect + github.com/golang/snappy v0.0.4 // indirect github.com/google/pprof v0.0.0-20241001023024-f4c0cfd0cf1d // indirect - github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect github.com/gookit/color v1.5.4 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -72,22 +75,22 @@ require ( github.com/klauspost/cpuid/v2 v2.2.8 // indirect github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect - github.com/lithammer/fuzzysearch v1.1.5 // indirect + github.com/lithammer/fuzzysearch v1.1.8 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae // indirect + github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a // indirect github.com/onsi/ginkgo/v2 v2.20.2 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/pterm/pterm v0.12.59 // indirect + github.com/pterm/pterm v0.12.79 // indirect github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect github.com/quic-go/qpack v0.5.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 // indirect + github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/tidwall/gjson v1.17.3 // indirect github.com/tidwall/match v1.1.1 // indirect diff --git a/go.sum b/go.sum index 83e37e7..eee4244 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,11 @@ atomicgo.dev/assert v0.0.2 h1:FiKeMiZSgRrZsPo9qn/7vmr7mCsh5SZyXY4YGYiYwrg= atomicgo.dev/assert v0.0.2/go.mod h1:ut4NcI3QDdJtlmAxQULOmA13Gz6e2DWbSAS8RUOmNYQ= -atomicgo.dev/cursor v0.1.1 h1:0t9sxQomCTRh5ug+hAMCs59x/UmC9QL6Ci5uosINKD4= -atomicgo.dev/cursor v0.1.1/go.mod h1:Lr4ZJB3U7DfPPOkbH7/6TOtJ4vFGHlgj1nc+n900IpU= +atomicgo.dev/cursor v0.2.0 h1:H6XN5alUJ52FZZUkI7AlJbUc1aW38GWZalpYRPpoPOw= +atomicgo.dev/cursor v0.2.0/go.mod h1:Lr4ZJB3U7DfPPOkbH7/6TOtJ4vFGHlgj1nc+n900IpU= atomicgo.dev/keyboard v0.2.9 h1:tOsIid3nlPLZ3lwgG8KZMp/SFmr7P0ssEN5JUsm78K8= atomicgo.dev/keyboard v0.2.9/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ= +atomicgo.dev/schedule v0.1.0 h1:nTthAbhZS5YZmgYbb2+DH8uQIZcTlIrd4eYr3UQxEjs= +atomicgo.dev/schedule v0.1.0/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8pEU= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs= @@ -34,8 +36,9 @@ github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/ github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= -github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro= +github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8 h1:Z9lwXumT5ACSmJ7WGnFl+OMLLjpz5uR2fyz7dC255FI= @@ -53,8 +56,9 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3 github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 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/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4= github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= @@ -86,8 +90,9 @@ github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -150,8 +155,8 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lithammer/fuzzysearch v1.1.5 h1:Ag7aKU08wp0R9QCfF4GoGST9HbmAIeLP7xwMrOBEp1c= -github.com/lithammer/fuzzysearch v1.1.5/go.mod h1:1R1LRNk7yKid1BaQkmuLQaHruxcC4HmAH30Dh61Ih1Q= +github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4= +github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -171,16 +176,14 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/nbd-wtf/go-nostr v0.37.2 h1:42rriFqqz07EdydERwYeQnewl+Rah1Gq46I+Wh0KYYg= -github.com/nbd-wtf/go-nostr v0.37.2/go.mod h1:TGKGj00BmJRXvRe0LlpDN3KKbELhhPXgBwUEhzu3Oq0= +github.com/nbd-wtf/go-nostr v0.38.1 h1:D0moEtIpjhWs2zbgeRyokA4TOLzBdumtpL1/O7/frww= +github.com/nbd-wtf/go-nostr v0.38.1/go.mod h1:TGKGj00BmJRXvRe0LlpDN3KKbELhhPXgBwUEhzu3Oq0= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae h1:7smdlrfdcZic4VfsGKD2ulWL804a4GVphr4s7WZxGiY= -github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae/go.mod h1:hVoHR2EVESiICEMbg137etN/Lx+lSrHPTD39Z/uE+2s= +github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a h1:dlRvE5fWabOchtH7znfiFCcOvmIYgOeAS5ifBXBlh9Q= +github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a/go.mod h1:hVoHR2EVESiICEMbg137etN/Lx+lSrHPTD39Z/uE+2s= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/nbd-wtf/go-nostr v0.38.1 h1:D0moEtIpjhWs2zbgeRyokA4TOLzBdumtpL1/O7/frww= -github.com/nbd-wtf/go-nostr v0.38.1/go.mod h1:TGKGj00BmJRXvRe0LlpDN3KKbELhhPXgBwUEhzu3Oq0= github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= @@ -202,8 +205,8 @@ github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEej github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE= github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8= github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s= -github.com/pterm/pterm v0.12.59 h1:VBStvXiZL+6L+nNYjlXsD/035RJF5crqOvgqAm/Rvns= -github.com/pterm/pterm v0.12.59/go.mod h1:Lt90KhnId704siiQtMZiLS7UfoC7TRUM1HufzdM0kjk= +github.com/pterm/pterm v0.12.79 h1:lH3yrYMhdpeqX9y5Ep1u7DejyHy7NSQg9qrBjF9dFT4= +github.com/pterm/pterm v0.12.79/go.mod h1:1v/gzOF1N0FsjbgTHZ1wVycRkKiatFvJSJC4IGaQAAo= github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4= github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= @@ -224,8 +227,8 @@ github.com/schollz/progressbar/v3 v3.16.0 h1:+MbBim/cE9DqDb8UXRfLJ6RZdyDkXG1BDy/ github.com/schollz/progressbar/v3 v3.16.0/go.mod h1:lLiKjKJ9/yzc9Q8jk+sVLfxWxgXKsktvUf6TO+4Y2nw= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 h1:aQKxg3+2p+IFXXg97McgDGT5zcMrQoi0EICZs8Pgchs= -github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3/go.mod h1:9/etS5gpQq9BJsJMWg1wpLbfuSnkm8dPF6FdW2JXVhA= +github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1 h1:NVK+OqnavpyFmUiKfUMHrpvbCi2VFoWTrcpI7aDaJ2I= +github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1/go.mod h1:9/etS5gpQq9BJsJMWg1wpLbfuSnkm8dPF6FdW2JXVhA= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= @@ -266,8 +269,8 @@ github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002 github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= -github.com/xssnick/tonutils-go v1.10.1 h1:xsSm6Is/KkJH3E+14J+CMzy4DNVOEFKlrPjZRFz74bo= -github.com/xssnick/tonutils-go v1.10.1/go.mod h1:p1l1Bxdv9sz6x2jfbuGQUGJn6g5cqg7xsTp8rBHFoJY= +github.com/xssnick/tonutils-go v1.10.2 h1:1wgnQPrzbOt+5PtuNrlMSUyh1/y0pvWRi0zeRNRLEbw= +github.com/xssnick/tonutils-go v1.10.2/go.mod h1:p1l1Bxdv9sz6x2jfbuGQUGJn6g5cqg7xsTp8rBHFoJY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= @@ -324,6 +327,7 @@ golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/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.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/server/http/auth_nip98.go b/server/http/auth_nip98.go index 4cf0c49..7ce70d7 100644 --- a/server/http/auth_nip98.go +++ b/server/http/auth_nip98.go @@ -1,14 +1,15 @@ +// SPDX-License-Identifier: ice License 1.0 + package http import ( + "encoding/base64" "fmt" "time" "github.com/gin-gonic/gin" "github.com/gookit/goutil/errorx" "github.com/nbd-wtf/go-nostr" - - "github.com/ice-blockchain/go/src/encoding/base64" ) type ( diff --git a/server/http/storage_nip96.go b/server/http/storage_nip96.go index ae8bf8d..89c559e 100644 --- a/server/http/storage_nip96.go +++ b/server/http/storage_nip96.go @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: ice License 1.0 + package http import ( @@ -20,8 +22,9 @@ import ( "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" "github.com/gookit/goutil/errorx" - "github.com/ice-blockchain/subzero/storage" "github.com/nbd-wtf/go-nostr" + + "github.com/ice-blockchain/subzero/storage" ) type ( diff --git a/server/http/storage_nip_96_test.go b/server/http/storage_nip_96_test.go index e16c9d4..6ca8b15 100644 --- a/server/http/storage_nip_96_test.go +++ b/server/http/storage_nip_96_test.go @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: ice License 1.0 + package http import ( diff --git a/server/server.go b/server/server.go index ba291f1..0d2b74a 100644 --- a/server/server.go +++ b/server/server.go @@ -25,6 +25,5 @@ func (r *router) RegisterRoutes(ctx context.Context, wsroutes wsserver.Router) { GET("/media", uploader.ListFiles()). GET("/media/:file", uploader.Download()). DELETE("/media/:file", uploader.Delete()). - GET("/.well-known/nostr/nip96.json", uploader.NIP96Info()) //. - //GET("/self.json", uploader.SelfDHT()) + GET("/.well-known/nostr/nip96.json", uploader.NIP96Info()) } diff --git a/storage/download.go b/storage/download.go index d645e2c..1594ff8 100644 --- a/storage/download.go +++ b/storage/download.go @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: ice License 1.0 + package storage import ( diff --git a/storage/fixture/fixture.go b/storage/fixture/fixture.go index 5654118..2244d8e 100644 --- a/storage/fixture/fixture.go +++ b/storage/fixture/fixture.go @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: ice License 1.0 + package fixture import ( diff --git a/storage/fs.go b/storage/fs.go index d7567fa..8918a67 100644 --- a/storage/fs.go +++ b/storage/fs.go @@ -1,9 +1,12 @@ +// SPDX-License-Identifier: ice License 1.0 + package storage import ( - "github.com/xssnick/tonutils-storage/storage" "io" "os" + + "github.com/xssnick/tonutils-storage/storage" ) func init() { diff --git a/storage/global.go b/storage/global.go index 491c0fe..0b0106d 100644 --- a/storage/global.go +++ b/storage/global.go @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: ice License 1.0 + package storage import ( @@ -6,7 +8,6 @@ import ( "encoding/hex" "errors" "fmt" - "github.com/ice-blockchain/subzero/storage/statistics" "log" "net" "net/url" @@ -28,6 +29,7 @@ import ( "github.com/ice-blockchain/subzero/database/query" "github.com/ice-blockchain/subzero/model" + "github.com/ice-blockchain/subzero/storage/statistics" ) var globalClient *client diff --git a/storage/statistics/metadata/generic.go b/storage/statistics/metadata/generic.go index 58e3634..fc3a917 100644 --- a/storage/statistics/metadata/generic.go +++ b/storage/statistics/metadata/generic.go @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: ice License 1.0 + package metadata import "path/filepath" diff --git a/storage/statistics/metadata/image.go b/storage/statistics/metadata/image.go index 4906f3e..61246d7 100644 --- a/storage/statistics/metadata/image.go +++ b/storage/statistics/metadata/image.go @@ -1,10 +1,12 @@ +// SPDX-License-Identifier: ice License 1.0 + package metadata import ( + "path/filepath" + "github.com/davidbyttow/govips/v2/vips" "github.com/gookit/goutil/errorx" - - "path/filepath" ) type imageMetaExtractor struct{} diff --git a/storage/statistics/metadata/metadata.go b/storage/statistics/metadata/metadata.go index 9844f28..38db6f9 100644 --- a/storage/statistics/metadata/metadata.go +++ b/storage/statistics/metadata/metadata.go @@ -1,10 +1,13 @@ +// SPDX-License-Identifier: ice License 1.0 + package metadata import ( - "github.com/gookit/goutil/errorx" - "github.com/hashicorp/go-multierror" "io" "strings" + + "github.com/gookit/goutil/errorx" + "github.com/hashicorp/go-multierror" ) type Extractor interface { diff --git a/storage/statistics/metadata/video.go b/storage/statistics/metadata/video.go index 791996a..3c6e443 100644 --- a/storage/statistics/metadata/video.go +++ b/storage/statistics/metadata/video.go @@ -1,12 +1,15 @@ +// SPDX-License-Identifier: ice License 1.0 + package metadata import ( "encoding/json" - "github.com/gookit/goutil/errorx" - ffmpeg "github.com/u2takey/ffmpeg-go" "log" "path/filepath" "strings" + + "github.com/gookit/goutil/errorx" + ffmpeg "github.com/u2takey/ffmpeg-go" ) type videoMetaExtractor struct{} diff --git a/storage/statistics/statistics.go b/storage/statistics/statistics.go index e3377b0..394c402 100644 --- a/storage/statistics/statistics.go +++ b/storage/statistics/statistics.go @@ -1,16 +1,20 @@ +// SPDX-License-Identifier: ice License 1.0 + package statistics import ( "fmt" - "github.com/gookit/goutil/errorx" - "github.com/ice-blockchain/subzero/storage/statistics/metadata" - "github.com/rcrowley/go-metrics" "io" "log" "os" "path/filepath" "strconv" "time" + + "github.com/gookit/goutil/errorx" + "github.com/rcrowley/go-metrics" + + "github.com/ice-blockchain/subzero/storage/statistics/metadata" ) type ( diff --git a/storage/storage.go b/storage/storage.go index 07b9c7b..d42160b 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: ice License 1.0 + package storage import ( @@ -5,7 +7,6 @@ import ( "encoding/hex" "encoding/json" "errors" - "github.com/ice-blockchain/subzero/storage/statistics" "io" "os" "path/filepath" @@ -23,6 +24,8 @@ import ( "github.com/xssnick/tonutils-go/adnl/overlay" "github.com/xssnick/tonutils-storage/db" "github.com/xssnick/tonutils-storage/storage" + + "github.com/ice-blockchain/subzero/storage/statistics" ) type ( diff --git a/storage/upload.go b/storage/upload.go index 0b7552e..5a199ae 100644 --- a/storage/upload.go +++ b/storage/upload.go @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: ice License 1.0 + package storage import ( @@ -7,12 +9,12 @@ import ( "encoding/hex" "encoding/json" "fmt" - gomime "github.com/cubewise-code/go-mime" "log" "path/filepath" "sync" "time" + gomime "github.com/cubewise-code/go-mime" "github.com/gookit/goutil/errorx" "github.com/xssnick/tonutils-go/adnl" "github.com/xssnick/tonutils-go/adnl/dht" From cf23c0b75ba6350b0f83d13e4d9a7306dd578a25 Mon Sep 17 00:00:00 2001 From: ice-cronus <105345303+ice-cronus@users.noreply.github.com> Date: Tue, 1 Oct 2024 19:36:55 +0300 Subject: [PATCH 05/18] fix ci --- .github/workflows/CICD.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/CICD.yaml b/.github/workflows/CICD.yaml index ed0824f..f321389 100644 --- a/.github/workflows/CICD.yaml +++ b/.github/workflows/CICD.yaml @@ -237,6 +237,8 @@ jobs: uses: actions/setup-go@v5 with: go-version: ${{ steps.findLatestGoVersion.outputs.latestGoVersion }} + - name: Install libvips and ffmpeg for meta extraction + run: sudo apt-get install -y libvips-dev ffmpeg - name: Benchmark ${{ matrix.package }} run: | cd ${{ matrix.package }} From 58d257d2a879d33e93c3a299fb1f7be4c54d9561 Mon Sep 17 00:00:00 2001 From: ice-cronus <105345303+ice-cronus@users.noreply.github.com> Date: Wed, 2 Oct 2024 12:01:54 +0300 Subject: [PATCH 06/18] fix persist upload state after downloading bag header, switch lib to a fix with reestablishing conn to peer if its not responding --- go.mod | 4 ++-- go.sum | 8 ++++---- storage/download.go | 11 ++++++++--- storage/fs.go | 5 ++++- 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index b01aef0..1e34b28 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.23.1 replace ( filippo.io/mkcert => github.com/kixelated/mkcert v1.4.4-days - github.com/xssnick/tonutils-storage => github.com/ice-cronus/tonutils-storage v0.0.0-20240924150703-eb2225c54ef2 + github.com/xssnick/tonutils-storage => github.com/ice-cronus/tonutils-storage v0.0.0-20241002084114-26ca96c9efc7 ) require ( @@ -92,7 +92,7 @@ require ( github.com/rivo/uniseg v0.4.7 // indirect github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/tidwall/gjson v1.17.3 // indirect + github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect diff --git a/go.sum b/go.sum index eee4244..94fb73d 100644 --- a/go.sum +++ b/go.sum @@ -117,8 +117,8 @@ github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ice-blockchain/go/src v0.0.0-20240529122316-8d9458949bdd h1:SY8V7ujSDgEYEdtMk26DEaiq8qvCTEZ7XlojacrWZj4= github.com/ice-blockchain/go/src v0.0.0-20240529122316-8d9458949bdd/go.mod h1:pVzkmaIwkLaJ5cKPHPtuKyxaXiTsa/V7kbbgLmsr4Hw= -github.com/ice-cronus/tonutils-storage v0.0.0-20240924150703-eb2225c54ef2 h1:ZAmeOwaDTYHEVp17aL7zoMFU2Mj+vYgNiFzmWGAv5WY= -github.com/ice-cronus/tonutils-storage v0.0.0-20240924150703-eb2225c54ef2/go.mod h1:kDD8rSR3Fcp7tTNZ1C/eHxq/JSYpXBJdHU3amOZoTcg= +github.com/ice-cronus/tonutils-storage v0.0.0-20241002084114-26ca96c9efc7 h1:gNEBSwI3pIyYC02cxVLbLakATq4Oz4YpKj4XH/NrQEk= +github.com/ice-cronus/tonutils-storage v0.0.0-20241002084114-26ca96c9efc7/go.mod h1:kDD8rSR3Fcp7tTNZ1C/eHxq/JSYpXBJdHU3amOZoTcg= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jamiealquiza/tachymeter v2.0.0+incompatible h1:mGiF1DGo8l6vnGT8FXNNcIXht/YmjzfraiUprXYwJ6g= @@ -249,8 +249,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= -github.com/tidwall/gjson v1.17.3 h1:bwWLZU7icoKRG+C+0PNwIKC6FCJO/Q3p2pZvuP0jN94= -github.com/tidwall/gjson v1.17.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= diff --git a/storage/download.go b/storage/download.go index 1594ff8..eed162c 100644 --- a/storage/download.go +++ b/storage/download.go @@ -95,7 +95,7 @@ func (c *client) download(ctx context.Context, bagID, user string, bootstrap *st log.Printf("failed to connect to bootstrap node, waiting for DHT: %v", err) } } - go c.startUploadAfterDownloadingHeader(tor) + go c.startUploadAfterDownloadingHeader(tor, user) if err = c.saveTorrent(tor, user); err != nil { return errorx.Withf(err, "failed to store new torrent %v", bagID) } @@ -143,7 +143,7 @@ func (c *client) saveTorrent(tr *storage.Torrent, userPubKey string) error { return nil } -func (c *client) startUploadAfterDownloadingHeader(tor *storage.Torrent) { +func (c *client) startUploadAfterDownloadingHeader(tor *storage.Torrent, user string) { for { select { case <-time.After(1 * time.Second): @@ -153,7 +153,12 @@ func (c *client) startUploadAfterDownloadingHeader(tor *storage.Torrent) { if tor.Header == nil { continue } - tor.Start(true, true, false) + if err := tor.Start(true, true, false); err != nil { + log.Printf("ERROR: failed to start torrent upload after downloading header: %v", err) + } + if err := c.saveTorrent(tor, user); err != nil { + log.Printf("ERROR: failed save torrent upload state after downloading header: %v", err) + } return } } diff --git a/storage/fs.go b/storage/fs.go index 8918a67..150b61d 100644 --- a/storage/fs.go +++ b/storage/fs.go @@ -21,6 +21,9 @@ type fd struct { func (f *fd) Get() io.ReaderAt { return f.f } +func (f *fd) Close() error { + return f.f.Close() +} func newFS() storage.FSController { return &fs{} @@ -35,5 +38,5 @@ func (f *fs) Acquire(path string) (storage.FDesc, error) { } func (fs *fs) Free(f storage.FDesc) { - f.(*fd).f.Close() + f.Close() } From 9880a6cebdbf4e0813e1cde2600a2543e78356f8 Mon Sep 17 00:00:00 2001 From: ice-cronus <105345303+ice-cronus@users.noreply.github.com> Date: Mon, 7 Oct 2024 14:09:25 +0300 Subject: [PATCH 07/18] fix some perf issues with high amount of downloading bags, fix PR comments --- go.mod | 28 +++---- go.sum | 56 +++++++------- server/http/auth_nip98.go | 41 ++++++---- server/http/nip11.go | 2 +- server/http/storage_nip96.go | 33 ++++---- server/http/storage_nip_96_test.go | 92 +++++++++++++++++++---- storage/download.go | 116 +++++++++++++++++++++-------- storage/global.go | 89 ++++++++++++++++------ storage/storage.go | 90 ++++++++++++++-------- storage/upload.go | 39 ++++++---- 10 files changed, 398 insertions(+), 188 deletions(-) diff --git a/go.mod b/go.mod index 1e34b28..75be9f9 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.23.1 replace ( filippo.io/mkcert => github.com/kixelated/mkcert v1.4.4-days - github.com/xssnick/tonutils-storage => github.com/ice-cronus/tonutils-storage v0.0.0-20241002084114-26ca96c9efc7 + github.com/xssnick/tonutils-storage => github.com/ice-cronus/tonutils-storage v0.0.0-20241007090031-eb90cffa7912 ) require ( @@ -17,18 +17,18 @@ require ( github.com/gobwas/httphead v0.1.0 github.com/gobwas/ws v1.4.0 github.com/google/uuid v1.6.0 - github.com/gookit/goutil v0.6.16 + github.com/gookit/goutil v0.6.17 github.com/hashicorp/go-multierror v1.1.1 github.com/ice-blockchain/go/src v0.0.0-20240529122316-8d9458949bdd github.com/jamiealquiza/tachymeter v2.0.0+incompatible github.com/jmoiron/sqlx v1.4.0 - github.com/mattn/go-sqlite3 v1.14.23 - github.com/nbd-wtf/go-nostr v0.38.1 + github.com/mattn/go-sqlite3 v1.14.24 + github.com/nbd-wtf/go-nostr v0.38.2 github.com/pkg/errors v0.9.1 github.com/quic-go/quic-go v0.47.0 github.com/quic-go/webtransport-go v0.8.0 github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 - github.com/schollz/progressbar/v3 v3.16.0 + github.com/schollz/progressbar/v3 v3.16.1 github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.9.0 github.com/syndtr/goleveldb v1.0.0 @@ -36,7 +36,7 @@ require ( github.com/xssnick/tonutils-go v1.10.2 github.com/xssnick/tonutils-storage v0.6.5 go.uber.org/goleak v1.3.0 - golang.org/x/net v0.29.0 + golang.org/x/net v0.30.0 pgregory.net/rand v1.0.2 ) @@ -100,16 +100,16 @@ require ( github.com/ugorji/go/codec v1.2.12 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect go.uber.org/mock v0.4.0 // indirect - golang.org/x/arch v0.10.0 // indirect - golang.org/x/crypto v0.27.0 // indirect - golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect - golang.org/x/image v0.20.0 // indirect + golang.org/x/arch v0.11.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/exp v0.0.0-20241004190924-225e2abe05e6 // indirect + golang.org/x/image v0.21.0 // indirect golang.org/x/mod v0.21.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect - golang.org/x/tools v0.25.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect + golang.org/x/tools v0.26.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 94fb73d..95aeaee 100644 --- a/go.sum +++ b/go.sum @@ -105,8 +105,8 @@ github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQ github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo= github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= -github.com/gookit/goutil v0.6.16 h1:9fRMCF4X9abdRD5+2HhBS/GwafjBlTUBjRtA5dgkvuw= -github.com/gookit/goutil v0.6.16/go.mod h1:op2q8AoPDFSiY2+qkHxcBWQMYxOLQ1GbLXqe7vrwscI= +github.com/gookit/goutil v0.6.17 h1:SxmbDz2sn2V+O+xJjJhJT/sq1/kQh6rCJ7vLBiRPZjI= +github.com/gookit/goutil v0.6.17/go.mod h1:rSw1LchE1I3TDWITZvefoAC9tS09SFu3lHXLCV7EaEY= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -117,8 +117,8 @@ github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ice-blockchain/go/src v0.0.0-20240529122316-8d9458949bdd h1:SY8V7ujSDgEYEdtMk26DEaiq8qvCTEZ7XlojacrWZj4= github.com/ice-blockchain/go/src v0.0.0-20240529122316-8d9458949bdd/go.mod h1:pVzkmaIwkLaJ5cKPHPtuKyxaXiTsa/V7kbbgLmsr4Hw= -github.com/ice-cronus/tonutils-storage v0.0.0-20241002084114-26ca96c9efc7 h1:gNEBSwI3pIyYC02cxVLbLakATq4Oz4YpKj4XH/NrQEk= -github.com/ice-cronus/tonutils-storage v0.0.0-20241002084114-26ca96c9efc7/go.mod h1:kDD8rSR3Fcp7tTNZ1C/eHxq/JSYpXBJdHU3amOZoTcg= +github.com/ice-cronus/tonutils-storage v0.0.0-20241007090031-eb90cffa7912 h1:nhY1xWbrbwbAfjyLd8QFgeatVzBEXp4S9KnRb0xgkX0= +github.com/ice-cronus/tonutils-storage v0.0.0-20241007090031-eb90cffa7912/go.mod h1:kDD8rSR3Fcp7tTNZ1C/eHxq/JSYpXBJdHU3amOZoTcg= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jamiealquiza/tachymeter v2.0.0+incompatible h1:mGiF1DGo8l6vnGT8FXNNcIXht/YmjzfraiUprXYwJ6g= @@ -165,8 +165,8 @@ github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/mattn/go-sqlite3 v1.14.23 h1:gbShiuAP1W5j9UOksQ06aiiqPMxYecovVGwmTxWtuw0= -github.com/mattn/go-sqlite3 v1.14.23/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= +github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -176,8 +176,8 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/nbd-wtf/go-nostr v0.38.1 h1:D0moEtIpjhWs2zbgeRyokA4TOLzBdumtpL1/O7/frww= -github.com/nbd-wtf/go-nostr v0.38.1/go.mod h1:TGKGj00BmJRXvRe0LlpDN3KKbELhhPXgBwUEhzu3Oq0= +github.com/nbd-wtf/go-nostr v0.38.2 h1:8PP+U8dx81jVEL89k/xMAejAlDeSDJ9ywNiyOj82so8= +github.com/nbd-wtf/go-nostr v0.38.2/go.mod h1:TGKGj00BmJRXvRe0LlpDN3KKbELhhPXgBwUEhzu3Oq0= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a h1:dlRvE5fWabOchtH7znfiFCcOvmIYgOeAS5ifBXBlh9Q= github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a/go.mod h1:hVoHR2EVESiICEMbg137etN/Lx+lSrHPTD39Z/uE+2s= @@ -223,8 +223,8 @@ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc 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/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/schollz/progressbar/v3 v3.16.0 h1:+MbBim/cE9DqDb8UXRfLJ6RZdyDkXG1BDy/sWc5s0Mc= -github.com/schollz/progressbar/v3 v3.16.0/go.mod h1:lLiKjKJ9/yzc9Q8jk+sVLfxWxgXKsktvUf6TO+4Y2nw= +github.com/schollz/progressbar/v3 v3.16.1 h1:RnF1neWZFzLCoGx8yp1yF7SDl4AzNDI5y4I0aUJRrZQ= +github.com/schollz/progressbar/v3 v3.16.1/go.mod h1:I2ILR76gz5VXqYMIY/LdLecvMHDPVcQm3W/MSKi1TME= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1 h1:NVK+OqnavpyFmUiKfUMHrpvbCi2VFoWTrcpI7aDaJ2I= @@ -277,21 +277,21 @@ go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= gocv.io/x/gocv v0.25.0/go.mod h1:Rar2PS6DV+T4FL+PM535EImD/h13hGVaHhnCu1xarBs= -golang.org/x/arch v0.10.0 h1:S3huipmSclq3PJMNe76NGwkBR504WFkQ5dhzWzP8ZW8= -golang.org/x/arch v0.10.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4= +golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/exp v0.0.0-20241004190924-225e2abe05e6 h1:1wqE9dj9NpSm04INVsJhhEUzhuDVjbcyKH91sVyPATw= +golang.org/x/exp v0.0.0-20241004190924-225e2abe05e6/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.10.0/go.mod h1:jtrku+n79PfroUbvDdeUWMAI+heR786BofxrbiSF+J0= -golang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw= -golang.org/x/image v0.20.0/go.mod h1:0a88To4CYVBAHp5FXJm8o7QbUl37Vd85ply1vyD8auM= +golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s= +golang.org/x/image v0.21.0/go.mod h1:vUbsLavqK/W303ZroQQVKQ+Af3Yl6Uz1Ppu5J/cLz78= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= @@ -306,8 +306,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -333,8 +333,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -343,8 +343,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -353,8 +353,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= @@ -363,8 +363,8 @@ golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= -golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= +golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= diff --git a/server/http/auth_nip98.go b/server/http/auth_nip98.go index 7ce70d7..b2af56c 100644 --- a/server/http/auth_nip98.go +++ b/server/http/auth_nip98.go @@ -4,7 +4,7 @@ package http import ( "encoding/base64" - "fmt" + "net/url" "time" "github.com/gin-gonic/gin" @@ -34,6 +34,11 @@ const ( nostrHttpAuthKind = 27235 ) +var ( + ErrTokenExpired = errorx.New("expired token") + ErrTokenInvalid = errorx.New("invalid token") +) + func NewAuth() AuthClient { return &authNostr{} } @@ -51,35 +56,45 @@ func (a *authNostr) VerifyToken(gCtx *gin.Context, token string) (Token, error) if ok, err = event.CheckSignature(); err != nil { return nil, errorx.Withf(err, "invalid token signature") } else if !ok { - return nil, errorx.New("invalid token signature") + return nil, errorx.Wrapf(ErrTokenInvalid, "invalid token signature") } if event.Kind != nostrHttpAuthKind { - return nil, errorx.Newf("invalid token event kind %v", event.Kind) + return nil, errorx.Wrapf(ErrTokenInvalid, "invalid token event kind %v", event.Kind) } now := time.Now() if event.CreatedAt.Time().After(now) || (event.CreatedAt.Time().Before(now) && now.Sub(event.CreatedAt.Time()) > tokenExpirationWindow) { - return nil, errorx.New("expired token") + return nil, ErrTokenExpired } if urlTag := event.Tags.GetFirst([]string{"u"}); urlTag != nil && len(*urlTag) > 1 { - url := (*urlTag)[1] - fullReqUrl := fmt.Sprintf("https://%v%v", gCtx.Request.Host, gCtx.Request.RequestURI) - if url != fullReqUrl { - return nil, errorx.Newf("invalid token: url mismatch token>%v url>%v", url, fullReqUrl) + var urlValue *url.URL + urlValue, err = url.Parse(urlTag.Value()) + if err != nil { + return nil, errorx.Wrapf(ErrTokenInvalid, "failed to parse url tag with %v", urlTag.Value()) + } + fullReqUrl := (&url.URL{ + Scheme: "https", + Host: gCtx.Request.Host, + Path: gCtx.Request.URL.Path, + RawQuery: gCtx.Request.URL.RawQuery, + Fragment: gCtx.Request.URL.Fragment, + }) + if urlValue.String() != fullReqUrl.String() { + return nil, errorx.Wrapf(ErrTokenInvalid, "url mismatch token>%v url>%v", urlValue, fullReqUrl) } } else { - return nil, errorx.Newf("invalid token: malformed u tag %v", urlTag) + return nil, errorx.Wrapf(ErrTokenInvalid, "malformed u tag %v", urlTag) } if methodTag := event.Tags.GetFirst([]string{"method"}); methodTag != nil && len(*methodTag) > 1 { - method := (*methodTag)[1] + method := methodTag.Value() if method != gCtx.Request.Method { - return nil, errorx.Newf("invalid token: method mismatch token>%v url>%v", method, gCtx.Request.Method) + return nil, errorx.Wrapf(ErrTokenInvalid, "method mismatch token>%v url>%v", method, gCtx.Request.Method) } } else { - return nil, errorx.Newf("invalid token: malformed method tag %v", methodTag) + return nil, errorx.Wrapf(ErrTokenInvalid, "malformed method tag %v", methodTag) } expectedHash := "" if payloadTag := event.Tags.GetFirst([]string{"payload"}); payloadTag != nil && len(*payloadTag) > 1 { - expectedHash = (*payloadTag)[1] + expectedHash = payloadTag.Value() } return &nostrToken{ev: event, expectedHash: expectedHash}, nil } diff --git a/server/http/nip11.go b/server/http/nip11.go index 0234418..1b41cb0 100644 --- a/server/http/nip11.go +++ b/server/http/nip11.go @@ -40,7 +40,7 @@ func (n *nip11handler) info() nip11.RelayInformationDocument { Description: "subzero", PubKey: "~", Contact: "~", - SupportedNIPs: []int{1, 2, 9, 10, 11, 13, 18, 23, 24, 25, 32, 40, 45, 50, 51, 56, 58, 65}, + SupportedNIPs: []int{1, 2, 9, 10, 11, 13, 18, 23, 24, 25, 32, 40, 45, 50, 51, 56, 58, 65, 96, 98}, Software: "subzero", } } diff --git a/server/http/storage_nip96.go b/server/http/storage_nip96.go index 89c559e..49947f3 100644 --- a/server/http/storage_nip96.go +++ b/server/http/storage_nip96.go @@ -74,8 +74,9 @@ type ( Total uint32 `json:"total"` Page uint32 `json:"page"` Files []struct { - Tags nostr.Tags `json:"tags"` - Content string `json:"content"` + Tags nostr.Tags `json:"tags"` + Content string `json:"content"` + CreatedAt uint64 `json:"created_at"` } } ) @@ -152,12 +153,12 @@ func (s *storageHandler) Upload() gin.HandlerFunc { hashHex := hex.EncodeToString(hash) if hashHex != token.ExpectedHash() { log.Printf("ERROR: endpoint authentification failed: %v", errorx.Errorf("payload hash mismatch actual>%v token>%v", hashHex, token.ExpectedHash())) - gCtx.JSON(http.StatusUnauthorized, uploadErr("Unauthorized")) + gCtx.JSON(http.StatusForbidden, uploadErr("Unauthorized")) os.Remove(uploadingFilePath) return } - now := time.Now().In(time.UTC) - bagID, url, err := s.storageClient.StartUpload(ctx, token.PubKey(), relativePath, hex.EncodeToString(hash), &storage.FileMeta{ + now := time.Now() + bagID, url, existed, err := s.storageClient.StartUpload(ctx, token.PubKey(), relativePath, hex.EncodeToString(hash), &storage.FileMetaInput{ Hash: hash, Caption: upload.Caption, Alt: upload.Alt, @@ -169,8 +170,11 @@ func (s *storageHandler) Upload() gin.HandlerFunc { gCtx.JSON(http.StatusInternalServerError, uploadErr("oops, error occured!")) return } - - gCtx.JSON(200, fileUploadResponse{ + resStatus := http.StatusCreated + if existed { + resStatus = http.StatusOK + } + gCtx.JSON(resStatus, fileUploadResponse{ Status: "success", Message: "Upload successful.", Nip94Event: struct { @@ -185,7 +189,6 @@ func (s *storageHandler) Upload() gin.HandlerFunc { nostr.Tag{"i", bagID}, nostr.Tag{"alt", upload.Alt}, nostr.Tag{"size", strconv.FormatUint(uint64(upload.File.Size), 10)}, - nostr.Tag{"createdAt", strconv.FormatUint(uint64(now.UnixNano()), 10)}, }, Content: upload.Caption, }, @@ -204,7 +207,7 @@ func (s *storageHandler) Download() gin.HandlerFunc { } file := gCtx.Param("file") if strings.TrimSpace(file) == "" { - gCtx.JSON(http.StatusUnprocessableEntity, uploadErr("filename is required")) + gCtx.JSON(http.StatusBadRequest, uploadErr("filename is required")) return } url, err := s.storageClient.DownloadUrl(token.PubKey(), file) @@ -278,15 +281,17 @@ func (s *storageHandler) ListFiles() gin.HandlerFunc { Total: total, Page: params.Page, Files: []struct { - Tags nostr.Tags `json:"tags"` - Content string `json:"content"` + Tags nostr.Tags `json:"tags"` + Content string `json:"content"` + CreatedAt uint64 `json:"created_at"` }{}, } for _, f := range filesList { res.Files = append(res.Files, struct { - Tags nostr.Tags `json:"tags"` - Content string `json:"content"` - }{Tags: f.ToTags(), Content: f.Content}) + Tags nostr.Tags `json:"tags"` + Content string `json:"content"` + CreatedAt uint64 `json:"created_at"` + }{Tags: f.ToTags(), Content: f.Content, CreatedAt: f.CreatedAt}) } gCtx.JSON(http.StatusOK, res) } diff --git a/server/http/storage_nip_96_test.go b/server/http/storage_nip_96_test.go index 6ca8b15..c67f51c 100644 --- a/server/http/storage_nip_96_test.go +++ b/server/http/storage_nip_96_test.go @@ -6,6 +6,7 @@ import ( "context" "crypto/ed25519" "crypto/rand" + "crypto/tls" "embed" "encoding/base64" "encoding/json" @@ -15,6 +16,7 @@ import ( "math/big" "net" "net/http" + "net/url" "os" "path/filepath" "strconv" @@ -23,11 +25,13 @@ import ( gomime "github.com/cubewise-code/go-mime" "github.com/gookit/goutil/errorx" + "github.com/jamiealquiza/tachymeter" "github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr/nip94" "github.com/nbd-wtf/go-nostr/nip96" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "golang.org/x/net/http2" "github.com/ice-blockchain/subzero/database/query" "github.com/ice-blockchain/subzero/model" @@ -59,20 +63,10 @@ func TestNIP96(t *testing.T) { responses <- resp }) close(responses) - var latest uint64 for resp := range responses { - var createdAt uint64 - if createdAtTag := resp.Nip94Event.Tags.GetFirst([]string{"createdAt"}); createdAtTag != nil && len(*createdAtTag) > 1 { - var err error - createdAt, err = strconv.ParseUint(createdAtTag.Value(), 10, 64) - require.NoError(t, err) - if createdAt > latest { - latest = createdAt - tagsToBroadcast = resp.Nip94Event.Tags - contentToBroadcast = resp.Nip94Event.Content - } - } verifyFile(t, resp.Nip94Event.Content, resp.Nip94Event.Tags) + tagsToBroadcast = resp.Nip94Event.Tags + contentToBroadcast = resp.Nip94Event.Content } }) const newStorageRoot = "./../../.test-uploads2" @@ -254,7 +248,7 @@ func authorizedReq(t *testing.T, ctx context.Context, sk, method, url string) *h t.Helper() uploadReq, err := http.NewRequest(method, url, nil) require.NoError(t, err) - auth, err := generateAuthHeader(t, sk, url, method) + auth, err := generateAuthHeader(t, sk, method, uploadReq.URL) require.NoError(t, err) uploadReq.Header.Set("Authorization", auth) resp, err := http.DefaultClient.Do(uploadReq.WithContext(ctx)) @@ -323,7 +317,7 @@ func initStorage(ctx context.Context, path string) { http.DefaultClient.Transport = transportOverride } -func generateAuthHeader(t *testing.T, sk, url, method string) (string, error) { +func generateAuthHeader(t *testing.T, sk, method string, urlValue *url.URL) (string, error) { t.Helper() pk, err := nostr.GetPublicKey(sk) if err != nil { @@ -335,7 +329,7 @@ func generateAuthHeader(t *testing.T, sk, url, method string) (string, error) { PubKey: pk, CreatedAt: nostr.Now(), Tags: nostr.Tags{ - nostr.Tag{"u", url}, + nostr.Tag{"u", urlValue.String()}, nostr.Tag{"method", method}, }, } @@ -350,3 +344,71 @@ func generateAuthHeader(t *testing.T, sk, url, method string) (string, error) { return fmt.Sprintf("Nostr %s", payload), nil } + +const benchParallelism = 100 + +func BenchmarkUploadFiles(b *testing.B) { + if os.Getenv("CI") != "" { + b.Skip() + } + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer cancel() + http.DefaultClient.Transport = &http2.Transport{TLSClientConfig: &tls.Config{}} + meter := tachymeter.New(&tachymeter.Config{Size: b.N}) + b.ResetTimer() + b.ReportAllocs() + fmt.Println(b.N) + b.SetParallelism(benchParallelism) + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + sk := nostr.GeneratePrivateKey() + img, _ := testdata.Open(".testdata/image2.png") + defer img.Close() + start := time.Now() + resp, err := nip96.Upload(ctx, nip96.UploadRequest{ + Host: "https://localhost:9997/media", + File: img, + Filename: "profile.png", + Caption: "ice profile pic", + ContentType: "image/png", + SK: sk, + SignPayload: true, + }) + require.NoError(b, err) + meter.AddTime(time.Since(start)) + nip94Event := &model.Event{nostr.Event{ + CreatedAt: nostr.Timestamp(time.Now().Unix()), + Kind: nostr.KindFileMetadata, + Tags: resp.Nip94Event.Tags, + }} + require.NoError(b, nip94Event.Sign(sk)) + + relay := nostr.NewRelay(ctx, "wss://localhost:9998/") + err = relay.ConnectWithTLS(ctx, &tls.Config{}) + if err = nip94Event.Sign(sk); err != nil { + log.Panic(err) + } + if err = relay.Publish(ctx, nip94Event.Event); err != nil { + log.Panic(err) + } + b.Log(nip94Event) + } + }) + helperBenchReportMetrics(b, meter) +} + +func helperBenchReportMetrics( + t interface { + Helper() + ReportMetric(float64, string) + }, + meter *tachymeter.Tachymeter, +) { + t.Helper() + + metric := meter.Calc() + t.ReportMetric(float64(metric.Time.Avg.Milliseconds()), "avg-ms/op") + t.ReportMetric(float64(metric.Time.StdDev.Milliseconds()), "stddev-ms/op") + t.ReportMetric(float64(metric.Time.P50.Milliseconds()), "p50-ms/op") + t.ReportMetric(float64(metric.Time.P95.Milliseconds()), "p95-ms/op") +} diff --git a/storage/download.go b/storage/download.go index eed162c..ff5cce1 100644 --- a/storage/download.go +++ b/storage/download.go @@ -38,12 +38,12 @@ func acceptNewBag(ctx context.Context, event *model.Event) error { var fileUrl *url.URL var err error if iTag := event.Tags.GetFirst([]string{"i"}); iTag != nil && len(*iTag) > 1 { - infohash = (*iTag)[1] + infohash = iTag.Value() } else { return errorx.Newf("malformed i tag %v", iTag) } if urlTag := event.Tags.GetFirst([]string{"url"}); urlTag != nil && len(*urlTag) > 1 { - fileUrl, err = url.Parse((*urlTag)[1]) + fileUrl, err = url.Parse(urlTag.Value()) if err != nil { return errorx.Withf(err, "malformed url in url tag %v", *urlTag) } @@ -87,16 +87,12 @@ func (c *client) download(ctx context.Context, bagID, user string, bootstrap *st if tor == nil { tor = storage.NewTorrent(c.rootStoragePath, c.progressStorage, c.conn) tor.BagID = bag - if err = tor.Start(false, true, false); err != nil { - return errorx.Withf(err, "failed to start new torrent %v", bagID) + c.downloadQueue <- queueItem{ + tor: tor, + bootstrap: bootstrap, + user: &user, } - if bootstrap != nil && *bootstrap != "" { - if err = c.connectToBootstrap(ctx, tor, *bootstrap); err != nil { - log.Printf("failed to connect to bootstrap node, waiting for DHT: %v", err) - } - } - go c.startUploadAfterDownloadingHeader(tor, user) - if err = c.saveTorrent(tor, user); err != nil { + if err = c.saveTorrent(tor, &user, bootstrap); err != nil { return errorx.Withf(err, "failed to store new torrent %v", bagID) } } else { @@ -107,6 +103,32 @@ func (c *client) download(ctx context.Context, bagID, user string, bootstrap *st return nil } +func (c *client) torrentStateCallback(tor *storage.Torrent, user *string) func(event storage.Event) { + return func(event storage.Event) { + switch event.Name { + case storage.EventDone: + tor.Stop() + if pErr := tor.Start(true, false, false); pErr != nil { + log.Printf("ERROR: failed to stop torrent upload after downloading data: %v", pErr) + } + c.activeDownloadsMx.Lock() + delete(c.activeDownloads, hex.EncodeToString(tor.BagID)) + c.activeDownloadsMx.Unlock() + + case storage.EventBagResolved: + if _, isUplActive := tor.IsActive(); !isUplActive { + if pErr := tor.StartWithCallback(true, true, false, c.torrentStateCallback(tor, user)); pErr != nil { + log.Printf("ERROR: failed to start torrent upload after downloading header: %v", pErr) + } + } + } + if pErr := c.saveTorrent(tor, user, nil); pErr != nil { + log.Printf("ERROR: failed save torrent with stopped download after downloading: %v", pErr) + } + + } +} + func (c *client) connectToBootstrap(ctx context.Context, torrent *storage.Torrent, bootstrap string) error { b64, err := base64.StdEncoding.DecodeString(bootstrap) if err != nil { @@ -130,36 +152,66 @@ func (c *client) connectToBootstrap(ctx context.Context, torrent *storage.Torren return nil } -func (c *client) saveTorrent(tr *storage.Torrent, userPubKey string) error { +func (c *client) saveTorrent(tr *storage.Torrent, userPubKey *string, bs *string) error { if err := c.progressStorage.SetTorrent(tr); err != nil { return errorx.With(err, "failed to save torrent into storage") } - k := make([]byte, 5+64) - copy(k, "desc:") - copy(k[5:], userPubKey) - if err := c.db.Put(k, tr.BagID, nil); err != nil { - return errorx.With(err, "failed to save userID:bag mapping") + if userPubKey != nil { + k := make([]byte, 3+64) + copy(k, "ub:") + copy(k[3:], *userPubKey) + if err := c.db.Put(k, tr.BagID, nil); err != nil { + return errorx.Withf(err, "failed to save userID:bag mapping for bag %v", hex.EncodeToString(tr.BagID)) + } } + if bs != nil { + k := make([]byte, 3+32) + copy(k, "bs:") + copy(k[3:], tr.BagID) + if err := c.db.Put(k, []byte(*bs), nil); err != nil { + return errorx.Withf(err, "failed to save bootstrap node for bag %v", hex.EncodeToString(tr.BagID)) + } + } + return nil } -func (c *client) startUploadAfterDownloadingHeader(tor *storage.Torrent, user string) { +func (c *client) startDownloadsFromQueue() { +outerLoop: for { - select { - case <-time.After(1 * time.Second): - if _, isUplActive := tor.IsActive(); isUplActive { - return + c.activeDownloadsMx.RLock() + l := len(c.activeDownloads) + c.activeDownloadsMx.RUnlock() + for l < ConcurrentBagsDownloading { + select { + case q := <-c.downloadQueue: + tor := q.tor + if downloading, _ := tor.IsActive(); downloading { + continue + } + if err := tor.StartWithCallback(false, true, false, c.torrentStateCallback(tor, q.user)); err != nil { + log.Printf("ERROR: %v", errorx.Withf(err, "failed to start new torrent %v", q.tor.BagID)) + } + if q.bootstrap != nil && *q.bootstrap != "" { + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + if err := c.connectToBootstrap(ctx, tor, *q.bootstrap); err != nil { + log.Printf("failed to connect to bootstrap node for bag %v, waiting for DHT: %v", hex.EncodeToString(q.tor.BagID), err) + } + cancel() + } + if err := c.saveTorrent(tor, q.user, q.bootstrap); err != nil { + log.Printf("failed save updated upload / download torrrent state %v: %v", hex.EncodeToString(q.tor.BagID), err) + } + c.activeDownloadsMx.Lock() + c.activeDownloads[hex.EncodeToString(tor.BagID)] = true + c.activeDownloadsMx.Unlock() + l += 1 + default: + time.Sleep(100 * time.Millisecond) + continue outerLoop } - if tor.Header == nil { - continue - } - if err := tor.Start(true, true, false); err != nil { - log.Printf("ERROR: failed to start torrent upload after downloading header: %v", err) - } - if err := c.saveTorrent(tor, user); err != nil { - log.Printf("ERROR: failed save torrent upload state after downloading header: %v", err) - } - return } + time.Sleep(100 * time.Millisecond) } + } diff --git a/storage/global.go b/storage/global.go index 0b0106d..21a62ac 100644 --- a/storage/global.go +++ b/storage/global.go @@ -1,5 +1,3 @@ -// SPDX-License-Identifier: ice License 1.0 - package storage import ( @@ -13,6 +11,7 @@ import ( "net/url" "os" "path/filepath" + "runtime" "strconv" "sync" "time" @@ -36,15 +35,19 @@ var globalClient *client const DefaultConfigUrl = "https://ton.org/global.config.json" +var ConcurrentBagsDownloading = runtime.NumCPU() * 10 + +const threadsPerBagForDownloading = 1 + func Client() StorageClient { return globalClient } func AcceptEvent(ctx context.Context, event *model.Event) error { - if !(event.Kind == nostr.KindFileMetadata || event.Kind == nostr.KindDeletion) { - return nil - } - if event.Kind == nostr.KindDeletion { + switch event.Kind { + case nostr.KindFileMetadata: + return acceptNewBag(ctx, event) + case nostr.KindDeletion: matchKTag := false if kTag := event.Tags.GetFirst([]string{"k"}); kTag != nil && len(*kTag) > 1 { if kTag.Value() == strconv.FormatInt(int64(nostr.KindFileMetadata), 10) { @@ -55,9 +58,11 @@ func AcceptEvent(ctx context.Context, event *model.Event) error { return acceptDeletion(ctx, event) } return nil + default: + return nil } - return acceptNewBag(ctx, event) } + func acceptDeletion(ctx context.Context, event *model.Event) error { refs, err := model.ParseEventReference(event.Tags) if err != nil { @@ -103,14 +108,22 @@ func acceptDeletion(ctx context.Context, event *model.Event) error { if err := os.Remove(filepath.Join(userRoot, file)); err != nil && !errors.Is(err, os.ErrNotExist) { return errorx.Withf(err, "failed to delete file %v", file) } - if _, _, err := globalClient.StartUpload(ctx, event.PubKey, file, fileHash, nil); err != nil { + if _, _, _, err := globalClient.StartUpload(ctx, event.PubKey, file, fileHash, nil); err != nil { return errorx.Withf(err, "failed to rebuild bag with deleted file") } return nil } func MustInit(ctx context.Context, nodeKey ed25519.PrivateKey, tonConfigUrl, rootStorage string, externalAddress net.IP, port int, debug bool) { - storage.Logger = log.Println + globalClient = mustInit(ctx, nodeKey, tonConfigUrl, rootStorage, externalAddress, port, debug) +} + +func mustInit(ctx context.Context, nodeKey ed25519.PrivateKey, tonConfigUrl, rootStorage string, externalAddress net.IP, port int, debug bool) *client { + if debug { + storage.Logger = log.Println + } + storage.DownloadThreads = threadsPerBagForDownloading + adnl.Logger = func(v ...any) {} var lsCfg *liteclient.GlobalConfig u, err := url.Parse(tonConfigUrl) if err != nil { @@ -154,22 +167,50 @@ func MustInit(ctx context.Context, nodeKey ed25519.PrivateKey, tonConfigUrl, roo if err != nil { log.Panic(errorx.Wrapf(err, "failed to open leveldb")) } - globalClient = &client{ - conn: conn, - db: progressDb, - server: srv, - gateway: gate, - dht: dhtClient, - events: make(chan db.Event), - rootStoragePath: rootStorage, - newFiles: make(map[string]map[string]*FileMeta), - newFilesMx: &sync.RWMutex{}, - stats: statistics.NewStatistics(rootStorage, debug), - } - progressStorage, err := db.NewStorage(progressDb, conn, true, globalClient.events) + cl := &client{ + conn: conn, + db: progressDb, + server: srv, + gateway: gate, + dht: dhtClient, + rootStoragePath: rootStorage, + newFiles: make(map[string]map[string]*FileMetaInput), + newFilesMx: &sync.RWMutex{}, + stats: statistics.NewStatistics(rootStorage, debug), + downloadQueue: make(chan queueItem, 1000000), + activeDownloads: make(map[string]bool), + activeDownloadsMx: &sync.RWMutex{}, + } + loadMonitoringCh := make(chan *db.Event, 1000000) + go func() { + for ev := range loadMonitoringCh { + if ev.Event == db.EventTorrentLoaded { + if ev.Torrent != nil { + if _, uploading := ev.Torrent.IsActive(); !uploading { + if downloading := ev.Torrent.IsDownloadAll(); !downloading { + bs, bsErr := cl.bootstrapForBag(ev.Torrent.BagID) + if bsErr != nil { + log.Printf("WARN: failed to find stored bootstrap for bag %v: %v", hex.EncodeToString(ev.Torrent.BagID), bsErr) + } + cl.downloadQueue <- queueItem{ + tor: ev.Torrent, + bootstrap: &bs, + user: nil, + } + } + } + } + } + } + }() + progressStorage, err := db.NewStorage(progressDb, conn, true, loadMonitoringCh) if err != nil { log.Panic(errorx.Wrapf(err, "failed to open storage")) } - globalClient.progressStorage = progressStorage - globalClient.server.SetStorage(progressStorage) + cl.progressStorage = progressStorage + cl.server.SetStorage(progressStorage) + cl.progressStorage.SetNotifier(nil) + close(loadMonitoringCh) + go cl.startDownloadsFromQueue() + return cl } diff --git a/storage/storage.go b/storage/storage.go index d42160b..fad07e9 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -13,6 +13,7 @@ import ( "strconv" "strings" "sync" + "time" gomime "github.com/cubewise-code/go-mime" "github.com/gookit/goutil/errorx" @@ -31,10 +32,10 @@ import ( type ( StorageClient interface { io.Closer - StartUpload(ctx context.Context, userPubkey, relativePathToFileForUrl, fileHash string, newFile *FileMeta) (bagID, url string, err error) + StartUpload(ctx context.Context, userPubkey, relativePathToFileForUrl, fileHash string, newFile *FileMetaInput) (bagID, url string, existed bool, err error) BuildUserPath(userPubKey, contentType string) (string, string) DownloadUrl(userPubkey, fileSha256 string) (string, error) - ListFiles(userPubkey string, page, count uint32) (totalFiles uint32, files []*nip94.FileMetadata, err error) + ListFiles(userPubkey string, page, count uint32) (totalFiles uint32, files []*FileMetadata, err error) Delete(userPubkey string, fileSha256 string) error } Bootstrap struct { @@ -42,28 +43,39 @@ type ( DHT *dht.Node } headerData struct { - User string `json:"u"` - FileMetadata map[string]*FileMeta `json:"f"` - FileHash map[string]string `json:"fh"` + User string `json:"u"` + FileMetadata map[string]*FileMetaInput `json:"f"` + FileHash map[string]string `json:"fh"` } - FileMeta struct { + FileMetaInput struct { Hash []byte `json:"h"` Caption string `json:"c"` Alt string `json:"a"` CreatedAt uint64 `json:"cAt"` } + FileMetadata struct { + *nip94.FileMetadata + CreatedAt uint64 `json:"created_at"` + } client struct { - conn *storage.Connector - db *leveldb.DB - server *storage.Server - progressStorage *db.Storage - gateway *adnl.Gateway - dht *dht.Client - events chan db.Event - rootStoragePath string - newFiles map[string]map[string]*FileMeta - newFilesMx *sync.RWMutex - stats statistics.Statistics + conn *storage.Connector + db *leveldb.DB + server *storage.Server + progressStorage *db.Storage + gateway *adnl.Gateway + dht *dht.Client + rootStoragePath string + newFiles map[string]map[string]*FileMetaInput + newFilesMx *sync.RWMutex + stats statistics.Statistics + downloadQueue chan queueItem + activeDownloads map[string]bool + activeDownloadsMx *sync.RWMutex + } + queueItem struct { + tor *storage.Torrent + bootstrap *string + user *string } ) @@ -95,9 +107,9 @@ func (c *client) detectFile(bag *storage.Torrent, fileHash string) (string, erro } func (c *client) bagByUser(userPubKey string) (*storage.Torrent, error) { - k := make([]byte, 5+64) - copy(k, "desc:") - copy(k[5:], userPubKey) + k := make([]byte, 3+64) + copy(k, "ub:") + copy(k[3:], userPubKey) bagID, err := c.db.Get(k, nil) if err != nil && !errors.Is(err, leveldb.ErrNotFound) { return nil, errorx.With(err, "failed to read userID:bag mapping") @@ -106,13 +118,23 @@ func (c *client) bagByUser(userPubKey string) (*storage.Torrent, error) { return tr, nil } +func (c *client) bootstrapForBag(bagID []byte) (string, error) { + k := make([]byte, 3+32) + copy(k, "bs:") + copy(k[3:], bagID) + bs, err := c.db.Get(k, nil) + if err != nil && !errors.Is(err, leveldb.ErrNotFound) { + return "", errorx.Withf(err, "failed to read stored bootstrap node for %v =, will wait for DHT discovery", hex.EncodeToString(bagID)) + } + return string(bs), nil +} func (c *client) BuildUserPath(userPubKey string, contentType string) (userStorage string, uploadPath string) { spl := strings.Split(contentType, "/") return filepath.Join(c.rootStoragePath, userPubKey), spl[0] } -func (c *client) ListFiles(userPubKey string, page, limit uint32) (total uint32, res []*nip94.FileMetadata, err error) { +func (c *client) ListFiles(userPubKey string, page, limit uint32) (total uint32, res []*FileMetadata, err error) { bag, err := c.bagByUser(userPubKey) if err != nil { return 0, nil, errorx.Withf(err, "failed to get bagID for the user %v", userPubKey) @@ -123,13 +145,13 @@ func (c *client) ListFiles(userPubKey string, page, limit uint32) (total uint32, } startOffset := page * limit if startOffset >= bag.Header.FilesCount { - return bag.Header.FilesCount, []*nip94.FileMetadata{}, nil + return bag.Header.FilesCount, []*FileMetadata{}, nil } endOffset := page*limit + limit if endOffset >= bag.Header.FilesCount { endOffset = bag.Header.FilesCount } - res = make([]*nip94.FileMetadata, 0, limit) + res = make([]*FileMetadata, 0, limit) bs, err := c.buildBootstrapNodeInfo(bag) if err != nil { return 0, nil, errorx.Withf(err, "failed to build bootstap for bag %v", hex.EncodeToString(bag.BagID)) @@ -146,15 +168,18 @@ func (c *client) ListFiles(userPubKey string, page, limit uint32) (total uint32, continue } url, _ := c.buildUrl(hex.EncodeToString(bag.BagID), f, []*Bootstrap{bs}) - res = append(res, &nip94.FileMetadata{ - Size: strconv.FormatUint(uint64(fileInfo.Size), 10), - Summary: md.Alt, - URL: url, - M: gomime.TypeByExtension(filepath.Ext(files[idx])), - X: hex.EncodeToString(md.Hash), - OX: hex.EncodeToString(md.Hash), - TorrentInfoHash: hex.EncodeToString(bag.BagID), - Content: md.Caption, + res = append(res, &FileMetadata{ + FileMetadata: &nip94.FileMetadata{ + Size: strconv.FormatUint(uint64(fileInfo.Size), 10), + Summary: md.Alt, + URL: url, + M: gomime.TypeByExtension(filepath.Ext(files[idx])), + X: hex.EncodeToString(md.Hash), + OX: hex.EncodeToString(md.Hash), + TorrentInfoHash: hex.EncodeToString(bag.BagID), + Content: md.Caption, + }, + CreatedAt: uint64(time.Unix(0, int64(md.CreatedAt)).Unix()), }) } return bag.Header.FilesCount, res, nil @@ -190,5 +215,6 @@ func (c *client) Close() error { if dErr := c.db.Close(); dErr != nil { err = multierror.Append(err, errorx.Withf(dErr, "failed to close db")) } + close(c.downloadQueue) return err.ErrorOrNil() } diff --git a/storage/upload.go b/storage/upload.go index 5a199ae..4afac8f 100644 --- a/storage/upload.go +++ b/storage/upload.go @@ -9,7 +9,6 @@ import ( "encoding/hex" "encoding/json" "fmt" - "log" "path/filepath" "sync" "time" @@ -23,31 +22,42 @@ import ( "github.com/xssnick/tonutils-storage/storage" ) -func (c *client) StartUpload(ctx context.Context, userPubkey, relativePathToFileForUrl, hash string, newFile *FileMeta) (bagID, url string, err error) { +func (c *client) StartUpload(ctx context.Context, userPubkey, relativePathToFileForUrl, hash string, newFile *FileMetaInput) (bagID, url string, existed bool, err error) { existingBagForUser, err := c.bagByUser(userPubkey) if err != nil { - return "", "", errorx.Withf(err, "failed to find existing bag for user %s", userPubkey) + return "", "", false, errorx.Withf(err, "failed to find existing bag for user %s", userPubkey) } var existingHD headerData if existingBagForUser != nil { if len(existingBagForUser.Header.Data) > 0 { if err = json.Unmarshal(existingBagForUser.Header.Data, &existingHD); err != nil { - return "", "", errorx.Withf(err, "corrupted header metadata for bag %v", hex.EncodeToString(existingBagForUser.BagID)) + return "", "", false, errorx.Withf(err, "corrupted header metadata for bag %v", hex.EncodeToString(existingBagForUser.BagID)) } } } + _, existed = existingHD.FileHash[hash] + if existed { + if existingBagForUser != nil { + url, err = c.DownloadUrl(userPubkey, hash) + if err != nil { + return "", "", false, + errorx.Withf(err, "failed to build download url for already existing file %v/%v(%v)", userPubkey, relativePathToFileForUrl, hash) + } + return hex.EncodeToString(existingBagForUser.BagID), url, existed, nil + } + } var bs []*Bootstrap var bag *storage.Torrent bag, bs, err = c.upload(ctx, userPubkey, relativePathToFileForUrl, hash, newFile, &existingHD) if err != nil { - return "", "", errorx.Withf(err, "failed to start upload of %v", relativePathToFileForUrl) + return "", "", false, errorx.Withf(err, "failed to start upload of %v", relativePathToFileForUrl) } bagID = hex.EncodeToString(bag.BagID) if newFile != nil { uplFile, err := bag.GetFileOffsets(relativePathToFileForUrl) if err != nil { - return "", "", errorx.Withf(err, "failed to get just created file from new bag") + return "", "", false, errorx.Withf(err, "failed to get just created file from new bag") } fullFilePath := filepath.Join(c.rootStoragePath, userPubkey, relativePathToFileForUrl) go c.stats.ProcessFile(fullFilePath, gomime.TypeByExtension(filepath.Ext(fullFilePath)), uplFile.Size) @@ -55,16 +65,16 @@ func (c *client) StartUpload(ctx context.Context, userPubkey, relativePathToFile url, err = c.buildUrl(bagID, relativePathToFileForUrl, bs) if err != nil { - return "", "", errorx.Withf(err, "failed to build url for %v (bag %v)", relativePathToFileForUrl, bagID) + return "", "", false, errorx.Withf(err, "failed to build url for %v (bag %v)", relativePathToFileForUrl, bagID) } - return bagID, url, err + return bagID, url, existed, err } -func (c *client) upload(ctx context.Context, user, relativePath, hash string, fileMeta *FileMeta, headerMetadata *headerData) (torrent *storage.Torrent, bootstrap []*Bootstrap, err error) { +func (c *client) upload(ctx context.Context, user, relativePath, hash string, fileMeta *FileMetaInput, headerMetadata *headerData) (torrent *storage.Torrent, bootstrap []*Bootstrap, err error) { if fileMeta != nil { c.newFilesMx.Lock() if userNewFiles, hasNewFiles := c.newFiles[user]; !hasNewFiles || userNewFiles == nil { - c.newFiles[user] = make(map[string]*FileMeta) + c.newFiles[user] = make(map[string]*FileMetaInput) } c.newFiles[user][relativePath] = fileMeta c.newFilesMx.Unlock() @@ -83,7 +93,7 @@ func (c *client) upload(ctx context.Context, user, relativePath, hash string, fi headerMD.FileHash = make(map[string]string) } if headerMD.FileMetadata == nil { - headerMD.FileMetadata = make(map[string]*FileMeta) + headerMD.FileMetadata = make(map[string]*FileMetaInput) } if fileMeta != nil { headerMD.FileMetadata[relativePath] = fileMeta @@ -103,7 +113,6 @@ func (c *client) upload(ctx context.Context, user, relativePath, hash string, fi if err != nil { return nil, nil, errorx.With(err, "failed to put file hashes") } - log.Println("UPLOAD META FOR ", relativePath, string(headerMDSerialized)) header := &storage.TorrentHeader{ DirNameSize: uint32(len(user)), DirName: []byte(user), @@ -116,11 +125,11 @@ func (c *client) upload(ctx context.Context, user, relativePath, hash string, fi if done == max { wg.Done() } - }) + }, false) if err != nil { return nil, nil, errorx.With(err, "failed to initialize bag") } - err = tr.Start(true, true, false) + err = tr.Start(true, false, false) if err != nil { return nil, nil, errorx.With(err, "failed to start bag upload") } @@ -175,7 +184,7 @@ func (c *client) buildUrl(bagID, relativePath string, bs []*Bootstrap) (string, } func (c *client) saveUploadTorrent(tr *storage.Torrent, userPubKey string) error { - if err := c.saveTorrent(tr, userPubKey); err != nil { + if err := c.saveTorrent(tr, &userPubKey, nil); err != nil { return errorx.With(err, "failed to save upload torrent into storage") } c.newFilesMx.Lock() From ab08c39d45dfe223b10e9dcef71a4b695c757c08 Mon Sep 17 00:00:00 2001 From: ice-cronus <105345303+ice-cronus@users.noreply.github.com> Date: Mon, 7 Oct 2024 14:34:23 +0300 Subject: [PATCH 08/18] lic fix --- storage/global.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/storage/global.go b/storage/global.go index 21a62ac..94ffbe6 100644 --- a/storage/global.go +++ b/storage/global.go @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: ice License 1.0 + package storage import ( From 1beeec40224147e9a0837151b7db274ece088acb Mon Sep 17 00:00:00 2001 From: ice-cronus <105345303+ice-cronus@users.noreply.github.com> Date: Mon, 7 Oct 2024 16:12:52 +0300 Subject: [PATCH 09/18] fix missed comment --- server/http/storage_nip96.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/server/http/storage_nip96.go b/server/http/storage_nip96.go index 49947f3..d121a64 100644 --- a/server/http/storage_nip96.go +++ b/server/http/storage_nip96.go @@ -8,6 +8,7 @@ import ( _ "embed" "encoding/hex" "errors" + "fmt" "io" "log" "mime/multipart" @@ -47,6 +48,8 @@ type storageHandler struct { const mediaEndpointTimeout = 60 * time.Second const maxUploadSize = 100 * 1024 * 1024 +const mediaTypeAvatar = "avatar" +const mediaTypeBanner = "banner" type ( fileUpload struct { @@ -112,6 +115,10 @@ func (s *storageHandler) Upload() gin.HandlerFunc { gCtx.JSON(http.StatusBadRequest, uploadErr("file required")) return } + if upload.MediaType != "" && upload.MediaType != mediaTypeAvatar && upload.MediaType != mediaTypeBanner { + gCtx.JSON(http.StatusBadRequest, uploadErr(fmt.Sprintf("unsupported media type %v", upload.MediaType))) + return + } if upload.ContentType == "" { upload.ContentType = gomime.TypeByExtension(filepath.Ext(upload.File.Filename)) } @@ -234,7 +241,7 @@ func (s *storageHandler) Delete() gin.HandlerFunc { } file := gCtx.Param("file") if strings.TrimSpace(file) == "" { - gCtx.JSON(http.StatusUnprocessableEntity, uploadErr("filehash is required")) + gCtx.JSON(http.StatusBadRequest, uploadErr("filehash is required")) return } if err := s.storageClient.Delete(token.PubKey(), file); err != nil { From 09891564f191994d833adecc6924682d6218f4ba Mon Sep 17 00:00:00 2001 From: ice-cronus <105345303+ice-cronus@users.noreply.github.com> Date: Mon, 7 Oct 2024 16:24:00 +0300 Subject: [PATCH 10/18] fix missed comment --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 75be9f9..de9a296 100644 --- a/go.mod +++ b/go.mod @@ -110,6 +110,6 @@ require ( golang.org/x/term v0.25.0 // indirect golang.org/x/text v0.19.0 // indirect golang.org/x/tools v0.26.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect + google.golang.org/protobuf v1.35.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 95aeaee..15f3da8 100644 --- a/go.sum +++ b/go.sum @@ -366,8 +366,8 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 1fcd1f76651aa13246567080b7e43ad958ab9783 Mon Sep 17 00:00:00 2001 From: ice-cronus <105345303+ice-cronus@users.noreply.github.com> Date: Thu, 17 Oct 2024 12:37:31 +0300 Subject: [PATCH 11/18] rebase fixes --- cmd/subzero/subzero.go | 9 ++-- database/command/command.go | 6 --- go.mod | 26 ++++++----- go.sum | 58 +++++++++++++++++++------ server/http/auth_nip98.go | 26 +++++------ server/http/storage_nip96.go | 35 ++++++++------- server/http/storage_nip_96_test.go | 6 +-- storage/download.go | 46 ++++++++++---------- storage/fixture/fixture.go | 19 ++++---- storage/global.go | 39 ++++++++--------- storage/statistics/metadata/image.go | 4 +- storage/statistics/metadata/metadata.go | 6 +-- storage/statistics/metadata/video.go | 10 ++--- storage/statistics/statistics.go | 28 ++++++------ storage/storage.go | 33 +++++++------- storage/upload.go | 34 +++++++-------- 16 files changed, 206 insertions(+), 179 deletions(-) diff --git a/cmd/subzero/subzero.go b/cmd/subzero/subzero.go index 95a09b1..f64a0e9 100644 --- a/cmd/subzero/subzero.go +++ b/cmd/subzero/subzero.go @@ -5,7 +5,6 @@ package main import ( "context" "crypto/ed25519" - "crypto/rand" "log" "net" @@ -65,9 +64,9 @@ var ( subzero.Flags().StringVar(&storageRootDir, "storage-root", "./.uploads", "root storage directory") subzero.Flags().StringVar(&globalConfigUrl, "global-config-url", storage.DefaultConfigUrl, "global config for ION storage") subzero.Flags().BytesHexVar(&adnlNodeKey, "adnl-node-key", func() []byte { - _, nodeKey, err := ed25519.GenerateKey(rand.Reader) + _, nodeKey, err := ed25519.GenerateKey(nil) if err != nil { - log.Panic(errorx.Wrapf(err, "failed to generate node key")) + log.Panic(errors.Wrapf(err, "failed to generate node key")) } return nodeKey }(), "adnl node key in hex") @@ -102,7 +101,9 @@ func init() { if err := query.AcceptEvent(ctx, event); err != nil { return errors.Wrapf(err, "failed to query.AcceptEvent(%#v)", event) } - + if sErr := storage.AcceptEvent(ctx, event); sErr != nil { + return errors.Wrapf(sErr, "failed to process NIP-94 event") + } return nil }) wsserver.RegisterWSSubscriptionListener(query.GetStoredEvents) diff --git a/database/command/command.go b/database/command/command.go index e8ad78c..06ab329 100644 --- a/database/command/command.go +++ b/database/command/command.go @@ -5,19 +5,13 @@ package command import ( "context" - "github.com/gookit/goutil/errorx" "github.com/hashicorp/go-multierror" "github.com/ice-blockchain/subzero/model" - "github.com/ice-blockchain/subzero/storage" ) func AcceptEvent(ctx context.Context, event *model.Event) error { var mErr *multierror.Error //TODO impl consensus - // After consensus pass it to storage - if sErr := storage.AcceptEvent(ctx, event); sErr != nil { - mErr = multierror.Append(mErr, errorx.Withf(sErr, "failed to process NIP-94 event")) - } return mErr.ErrorOrNil() } diff --git a/go.mod b/go.mod index de9a296..a48ffe2 100644 --- a/go.mod +++ b/go.mod @@ -1,15 +1,15 @@ module github.com/ice-blockchain/subzero -go 1.23.0 - -toolchain go1.23.1 +go 1.23.1 replace ( filippo.io/mkcert => github.com/kixelated/mkcert v1.4.4-days + github.com/quic-go/quic-go v0.48.0 => github.com/quic-go/quic-go v0.47.0 github.com/xssnick/tonutils-storage => github.com/ice-cronus/tonutils-storage v0.0.0-20241007090031-eb90cffa7912 ) require ( + github.com/cockroachdb/errors v1.11.3 github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8 github.com/davidbyttow/govips/v2 v2.15.0 github.com/fsnotify/fsnotify v1.7.0 @@ -17,15 +17,14 @@ require ( github.com/gobwas/httphead v0.1.0 github.com/gobwas/ws v1.4.0 github.com/google/uuid v1.6.0 - github.com/gookit/goutil v0.6.17 github.com/hashicorp/go-multierror v1.1.1 github.com/ice-blockchain/go/src v0.0.0-20240529122316-8d9458949bdd github.com/jamiealquiza/tachymeter v2.0.0+incompatible github.com/jmoiron/sqlx v1.4.0 github.com/mattn/go-sqlite3 v1.14.24 - github.com/nbd-wtf/go-nostr v0.38.2 - github.com/pkg/errors v0.9.1 - github.com/quic-go/quic-go v0.47.0 + github.com/mxschmitt/golang-combinations v1.2.0 + github.com/nbd-wtf/go-nostr v0.39.0 + github.com/quic-go/quic-go v0.48.0 github.com/quic-go/webtransport-go v0.8.0 github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 github.com/schollz/progressbar/v3 v3.16.1 @@ -51,11 +50,14 @@ require ( github.com/bytedance/sonic/loader v0.2.0 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect + github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect + github.com/cockroachdb/redact v1.1.5 // indirect github.com/containerd/console v1.0.4 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/decred/dcrd/crypto/blake256 v1.1.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.5 // indirect + github.com/gabriel-vasile/mimetype v1.4.6 // indirect + github.com/getsentry/sentry-go v0.29.1 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect @@ -63,8 +65,9 @@ require ( github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gobwas/pool v0.2.1 // indirect github.com/goccy/go-json v0.10.3 // indirect + github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/google/pprof v0.0.0-20241001023024-f4c0cfd0cf1d // indirect + github.com/google/pprof v0.0.0-20241009165004-a3522334989c // indirect github.com/gookit/color v1.5.4 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -73,6 +76,7 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/kevinms/leakybucket-go v0.0.0-20200115003610-082473db97ca // indirect github.com/klauspost/cpuid/v2 v2.2.8 // indirect + github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/lithammer/fuzzysearch v1.1.8 // indirect @@ -85,11 +89,13 @@ require ( github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a // indirect github.com/onsi/ginkgo/v2 v2.20.2 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pterm/pterm v0.12.79 // indirect github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect github.com/quic-go/qpack v0.5.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect + github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/tidwall/gjson v1.18.0 // indirect @@ -102,7 +108,7 @@ require ( go.uber.org/mock v0.4.0 // indirect golang.org/x/arch v0.11.0 // indirect golang.org/x/crypto v0.28.0 // indirect - golang.org/x/exp v0.0.0-20241004190924-225e2abe05e6 // indirect + golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect golang.org/x/image v0.21.0 // indirect golang.org/x/mod v0.21.0 // indirect golang.org/x/sync v0.8.0 // indirect diff --git a/go.sum b/go.sum index 15f3da8..c4e126c 100644 --- a/go.sum +++ b/go.sum @@ -36,6 +36,12 @@ github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/ github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I= +github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= +github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= +github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro= github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= @@ -59,12 +65,16 @@ github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiD 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/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4= -github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4= +github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc= +github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc= +github.com/getsentry/sentry-go v0.29.1 h1:DyZuChN8Hz3ARxGVV8ePaNXh1dQ7d76AiB117xcREwA= +github.com/getsentry/sentry-go v0.29.1/go.mod h1:x3AtIzN01d6SiWkderzaH28Tm0lgkafpJ5Bm3li39O0= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -89,6 +99,8 @@ github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakr github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= @@ -96,8 +108,8 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20241001023024-f4c0cfd0cf1d h1:Jaz2JzpQaQXyET0AjLBXShrthbpqMkhGiEfkcQAiAUs= -github.com/google/pprof v0.0.0-20241001023024-f4c0cfd0cf1d/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/pprof v0.0.0-20241009165004-a3522334989c h1:NDovD0SMpBYXlE1zJmS1q55vWB/fUQBcPAqAboZSccA= +github.com/google/pprof v0.0.0-20241009165004-a3522334989c/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/uuid v1.1.1/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= @@ -105,8 +117,6 @@ github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQ github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo= github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= -github.com/gookit/goutil v0.6.17 h1:SxmbDz2sn2V+O+xJjJhJT/sq1/kQh6rCJ7vLBiRPZjI= -github.com/gookit/goutil v0.6.17/go.mod h1:rSw1LchE1I3TDWITZvefoAC9tS09SFu3lHXLCV7EaEY= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -137,6 +147,7 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/kevinms/leakybucket-go v0.0.0-20200115003610-082473db97ca h1:qNtd6alRqd3qOdPrKXMZImV192ngQ0WSh1briEO33Tk= github.com/kevinms/leakybucket-go v0.0.0-20200115003610-082473db97ca/go.mod h1:ph+C5vpnCcQvKBwJwKLTK3JLNGnBXYlG7m7JjoC/zYA= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +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/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= @@ -176,8 +187,10 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/nbd-wtf/go-nostr v0.38.2 h1:8PP+U8dx81jVEL89k/xMAejAlDeSDJ9ywNiyOj82so8= -github.com/nbd-wtf/go-nostr v0.38.2/go.mod h1:TGKGj00BmJRXvRe0LlpDN3KKbELhhPXgBwUEhzu3Oq0= +github.com/mxschmitt/golang-combinations v1.2.0 h1:V5E7MncIK8Yr1SL/SpdqMuSquFsfoIs5auI7Y3n8z14= +github.com/mxschmitt/golang-combinations v1.2.0/go.mod h1:RCm5eR03B+JrBOMRDLsKZWShluXdrHu+qwhPEJ0miBM= +github.com/nbd-wtf/go-nostr v0.39.0 h1:C2s6+wUqOW1ngzIn10tTH8VOOJ9MWWg1rbJDGDbOIBU= +github.com/nbd-wtf/go-nostr v0.39.0/go.mod h1:UTFitPKazxHbGlFMh0QvB8MpOyuTViBU4Zf05Z1aUdk= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a h1:dlRvE5fWabOchtH7znfiFCcOvmIYgOeAS5ifBXBlh9Q= github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a/go.mod h1:hVoHR2EVESiICEMbg137etN/Lx+lSrHPTD39Z/uE+2s= @@ -192,6 +205,9 @@ github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeD github.com/panjf2000/ants/v2 v2.4.2/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OIhFxRNFr4A= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -220,8 +236,9 @@ github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqn github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -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/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/schollz/progressbar/v3 v3.16.1 h1:RnF1neWZFzLCoGx8yp1yF7SDl4AzNDI5y4I0aUJRrZQ= github.com/schollz/progressbar/v3 v3.16.1/go.mod h1:I2ILR76gz5VXqYMIY/LdLecvMHDPVcQm3W/MSKi1TME= @@ -271,6 +288,8 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/xssnick/tonutils-go v1.10.2 h1:1wgnQPrzbOt+5PtuNrlMSUyh1/y0pvWRi0zeRNRLEbw= github.com/xssnick/tonutils-go v1.10.2/go.mod h1:p1l1Bxdv9sz6x2jfbuGQUGJn6g5cqg7xsTp8rBHFoJY= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= @@ -280,18 +299,21 @@ gocv.io/x/gocv v0.25.0/go.mod h1:Rar2PS6DV+T4FL+PM535EImD/h13hGVaHhnCu1xarBs= golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4= golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= -golang.org/x/exp v0.0.0-20241004190924-225e2abe05e6 h1:1wqE9dj9NpSm04INVsJhhEUzhuDVjbcyKH91sVyPATw= -golang.org/x/exp v0.0.0-20241004190924-225e2abe05e6/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= +golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= +golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.10.0/go.mod h1:jtrku+n79PfroUbvDdeUWMAI+heR786BofxrbiSF+J0= golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s= golang.org/x/image v0.21.0/go.mod h1:vUbsLavqK/W303ZroQQVKQ+Af3Yl6Uz1Ppu5J/cLz78= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= @@ -299,6 +321,8 @@ golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -310,6 +334,8 @@ golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= @@ -361,11 +387,16 @@ golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= 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= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -381,8 +412,9 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/server/http/auth_nip98.go b/server/http/auth_nip98.go index b2af56c..677823d 100644 --- a/server/http/auth_nip98.go +++ b/server/http/auth_nip98.go @@ -7,8 +7,8 @@ import ( "net/url" "time" + "github.com/cockroachdb/errors" "github.com/gin-gonic/gin" - "github.com/gookit/goutil/errorx" "github.com/nbd-wtf/go-nostr" ) @@ -35,8 +35,8 @@ const ( ) var ( - ErrTokenExpired = errorx.New("expired token") - ErrTokenInvalid = errorx.New("invalid token") + ErrTokenExpired = errors.New("expired token") + ErrTokenInvalid = errors.New("invalid token") ) func NewAuth() AuthClient { @@ -46,20 +46,20 @@ func NewAuth() AuthClient { func (a *authNostr) VerifyToken(gCtx *gin.Context, token string) (Token, error) { bToken, err := base64.StdEncoding.DecodeString(token) if err != nil { - return nil, errorx.With(err, "failed to unmarshal auth token: malformed base64") + return nil, errors.Wrap(err, "failed to unmarshal auth token: malformed base64") } var event nostr.Event if err = event.UnmarshalJSON(bToken); err != nil { - return nil, errorx.With(err, "failed to unmarshal auth token: malformed event json") + return nil, errors.Wrap(err, "failed to unmarshal auth token: malformed event json") } var ok bool if ok, err = event.CheckSignature(); err != nil { - return nil, errorx.Withf(err, "invalid token signature") + return nil, errors.Wrapf(err, "invalid token signature") } else if !ok { - return nil, errorx.Wrapf(ErrTokenInvalid, "invalid token signature") + return nil, errors.Wrapf(ErrTokenInvalid, "invalid token signature") } if event.Kind != nostrHttpAuthKind { - return nil, errorx.Wrapf(ErrTokenInvalid, "invalid token event kind %v", event.Kind) + return nil, errors.Wrapf(ErrTokenInvalid, "invalid token event kind %v", event.Kind) } now := time.Now() if event.CreatedAt.Time().After(now) || (event.CreatedAt.Time().Before(now) && now.Sub(event.CreatedAt.Time()) > tokenExpirationWindow) { @@ -69,7 +69,7 @@ func (a *authNostr) VerifyToken(gCtx *gin.Context, token string) (Token, error) var urlValue *url.URL urlValue, err = url.Parse(urlTag.Value()) if err != nil { - return nil, errorx.Wrapf(ErrTokenInvalid, "failed to parse url tag with %v", urlTag.Value()) + return nil, errors.Wrapf(ErrTokenInvalid, "failed to parse url tag with %v", urlTag.Value()) } fullReqUrl := (&url.URL{ Scheme: "https", @@ -79,18 +79,18 @@ func (a *authNostr) VerifyToken(gCtx *gin.Context, token string) (Token, error) Fragment: gCtx.Request.URL.Fragment, }) if urlValue.String() != fullReqUrl.String() { - return nil, errorx.Wrapf(ErrTokenInvalid, "url mismatch token>%v url>%v", urlValue, fullReqUrl) + return nil, errors.Wrapf(ErrTokenInvalid, "url mismatch token>%v url>%v", urlValue, fullReqUrl) } } else { - return nil, errorx.Wrapf(ErrTokenInvalid, "malformed u tag %v", urlTag) + return nil, errors.Wrapf(ErrTokenInvalid, "malformed u tag %v", urlTag) } if methodTag := event.Tags.GetFirst([]string{"method"}); methodTag != nil && len(*methodTag) > 1 { method := methodTag.Value() if method != gCtx.Request.Method { - return nil, errorx.Wrapf(ErrTokenInvalid, "method mismatch token>%v url>%v", method, gCtx.Request.Method) + return nil, errors.Wrapf(ErrTokenInvalid, "method mismatch token>%v url>%v", method, gCtx.Request.Method) } } else { - return nil, errorx.Wrapf(ErrTokenInvalid, "malformed method tag %v", methodTag) + return nil, errors.Wrapf(ErrTokenInvalid, "malformed method tag %v", methodTag) } expectedHash := "" if payloadTag := event.Tags.GetFirst([]string{"payload"}); payloadTag != nil && len(*payloadTag) > 1 { diff --git a/server/http/storage_nip96.go b/server/http/storage_nip96.go index d121a64..9dc8cba 100644 --- a/server/http/storage_nip96.go +++ b/server/http/storage_nip96.go @@ -7,7 +7,6 @@ import ( "crypto/sha256" _ "embed" "encoding/hex" - "errors" "fmt" "io" "log" @@ -19,10 +18,10 @@ import ( "strings" "time" + "github.com/cockroachdb/errors" gomime "github.com/cubewise-code/go-mime" "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" - "github.com/gookit/goutil/errorx" "github.com/nbd-wtf/go-nostr" "github.com/ice-blockchain/subzero/storage" @@ -97,13 +96,13 @@ func (s *storageHandler) Upload() gin.HandlerFunc { authHeader := strings.TrimPrefix(gCtx.GetHeader("Authorization"), "Nostr ") token, authErr := s.auth.VerifyToken(gCtx, authHeader) if authErr != nil { - log.Printf("ERROR: endpoint authentification failed: %v", errorx.With(authErr, "endpoint authentification failed")) + log.Printf("ERROR: endpoint authentification failed: %v", errors.Wrap(authErr, "endpoint authentification failed")) gCtx.JSON(http.StatusUnauthorized, uploadErr("Unauthorized")) return } var upload fileUpload if err := gCtx.ShouldBindWith(&upload, binding.FormMultipart); err != nil { - log.Printf("ERROR: failed to bind multipart form: %v", errorx.With(err, "failed to bind multipart form")) + log.Printf("ERROR: failed to bind multipart form: %v", errors.Wrap(err, "failed to bind multipart form")) gCtx.JSON(http.StatusBadRequest, uploadErr("invalid multipart data")) return } @@ -127,39 +126,39 @@ func (s *storageHandler) Upload() gin.HandlerFunc { uploadingFilePath := filepath.Join(storagePath, fileType, upload.File.Filename) relativePath := filepath.Join(fileType, upload.File.Filename) if err := os.MkdirAll(filepath.Dir(uploadingFilePath), 0o755); err != nil { - log.Printf("ERROR: %v", errorx.With(err, "failed to open temp file while processing upload")) + log.Printf("ERROR: %v", errors.Wrap(err, "failed to open temp file while processing upload")) gCtx.JSON(http.StatusInternalServerError, uploadErr("failed to open temporary file")) return } fileUploadTo, err := os.Create(uploadingFilePath) if err != nil { - log.Printf("ERROR: %v", errorx.With(err, "failed to open temp file while processing upload")) + log.Printf("ERROR: %v", errors.Wrap(err, "failed to open temp file while processing upload")) gCtx.JSON(http.StatusInternalServerError, uploadErr("failed to open temporary file")) return } defer fileUploadTo.Close() mpFile, err := upload.File.Open() if err != nil { - log.Printf("ERROR: %v", errorx.With(err, "failed to open upload file")) + log.Printf("ERROR: %v", errors.Wrap(err, "failed to open upload file")) gCtx.JSON(http.StatusInternalServerError, uploadErr("failed to open upload file")) return } defer mpFile.Close() hashCalc := sha256.New() if _, err = io.Copy(fileUploadTo, io.TeeReader(mpFile, hashCalc)); err != nil { - log.Printf("ERROR: %v", errorx.With(err, "failed to copy temp file while processing upload")) + log.Printf("ERROR: %v", errors.Wrap(err, "failed to copy temp file while processing upload")) gCtx.JSON(http.StatusBadRequest, uploadErr("failed to store temporary file")) return } if err = fileUploadTo.Sync(); err != nil { - log.Printf("ERROR: %v", errorx.With(err, "failed to copy temp file while processing upload")) + log.Printf("ERROR: %v", errors.Wrap(err, "failed to copy temp file while processing upload")) gCtx.JSON(http.StatusBadRequest, uploadErr("failed to store temporary file")) return } hash := hashCalc.Sum(nil) hashHex := hex.EncodeToString(hash) if hashHex != token.ExpectedHash() { - log.Printf("ERROR: endpoint authentification failed: %v", errorx.Errorf("payload hash mismatch actual>%v token>%v", hashHex, token.ExpectedHash())) + log.Printf("ERROR: endpoint authentification failed: %v", errors.Errorf("payload hash mismatch actual>%v token>%v", hashHex, token.ExpectedHash())) gCtx.JSON(http.StatusForbidden, uploadErr("Unauthorized")) os.Remove(uploadingFilePath) return @@ -173,7 +172,7 @@ func (s *storageHandler) Upload() gin.HandlerFunc { }) if err != nil { - log.Printf("ERROR: failed to upload file: %v", errorx.With(err, "failed to upload file to ion storage")) + log.Printf("ERROR: failed to upload file: %v", errors.Wrap(err, "failed to upload file to ion storage")) gCtx.JSON(http.StatusInternalServerError, uploadErr("oops, error occured!")) return } @@ -208,7 +207,7 @@ func (s *storageHandler) Download() gin.HandlerFunc { authHeader := strings.TrimPrefix(gCtx.GetHeader("Authorization"), "Nostr ") token, authErr := s.auth.VerifyToken(gCtx, authHeader) if authErr != nil { - log.Printf("ERROR: endpoint authentification failed: %v", errorx.With(authErr, "endpoint authentification failed")) + log.Printf("ERROR: endpoint authentification failed: %v", errors.Wrap(authErr, "endpoint authentification failed")) gCtx.JSON(http.StatusUnauthorized, uploadErr("Unauthorized")) return } @@ -223,7 +222,7 @@ func (s *storageHandler) Download() gin.HandlerFunc { gCtx.Status(http.StatusNotFound) return } - log.Printf("ERROR: %v", errorx.With(err, "failed to build download url")) + log.Printf("ERROR: %v", errors.Wrap(err, "failed to build download url")) gCtx.JSON(http.StatusInternalServerError, uploadErr("oops, error occured!")) return } @@ -235,7 +234,7 @@ func (s *storageHandler) Delete() gin.HandlerFunc { authHeader := strings.TrimPrefix(gCtx.GetHeader("Authorization"), "Nostr ") token, authErr := s.auth.VerifyToken(gCtx, authHeader) if authErr != nil { - log.Printf("ERROR: endpoint authentification failed: %v", errorx.With(authErr, "endpoint authentification failed")) + log.Printf("ERROR: endpoint authentification failed: %v", errors.Wrap(authErr, "endpoint authentification failed")) gCtx.JSON(http.StatusUnauthorized, uploadErr("Unauthorized")) return } @@ -245,7 +244,7 @@ func (s *storageHandler) Delete() gin.HandlerFunc { return } if err := s.storageClient.Delete(token.PubKey(), file); err != nil { - log.Printf("ERROR: %v", errorx.With(err, "failed to delete file")) + log.Printf("ERROR: %v", errors.Wrap(err, "failed to delete file")) if errors.Is(err, storage.ErrNotFound) { gCtx.JSON(http.StatusForbidden, uploadErr("user do not own file")) return @@ -262,7 +261,7 @@ func (s *storageHandler) ListFiles() gin.HandlerFunc { authHeader := strings.TrimPrefix(gCtx.GetHeader("Authorization"), "Nostr ") token, authErr := s.auth.VerifyToken(gCtx, authHeader) if authErr != nil { - log.Printf("ERROR: endpoint authentification failed: %v", errorx.With(authErr, "endpoint authentification failed")) + log.Printf("ERROR: endpoint authentification failed: %v", errors.Wrap(authErr, "endpoint authentification failed")) gCtx.JSON(http.StatusUnauthorized, uploadErr("Unauthorized")) return } @@ -271,7 +270,7 @@ func (s *storageHandler) ListFiles() gin.HandlerFunc { Count uint32 `form:"count"` } if err := gCtx.ShouldBindWith(¶ms, binding.Query); err != nil { - log.Printf("ERROR: failed to bind data : %v", errorx.With(err, "failed to bind data")) + log.Printf("ERROR: failed to bind data : %v", errors.Wrap(err, "failed to bind data")) gCtx.JSON(http.StatusBadRequest, uploadErr("invalid data")) return } @@ -280,7 +279,7 @@ func (s *storageHandler) ListFiles() gin.HandlerFunc { } total, filesList, err := s.storageClient.ListFiles(token.PubKey(), params.Page, params.Count) if err != nil { - log.Printf("ERROR: %v", errorx.Withf(err, "failed to list files for user %v", token.PubKey())) + log.Printf("ERROR: %v", errors.Wrapf(err, "failed to list files for user %v", token.PubKey())) gCtx.JSON(http.StatusInternalServerError, uploadErr("oops, error occured!")) return } diff --git a/server/http/storage_nip_96_test.go b/server/http/storage_nip_96_test.go index c67f51c..c284271 100644 --- a/server/http/storage_nip_96_test.go +++ b/server/http/storage_nip_96_test.go @@ -23,8 +23,8 @@ import ( "testing" "time" + "github.com/cockroachdb/errors" gomime "github.com/cubewise-code/go-mime" - "github.com/gookit/goutil/errorx" "github.com/jamiealquiza/tachymeter" "github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr/nip94" @@ -304,11 +304,11 @@ func initStorage(ctx context.Context, path string) { http.DefaultClient.Transport = http.DefaultTransport _, nodeKey, err := ed25519.GenerateKey(rand.Reader) if err != nil { - log.Panic(errorx.Wrapf(err, "failed to generate node key")) + log.Panic(errors.Wrapf(err, "failed to generate node key")) } storagePort, err := rand.Int(rand.Reader, big.NewInt(63500)) if err != nil { - log.Panic(errorx.Wrapf(err, "failed to generate port number")) + log.Panic(errors.Wrapf(err, "failed to generate port number")) } wd, _ := os.Getwd() rootStorage := filepath.Join(wd, path) diff --git a/storage/download.go b/storage/download.go index ff5cce1..25e7542 100644 --- a/storage/download.go +++ b/storage/download.go @@ -11,7 +11,7 @@ import ( "net/url" "time" - "github.com/gookit/goutil/errorx" + "github.com/cockroachdb/errors" "github.com/xssnick/tonutils-storage/storage" "github.com/ice-blockchain/subzero/model" @@ -20,15 +20,15 @@ import ( func (c *client) DownloadUrl(userPubkey string, fileHash string) (string, error) { bag, err := c.bagByUser(userPubkey) if err != nil { - return "", errorx.Withf(err, "failed to get bagID for the user %v", userPubkey) + return "", errors.Wrapf(err, "failed to get bagID for the user %v", userPubkey) } bs, err := c.buildBootstrapNodeInfo(bag) if err != nil { - return "", errorx.Withf(err, "failed to build bootstap for bag %v", hex.EncodeToString(bag.BagID)) + return "", errors.Wrapf(err, "failed to build bootstap for bag %v", hex.EncodeToString(bag.BagID)) } file, err := c.detectFile(bag, fileHash) if err != nil { - return "", errorx.Withf(err, "failed to detect file %v in bag %v", fileHash, hex.EncodeToString(bag.BagID)) + return "", errors.Wrapf(err, "failed to detect file %v in bag %v", fileHash, hex.EncodeToString(bag.BagID)) } return c.buildUrl(hex.EncodeToString(bag.BagID), file, []*Bootstrap{bs}) } @@ -40,19 +40,19 @@ func acceptNewBag(ctx context.Context, event *model.Event) error { if iTag := event.Tags.GetFirst([]string{"i"}); iTag != nil && len(*iTag) > 1 { infohash = iTag.Value() } else { - return errorx.Newf("malformed i tag %v", iTag) + return errors.Newf("malformed i tag %v", iTag) } if urlTag := event.Tags.GetFirst([]string{"url"}); urlTag != nil && len(*urlTag) > 1 { fileUrl, err = url.Parse(urlTag.Value()) if err != nil { - return errorx.Withf(err, "malformed url in url tag %v", *urlTag) + return errors.Wrapf(err, "malformed url in url tag %v", *urlTag) } } else { - return errorx.Newf("malformed url tag %v", urlTag) + return errors.Newf("malformed url tag %v", urlTag) } bootstrap := fileUrl.Query().Get("bootstrap") if err = globalClient.newBagIDPromoted(ctx, event.PubKey, infohash, &bootstrap); err != nil { - return errorx.Withf(err, "failed to promote new bag ID %v for user %v", infohash, event.PubKey) + return errors.Wrapf(err, "failed to promote new bag ID %v for user %v", infohash, event.PubKey) } return nil } @@ -60,16 +60,16 @@ func acceptNewBag(ctx context.Context, event *model.Event) error { func (c *client) newBagIDPromoted(ctx context.Context, user, bagID string, bootstap *string) error { existingBagForUser, err := c.bagByUser(user) if err != nil { - return errorx.Withf(err, "failed to find existing bag for user %s", user) + return errors.Wrapf(err, "failed to find existing bag for user %s", user) } if existingBagForUser != nil { existingBagForUser.Stop() if err = c.progressStorage.RemoveTorrent(existingBagForUser, false); err != nil { - return errorx.Withf(err, "failed to replace bag for user %s", user) + return errors.Wrapf(err, "failed to replace bag for user %s", user) } } if err = c.download(ctx, bagID, user, bootstap); err != nil { - return errorx.Withf(err, "failed to download new bag ID %v for user %v", bagID, user) + return errors.Wrapf(err, "failed to download new bag ID %v for user %v", bagID, user) } return nil } @@ -77,10 +77,10 @@ func (c *client) newBagIDPromoted(ctx context.Context, user, bagID string, boots func (c *client) download(ctx context.Context, bagID, user string, bootstrap *string) (err error) { bag, err := hex.DecodeString(bagID) if err != nil { - return errorx.Withf(err, "invalid bagID %v", bagID) + return errors.Wrapf(err, "invalid bagID %v", bagID) } if len(bag) != 32 { - return errorx.Withf(err, "invalid bagID %v, should be len 32", bagID) + return errors.Wrapf(err, "invalid bagID %v, should be len 32", bagID) } tor := c.progressStorage.GetTorrent(bag) @@ -93,11 +93,11 @@ func (c *client) download(ctx context.Context, bagID, user string, bootstrap *st user: &user, } if err = c.saveTorrent(tor, &user, bootstrap); err != nil { - return errorx.Withf(err, "failed to store new torrent %v", bagID) + return errors.Wrapf(err, "failed to store new torrent %v", bagID) } } else { if err = tor.Start(true, true, false); err != nil { - return errorx.Withf(err, "failed to start existing torrent %v", bagID) + return errors.Wrapf(err, "failed to start existing torrent %v", bagID) } } return nil @@ -132,21 +132,21 @@ func (c *client) torrentStateCallback(tor *storage.Torrent, user *string) func(e func (c *client) connectToBootstrap(ctx context.Context, torrent *storage.Torrent, bootstrap string) error { b64, err := base64.StdEncoding.DecodeString(bootstrap) if err != nil { - return errorx.Withf(err, "failed to decode bootstrap %v", bootstrap) + return errors.Wrapf(err, "failed to decode bootstrap %v", bootstrap) } var bootstraps []Bootstrap if err = json.Unmarshal(b64, &bootstraps); err != nil { - return errorx.Withf(err, "failed to decode bootstrap %v", string(b64)) + return errors.Wrapf(err, "failed to decode bootstrap %v", string(b64)) } for _, bs := range bootstraps { pk := bs.Overlay.ID.(map[string]any)["Key"].(string) var pubKey []byte pubKey, err = base64.StdEncoding.DecodeString(pk) if err != nil { - return errorx.Withf(err, "failed to decode bootstrap %v, invalid pubkey %v", string(b64), string(pk)) + return errors.Wrapf(err, "failed to decode bootstrap %v, invalid pubkey %v", string(b64), string(pk)) } if err = c.server.ConnectToNode(ctx, torrent, bs.Overlay, bs.DHT.AddrList, pubKey); err != nil { - return errorx.Withf(err, "failed to connect to bootstrap node %#v", bs.DHT.AddrList.Addresses[0]) + return errors.Wrapf(err, "failed to connect to bootstrap node %#v", bs.DHT.AddrList.Addresses[0]) } } return nil @@ -154,14 +154,14 @@ func (c *client) connectToBootstrap(ctx context.Context, torrent *storage.Torren func (c *client) saveTorrent(tr *storage.Torrent, userPubKey *string, bs *string) error { if err := c.progressStorage.SetTorrent(tr); err != nil { - return errorx.With(err, "failed to save torrent into storage") + return errors.Wrap(err, "failed to save torrent into storage") } if userPubKey != nil { k := make([]byte, 3+64) copy(k, "ub:") copy(k[3:], *userPubKey) if err := c.db.Put(k, tr.BagID, nil); err != nil { - return errorx.Withf(err, "failed to save userID:bag mapping for bag %v", hex.EncodeToString(tr.BagID)) + return errors.Wrapf(err, "failed to save userID:bag mapping for bag %v", hex.EncodeToString(tr.BagID)) } } if bs != nil { @@ -169,7 +169,7 @@ func (c *client) saveTorrent(tr *storage.Torrent, userPubKey *string, bs *string copy(k, "bs:") copy(k[3:], tr.BagID) if err := c.db.Put(k, []byte(*bs), nil); err != nil { - return errorx.Withf(err, "failed to save bootstrap node for bag %v", hex.EncodeToString(tr.BagID)) + return errors.Wrapf(err, "failed to save bootstrap node for bag %v", hex.EncodeToString(tr.BagID)) } } @@ -190,7 +190,7 @@ outerLoop: continue } if err := tor.StartWithCallback(false, true, false, c.torrentStateCallback(tor, q.user)); err != nil { - log.Printf("ERROR: %v", errorx.Withf(err, "failed to start new torrent %v", q.tor.BagID)) + log.Printf("ERROR: %v", errors.Wrapf(err, "failed to start new torrent %v", q.tor.BagID)) } if q.bootstrap != nil && *q.bootstrap != "" { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) diff --git a/storage/fixture/fixture.go b/storage/fixture/fixture.go index 2244d8e..d6e406a 100644 --- a/storage/fixture/fixture.go +++ b/storage/fixture/fixture.go @@ -6,13 +6,12 @@ import ( "context" "crypto/sha256" "encoding/hex" - "errors" + "github.com/cockroachdb/errors" "io" "os" "time" "github.com/fsnotify/fsnotify" - "github.com/gookit/goutil/errorx" ) func WaitForFile(ctx context.Context, watchPath, expectedPath, expectedHash string, expectedSize int64) (hash string, err error) { @@ -26,18 +25,18 @@ func WaitForFile(ctx context.Context, watchPath, expectedPath, expectedHash stri } if !skipWatch { if err = watchFile(ctx, watchPath, expectedPath, expectedSize); err != nil { - return "", errorx.Withf(err, "failed to monitor file %v", expectedPath) + return "", errors.Wrapf(err, "failed to monitor file %v", expectedPath) } } f, err := os.Open(expectedPath) if err != nil { - return "", errorx.Withf(err, "failed to open %v to check hash", expectedPath) + return "", errors.Wrapf(err, "failed to open %v to check hash", expectedPath) } defer f.Close() hashCalc := sha256.New() var n int64 if n, err = io.Copy(hashCalc, f); err != nil { - return "", errorx.Withf(err, "failed to calc hash of %v", expectedPath) + return "", errors.Wrapf(err, "failed to calc hash of %v", expectedPath) } if n != expectedSize { return WaitForFile(ctx, watchPath, expectedPath, expectedHash, expectedSize) @@ -52,12 +51,12 @@ func WaitForFile(ctx context.Context, watchPath, expectedPath, expectedHash stri func watchFile(ctx context.Context, monitorPath, expectedPath string, expectedSize int64) error { watcher, err := fsnotify.NewWatcher() if err != nil { - return errorx.Withf(err, "failed to create fsnotify watcher %s", expectedPath) + return errors.Wrapf(err, "failed to create fsnotify watcher %s", expectedPath) } defer watcher.Close() err = watcher.Add(monitorPath) if err != nil { - return errorx.Withf(err, "failed to monitor expectedPath %s", expectedPath) + return errors.Wrapf(err, "failed to monitor expectedPath %s", expectedPath) } loop: for { @@ -66,7 +65,7 @@ loop: if event.Op == fsnotify.Write && event.Name == expectedPath { fileInfo, err := os.Stat(expectedPath) if err != nil { - return errorx.Withf(err, "failed to stat file %s", expectedPath) + return errors.Wrapf(err, "failed to stat file %s", expectedPath) } if fileInfo.Size() == expectedSize { break loop @@ -78,13 +77,13 @@ loop: if errors.Is(err, os.ErrNotExist) { continue loop } - return errorx.Withf(err, "failed to stat file %s", expectedPath) + return errors.Wrapf(err, "failed to stat file %s", expectedPath) } if fileInfo.Size() == expectedSize { break loop } case err = <-watcher.Errors: - return errorx.Withf(err, "got error from fsnotify") + return errors.Wrapf(err, "got error from fsnotify") case <-ctx.Done(): return errors.New("timeout") } diff --git a/storage/global.go b/storage/global.go index 94ffbe6..f81ace8 100644 --- a/storage/global.go +++ b/storage/global.go @@ -6,7 +6,6 @@ import ( "context" "crypto/ed25519" "encoding/hex" - "errors" "fmt" "log" "net" @@ -18,7 +17,7 @@ import ( "sync" "time" - "github.com/gookit/goutil/errorx" + "github.com/cockroachdb/errors" "github.com/nbd-wtf/go-nostr" "github.com/syndtr/goleveldb/leveldb" ldbstorage "github.com/syndtr/goleveldb/leveldb/storage" @@ -68,7 +67,7 @@ func AcceptEvent(ctx context.Context, event *model.Event) error { func acceptDeletion(ctx context.Context, event *model.Event) error { refs, err := model.ParseEventReference(event.Tags) if err != nil { - return errorx.Withf(err, "failed to detect events for delete") + return errors.Wrapf(err, "failed to detect events for delete") } filters := model.Filters{} for _, r := range refs { @@ -78,13 +77,13 @@ func acceptDeletion(ctx context.Context, event *model.Event) error { var originalEvent *model.Event for fileEvent, err := range events { if err != nil { - return errorx.Withf(err, "failed to query referenced deletion file event") + return errors.Wrapf(err, "failed to query referenced deletion file event") } if fileEvent.Kind != nostr.KindFileMetadata { - return errorx.Errorf("event mismatch: event %v is %v not file metadata (%v)", fileEvent.ID, fileEvent.Kind, nostr.KindFileMetadata) + return errors.Errorf("event mismatch: event %v is %v not file metadata (%v)", fileEvent.ID, fileEvent.Kind, nostr.KindFileMetadata) } if fileEvent.PubKey != event.PubKey { - return errorx.Errorf("user mismatch: event %v is signed by %v not %v", fileEvent.ID, fileEvent.PubKey, event.PubKey) + return errors.Errorf("user mismatch: event %v is signed by %v not %v", fileEvent.ID, fileEvent.PubKey, event.PubKey) } originalEvent = fileEvent break @@ -96,22 +95,22 @@ func acceptDeletion(ctx context.Context, event *model.Event) error { if xTag := originalEvent.Tags.GetFirst([]string{"x"}); xTag != nil && len(*xTag) > 1 { fileHash = xTag.Value() } else { - return errorx.Errorf("malformed x tag in event %v", originalEvent.ID) + return errors.Errorf("malformed x tag in event %v", originalEvent.ID) } bag, err := globalClient.bagByUser(event.PubKey) if err != nil { - return errorx.Withf(err, "failed to get bagID for the user %v", event.PubKey) + return errors.Wrapf(err, "failed to get bagID for the user %v", event.PubKey) } file, err := globalClient.detectFile(bag, fileHash) if err != nil { - return errorx.Withf(err, "failed to detect file %v in bag %v", fileHash, hex.EncodeToString(bag.BagID)) + return errors.Wrapf(err, "failed to detect file %v in bag %v", fileHash, hex.EncodeToString(bag.BagID)) } userRoot, _ := globalClient.BuildUserPath(event.PubKey, "") if err := os.Remove(filepath.Join(userRoot, file)); err != nil && !errors.Is(err, os.ErrNotExist) { - return errorx.Withf(err, "failed to delete file %v", file) + return errors.Wrapf(err, "failed to delete file %v", file) } if _, _, _, err := globalClient.StartUpload(ctx, event.PubKey, file, fileHash, nil); err != nil { - return errorx.Withf(err, "failed to rebuild bag with deleted file") + return errors.Wrapf(err, "failed to rebuild bag with deleted file") } return nil } @@ -129,45 +128,45 @@ func mustInit(ctx context.Context, nodeKey ed25519.PrivateKey, tonConfigUrl, roo var lsCfg *liteclient.GlobalConfig u, err := url.Parse(tonConfigUrl) if err != nil { - log.Panic(errorx.Wrapf(err, "invalid ton config url: %v", tonConfigUrl)) + log.Panic(errors.Wrapf(err, "invalid ton config url: %v", tonConfigUrl)) } if u.Scheme == "file" { lsCfg, err = liteclient.GetConfigFromFile(u.Path) if err != nil { - log.Panic(errorx.Wrapf(err, "failed to load ton network config from file: %v", u.Path)) + log.Panic(errors.Wrapf(err, "failed to load ton network config from file: %v", u.Path)) } } else { downloadConfigCtx, cancelDownloadConfig := context.WithTimeout(ctx, 30*time.Second) defer cancelDownloadConfig() lsCfg, err = liteclient.GetConfigFromUrl(downloadConfigCtx, tonConfigUrl) if err != nil { - log.Panic(errorx.Wrapf(err, "failed to load ton network config from url: %v", u.String())) + log.Panic(errors.Wrapf(err, "failed to load ton network config from url: %v", u.String())) } } gate := adnl.NewGateway(nodeKey) gate.SetExternalIP(externalAddress) if err = gate.StartServer(fmt.Sprintf(":%v", port)); err != nil { - log.Panic(errorx.Wrapf(err, "failed to start adnl gateway")) + log.Panic(errors.Wrapf(err, "failed to start adnl gateway")) } dhtGate := adnl.NewGateway(nodeKey) if err = dhtGate.StartClient(); err != nil { - log.Panic(errorx.Wrapf(err, "failed to start dht")) + log.Panic(errors.Wrapf(err, "failed to start dht")) } dhtClient, err := dht.NewClientFromConfig(dhtGate, lsCfg) if err != nil { - log.Panic(errorx.Wrapf(err, "failed to create dht client")) + log.Panic(errors.Wrapf(err, "failed to create dht client")) } srv := storage.NewServer(dhtClient, gate, nodeKey, true) conn := storage.NewConnector(srv) fStorage, err := ldbstorage.OpenFile(filepath.Join(rootStorage, "db"), false) if err != nil { - log.Panic(errorx.Wrapf(err, "failed to open leveldb storage %v", filepath.Join(rootStorage, "db"))) + log.Panic(errors.Wrapf(err, "failed to open leveldb storage %v", filepath.Join(rootStorage, "db"))) } progressDb, err := leveldb.Open(fStorage, nil) if err != nil { - log.Panic(errorx.Wrapf(err, "failed to open leveldb")) + log.Panic(errors.Wrapf(err, "failed to open leveldb")) } cl := &client{ conn: conn, @@ -207,7 +206,7 @@ func mustInit(ctx context.Context, nodeKey ed25519.PrivateKey, tonConfigUrl, roo }() progressStorage, err := db.NewStorage(progressDb, conn, true, loadMonitoringCh) if err != nil { - log.Panic(errorx.Wrapf(err, "failed to open storage")) + log.Panic(errors.Wrapf(err, "failed to open storage")) } cl.progressStorage = progressStorage cl.server.SetStorage(progressStorage) diff --git a/storage/statistics/metadata/image.go b/storage/statistics/metadata/image.go index 61246d7..f9963f7 100644 --- a/storage/statistics/metadata/image.go +++ b/storage/statistics/metadata/image.go @@ -3,10 +3,10 @@ package metadata import ( + "github.com/cockroachdb/errors" "path/filepath" "github.com/davidbyttow/govips/v2/vips" - "github.com/gookit/goutil/errorx" ) type imageMetaExtractor struct{} @@ -25,7 +25,7 @@ func (i *imageMetaExtractor) Extract(filePath, _ string, size uint64) (*Metadata ext := filepath.Ext(filePath) im, err := vips.LoadImageFromFile(filePath, nil) if err != nil { - return nil, errorx.Withf(err, "failed to load image %v", filePath) + return nil, errors.Wrapf(err, "failed to load image %v", filePath) } defer im.Close() return &Metadata{ diff --git a/storage/statistics/metadata/metadata.go b/storage/statistics/metadata/metadata.go index 38db6f9..35c88e7 100644 --- a/storage/statistics/metadata/metadata.go +++ b/storage/statistics/metadata/metadata.go @@ -3,10 +3,10 @@ package metadata import ( + "github.com/cockroachdb/errors" "io" "strings" - "github.com/gookit/goutil/errorx" "github.com/hashicorp/go-multierror" ) @@ -43,12 +43,12 @@ func (e *extractor) Close() error { var mErr *multierror.Error for k, ex := range e.extractorsByFileType { if clErr := ex.Close(); clErr != nil { - mErr = multierror.Append(mErr, errorx.Withf(clErr, "failed to close %v meta extractor", k)) + mErr = multierror.Append(mErr, errors.Wrapf(clErr, "failed to close %v meta extractor", k)) } } if err := e.generic.Close(); err != nil { - mErr = multierror.Append(mErr, errorx.Withf(err, "failed to close generic meta extractor")) + mErr = multierror.Append(mErr, errors.Wrapf(err, "failed to close generic meta extractor")) } return mErr.ErrorOrNil() } diff --git a/storage/statistics/metadata/video.go b/storage/statistics/metadata/video.go index 3c6e443..53c6802 100644 --- a/storage/statistics/metadata/video.go +++ b/storage/statistics/metadata/video.go @@ -8,7 +8,7 @@ import ( "path/filepath" "strings" - "github.com/gookit/goutil/errorx" + "github.com/cockroachdb/errors" ffmpeg "github.com/u2takey/ffmpeg-go" ) @@ -92,11 +92,11 @@ func (v *videoMetaExtractor) Close() error { func (v *videoMetaExtractor) Extract(filePath, _ string, size uint64) (*Metadata, error) { res, err := ffmpeg.Probe(filePath) if err != nil { - return nil, errorx.Withf(err, "failed to fetch video metadata from %s", filePath) + return nil, errors.Wrapf(err, "failed to fetch video metadata from %s", filePath) } var md VideoMetadata if err = json.Unmarshal([]byte(res), &md); err != nil { - return nil, errorx.Withf(err, "failed to unmarshal video metadata from %s", filePath) + return nil, errors.Wrapf(err, "failed to unmarshal video metadata from %s", filePath) } ext := filepath.Ext(filePath) return &Metadata{ @@ -110,13 +110,13 @@ func newVideoExtractor() Extractor { res, err := ffmpeg.Probe("") if err != nil || !strings.Contains(res, "You have to specify one input file") { if err == nil { - err = errorx.New(res) + err = errors.New(res) } if strings.Contains(err.Error(), "ffprobe version ") { err = nil } if err != nil { - log.Panic(errorx.Withf(err, "failed to call ffprobe, is ffmpeg installed?")) + log.Panic(errors.Wrapf(err, "failed to call ffprobe, is ffmpeg installed?")) } } return &videoMetaExtractor{} diff --git a/storage/statistics/statistics.go b/storage/statistics/statistics.go index 394c402..03fc5bb 100644 --- a/storage/statistics/statistics.go +++ b/storage/statistics/statistics.go @@ -4,17 +4,15 @@ package statistics import ( "fmt" + "github.com/cockroachdb/errors" + "github.com/ice-blockchain/subzero/storage/statistics/metadata" + "github.com/rcrowley/go-metrics" "io" "log" "os" "path/filepath" "strconv" "time" - - "github.com/gookit/goutil/errorx" - "github.com/rcrowley/go-metrics" - - "github.com/ice-blockchain/subzero/storage/statistics/metadata" ) type ( @@ -58,28 +56,28 @@ func NewStatistics(rootStorageDir string, debug bool) Statistics { rootStorageDir: rootStorageDir, } if err := s.metrics.Register(imageWidth, metrics.NewHistogram(metrics.NewExpDecaySample(10000, 0.15))); err != nil { - log.Panic(errorx.Withf(err, "failed to register metric %v", imageWidth)) + log.Panic(errors.Wrapf(err, "failed to register metric %v", imageWidth)) } if err := s.metrics.Register(imageHeight, metrics.NewHistogram(metrics.NewExpDecaySample(10000, 0.15))); err != nil { - log.Panic(errorx.Withf(err, "failed to register metric %v", imageHeight)) + log.Panic(errors.Wrapf(err, "failed to register metric %v", imageHeight)) } if err := s.metrics.Register(videoWidth, metrics.NewHistogram(metrics.NewExpDecaySample(10000, 0.15))); err != nil { - log.Panic(errorx.Withf(err, "failed to register metric %v", videoWidth)) + log.Panic(errors.Wrapf(err, "failed to register metric %v", videoWidth)) } if err := s.metrics.Register(videoHeight, metrics.NewHistogram(metrics.NewExpDecaySample(10000, 0.15))); err != nil { - log.Panic(errorx.Withf(err, "failed to register metric %v", videoHeight)) + log.Panic(errors.Wrapf(err, "failed to register metric %v", videoHeight)) } if err := s.metrics.Register(duration, metrics.NewHistogram(metrics.NewExpDecaySample(10000, 0.15))); err != nil { - log.Panic(errorx.Withf(err, "failed to register metric %v", duration)) + log.Panic(errors.Wrapf(err, "failed to register metric %v", duration)) } if err := s.metrics.Register(videoBitrate, metrics.NewHistogram(metrics.NewExpDecaySample(10000, 0.15))); err != nil { - log.Panic(errorx.Withf(err, "failed to register metric %v", videoBitrate)) + log.Panic(errors.Wrapf(err, "failed to register metric %v", videoBitrate)) } if err := s.metrics.Register(audioBitrate, metrics.NewHistogram(metrics.NewExpDecaySample(10000, 0.15))); err != nil { - log.Panic(errorx.Withf(err, "failed to register metric %v", audioBitrate)) + log.Panic(errors.Wrapf(err, "failed to register metric %v", audioBitrate)) } if err := s.metrics.Register(fileSize, metrics.NewHistogram(metrics.NewExpDecaySample(10000, 0.15))); err != nil { - log.Panic(errorx.Withf(err, "failed to register metric %v", fileSize)) + log.Panic(errors.Wrapf(err, "failed to register metric %v", fileSize)) } go func() { for _ = range time.Tick(60 * time.Second) { @@ -92,7 +90,7 @@ func NewStatistics(rootStorageDir string, debug bool) Statistics { func (s *statistics) writeJSON() { statsFile, err := os.OpenFile(filepath.Join(s.rootStorageDir, "stats.json"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755) if err != nil { - log.Printf("ERROR: %v", errorx.Withf(err, "failed to open file for stats collection")) + log.Printf("ERROR: %v", errors.Wrapf(err, "failed to open file for stats collection")) } defer func() { statsFile.Sync() @@ -105,7 +103,7 @@ func (s *statistics) writeJSON() { func (s *statistics) Close() error { s.writeJSON() if err := s.metaExtractor.Close(); err != nil { - return errorx.Withf(err, "failed to close metadata extractors") + return errors.Wrapf(err, "failed to close metadata extractors") } return nil diff --git a/storage/storage.go b/storage/storage.go index fad07e9..b9fa0ae 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -6,7 +6,6 @@ import ( "context" "encoding/hex" "encoding/json" - "errors" "io" "os" "path/filepath" @@ -15,8 +14,8 @@ import ( "sync" "time" + "github.com/cockroachdb/errors" gomime "github.com/cubewise-code/go-mime" - "github.com/gookit/goutil/errorx" "github.com/hashicorp/go-multierror" "github.com/nbd-wtf/go-nostr/nip94" "github.com/syndtr/goleveldb/leveldb" @@ -88,7 +87,7 @@ func (c *client) fileMeta(bag *storage.Torrent) (*headerData, error) { hData = []byte("{}") } if err := json.Unmarshal(hData, &desc); err != nil { - return nil, errorx.With(err, "failed to unmarshal bag header data") + return nil, errors.Wrap(err, "failed to unmarshal bag header data") } return &desc, nil } @@ -96,12 +95,12 @@ func (c *client) fileMeta(bag *storage.Torrent) (*headerData, error) { func (c *client) detectFile(bag *storage.Torrent, fileHash string) (string, error) { metadata, err := c.fileMeta(bag) if err != nil { - return "", errorx.Withf(err, "failed to parse bag header data %v", hex.EncodeToString(bag.BagID)) + return "", errors.Wrapf(err, "failed to parse bag header data %v", hex.EncodeToString(bag.BagID)) } name := metadata.FileHash[fileHash] f, err := bag.GetFileOffsets(name) if err != nil { - return "", errorx.Withf(err, "failed to locate file %v in bag %v", name, hex.EncodeToString(bag.BagID)) + return "", errors.Wrapf(err, "failed to locate file %v in bag %v", name, hex.EncodeToString(bag.BagID)) } return f.Name, nil } @@ -112,7 +111,7 @@ func (c *client) bagByUser(userPubKey string) (*storage.Torrent, error) { copy(k[3:], userPubKey) bagID, err := c.db.Get(k, nil) if err != nil && !errors.Is(err, leveldb.ErrNotFound) { - return nil, errorx.With(err, "failed to read userID:bag mapping") + return nil, errors.Wrap(err, "failed to read userID:bag mapping") } tr := c.progressStorage.GetTorrent(bagID) @@ -124,7 +123,7 @@ func (c *client) bootstrapForBag(bagID []byte) (string, error) { copy(k[3:], bagID) bs, err := c.db.Get(k, nil) if err != nil && !errors.Is(err, leveldb.ErrNotFound) { - return "", errorx.Withf(err, "failed to read stored bootstrap node for %v =, will wait for DHT discovery", hex.EncodeToString(bagID)) + return "", errors.Wrapf(err, "failed to read stored bootstrap node for %v =, will wait for DHT discovery", hex.EncodeToString(bagID)) } return string(bs), nil } @@ -137,11 +136,11 @@ func (c *client) BuildUserPath(userPubKey string, contentType string) (userStora func (c *client) ListFiles(userPubKey string, page, limit uint32) (total uint32, res []*FileMetadata, err error) { bag, err := c.bagByUser(userPubKey) if err != nil { - return 0, nil, errorx.Withf(err, "failed to get bagID for the user %v", userPubKey) + return 0, nil, errors.Wrapf(err, "failed to get bagID for the user %v", userPubKey) } metadata, err := c.fileMeta(bag) if err != nil { - return 0, nil, errorx.Withf(err, "failed to parse bag header data %v", hex.EncodeToString(bag.BagID)) + return 0, nil, errors.Wrapf(err, "failed to parse bag header data %v", hex.EncodeToString(bag.BagID)) } startOffset := page * limit if startOffset >= bag.Header.FilesCount { @@ -154,11 +153,11 @@ func (c *client) ListFiles(userPubKey string, page, limit uint32) (total uint32, res = make([]*FileMetadata, 0, limit) bs, err := c.buildBootstrapNodeInfo(bag) if err != nil { - return 0, nil, errorx.Withf(err, "failed to build bootstap for bag %v", hex.EncodeToString(bag.BagID)) + return 0, nil, errors.Wrapf(err, "failed to build bootstap for bag %v", hex.EncodeToString(bag.BagID)) } files, err := bag.ListFiles() if err != nil { - return 0, nil, errorx.Withf(err, "failed to parse bag info for files %v", hex.EncodeToString(bag.BagID)) + return 0, nil, errors.Wrapf(err, "failed to parse bag info for files %v", hex.EncodeToString(bag.BagID)) } for i, f := range files[startOffset:endOffset] { idx := page*limit + uint32(i) @@ -188,16 +187,16 @@ func (c *client) ListFiles(userPubKey string, page, limit uint32) (total uint32, func (c *client) Delete(userPubKey, fileHash string) error { bag, err := c.bagByUser(userPubKey) if err != nil { - return errorx.Withf(err, "failed to get bagID for the user %v", userPubKey) + return errors.Wrapf(err, "failed to get bagID for the user %v", userPubKey) } file, err := c.detectFile(bag, fileHash) if err != nil { - return errorx.Withf(err, "failed to detect file %v in bag %v", fileHash, hex.EncodeToString(bag.BagID)) + return errors.Wrapf(err, "failed to detect file %v in bag %v", fileHash, hex.EncodeToString(bag.BagID)) } userPath, _ := c.BuildUserPath(userPubKey, "") err = os.Remove(filepath.Join(userPath, file)) if err != nil { - return errorx.Withf(err, "failed to remove file %v (%v)", fileHash, filepath.Join(userPath, file)) + return errors.Wrapf(err, "failed to remove file %v (%v)", fileHash, filepath.Join(userPath, file)) } return nil } @@ -207,13 +206,13 @@ func (c *client) Close() error { c.server.Stop() c.dht.Close() if gClose := c.gateway.Close(); gClose != nil { - err = multierror.Append(err, errorx.Withf(gClose, "failed to stop gateway")) + err = multierror.Append(err, errors.Wrapf(gClose, "failed to stop gateway")) } if sClose := c.stats.Close(); sClose != nil { - err = multierror.Append(err, errorx.Withf(sClose, "failed to close stats file")) + err = multierror.Append(err, errors.Wrapf(sClose, "failed to close stats file")) } if dErr := c.db.Close(); dErr != nil { - err = multierror.Append(err, errorx.Withf(dErr, "failed to close db")) + err = multierror.Append(err, errors.Wrapf(dErr, "failed to close db")) } close(c.downloadQueue) return err.ErrorOrNil() diff --git a/storage/upload.go b/storage/upload.go index 4afac8f..6d416f5 100644 --- a/storage/upload.go +++ b/storage/upload.go @@ -13,8 +13,8 @@ import ( "sync" "time" + "github.com/cockroachdb/errors" gomime "github.com/cubewise-code/go-mime" - "github.com/gookit/goutil/errorx" "github.com/xssnick/tonutils-go/adnl" "github.com/xssnick/tonutils-go/adnl/dht" "github.com/xssnick/tonutils-go/adnl/overlay" @@ -25,13 +25,13 @@ import ( func (c *client) StartUpload(ctx context.Context, userPubkey, relativePathToFileForUrl, hash string, newFile *FileMetaInput) (bagID, url string, existed bool, err error) { existingBagForUser, err := c.bagByUser(userPubkey) if err != nil { - return "", "", false, errorx.Withf(err, "failed to find existing bag for user %s", userPubkey) + return "", "", false, errors.Wrapf(err, "failed to find existing bag for user %s", userPubkey) } var existingHD headerData if existingBagForUser != nil { if len(existingBagForUser.Header.Data) > 0 { if err = json.Unmarshal(existingBagForUser.Header.Data, &existingHD); err != nil { - return "", "", false, errorx.Withf(err, "corrupted header metadata for bag %v", hex.EncodeToString(existingBagForUser.BagID)) + return "", "", false, errors.Wrapf(err, "corrupted header metadata for bag %v", hex.EncodeToString(existingBagForUser.BagID)) } } } @@ -41,7 +41,7 @@ func (c *client) StartUpload(ctx context.Context, userPubkey, relativePathToFile url, err = c.DownloadUrl(userPubkey, hash) if err != nil { return "", "", false, - errorx.Withf(err, "failed to build download url for already existing file %v/%v(%v)", userPubkey, relativePathToFileForUrl, hash) + errors.Wrapf(err, "failed to build download url for already existing file %v/%v(%v)", userPubkey, relativePathToFileForUrl, hash) } return hex.EncodeToString(existingBagForUser.BagID), url, existed, nil } @@ -51,13 +51,13 @@ func (c *client) StartUpload(ctx context.Context, userPubkey, relativePathToFile var bag *storage.Torrent bag, bs, err = c.upload(ctx, userPubkey, relativePathToFileForUrl, hash, newFile, &existingHD) if err != nil { - return "", "", false, errorx.Withf(err, "failed to start upload of %v", relativePathToFileForUrl) + return "", "", false, errors.Wrapf(err, "failed to start upload of %v", relativePathToFileForUrl) } bagID = hex.EncodeToString(bag.BagID) if newFile != nil { uplFile, err := bag.GetFileOffsets(relativePathToFileForUrl) if err != nil { - return "", "", false, errorx.Withf(err, "failed to get just created file from new bag") + return "", "", false, errors.Wrapf(err, "failed to get just created file from new bag") } fullFilePath := filepath.Join(c.rootStoragePath, userPubkey, relativePathToFileForUrl) go c.stats.ProcessFile(fullFilePath, gomime.TypeByExtension(filepath.Ext(fullFilePath)), uplFile.Size) @@ -65,7 +65,7 @@ func (c *client) StartUpload(ctx context.Context, userPubkey, relativePathToFile url, err = c.buildUrl(bagID, relativePathToFileForUrl, bs) if err != nil { - return "", "", false, errorx.Withf(err, "failed to build url for %v (bag %v)", relativePathToFileForUrl, bagID) + return "", "", false, errors.Wrapf(err, "failed to build url for %v (bag %v)", relativePathToFileForUrl, bagID) } return bagID, url, existed, err } @@ -82,7 +82,7 @@ func (c *client) upload(ctx context.Context, user, relativePath, hash string, fi rootUserPath, _ := c.BuildUserPath(user, "") refs, err := c.progressStorage.GetAllFilesRefsInDir(rootUserPath) if err != nil { - return nil, nil, errorx.With(err, "failed to detect shareable files") + return nil, nil, errors.Wrap(err, "failed to detect shareable files") } headerMD := &headerData{ User: user, @@ -111,7 +111,7 @@ func (c *client) upload(ctx context.Context, user, relativePath, hash string, fi var headerMDSerialized []byte headerMDSerialized, err = json.Marshal(headerMD) if err != nil { - return nil, nil, errorx.With(err, "failed to put file hashes") + return nil, nil, errors.Wrap(err, "failed to put file hashes") } header := &storage.TorrentHeader{ DirNameSize: uint32(len(user)), @@ -127,20 +127,20 @@ func (c *client) upload(ctx context.Context, user, relativePath, hash string, fi } }, false) if err != nil { - return nil, nil, errorx.With(err, "failed to initialize bag") + return nil, nil, errors.Wrap(err, "failed to initialize bag") } err = tr.Start(true, false, false) if err != nil { - return nil, nil, errorx.With(err, "failed to start bag upload") + return nil, nil, errors.Wrap(err, "failed to start bag upload") } wg.Wait() err = c.saveUploadTorrent(tr, user) if err != nil { - return nil, nil, errorx.With(err, "failed to save updated bag") + return nil, nil, errors.Wrap(err, "failed to save updated bag") } bootstrapNode, err := c.buildBootstrapNodeInfo(tr) if err != nil { - return nil, nil, errorx.With(err, "failed to build bootstrap node info") + return nil, nil, errors.Wrap(err, "failed to build bootstrap node info") } return tr, []*Bootstrap{bootstrapNode}, nil } @@ -149,7 +149,7 @@ func (c *client) buildBootstrapNodeInfo(tr *storage.Torrent) (*Bootstrap, error) key := c.server.GetADNLPrivateKey() overlayNode, err := overlay.NewNode(tr.BagID, key) if err != nil { - return nil, errorx.With(err, "failed to build overlay node") + return nil, errors.Wrap(err, "failed to build overlay node") } addr := c.gateway.GetAddressList() @@ -162,7 +162,7 @@ func (c *client) buildBootstrapNodeInfo(tr *storage.Torrent) (*Bootstrap, error) toVerify, err := tl.Serialize(dNode, true) if err != nil { - return nil, errorx.Withf(err, "failed to sign dht bootstrap, serialize failure") + return nil, errors.Wrapf(err, "failed to sign dht bootstrap, serialize failure") } dNode.Signature = ed25519.Sign(key, toVerify) @@ -175,7 +175,7 @@ func (c *client) buildBootstrapNodeInfo(tr *storage.Torrent) (*Bootstrap, error) func (c *client) buildUrl(bagID, relativePath string, bs []*Bootstrap) (string, error) { b, err := json.Marshal(bs) if err != nil { - return "", errorx.Withf(err, "failed to marshal %#v", bs) + return "", errors.Wrapf(err, "failed to marshal %#v", bs) } bootstrap := base64.StdEncoding.EncodeToString(b) url := fmt.Sprintf("http://%v.bag/%v?bootstrap=%v", bagID, relativePath, bootstrap) @@ -185,7 +185,7 @@ func (c *client) buildUrl(bagID, relativePath string, bs []*Bootstrap) (string, func (c *client) saveUploadTorrent(tr *storage.Torrent, userPubKey string) error { if err := c.saveTorrent(tr, &userPubKey, nil); err != nil { - return errorx.With(err, "failed to save upload torrent into storage") + return errors.Wrap(err, "failed to save upload torrent into storage") } c.newFilesMx.Lock() for k := range c.newFiles[userPubKey] { From cbc3dcbfd8b9b0f3dd5efe30bf4d9109f7c14368 Mon Sep 17 00:00:00 2001 From: ice-cronus <105345303+ice-cronus@users.noreply.github.com> Date: Thu, 17 Oct 2024 18:52:10 +0300 Subject: [PATCH 12/18] storage on-behave attestation support --- database/command/command.go | 6 +- database/query/query.go | 3 +- database/query/query_test.go | 4 +- go.mod | 3 + go.sum | 12 ++ model/model.go | 5 +- server/http/.testdata/text-master.txt | 1 + server/http/.testdata/text.txt | 1 + server/http/auth_nip98.go | 45 +++++- server/http/nip11_test.go | 8 +- server/http/nip96.json | 6 +- server/http/storage_nip96.go | 46 ++++-- server/http/storage_nip_96_test.go | 197 +++++++++++++++++++------- server/server.go | 8 +- storage/download.go | 5 +- storage/global.go | 11 +- storage/storage.go | 43 ++++-- storage/upload.go | 46 +++--- 18 files changed, 324 insertions(+), 126 deletions(-) create mode 100644 server/http/.testdata/text-master.txt create mode 100644 server/http/.testdata/text.txt diff --git a/database/command/command.go b/database/command/command.go index 06ab329..54e9395 100644 --- a/database/command/command.go +++ b/database/command/command.go @@ -5,13 +5,11 @@ package command import ( "context" - "github.com/hashicorp/go-multierror" - "github.com/ice-blockchain/subzero/model" ) func AcceptEvent(ctx context.Context, event *model.Event) error { - var mErr *multierror.Error //TODO impl consensus - return mErr.ErrorOrNil() + + return nil } diff --git a/database/query/query.go b/database/query/query.go index 906a07d..34c4706 100644 --- a/database/query/query.go +++ b/database/query/query.go @@ -25,7 +25,6 @@ const ( var ( ErrUnexpectedRowsAffected = errors.New("unexpected rows affected") ErrTargetReactionEventNotFound = errors.New("target reaction event not found") - ErrOnBehalfAccessDenied = errors.New("on-behalf access denied") ErrAttestationUpdateRejected = errors.New("attestation update rejected") errEventIteratorInterrupted = errors.New("interrupted") ) @@ -219,7 +218,7 @@ func (db *dbClient) handleError(err error) error { if errors.As(err, &sqlError) && sqlError.Code == sqlite3.ErrConstraint { switch sqlError.Error() { case "onbehalf permission denied": - err = ErrOnBehalfAccessDenied + err = model.ErrOnBehalfAccessDenied case "attestation list update must be linear": err = ErrAttestationUpdateRejected } diff --git a/database/query/query_test.go b/database/query/query_test.go index ca42f46..e0caa7e 100644 --- a/database/query/query_test.go +++ b/database/query/query_test.go @@ -975,7 +975,7 @@ func TestQueryEventAttestation(t *testing.T) { ev.Tags = model.Tags{{model.CustomIONTagOnBehalfOf, nostr.GeneratePrivateKey()}} require.NoError(t, ev.Sign(active)) t.Logf("event %+v", ev) - require.ErrorIs(t, db.AcceptEvent(context.TODO(), &ev), ErrOnBehalfAccessDenied) + require.ErrorIs(t, db.AcceptEvent(context.TODO(), &ev), model.ErrOnBehalfAccessDenied) }) t.Run("Count", func(t *testing.T) { count, err := db.CountEvents(context.TODO(), helperNewFilterSubscription(func(apply *model.Filter) { @@ -1090,7 +1090,7 @@ func TestEventDeleteWithAttestation(t *testing.T) { ev.Tags = append(ev.Tags, model.Tag{model.TagAttestationName, hackerPublic, "", model.CustomIONAttestationKindActive + ":" + strconv.Itoa(int(now-1))}) ev.Tags = append(ev.Tags, model.Tag{model.CustomIONTagOnBehalfOf, masterPublic}) require.NoError(t, ev.Sign(user2Private)) - require.ErrorIs(t, db.AcceptEvent(context.TODO(), &ev), ErrOnBehalfAccessDenied) + require.ErrorIs(t, db.AcceptEvent(context.TODO(), &ev), model.ErrOnBehalfAccessDenied) }) }) t.Run("DeleteEvents", func(t *testing.T) { diff --git a/go.mod b/go.mod index a48ffe2..5d3da23 100644 --- a/go.mod +++ b/go.mod @@ -43,6 +43,7 @@ require ( atomicgo.dev/cursor v0.2.0 // indirect atomicgo.dev/keyboard v0.2.9 // indirect atomicgo.dev/schedule v0.1.0 // indirect + filippo.io/mkcert v0.0.0-00010101000000-000000000000 // indirect github.com/aws/aws-sdk-go v1.55.5 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect @@ -118,4 +119,6 @@ require ( golang.org/x/tools v0.26.0 // indirect google.golang.org/protobuf v1.35.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + howett.net/plist v1.0.0 // indirect + software.sslmate.com/src/go-pkcs12 v0.2.0 // indirect ) diff --git a/go.sum b/go.sum index c4e126c..b37cd63 100644 --- a/go.sum +++ b/go.sum @@ -133,6 +133,7 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jamiealquiza/tachymeter v2.0.0+incompatible h1:mGiF1DGo8l6vnGT8FXNNcIXht/YmjzfraiUprXYwJ6g= github.com/jamiealquiza/tachymeter v2.0.0+incompatible/go.mod h1:Ayf6zPZKEnLsc3winWEXJRkTBhdHo58HODAu1oFJkYU= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= @@ -149,6 +150,8 @@ github.com/kevinms/leakybucket-go v0.0.0-20200115003610-082473db97ca/go.mod h1:p github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 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/kixelated/mkcert v1.4.4-days h1:T2P9W4ruEfgLHOl5UljPwh0d79FbFWkSe2IONcUBxG8= +github.com/kixelated/mkcert v1.4.4-days/go.mod h1:VyvOchVuAye3BoUsPUOOofKygVwLV2KQMVFJNRq+1dA= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= @@ -302,6 +305,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= @@ -325,6 +329,7 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +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.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= @@ -348,6 +353,7 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -374,6 +380,7 @@ golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 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.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= @@ -408,6 +415,7 @@ gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -419,9 +427,13 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= +howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= pgregory.net/rand v1.0.2 h1:ASEbkvwOmY/UPF2evJPBJ8XZg71xdKWYdByqKapI7Vw= pgregory.net/rand v1.0.2/go.mod h1:EyNx8APnDE3Svi8sWgUZ5lOiz60cNZUPPBTyzOUpPl4= pgregory.net/rapid v0.4.8 h1:d+5SGZWUbJPbl3ss6tmPFqnNeQR6VDOFly+eTjwPiEw= pgregory.net/rapid v0.4.8/go.mod h1:Z5PbWqjvWR1I3UGjvboUuan4fe4ZYEYNLNQLExzCoUs= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +software.sslmate.com/src/go-pkcs12 v0.2.0 h1:nlFkj7bTysH6VkC4fGphtjXRbezREPgrHuJG20hBGPE= +software.sslmate.com/src/go-pkcs12 v0.2.0/go.mod h1:23rNcYsMabIc1otwLpTkCCPwUq6kQsTyowttG/as0kQ= diff --git a/model/model.go b/model/model.go index 9cc8b93..24ee645 100644 --- a/model/model.go +++ b/model/model.go @@ -33,8 +33,9 @@ type ( ) var ( - ErrDuplicate = errors.New("duplicate") - ErrUnsupportedAlg = errors.New("unsupported signature/key algorithm combination") + ErrDuplicate = errors.New("duplicate") + ErrUnsupportedAlg = errors.New("unsupported signature/key algorithm combination") + ErrOnBehalfAccessDenied = errors.New("on-behalf access denied") ) const ( diff --git a/server/http/.testdata/text-master.txt b/server/http/.testdata/text-master.txt new file mode 100644 index 0000000..8b25206 --- /dev/null +++ b/server/http/.testdata/text-master.txt @@ -0,0 +1 @@ +master \ No newline at end of file diff --git a/server/http/.testdata/text.txt b/server/http/.testdata/text.txt new file mode 100644 index 0000000..f3a3485 --- /dev/null +++ b/server/http/.testdata/text.txt @@ -0,0 +1 @@ +text \ No newline at end of file diff --git a/server/http/auth_nip98.go b/server/http/auth_nip98.go index 677823d..f145284 100644 --- a/server/http/auth_nip98.go +++ b/server/http/auth_nip98.go @@ -3,7 +3,10 @@ package http import ( + "context" "encoding/base64" + "github.com/ice-blockchain/subzero/database/query" + "github.com/ice-blockchain/subzero/model" "net/url" "time" @@ -15,14 +18,16 @@ import ( type ( Token interface { PubKey() string + MasterKey() string ExpectedHash() string + ValidateAttestation(ctx context.Context, kind int, now time.Time) error } AuthClient interface { - VerifyToken(gCtx *gin.Context, token string) (Token, error) + VerifyToken(gCtx *gin.Context, token string, now time.Time) (Token, error) } nostrToken struct { - ev nostr.Event + ev model.Event expectedHash string } authNostr struct { @@ -43,7 +48,7 @@ func NewAuth() AuthClient { return &authNostr{} } -func (a *authNostr) VerifyToken(gCtx *gin.Context, token string) (Token, error) { +func (a *authNostr) VerifyToken(gCtx *gin.Context, token string, now time.Time) (Token, error) { bToken, err := base64.StdEncoding.DecodeString(token) if err != nil { return nil, errors.Wrap(err, "failed to unmarshal auth token: malformed base64") @@ -61,7 +66,6 @@ func (a *authNostr) VerifyToken(gCtx *gin.Context, token string) (Token, error) if event.Kind != nostrHttpAuthKind { return nil, errors.Wrapf(ErrTokenInvalid, "invalid token event kind %v", event.Kind) } - now := time.Now() if event.CreatedAt.Time().After(now) || (event.CreatedAt.Time().Before(now) && now.Sub(event.CreatedAt.Time()) > tokenExpirationWindow) { return nil, ErrTokenExpired } @@ -96,11 +100,42 @@ func (a *authNostr) VerifyToken(gCtx *gin.Context, token string) (Token, error) if payloadTag := event.Tags.GetFirst([]string{"payload"}); payloadTag != nil && len(*payloadTag) > 1 { expectedHash = payloadTag.Value() } - return &nostrToken{ev: event, expectedHash: expectedHash}, nil + return &nostrToken{ev: model.Event{event}, expectedHash: expectedHash}, nil } func (t *nostrToken) PubKey() string { return t.ev.PubKey } +func (t *nostrToken) MasterKey() string { + return t.ev.GetMasterPublicKey() +} func (t *nostrToken) ExpectedHash() string { return t.expectedHash } + +func (t *nostrToken) ValidateAttestation(ctx context.Context, kind int, now time.Time) error { + if t.ev.PubKey == t.MasterKey() { + return nil + } + attestationEvent := query.GetStoredEvents(ctx, &model.Subscription{model.Filters{model.Filter{ + Kinds: []int{model.IceKindAttestation}, + Tags: model.TagMap{ + "p": []string{t.PubKey()}, + }, + }, + }}) + var allowed bool + for attestation, err := range attestationEvent { + if err != nil { + return errors.Wrapf(err, "failed to get attestation event") + } + allowed, err = model.OnBehalfIsAccessAllowed(attestation.Tags, t.ev.PubKey, kind, now.Unix()) + if err != nil { + return errors.Wrapf(err, "failed to parse attestation event") + } + break + } + if !allowed { + return model.ErrOnBehalfAccessDenied + } + return nil +} diff --git a/server/http/nip11_test.go b/server/http/nip11_test.go index fa7f4ed..d3636ba 100644 --- a/server/http/nip11_test.go +++ b/server/http/nip11_test.go @@ -51,10 +51,10 @@ func initServer(serverCtx context.Context, serverCancel context.CancelFunc, port KeyPath: keyFilePath, Port: port, }, nil, NewNIP11Handler(), map[string]gin.HandlerFunc{ - "POST /media": uploader.Upload(), - "GET /media": uploader.ListFiles(), - "GET /media/:file": uploader.Download(), - "DELETE /media/:file": uploader.Delete(), + "POST /files": uploader.Upload(), + "GET /files": uploader.ListFiles(), + "GET /files/:file": uploader.Download(), + "DELETE /files/:file": uploader.Delete(), }) time.Sleep(100 * time.Millisecond) } diff --git a/server/http/nip96.json b/server/http/nip96.json index d0777fd..e0ce224 100644 --- a/server/http/nip96.json +++ b/server/http/nip96.json @@ -1,8 +1,8 @@ { - "api_url": "/media", - "download_url": "/media", + "api_url": "/files", + "download_url": "/files", "delegated_to_url": "", - "content_types": ["image/webp", "video/mpeg4", "*/*"], + "content_types": ["image/webp", "video/mpeg4", "application/x-brotli"], "plans": { "free": { "name": "Free Tier", diff --git a/server/http/storage_nip96.go b/server/http/storage_nip96.go index 9dc8cba..5f376db 100644 --- a/server/http/storage_nip96.go +++ b/server/http/storage_nip96.go @@ -91,15 +91,23 @@ func (storageHandler) NIP96Info() gin.HandlerFunc { func (s *storageHandler) Upload() gin.HandlerFunc { return func(gCtx *gin.Context) { + now := time.Now() ctx, cancel := context.WithTimeout(gCtx, mediaEndpointTimeout) defer cancel() authHeader := strings.TrimPrefix(gCtx.GetHeader("Authorization"), "Nostr ") - token, authErr := s.auth.VerifyToken(gCtx, authHeader) + token, authErr := s.auth.VerifyToken(gCtx, authHeader, now) if authErr != nil { log.Printf("ERROR: endpoint authentification failed: %v", errors.Wrap(authErr, "endpoint authentification failed")) gCtx.JSON(http.StatusUnauthorized, uploadErr("Unauthorized")) return } + attestationValid := token.ValidateAttestation(ctx, nostr.KindFileMetadata, now) + if attestationValid != nil { + log.Printf("ERROR: on-behalf attestation failed: %v", errors.Wrap(attestationValid, "endpoint authentification failed")) + gCtx.JSON(http.StatusForbidden, uploadErr("Forbidden: on-behalf attestation failed")) + return + } + var upload fileUpload if err := gCtx.ShouldBindWith(&upload, binding.FormMultipart); err != nil { log.Printf("ERROR: failed to bind multipart form: %v", errors.Wrap(err, "failed to bind multipart form")) @@ -122,9 +130,9 @@ func (s *storageHandler) Upload() gin.HandlerFunc { upload.ContentType = gomime.TypeByExtension(filepath.Ext(upload.File.Filename)) } - storagePath, fileType := s.storageClient.BuildUserPath(token.PubKey(), upload.ContentType) - uploadingFilePath := filepath.Join(storagePath, fileType, upload.File.Filename) - relativePath := filepath.Join(fileType, upload.File.Filename) + storagePath, _ := s.storageClient.BuildUserPath(token.MasterKey(), upload.ContentType) + uploadingFilePath := filepath.Join(storagePath, upload.File.Filename) + relativePath := upload.File.Filename if err := os.MkdirAll(filepath.Dir(uploadingFilePath), 0o755); err != nil { log.Printf("ERROR: %v", errors.Wrap(err, "failed to open temp file while processing upload")) gCtx.JSON(http.StatusInternalServerError, uploadErr("failed to open temporary file")) @@ -163,8 +171,7 @@ func (s *storageHandler) Upload() gin.HandlerFunc { os.Remove(uploadingFilePath) return } - now := time.Now() - bagID, url, existed, err := s.storageClient.StartUpload(ctx, token.PubKey(), relativePath, hex.EncodeToString(hash), &storage.FileMetaInput{ + bagID, url, existed, err := s.storageClient.StartUpload(ctx, token.PubKey(), token.MasterKey(), relativePath, hex.EncodeToString(hash), &storage.FileMetaInput{ Hash: hash, Caption: upload.Caption, Alt: upload.Alt, @@ -204,8 +211,9 @@ func (s *storageHandler) Upload() gin.HandlerFunc { } func (s *storageHandler) Download() gin.HandlerFunc { return func(gCtx *gin.Context) { + now := time.Now() authHeader := strings.TrimPrefix(gCtx.GetHeader("Authorization"), "Nostr ") - token, authErr := s.auth.VerifyToken(gCtx, authHeader) + token, authErr := s.auth.VerifyToken(gCtx, authHeader, now) if authErr != nil { log.Printf("ERROR: endpoint authentification failed: %v", errors.Wrap(authErr, "endpoint authentification failed")) gCtx.JSON(http.StatusUnauthorized, uploadErr("Unauthorized")) @@ -216,7 +224,7 @@ func (s *storageHandler) Download() gin.HandlerFunc { gCtx.JSON(http.StatusBadRequest, uploadErr("filename is required")) return } - url, err := s.storageClient.DownloadUrl(token.PubKey(), file) + url, err := s.storageClient.DownloadUrl(token.MasterKey(), file) if err != nil { if errors.Is(err, storage.ErrNotFound) { gCtx.Status(http.StatusNotFound) @@ -231,21 +239,30 @@ func (s *storageHandler) Download() gin.HandlerFunc { } func (s *storageHandler) Delete() gin.HandlerFunc { return func(gCtx *gin.Context) { + now := time.Now() + ctx, cancel := context.WithTimeout(gCtx, mediaEndpointTimeout) + defer cancel() authHeader := strings.TrimPrefix(gCtx.GetHeader("Authorization"), "Nostr ") - token, authErr := s.auth.VerifyToken(gCtx, authHeader) + token, authErr := s.auth.VerifyToken(gCtx, authHeader, now) if authErr != nil { log.Printf("ERROR: endpoint authentification failed: %v", errors.Wrap(authErr, "endpoint authentification failed")) gCtx.JSON(http.StatusUnauthorized, uploadErr("Unauthorized")) return } + attestationValid := token.ValidateAttestation(ctx, nostr.KindFileMetadata, now) + if attestationValid != nil { + log.Printf("ERROR: on-behalf attestation failed: %v", errors.Wrap(attestationValid, "endpoint authentification failed")) + gCtx.JSON(http.StatusForbidden, uploadErr("Forbidden: on-behalf attestation failed")) + return + } file := gCtx.Param("file") if strings.TrimSpace(file) == "" { gCtx.JSON(http.StatusBadRequest, uploadErr("filehash is required")) return } - if err := s.storageClient.Delete(token.PubKey(), file); err != nil { + if err := s.storageClient.Delete(token.PubKey(), token.MasterKey(), file); err != nil { log.Printf("ERROR: %v", errors.Wrap(err, "failed to delete file")) - if errors.Is(err, storage.ErrNotFound) { + if errors.Is(err, storage.ErrNotFound) || errors.Is(err, storage.ErrForbidden) { gCtx.JSON(http.StatusForbidden, uploadErr("user do not own file")) return } @@ -258,8 +275,9 @@ func (s *storageHandler) Delete() gin.HandlerFunc { func (s *storageHandler) ListFiles() gin.HandlerFunc { return func(gCtx *gin.Context) { + now := time.Now() authHeader := strings.TrimPrefix(gCtx.GetHeader("Authorization"), "Nostr ") - token, authErr := s.auth.VerifyToken(gCtx, authHeader) + token, authErr := s.auth.VerifyToken(gCtx, authHeader, now) if authErr != nil { log.Printf("ERROR: endpoint authentification failed: %v", errors.Wrap(authErr, "endpoint authentification failed")) gCtx.JSON(http.StatusUnauthorized, uploadErr("Unauthorized")) @@ -277,9 +295,9 @@ func (s *storageHandler) ListFiles() gin.HandlerFunc { if params.Count == 0 { params.Count = 10 } - total, filesList, err := s.storageClient.ListFiles(token.PubKey(), params.Page, params.Count) + total, filesList, err := s.storageClient.ListFiles(token.MasterKey(), params.Page, params.Count) if err != nil { - log.Printf("ERROR: %v", errors.Wrapf(err, "failed to list files for user %v", token.PubKey())) + log.Printf("ERROR: %v", errors.Wrapf(err, "failed to list files for user %v", token.MasterKey())) gCtx.JSON(http.StatusInternalServerError, uploadErr("oops, error occured!")) return } diff --git a/server/http/storage_nip_96_test.go b/server/http/storage_nip_96_test.go index c284271..ca60d79 100644 --- a/server/http/storage_nip_96_test.go +++ b/server/http/storage_nip_96_test.go @@ -3,17 +3,21 @@ package http import ( + "bytes" "context" "crypto/ed25519" "crypto/rand" + "crypto/sha256" "crypto/tls" "embed" "encoding/base64" + "encoding/hex" "encoding/json" "fmt" "io" "log" "math/big" + "mime/multipart" "net" "net/http" "net/url" @@ -43,25 +47,47 @@ import ( var testdata embed.FS func TestNIP96(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + t.Parallel() + now := time.Now().Unix() + ctx, cancel := context.WithTimeout(context.Background(), 300*time.Second) defer cancel() defer func() { require.NoError(t, storage.Client().Close()) require.NoError(t, os.RemoveAll("./../../.test-uploads")) require.NoError(t, os.RemoveAll("./../../.test-uploads2")) }() - user1 := nostr.GeneratePrivateKey() + master := "c24f7ab5b42254d6558e565ec1c170b266a7cd2be1edf9f42bfb375640f7f559" + masterPubKey, _ := nostr.GetPublicKey(master) + user1 := "3c00c01e6556c4b603b4c49d12059e02c42161d055b658e5635fa6206f594306" + user2 := "cea41ff6c6e9eb0cde6740a1fbe8c134bda650ce819e43b68bf61add2c68f8d9" var tagsToBroadcast nostr.Tags var contentToBroadcast string + t.Run("create on-behalf attestations", func(t *testing.T) { + user1PubKey, _ := nostr.GetPublicKey(user1) + user2PubKey, _ := nostr.GetPublicKey(user2) + var ev model.Event + ev.Kind = model.IceKindAttestation + ev.CreatedAt = 1 + ev.Tags = model.Tags{ + {model.TagAttestationName, user1PubKey, "", model.IceAttestationKindActive + ":" + strconv.Itoa(int(now-10))}, + {model.TagAttestationName, user2PubKey, "", model.IceAttestationKindActive + ":" + strconv.Itoa(int(now-5))}, + } + require.NoError(t, ev.Sign(master)) + require.NoError(t, query.AcceptEvent(ctx, &ev)) + }) t.Run("files are uploaded, response is ok", func(t *testing.T) { var responses chan *nip96.UploadResponse responses = make(chan *nip96.UploadResponse, 2) - upload(t, ctx, user1, ".testdata/image2.png", "profile.png", "ice profile pic", func(resp *nip96.UploadResponse) { + upload(t, ctx, user1, masterPubKey, ".testdata/image2.png", "profile.png", "ice profile pic", func(resp *nip96.UploadResponse) { responses <- resp }) - upload(t, ctx, user1, ".testdata/image.jpg", "ice.jpg", "ice logo", func(resp *nip96.UploadResponse) { + upload(t, ctx, user1, masterPubKey, ".testdata/image.jpg", "ice.jpg", "ice logo", func(resp *nip96.UploadResponse) { responses <- resp }) + upload(t, ctx, user2, masterPubKey, ".testdata/image2.png", "profile.png", "ice profile pic", func(resp *nip96.UploadResponse) {}) + upload(t, ctx, user2, masterPubKey, ".testdata/image.jpg", "ice.jpg", "ice logo", func(resp *nip96.UploadResponse) {}) + upload(t, ctx, user2, masterPubKey, ".testdata/text.txt", "text.txt", "text file", func(resp *nip96.UploadResponse) {}) + upload(t, ctx, master, "", ".testdata/text-master.txt", "master.txt", "master's file", func(resp *nip96.UploadResponse) {}) close(responses) for resp := range responses { verifyFile(t, resp.Nip94Event.Content, resp.Nip94Event.Tags) @@ -72,6 +98,7 @@ func TestNIP96(t *testing.T) { const newStorageRoot = "./../../.test-uploads2" var nip94EventToSign *model.Event t.Run("nip-94 event is broadcasted, it causes download to other node", func(t *testing.T) { + tagsToBroadcast = tagsToBroadcast.AppendUnique(model.Tag{"b", masterPubKey}) nip94EventToSign = &model.Event{nostr.Event{ CreatedAt: nostr.Timestamp(time.Now().Unix()), Kind: nostr.KindFileMetadata, @@ -83,48 +110,46 @@ func TestNIP96(t *testing.T) { initStorage(ctx, newStorageRoot) require.NoError(t, query.AcceptEvent(ctx, nip94EventToSign)) require.NoError(t, storage.AcceptEvent(ctx, nip94EventToSign)) - pk, err := nostr.GetPublicKey(user1) - require.NoError(t, err) - downloadedProfileHash, err := storagefixture.WaitForFile(ctx, newStorageRoot, filepath.Join(newStorageRoot, pk, "image/profile.png"), "b2b8cf9202b45dad7e137516bcf44b915ce30b39c3b294629a9b6b8fa1585292", int64(182744)) + downloadedProfileHash, err := storagefixture.WaitForFile(ctx, newStorageRoot, filepath.Join(newStorageRoot, masterPubKey, "profile.png"), "b2b8cf9202b45dad7e137516bcf44b915ce30b39c3b294629a9b6b8fa1585292", int64(182744)) require.NoError(t, err) require.Equal(t, "b2b8cf9202b45dad7e137516bcf44b915ce30b39c3b294629a9b6b8fa1585292", downloadedProfileHash) - downloadedLogoHash, err := storagefixture.WaitForFile(ctx, newStorageRoot, filepath.Join(newStorageRoot, pk, "image/ice.jpg"), "777d453395088530ce8de776fe54c3e5ace548381007b743e067844858962218", int64(415939)) + downloadedLogoHash, err := storagefixture.WaitForFile(ctx, newStorageRoot, filepath.Join(newStorageRoot, masterPubKey, "ice.jpg"), "777d453395088530ce8de776fe54c3e5ace548381007b743e067844858962218", int64(415939)) require.NoError(t, err) require.Equal(t, "777d453395088530ce8de776fe54c3e5ace548381007b743e067844858962218", downloadedLogoHash) }) t.Run("download endpoint redirects to same download url over ton storage", func(t *testing.T) { expected := nip94.ParseFileMetadata(nostr.Event{Tags: expectedResponse("ice logo").Nip94Event.Tags}) - status, location := download(t, ctx, user1, "777d453395088530ce8de776fe54c3e5ace548381007b743e067844858962218") + status, location := download(t, ctx, user1, "777d453395088530ce8de776fe54c3e5ace548381007b743e067844858962218", masterPubKey) require.Equal(t, http.StatusFound, status) require.Regexp(t, fmt.Sprintf("^http://[0-9a-fA-F]{64}.bag/%v", expected.Summary), location) expected = nip94.ParseFileMetadata(nostr.Event{Tags: expectedResponse("ice profile pic").Nip94Event.Tags}) - status, location = download(t, ctx, user1, "b2b8cf9202b45dad7e137516bcf44b915ce30b39c3b294629a9b6b8fa1585292") + status, location = download(t, ctx, user1, "b2b8cf9202b45dad7e137516bcf44b915ce30b39c3b294629a9b6b8fa1585292", masterPubKey) require.Equal(t, http.StatusFound, status) require.Regexp(t, fmt.Sprintf("^http://[0-9a-fA-F]{64}.bag/%v", expected.Summary), location) status, _ = download(t, ctx, user1, "non_valid_hash") require.Equal(t, http.StatusNotFound, status) }) t.Run("list files responds with up to all files for the user when total is less than page", func(t *testing.T) { - files := list(t, ctx, user1, 0, 0) - assert.Equal(t, uint32(2), files.Total) - assert.Len(t, files.Files, 2) + files := list(t, ctx, user1, 0, 0, masterPubKey) + assert.Equal(t, uint32(4), files.Total) + assert.Len(t, files.Files, 4) for _, f := range files.Files { verifyFile(t, f.Content, f.Tags) } }) t.Run("list files with pagination", func(t *testing.T) { - files := list(t, ctx, user1, 0, 1) - assert.Equal(t, uint32(2), files.Total) + files := list(t, ctx, user1, 0, 1, masterPubKey) + assert.Equal(t, uint32(4), files.Total) assert.Len(t, files.Files, 1) uniqFiles := map[string]struct{}{} for _, f := range files.Files { verifyFile(t, f.Content, f.Tags) uniqFiles[f.Content] = struct{}{} } - files = list(t, ctx, user1, 1, 1) - assert.Equal(t, uint32(2), files.Total) + files = list(t, ctx, user1, 1, 1, masterPubKey) + assert.Equal(t, uint32(4), files.Total) assert.Len(t, files.Files, 1) for _, f := range files.Files { verifyFile(t, f.Content, f.Tags) @@ -132,30 +157,53 @@ func TestNIP96(t *testing.T) { require.False(t, presentedBefore) } }) - t.Run("delete file", func(t *testing.T) { + t.Run("delete file owned by user 1 on behave of usr 1 (normally)", func(t *testing.T) { fileHash := "" if xTag := nip94EventToSign.Tags.GetFirst([]string{"x"}); xTag != nil && len(*xTag) > 1 { fileHash = xTag.Value() } else { t.Fatalf("malformed x tag in nip94 event %v", nip94EventToSign.ID) } - status := deleteFile(t, ctx, user1, fileHash) + status := deleteFile(t, ctx, user1, fileHash, masterPubKey) + require.Equal(t, http.StatusOK, status) fileName := nip94.ParseFileMetadata(nostr.Event{Tags: expectedResponse(nip94EventToSign.Content).Nip94Event.Tags}).Summary + require.NoFileExists(t, filepath.Join(storageRoot, masterPubKey, fileName)) + deletionEventToSign := &model.Event{nostr.Event{ + CreatedAt: nostr.Timestamp(time.Now().Unix()), + Kind: nostr.KindDeletion, + Tags: nostr.Tags{ + nostr.Tag{"e", nip94EventToSign.ID}, + nostr.Tag{"k", strconv.FormatInt(int64(nostr.KindFileMetadata), 10)}, + nostr.Tag{"b", masterPubKey}, + }, + }} + require.NoError(t, deletionEventToSign.Sign(user1)) + require.NoError(t, storage.AcceptEvent(ctx, deletionEventToSign)) + require.NoFileExists(t, filepath.Join(newStorageRoot, masterPubKey, fileName)) + }) + t.Run("delete file owned by user 2 on behave of usr 1 (attestation)", func(t *testing.T) { + status := deleteFile(t, ctx, user1, "982d9e3eb996f559e633f4d194def3761d909f5a3b647d1a851fead67c32c9d1", masterPubKey) require.Equal(t, http.StatusOK, status) - pk, err := nostr.GetPublicKey(user1) - require.NoError(t, err) - require.NoFileExists(t, filepath.Join(storageRoot, pk, fileName)) + fileName := "text.txt" + require.NoFileExists(t, filepath.Join(storageRoot, masterPubKey, fileName)) deletionEventToSign := &model.Event{nostr.Event{ CreatedAt: nostr.Timestamp(time.Now().Unix()), Kind: nostr.KindDeletion, Tags: nostr.Tags{ nostr.Tag{"e", nip94EventToSign.ID}, nostr.Tag{"k", strconv.FormatInt(int64(nostr.KindFileMetadata), 10)}, + nostr.Tag{"b", masterPubKey}, }, }} require.NoError(t, deletionEventToSign.Sign(user1)) require.NoError(t, storage.AcceptEvent(ctx, deletionEventToSign)) - require.NoFileExists(t, filepath.Join(newStorageRoot, pk, fileName)) + require.NoFileExists(t, filepath.Join(newStorageRoot, masterPubKey, fileName)) + }) + t.Run("delete file owned by master by usr1 (Forbidden)", func(t *testing.T) { + status := deleteFile(t, ctx, user1, "fc613b4dfd6736a7bd268c8a0e74ed0d1c04a959f59dd74ef2874983fd443fc9", masterPubKey) + require.Equal(t, http.StatusForbidden, status) + fileName := "master.txt" + require.FileExists(t, filepath.Join(storageRoot, masterPubKey, fileName)) }) } @@ -176,28 +224,36 @@ func verifyFile(t *testing.T, content string, tags nostr.Tags) { require.Regexp(t, "^[0-9a-fA-F]{64}$", bagID) } -func upload(t *testing.T, ctx context.Context, sk, path, filename, caption string, result func(resp *nip96.UploadResponse)) { +func upload(t *testing.T, ctx context.Context, sk, master, path, filename, caption string, result func(resp *nip96.UploadResponse)) { t.Helper() img, _ := testdata.Open(path) defer img.Close() - resp, err := nip96.Upload(ctx, nip96.UploadRequest{ - Host: "https://localhost:9997/media", - File: img, - Filename: filename, - Caption: caption, - ContentType: gomime.TypeByExtension(filepath.Ext(path)), - SK: sk, - SignPayload: true, - }) + var requestBody bytes.Buffer + fileHash := sha256.New() + writer := multipart.NewWriter(&requestBody) + fileWriter, err := writer.CreateFormFile("file", filename) require.NoError(t, err) - require.NotNil(t, resp) - jResp, err := json.Marshal(resp) + _, err = io.Copy(fileWriter, io.TeeReader(img, fileHash)) + require.NoError(t, err) + require.NoError(t, writer.WriteField("caption", caption)) + require.NoError(t, writer.WriteField("content_type", gomime.TypeByExtension(filepath.Ext(path)))) + require.NoError(t, writer.WriteField("no_transform", "true")) + err = writer.Close() require.NoError(t, err) - log.Println(filename, string(jResp)) - result(resp) + httpResp := authorizedReq(t, ctx, sk, "POST", "https://localhost:9997/files", hex.EncodeToString(fileHash.Sum(nil)), writer.FormDataContentType(), &requestBody, master) + require.NotNil(t, httpResp) + switch httpResp.StatusCode { + case http.StatusOK, http.StatusCreated, http.StatusAccepted: + var resp nip96.UploadResponse + err = json.NewDecoder(httpResp.Body).Decode(&resp) + require.NoError(t, err) + result(&resp) + default: + t.Fatalf("unexpected http status code %v for upload %v by %v", httpResp.StatusCode, filename, sk) + } } -func download(t *testing.T, ctx context.Context, sk, fileHash string) (status int, locationUrl string) { +func download(t *testing.T, ctx context.Context, sk, fileHash string, masterPubkey ...string) (status int, locationUrl string) { t.Helper() http.DefaultClient.CheckRedirect = func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse @@ -205,7 +261,7 @@ func download(t *testing.T, ctx context.Context, sk, fileHash string) (status in defer func() { http.DefaultClient.CheckRedirect = func(req *http.Request, via []*http.Request) error { return nil } }() - resp := authorizedReq(t, ctx, sk, "GET", fmt.Sprintf("https://localhost:9997/media/%v", fileHash)) + resp := authorizedReq(t, ctx, sk, "GET", fmt.Sprintf("https://localhost:9997/files/%v", fileHash), "", "", nil, masterPubkey...) if resp.StatusCode == http.StatusFound { require.Equal(t, http.StatusFound, resp.StatusCode) locationUrl = resp.Header.Get("location") @@ -215,9 +271,9 @@ func download(t *testing.T, ctx context.Context, sk, fileHash string) (status in return resp.StatusCode, "" } -func list(t *testing.T, ctx context.Context, sk string, page, limit uint32) *listedFiles { +func list(t *testing.T, ctx context.Context, sk string, page, limit uint32, masterPubkey ...string) *listedFiles { t.Helper() - resp := authorizedReq(t, ctx, sk, "GET", fmt.Sprintf("https://localhost:9997/media?page=%v&count=%v", page, limit)) + resp := authorizedReq(t, ctx, sk, "GET", fmt.Sprintf("https://localhost:9997/files?page=%v&count=%v", page, limit), "", "", nil, masterPubkey...) require.Equal(t, http.StatusOK, resp.StatusCode) var files listedFiles body, err := io.ReadAll(resp.Body) @@ -226,9 +282,9 @@ func list(t *testing.T, ctx context.Context, sk string, page, limit uint32) *lis return &files } -func deleteFile(t *testing.T, ctx context.Context, sk string, fileHash string) int { +func deleteFile(t *testing.T, ctx context.Context, sk string, fileHash string, masterKey ...string) int { t.Helper() - resp := authorizedReq(t, ctx, sk, "DELETE", fmt.Sprintf("https://localhost:9997/media/%v", fileHash)) + resp := authorizedReq(t, ctx, sk, "DELETE", fmt.Sprintf("https://localhost:9997/files/%v", fileHash), "", "", nil, masterKey...) if resp.StatusCode == http.StatusOK { body, err := io.ReadAll(resp.Body) require.NoError(t, err) @@ -244,11 +300,12 @@ func deleteFile(t *testing.T, ctx context.Context, sk string, fileHash string) i return resp.StatusCode } -func authorizedReq(t *testing.T, ctx context.Context, sk, method, url string) *http.Response { +func authorizedReq(t *testing.T, ctx context.Context, sk, method, url, fileHash, contentType string, body io.Reader, masterKey ...string) *http.Response { t.Helper() - uploadReq, err := http.NewRequest(method, url, nil) + uploadReq, err := http.NewRequest(method, url, body) + uploadReq.Header.Set("Content-Type", contentType) require.NoError(t, err) - auth, err := generateAuthHeader(t, sk, method, uploadReq.URL) + auth, err := generateAuthHeader(t, sk, method, fileHash, uploadReq.URL, masterKey...) require.NoError(t, err) uploadReq.Header.Set("Authorization", auth) resp, err := http.DefaultClient.Do(uploadReq.WithContext(ctx)) @@ -268,7 +325,7 @@ func expectedResponse(caption string) *nip96.UploadResponse { Content string `json:"content"` }{ Tags: nostr.Tags{ - nostr.Tag{"summary", "image/profile.png"}, + nostr.Tag{"summary", "profile.png"}, nostr.Tag{"ox", "b2b8cf9202b45dad7e137516bcf44b915ce30b39c3b294629a9b6b8fa1585292"}, nostr.Tag{"x", "b2b8cf9202b45dad7e137516bcf44b915ce30b39c3b294629a9b6b8fa1585292"}, nostr.Tag{"m", "image/png"}, @@ -286,7 +343,7 @@ func expectedResponse(caption string) *nip96.UploadResponse { Content string `json:"content"` }{ Tags: nostr.Tags{ - nostr.Tag{"summary", "image/ice.jpg"}, + nostr.Tag{"summary", "ice.jpg"}, nostr.Tag{"ox", "777d453395088530ce8de776fe54c3e5ace548381007b743e067844858962218"}, nostr.Tag{"x", "777d453395088530ce8de776fe54c3e5ace548381007b743e067844858962218"}, nostr.Tag{"m", "image/png"}, @@ -295,6 +352,42 @@ func expectedResponse(caption string) *nip96.UploadResponse { Content: "ice profile pic", }, }, + "text file": &nip96.UploadResponse{ + Status: "success", + Message: "Upload successful.", + ProcessingURL: "", + Nip94Event: struct { + Tags nostr.Tags `json:"tags"` + Content string `json:"content"` + }{ + Tags: nostr.Tags{ + nostr.Tag{"summary", "text.txt"}, + nostr.Tag{"ox", "982d9e3eb996f559e633f4d194def3761d909f5a3b647d1a851fead67c32c9d1"}, + nostr.Tag{"x", "982d9e3eb996f559e633f4d194def3761d909f5a3b647d1a851fead67c32c9d1"}, + nostr.Tag{"m", "text/plain"}, + nostr.Tag{"size", "4"}, + }, + Content: "text file", + }, + }, + "master's file": &nip96.UploadResponse{ + Status: "success", + Message: "Upload successful.", + ProcessingURL: "", + Nip94Event: struct { + Tags nostr.Tags `json:"tags"` + Content string `json:"content"` + }{ + Tags: nostr.Tags{ + nostr.Tag{"summary", "master.txt"}, + nostr.Tag{"ox", "fc613b4dfd6736a7bd268c8a0e74ed0d1c04a959f59dd74ef2874983fd443fc9"}, + nostr.Tag{"x", "fc613b4dfd6736a7bd268c8a0e74ed0d1c04a959f59dd74ef2874983fd443fc9"}, + nostr.Tag{"m", "text/plain"}, + nostr.Tag{"size", "6"}, + }, + Content: "master's file", + }, + }, } return expectedResponses[caption] } @@ -317,7 +410,7 @@ func initStorage(ctx context.Context, path string) { http.DefaultClient.Transport = transportOverride } -func generateAuthHeader(t *testing.T, sk, method string, urlValue *url.URL) (string, error) { +func generateAuthHeader(t *testing.T, sk, method, fileHash string, urlValue *url.URL, masterPubkey ...string) (string, error) { t.Helper() pk, err := nostr.GetPublicKey(sk) if err != nil { @@ -331,8 +424,12 @@ func generateAuthHeader(t *testing.T, sk, method string, urlValue *url.URL) (str Tags: nostr.Tags{ nostr.Tag{"u", urlValue.String()}, nostr.Tag{"method", method}, + nostr.Tag{"payload", fileHash}, }, } + if len(masterPubkey) > 0 && masterPubkey[0] != "" { + event.Tags = append(event.Tags, nostr.Tag{"b", masterPubkey[0]}) + } require.NoError(t, event.Sign(sk)) b, err := json.Marshal(event) @@ -366,7 +463,7 @@ func BenchmarkUploadFiles(b *testing.B) { defer img.Close() start := time.Now() resp, err := nip96.Upload(ctx, nip96.UploadRequest{ - Host: "https://localhost:9997/media", + Host: "https://localhost:9997/files", File: img, Filename: "profile.png", Caption: "ice profile pic", diff --git a/server/server.go b/server/server.go index 0d2b74a..8a2a4c4 100644 --- a/server/server.go +++ b/server/server.go @@ -21,9 +21,9 @@ func ListenAndServe(ctx context.Context, cancel context.CancelFunc, config *wsse func (r *router) RegisterRoutes(ctx context.Context, wsroutes wsserver.Router) { uploader := httpserver.NewUploadHandler(ctx) wsroutes.Any("/", wsserver.WithWS(wsserver.NewHandler(), httpserver.NewNIP11Handler())). - POST("/media", uploader.Upload()). - GET("/media", uploader.ListFiles()). - GET("/media/:file", uploader.Download()). - DELETE("/media/:file", uploader.Delete()). + POST("/files", uploader.Upload()). + GET("/files", uploader.ListFiles()). + GET("/files/:file", uploader.Download()). + DELETE("/files/:file", uploader.Delete()). GET("/.well-known/nostr/nip96.json", uploader.NIP96Info()) } diff --git a/storage/download.go b/storage/download.go index 25e7542..8a81781 100644 --- a/storage/download.go +++ b/storage/download.go @@ -22,6 +22,9 @@ func (c *client) DownloadUrl(userPubkey string, fileHash string) (string, error) if err != nil { return "", errors.Wrapf(err, "failed to get bagID for the user %v", userPubkey) } + if bag == nil { + return "", ErrNotFound + } bs, err := c.buildBootstrapNodeInfo(bag) if err != nil { return "", errors.Wrapf(err, "failed to build bootstap for bag %v", hex.EncodeToString(bag.BagID)) @@ -51,7 +54,7 @@ func acceptNewBag(ctx context.Context, event *model.Event) error { return errors.Newf("malformed url tag %v", urlTag) } bootstrap := fileUrl.Query().Get("bootstrap") - if err = globalClient.newBagIDPromoted(ctx, event.PubKey, infohash, &bootstrap); err != nil { + if err = globalClient.newBagIDPromoted(ctx, event.GetMasterPublicKey(), infohash, &bootstrap); err != nil { return errors.Wrapf(err, "failed to promote new bag ID %v for user %v", infohash, event.PubKey) } return nil diff --git a/storage/global.go b/storage/global.go index f81ace8..2037479 100644 --- a/storage/global.go +++ b/storage/global.go @@ -97,19 +97,22 @@ func acceptDeletion(ctx context.Context, event *model.Event) error { } else { return errors.Errorf("malformed x tag in event %v", originalEvent.ID) } - bag, err := globalClient.bagByUser(event.PubKey) + bag, err := globalClient.bagByUser(event.GetMasterPublicKey()) if err != nil { - return errors.Wrapf(err, "failed to get bagID for the user %v", event.PubKey) + return errors.Wrapf(err, "failed to get bagID for the user %v", event.GetMasterPublicKey()) + } + if bag == nil { + return errors.Errorf("bagID for user %v not found", event.GetMasterPublicKey()) } file, err := globalClient.detectFile(bag, fileHash) if err != nil { return errors.Wrapf(err, "failed to detect file %v in bag %v", fileHash, hex.EncodeToString(bag.BagID)) } - userRoot, _ := globalClient.BuildUserPath(event.PubKey, "") + userRoot, _ := globalClient.BuildUserPath(event.GetMasterPublicKey(), "") if err := os.Remove(filepath.Join(userRoot, file)); err != nil && !errors.Is(err, os.ErrNotExist) { return errors.Wrapf(err, "failed to delete file %v", file) } - if _, _, _, err := globalClient.StartUpload(ctx, event.PubKey, file, fileHash, nil); err != nil { + if _, _, _, err := globalClient.StartUpload(ctx, event.PubKey, event.GetMasterPublicKey(), file, fileHash, nil); err != nil { return errors.Wrapf(err, "failed to rebuild bag with deleted file") } return nil diff --git a/storage/storage.go b/storage/storage.go index b9fa0ae..f6133eb 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -31,18 +31,18 @@ import ( type ( StorageClient interface { io.Closer - StartUpload(ctx context.Context, userPubkey, relativePathToFileForUrl, fileHash string, newFile *FileMetaInput) (bagID, url string, existed bool, err error) - BuildUserPath(userPubKey, contentType string) (string, string) - DownloadUrl(userPubkey, fileSha256 string) (string, error) - ListFiles(userPubkey string, page, count uint32) (totalFiles uint32, files []*FileMetadata, err error) - Delete(userPubkey string, fileSha256 string) error + StartUpload(ctx context.Context, userPubKey, masterKey, relativePathToFileForUrl, fileHash string, newFile *FileMetaInput) (bagID, url string, existed bool, err error) + BuildUserPath(masterKey, contentType string) (string, string) + DownloadUrl(masterKey, fileSha256 string) (string, error) + ListFiles(masterKey string, page, count uint32) (totalFiles uint32, files []*FileMetadata, err error) + Delete(userPubkey, masterKey string, fileSha256 string) error } Bootstrap struct { Overlay *overlay.Node DHT *dht.Node } headerData struct { - User string `json:"u"` + Master string `json:"m"` FileMetadata map[string]*FileMetaInput `json:"f"` FileHash map[string]string `json:"fh"` } @@ -51,6 +51,7 @@ type ( Caption string `json:"c"` Alt string `json:"a"` CreatedAt uint64 `json:"cAt"` + Owner string `json:"o"` } FileMetadata struct { *nip94.FileMetadata @@ -78,7 +79,10 @@ type ( } ) -var ErrNotFound = storage.ErrFileNotExist +var ( + ErrNotFound = storage.ErrFileNotExist + ErrForbidden = errors.New("forbidden") +) func (c *client) fileMeta(bag *storage.Torrent) (*headerData, error) { var desc headerData @@ -97,6 +101,10 @@ func (c *client) detectFile(bag *storage.Torrent, fileHash string) (string, erro if err != nil { return "", errors.Wrapf(err, "failed to parse bag header data %v", hex.EncodeToString(bag.BagID)) } + return c.detectFileFromMeta(bag, metadata, fileHash) +} + +func (c *client) detectFileFromMeta(bag *storage.Torrent, metadata *headerData, fileHash string) (string, error) { name := metadata.FileHash[fileHash] f, err := bag.GetFileOffsets(name) if err != nil { @@ -184,16 +192,29 @@ func (c *client) ListFiles(userPubKey string, page, limit uint32) (total uint32, return bag.Header.FilesCount, res, nil } -func (c *client) Delete(userPubKey, fileHash string) error { - bag, err := c.bagByUser(userPubKey) +func (c *client) Delete(userPubKey, masterKey, fileHash string) error { + bag, err := c.bagByUser(masterKey) if err != nil { return errors.Wrapf(err, "failed to get bagID for the user %v", userPubKey) } - file, err := c.detectFile(bag, fileHash) + if bag == nil { + return ErrNotFound + } + var metadata *headerData + metadata, err = c.fileMeta(bag) + if err != nil { + return errors.Wrapf(err, "failed to parse bag header data %v", hex.EncodeToString(bag.BagID)) + } + file, err := c.detectFileFromMeta(bag, metadata, fileHash) if err != nil { return errors.Wrapf(err, "failed to detect file %v in bag %v", fileHash, hex.EncodeToString(bag.BagID)) } - userPath, _ := c.BuildUserPath(userPubKey, "") + if userPubKey != masterKey { + if metadata.FileMetadata[file].Owner == masterKey { + return ErrForbidden + } + } + userPath, _ := c.BuildUserPath(masterKey, "") err = os.Remove(filepath.Join(userPath, file)) if err != nil { return errors.Wrapf(err, "failed to remove file %v (%v)", fileHash, filepath.Join(userPath, file)) diff --git a/storage/upload.go b/storage/upload.go index 6d416f5..d608e89 100644 --- a/storage/upload.go +++ b/storage/upload.go @@ -22,10 +22,10 @@ import ( "github.com/xssnick/tonutils-storage/storage" ) -func (c *client) StartUpload(ctx context.Context, userPubkey, relativePathToFileForUrl, hash string, newFile *FileMetaInput) (bagID, url string, existed bool, err error) { - existingBagForUser, err := c.bagByUser(userPubkey) +func (c *client) StartUpload(ctx context.Context, userPubKey, masterPubKey, relativePathToFileForUrl, hash string, newFile *FileMetaInput) (bagID, url string, existed bool, err error) { + existingBagForUser, err := c.bagByUser(masterPubKey) if err != nil { - return "", "", false, errors.Wrapf(err, "failed to find existing bag for user %s", userPubkey) + return "", "", false, errors.Wrapf(err, "failed to find existing bag for user %s", masterPubKey) } var existingHD headerData if existingBagForUser != nil { @@ -38,18 +38,23 @@ func (c *client) StartUpload(ctx context.Context, userPubkey, relativePathToFile _, existed = existingHD.FileHash[hash] if existed { if existingBagForUser != nil { - url, err = c.DownloadUrl(userPubkey, hash) + url, err = c.DownloadUrl(masterPubKey, hash) if err != nil { + if errors.Is(err, storage.ErrFileNotExist) { + existed = false + } return "", "", false, - errors.Wrapf(err, "failed to build download url for already existing file %v/%v(%v)", userPubkey, relativePathToFileForUrl, hash) + errors.Wrapf(err, "failed to build download url for already existing file %v/%v(%v)", masterPubKey, relativePathToFileForUrl, hash) + } + if existed { + return hex.EncodeToString(existingBagForUser.BagID), url, existed, nil } - return hex.EncodeToString(existingBagForUser.BagID), url, existed, nil - } + } } var bs []*Bootstrap var bag *storage.Torrent - bag, bs, err = c.upload(ctx, userPubkey, relativePathToFileForUrl, hash, newFile, &existingHD) + bag, bs, err = c.upload(ctx, userPubKey, masterPubKey, relativePathToFileForUrl, hash, newFile, &existingHD) if err != nil { return "", "", false, errors.Wrapf(err, "failed to start upload of %v", relativePathToFileForUrl) } @@ -59,7 +64,7 @@ func (c *client) StartUpload(ctx context.Context, userPubkey, relativePathToFile if err != nil { return "", "", false, errors.Wrapf(err, "failed to get just created file from new bag") } - fullFilePath := filepath.Join(c.rootStoragePath, userPubkey, relativePathToFileForUrl) + fullFilePath := filepath.Join(c.rootStoragePath, masterPubKey, relativePathToFileForUrl) go c.stats.ProcessFile(fullFilePath, gomime.TypeByExtension(filepath.Ext(fullFilePath)), uplFile.Size) } @@ -70,22 +75,22 @@ func (c *client) StartUpload(ctx context.Context, userPubkey, relativePathToFile return bagID, url, existed, err } -func (c *client) upload(ctx context.Context, user, relativePath, hash string, fileMeta *FileMetaInput, headerMetadata *headerData) (torrent *storage.Torrent, bootstrap []*Bootstrap, err error) { +func (c *client) upload(ctx context.Context, user, master, relativePath, hash string, fileMeta *FileMetaInput, headerMetadata *headerData) (torrent *storage.Torrent, bootstrap []*Bootstrap, err error) { if fileMeta != nil { c.newFilesMx.Lock() - if userNewFiles, hasNewFiles := c.newFiles[user]; !hasNewFiles || userNewFiles == nil { - c.newFiles[user] = make(map[string]*FileMetaInput) + if userNewFiles, hasNewFiles := c.newFiles[master]; !hasNewFiles || userNewFiles == nil { + c.newFiles[master] = make(map[string]*FileMetaInput) } - c.newFiles[user][relativePath] = fileMeta + c.newFiles[master][relativePath] = fileMeta c.newFilesMx.Unlock() } - rootUserPath, _ := c.BuildUserPath(user, "") + rootUserPath, _ := c.BuildUserPath(master, "") refs, err := c.progressStorage.GetAllFilesRefsInDir(rootUserPath) if err != nil { return nil, nil, errors.Wrap(err, "failed to detect shareable files") } headerMD := &headerData{ - User: user, + Master: master, FileMetadata: headerMetadata.FileMetadata, FileHash: headerMetadata.FileHash, } @@ -96,6 +101,7 @@ func (c *client) upload(ctx context.Context, user, relativePath, hash string, fi headerMD.FileMetadata = make(map[string]*FileMetaInput) } if fileMeta != nil { + fileMeta.Owner = user headerMD.FileMetadata[relativePath] = fileMeta headerMD.FileHash[hex.EncodeToString(fileMeta.Hash)] = relativePath } else { @@ -103,7 +109,7 @@ func (c *client) upload(ctx context.Context, user, relativePath, hash string, fi delete(headerMD.FileHash, hash) } c.newFilesMx.RLock() - for key, value := range c.newFiles[user] { + for key, value := range c.newFiles[master] { headerMD.FileMetadata[key] = value headerMD.FileHash[hex.EncodeToString(value.Hash)] = key } @@ -114,14 +120,14 @@ func (c *client) upload(ctx context.Context, user, relativePath, hash string, fi return nil, nil, errors.Wrap(err, "failed to put file hashes") } header := &storage.TorrentHeader{ - DirNameSize: uint32(len(user)), - DirName: []byte(user), + DirNameSize: uint32(len(master)), + DirName: []byte(master), Data: headerMDSerialized, TotalDataSize: uint64(len(headerMDSerialized)), } var wg sync.WaitGroup wg.Add(1) - tr, err := storage.CreateTorrentWithInitialHeader(ctx, c.rootStoragePath, user, header, c.progressStorage, c.conn, refs, func(done uint64, max uint64) { + tr, err := storage.CreateTorrentWithInitialHeader(ctx, c.rootStoragePath, master, header, c.progressStorage, c.conn, refs, func(done uint64, max uint64) { if done == max { wg.Done() } @@ -134,7 +140,7 @@ func (c *client) upload(ctx context.Context, user, relativePath, hash string, fi return nil, nil, errors.Wrap(err, "failed to start bag upload") } wg.Wait() - err = c.saveUploadTorrent(tr, user) + err = c.saveUploadTorrent(tr, master) if err != nil { return nil, nil, errors.Wrap(err, "failed to save updated bag") } From a46627cc66c2866e9e932224b1d1f229041e36dd Mon Sep 17 00:00:00 2001 From: ice-cronus <105345303+ice-cronus@users.noreply.github.com> Date: Fri, 18 Oct 2024 14:30:05 +0300 Subject: [PATCH 13/18] improve logging, rebase --- go.mod | 9 ++-- go.sum | 24 +++------- server/http/auth_nip98.go | 7 +-- server/http/storage_nip_96_test.go | 6 +-- server/ws/fixture/contract.go | 2 +- storage/download.go | 47 +++++++++++++++----- storage/fixture/fixture.go | 2 +- storage/global.go | 32 ++++++++++++-- storage/statistics/metadata/image.go | 2 +- storage/statistics/metadata/metadata.go | 4 +- storage/statistics/metadata/video.go | 58 ++++++++++++------------- storage/statistics/statistics.go | 8 ++-- storage/storage.go | 55 +++++++++++++++++++---- storage/upload.go | 3 +- 14 files changed, 170 insertions(+), 89 deletions(-) diff --git a/go.mod b/go.mod index 5d3da23..6101769 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/jmoiron/sqlx v1.4.0 github.com/mattn/go-sqlite3 v1.14.24 github.com/mxschmitt/golang-combinations v1.2.0 - github.com/nbd-wtf/go-nostr v0.39.0 + github.com/nbd-wtf/go-nostr v0.39.1 github.com/quic-go/quic-go v0.48.0 github.com/quic-go/webtransport-go v0.8.0 github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 @@ -43,7 +43,6 @@ require ( atomicgo.dev/cursor v0.2.0 // indirect atomicgo.dev/keyboard v0.2.9 // indirect atomicgo.dev/schedule v0.1.0 // indirect - filippo.io/mkcert v0.0.0-00010101000000-000000000000 // indirect github.com/aws/aws-sdk-go v1.55.5 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect @@ -68,7 +67,7 @@ require ( github.com/goccy/go-json v0.10.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/google/pprof v0.0.0-20241009165004-a3522334989c // indirect + github.com/google/pprof v0.0.0-20241017200806-017d972448fc // indirect github.com/gookit/color v1.5.4 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -106,7 +105,7 @@ require ( github.com/u2takey/go-utils v0.3.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - go.uber.org/mock v0.4.0 // indirect + go.uber.org/mock v0.5.0 // indirect golang.org/x/arch v0.11.0 // indirect golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect @@ -119,6 +118,4 @@ require ( golang.org/x/tools v0.26.0 // indirect google.golang.org/protobuf v1.35.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - howett.net/plist v1.0.0 // indirect - software.sslmate.com/src/go-pkcs12 v0.2.0 // indirect ) diff --git a/go.sum b/go.sum index b37cd63..08b12ea 100644 --- a/go.sum +++ b/go.sum @@ -108,8 +108,8 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20241009165004-a3522334989c h1:NDovD0SMpBYXlE1zJmS1q55vWB/fUQBcPAqAboZSccA= -github.com/google/pprof v0.0.0-20241009165004-a3522334989c/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/pprof v0.0.0-20241017200806-017d972448fc h1:NGyrhhFhwvRAZg02jnYVg3GBQy0qGBKmFQJwaPmpmxs= +github.com/google/pprof v0.0.0-20241017200806-017d972448fc/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/uuid v1.1.1/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= @@ -133,7 +133,6 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jamiealquiza/tachymeter v2.0.0+incompatible h1:mGiF1DGo8l6vnGT8FXNNcIXht/YmjzfraiUprXYwJ6g= github.com/jamiealquiza/tachymeter v2.0.0+incompatible/go.mod h1:Ayf6zPZKEnLsc3winWEXJRkTBhdHo58HODAu1oFJkYU= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= @@ -150,8 +149,6 @@ github.com/kevinms/leakybucket-go v0.0.0-20200115003610-082473db97ca/go.mod h1:p github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 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/kixelated/mkcert v1.4.4-days h1:T2P9W4ruEfgLHOl5UljPwh0d79FbFWkSe2IONcUBxG8= -github.com/kixelated/mkcert v1.4.4-days/go.mod h1:VyvOchVuAye3BoUsPUOOofKygVwLV2KQMVFJNRq+1dA= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= @@ -192,8 +189,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mxschmitt/golang-combinations v1.2.0 h1:V5E7MncIK8Yr1SL/SpdqMuSquFsfoIs5auI7Y3n8z14= github.com/mxschmitt/golang-combinations v1.2.0/go.mod h1:RCm5eR03B+JrBOMRDLsKZWShluXdrHu+qwhPEJ0miBM= -github.com/nbd-wtf/go-nostr v0.39.0 h1:C2s6+wUqOW1ngzIn10tTH8VOOJ9MWWg1rbJDGDbOIBU= -github.com/nbd-wtf/go-nostr v0.39.0/go.mod h1:UTFitPKazxHbGlFMh0QvB8MpOyuTViBU4Zf05Z1aUdk= +github.com/nbd-wtf/go-nostr v0.39.1 h1:sz9GxRzMxofil9w5l0eyNs+3nsyQlHU3BZf0Cc79vpA= +github.com/nbd-wtf/go-nostr v0.39.1/go.mod h1:UTFitPKazxHbGlFMh0QvB8MpOyuTViBU4Zf05Z1aUdk= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a h1:dlRvE5fWabOchtH7znfiFCcOvmIYgOeAS5ifBXBlh9Q= github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a/go.mod h1:hVoHR2EVESiICEMbg137etN/Lx+lSrHPTD39Z/uE+2s= @@ -296,8 +293,8 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= -go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= gocv.io/x/gocv v0.25.0/go.mod h1:Rar2PS6DV+T4FL+PM535EImD/h13hGVaHhnCu1xarBs= golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4= golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= @@ -305,7 +302,6 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= @@ -329,7 +325,6 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -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.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= @@ -353,7 +348,6 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -380,7 +374,6 @@ golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 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.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= @@ -415,7 +408,6 @@ gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -427,13 +419,9 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= -howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= pgregory.net/rand v1.0.2 h1:ASEbkvwOmY/UPF2evJPBJ8XZg71xdKWYdByqKapI7Vw= pgregory.net/rand v1.0.2/go.mod h1:EyNx8APnDE3Svi8sWgUZ5lOiz60cNZUPPBTyzOUpPl4= pgregory.net/rapid v0.4.8 h1:d+5SGZWUbJPbl3ss6tmPFqnNeQR6VDOFly+eTjwPiEw= pgregory.net/rapid v0.4.8/go.mod h1:Z5PbWqjvWR1I3UGjvboUuan4fe4ZYEYNLNQLExzCoUs= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= -software.sslmate.com/src/go-pkcs12 v0.2.0 h1:nlFkj7bTysH6VkC4fGphtjXRbezREPgrHuJG20hBGPE= -software.sslmate.com/src/go-pkcs12 v0.2.0/go.mod h1:23rNcYsMabIc1otwLpTkCCPwUq6kQsTyowttG/as0kQ= diff --git a/server/http/auth_nip98.go b/server/http/auth_nip98.go index f145284..3dca1ec 100644 --- a/server/http/auth_nip98.go +++ b/server/http/auth_nip98.go @@ -5,14 +5,15 @@ package http import ( "context" "encoding/base64" - "github.com/ice-blockchain/subzero/database/query" - "github.com/ice-blockchain/subzero/model" "net/url" "time" "github.com/cockroachdb/errors" "github.com/gin-gonic/gin" "github.com/nbd-wtf/go-nostr" + + "github.com/ice-blockchain/subzero/database/query" + "github.com/ice-blockchain/subzero/model" ) type ( @@ -117,7 +118,7 @@ func (t *nostrToken) ValidateAttestation(ctx context.Context, kind int, now time return nil } attestationEvent := query.GetStoredEvents(ctx, &model.Subscription{model.Filters{model.Filter{ - Kinds: []int{model.IceKindAttestation}, + Kinds: []int{model.CustomIONKindAttestation}, Tags: model.TagMap{ "p": []string{t.PubKey()}, }, diff --git a/server/http/storage_nip_96_test.go b/server/http/storage_nip_96_test.go index ca60d79..5207fca 100644 --- a/server/http/storage_nip_96_test.go +++ b/server/http/storage_nip_96_test.go @@ -66,11 +66,11 @@ func TestNIP96(t *testing.T) { user1PubKey, _ := nostr.GetPublicKey(user1) user2PubKey, _ := nostr.GetPublicKey(user2) var ev model.Event - ev.Kind = model.IceKindAttestation + ev.Kind = model.CustomIONKindAttestation ev.CreatedAt = 1 ev.Tags = model.Tags{ - {model.TagAttestationName, user1PubKey, "", model.IceAttestationKindActive + ":" + strconv.Itoa(int(now-10))}, - {model.TagAttestationName, user2PubKey, "", model.IceAttestationKindActive + ":" + strconv.Itoa(int(now-5))}, + {model.TagAttestationName, user1PubKey, "", model.CustomIONAttestationKindActive + ":" + strconv.Itoa(int(now-10))}, + {model.TagAttestationName, user2PubKey, "", model.CustomIONAttestationKindActive + ":" + strconv.Itoa(int(now-5))}, } require.NoError(t, ev.Sign(master)) require.NoError(t, query.AcceptEvent(ctx, &ev)) diff --git a/server/ws/fixture/contract.go b/server/ws/fixture/contract.go index 4e4e104..257085f 100644 --- a/server/ws/fixture/contract.go +++ b/server/ws/fixture/contract.go @@ -29,7 +29,7 @@ var ( type ( MockService struct { - server internal.Server + server internal.Server handlersMx sync.Mutex Handlers map[adapters.WSWriter]struct{} diff --git a/storage/download.go b/storage/download.go index 8a81781..ed388f7 100644 --- a/storage/download.go +++ b/storage/download.go @@ -66,6 +66,7 @@ func (c *client) newBagIDPromoted(ctx context.Context, user, bagID string, boots return errors.Wrapf(err, "failed to find existing bag for user %s", user) } if existingBagForUser != nil { + log.Printf("[STORAGE] INFO: GOT NIP-94 with new files for user %v, replacing %v with %v", user, hex.EncodeToString(existingBagForUser.BagID), bagID) existingBagForUser.Stop() if err = c.progressStorage.RemoveTorrent(existingBagForUser, false); err != nil { return errors.Wrapf(err, "failed to replace bag for user %s", user) @@ -85,7 +86,7 @@ func (c *client) download(ctx context.Context, bagID, user string, bootstrap *st if len(bag) != 32 { return errors.Wrapf(err, "invalid bagID %v, should be len 32", bagID) } - + log.Printf("[STORAGE] INFO: ADDING %v for user %v TO DOWNLOADS, Q %v", bagID, user, len(c.downloadQueue)) tor := c.progressStorage.GetTorrent(bag) if tor == nil { tor = storage.NewTorrent(c.rootStoragePath, c.progressStorage, c.conn) @@ -108,27 +109,48 @@ func (c *client) download(ctx context.Context, bagID, user string, bootstrap *st func (c *client) torrentStateCallback(tor *storage.Torrent, user *string) func(event storage.Event) { return func(event storage.Event) { + usr := "" + if user != nil { + usr = *user + } switch event.Name { case storage.EventDone: tor.Stop() + log.Printf("[STORAGE] INFO: bag %v for user %v downloaded (%v files, %v bytes), disabling download", hex.EncodeToString(tor.BagID), usr, tor.Header.FilesCount, tor.Info.FileSize) if pErr := tor.Start(true, false, false); pErr != nil { - log.Printf("ERROR: failed to stop torrent upload after downloading data: %v", pErr) + log.Printf("ERROR: failed to stop torrent download after downloading data for bag %v user %v: %v", hex.EncodeToString(tor.BagID), usr, pErr) } c.activeDownloadsMx.Lock() delete(c.activeDownloads, hex.EncodeToString(tor.BagID)) c.activeDownloadsMx.Unlock() + if pErr := c.saveTorrent(tor, user, nil); pErr != nil { + log.Printf("ERROR: failed save torrent %v with stopped download after downloading: %v", hex.EncodeToString(tor.BagID), pErr) + } case storage.EventBagResolved: if _, isUplActive := tor.IsActive(); !isUplActive { + log.Printf("[STORAGE] INFO: bag %v for user %v header resolved (%v files, %v bytes), enabling upload to serve clients with chunks we own", hex.EncodeToString(tor.BagID), usr, tor.Header.FilesCount, tor.Info.FileSize) if pErr := tor.StartWithCallback(true, true, false, c.torrentStateCallback(tor, user)); pErr != nil { - log.Printf("ERROR: failed to start torrent upload after downloading header: %v", pErr) + log.Printf("ERROR: failed to start torrent %v upload after downloading header: %v", hex.EncodeToString(tor.BagID), pErr) + } + if user != nil { + m, err := c.fileMeta(tor) + if err != nil { + log.Printf("[STORAGE] ERROR: failed to get file meta for bag althrough it is resolved %v: %v", hex.EncodeToString(tor.BagID), err) + } + if m != nil { + *user = m.Master + } + } + if pErr := c.saveTorrent(tor, user, nil); pErr != nil { + log.Printf("ERROR: failed save torrent %v with stopped download after downloading: %v", hex.EncodeToString(tor.BagID), pErr) } } + case storage.EventFileDownloaded: + log.Printf("[STORAGE] DEBUG: bag %v for user %v downloaded FILE %v", hex.EncodeToString(tor.BagID), usr, event.Value) + case storage.EventErr: + log.Printf("[STORAGE] ERROR: bag %v for user %v, occured error: %v", hex.EncodeToString(tor.BagID), usr, event.Value) } - if pErr := c.saveTorrent(tor, user, nil); pErr != nil { - log.Printf("ERROR: failed save torrent with stopped download after downloading: %v", pErr) - } - } } @@ -159,7 +181,7 @@ func (c *client) saveTorrent(tr *storage.Torrent, userPubKey *string, bs *string if err := c.progressStorage.SetTorrent(tr); err != nil { return errors.Wrap(err, "failed to save torrent into storage") } - if userPubKey != nil { + if userPubKey != nil && *userPubKey != "" { k := make([]byte, 3+64) copy(k, "ub:") copy(k[3:], *userPubKey) @@ -192,18 +214,23 @@ outerLoop: if downloading, _ := tor.IsActive(); downloading { continue } + usr := "" + if q.user != nil { + usr = *q.user + } + log.Printf("[STORAGE] INFO: starting download %v for user %v Q %v", hex.EncodeToString(tor.BagID), usr, len(c.downloadQueue)) if err := tor.StartWithCallback(false, true, false, c.torrentStateCallback(tor, q.user)); err != nil { log.Printf("ERROR: %v", errors.Wrapf(err, "failed to start new torrent %v", q.tor.BagID)) } if q.bootstrap != nil && *q.bootstrap != "" { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) if err := c.connectToBootstrap(ctx, tor, *q.bootstrap); err != nil { - log.Printf("failed to connect to bootstrap node for bag %v, waiting for DHT: %v", hex.EncodeToString(q.tor.BagID), err) + log.Printf("WARN: failed to connect to bootstrap node for bag %v, waiting for DHT: %v", hex.EncodeToString(q.tor.BagID), err) } cancel() } if err := c.saveTorrent(tor, q.user, q.bootstrap); err != nil { - log.Printf("failed save updated upload / download torrrent state %v: %v", hex.EncodeToString(q.tor.BagID), err) + log.Printf("ERROR: failed save updated upload / download torrrent state %v: %v", hex.EncodeToString(q.tor.BagID), err) } c.activeDownloadsMx.Lock() c.activeDownloads[hex.EncodeToString(tor.BagID)] = true diff --git a/storage/fixture/fixture.go b/storage/fixture/fixture.go index d6e406a..18b28e8 100644 --- a/storage/fixture/fixture.go +++ b/storage/fixture/fixture.go @@ -6,11 +6,11 @@ import ( "context" "crypto/sha256" "encoding/hex" - "github.com/cockroachdb/errors" "io" "os" "time" + "github.com/cockroachdb/errors" "github.com/fsnotify/fsnotify" ) diff --git a/storage/global.go b/storage/global.go index 2037479..46789a9 100644 --- a/storage/global.go +++ b/storage/global.go @@ -14,6 +14,7 @@ import ( "path/filepath" "runtime" "strconv" + "strings" "sync" "time" @@ -123,8 +124,17 @@ func MustInit(ctx context.Context, nodeKey ed25519.PrivateKey, tonConfigUrl, roo } func mustInit(ctx context.Context, nodeKey ed25519.PrivateKey, tonConfigUrl, rootStorage string, externalAddress net.IP, port int, debug bool) *client { - if debug { - storage.Logger = log.Println + storage.Logger = func(a ...any) { + if debug { + log.Println(a...) + } + if len(a) > 0 { + if s, isStr := a[0].(string); isStr { + if strings.Contains(strings.ToLower(s), "err") { + log.Println(a) + } + } + } } storage.DownloadThreads = threadsPerBagForDownloading adnl.Logger = func(v ...any) {} @@ -184,6 +194,10 @@ func mustInit(ctx context.Context, nodeKey ed25519.PrivateKey, tonConfigUrl, roo downloadQueue: make(chan queueItem, 1000000), activeDownloads: make(map[string]bool), activeDownloadsMx: &sync.RWMutex{}, + debug: debug, + } + if debug { + go cl.report(ctx) } loadMonitoringCh := make(chan *db.Event, 1000000) go func() { @@ -196,10 +210,22 @@ func mustInit(ctx context.Context, nodeKey ed25519.PrivateKey, tonConfigUrl, roo if bsErr != nil { log.Printf("WARN: failed to find stored bootstrap for bag %v: %v", hex.EncodeToString(ev.Torrent.BagID), bsErr) } + var usr string + if ev.Torrent.Header != nil { + var m *headerData + m, err = cl.fileMeta(ev.Torrent) + if err != nil { + log.Printf("INFO:loading bag %v into queue but it is not resolved yet: %v", hex.EncodeToString(ev.Torrent.BagID), err) + } + if m != nil { + usr = m.Master + } + } + log.Printf("[STORAGE] INFO: bag %v not yet started before restart put it into queue", hex.EncodeToString(ev.Torrent.BagID)) cl.downloadQueue <- queueItem{ tor: ev.Torrent, bootstrap: &bs, - user: nil, + user: &usr, } } } diff --git a/storage/statistics/metadata/image.go b/storage/statistics/metadata/image.go index f9963f7..1f363c3 100644 --- a/storage/statistics/metadata/image.go +++ b/storage/statistics/metadata/image.go @@ -3,9 +3,9 @@ package metadata import ( - "github.com/cockroachdb/errors" "path/filepath" + "github.com/cockroachdb/errors" "github.com/davidbyttow/govips/v2/vips" ) diff --git a/storage/statistics/metadata/metadata.go b/storage/statistics/metadata/metadata.go index 35c88e7..2484906 100644 --- a/storage/statistics/metadata/metadata.go +++ b/storage/statistics/metadata/metadata.go @@ -3,10 +3,10 @@ package metadata import ( - "github.com/cockroachdb/errors" "io" "strings" + "github.com/cockroachdb/errors" "github.com/hashicorp/go-multierror" ) @@ -15,9 +15,9 @@ type Extractor interface { Extract(filePath, contentType string, size uint64) (*Metadata, error) } type Metadata struct { + TypeMeta any Ext string Size uint64 - TypeMeta any } type extractor struct { extractorsByFileType map[string]Extractor diff --git a/storage/statistics/metadata/video.go b/storage/statistics/metadata/video.go index 53c6802..e46d981 100644 --- a/storage/statistics/metadata/video.go +++ b/storage/statistics/metadata/video.go @@ -19,51 +19,48 @@ type ( Streams []*VideoStream `json:"streams"` } VideoFormat struct { + Tags struct { + MajorBrand string `json:"major_brand"` + MinorVersion string `json:"minor_version"` + CompatibleBrands string `json:"compatible_brands"` + Encoder string `json:"encoder"` + LocationEng string `json:"location-eng"` + Location string `json:"location"` + } `json:"tags"` Filename string `json:"filename"` - NbStreams int `json:"nb_streams"` - NbPrograms int `json:"nb_programs"` FormatName string `json:"format_name"` FormatLongName string `json:"format_long_name"` StartTime string `json:"start_time"` Duration string `json:"duration"` Size string `json:"size"` BitRate string `json:"bit_rate"` + NbStreams int `json:"nb_streams"` + NbPrograms int `json:"nb_programs"` ProbeScore int `json:"probe_score"` - Tags struct { - MajorBrand string `json:"major_brand"` - MinorVersion string `json:"minor_version"` - CompatibleBrands string `json:"compatible_brands"` - Encoder string `json:"encoder"` - LocationEng string `json:"location-eng"` - Location string `json:"location"` - } `json:"tags"` } VideoStream struct { - Index int `json:"index"` - CodecName string `json:"codec_name"` - CodecLongName string `json:"codec_long_name"` - Profile string `json:"profile"` - CodecType string `json:"codec_type"` + Tags struct { + Language string `json:"language"` + HandlerName string `json:"handler_name"` + } `json:"tags"` CodecTimeBase string `json:"codec_time_base"` + Duration string `json:"duration"` + SampleRate string `json:"sample_rate"` + CodecType string `json:"codec_type"` + BitRate string `json:"bit_rate"` CodecTagString string `json:"codec_tag_string"` CodecTag string `json:"codec_tag"` SampleFmt string `json:"sample_fmt"` - SampleRate string `json:"sample_rate"` - Channels int `json:"channels"` + Profile string `json:"profile"` + CodecLongName string `json:"codec_long_name"` + CodecName string `json:"codec_name"` ChannelLayout string `json:"channel_layout"` - BitsPerSample int `json:"bits_per_sample"` RFrameRate string `json:"r_frame_rate"` AvgFrameRate string `json:"avg_frame_rate"` TimeBase string `json:"time_base"` - StartPts int `json:"start_pts"` + NbFrames string `json:"nb_frames"` StartTime string `json:"start_time"` - DurationTs int `json:"duration_ts"` - Duration string `json:"duration"` - BitRate string `json:"bit_rate"` MaxBitRate string `json:"max_bit_rate"` - NbFrames string `json:"nb_frames"` - Width int `json:"width"` - Height int `json:"height"` Disposition struct { Default int `json:"default"` Dub int `json:"dub"` @@ -78,10 +75,13 @@ type ( AttachedPic int `json:"attached_pic"` TimedThumbnails int `json:"timed_thumbnails"` } `json:"disposition"` - Tags struct { - Language string `json:"language"` - HandlerName string `json:"handler_name"` - } `json:"tags"` + BitsPerSample int `json:"bits_per_sample"` + DurationTs int `json:"duration_ts"` + StartPts int `json:"start_pts"` + Width int `json:"width"` + Height int `json:"height"` + Index int `json:"index"` + Channels int `json:"channels"` } ) diff --git a/storage/statistics/statistics.go b/storage/statistics/statistics.go index 03fc5bb..010964d 100644 --- a/storage/statistics/statistics.go +++ b/storage/statistics/statistics.go @@ -4,15 +4,17 @@ package statistics import ( "fmt" - "github.com/cockroachdb/errors" - "github.com/ice-blockchain/subzero/storage/statistics/metadata" - "github.com/rcrowley/go-metrics" "io" "log" "os" "path/filepath" "strconv" "time" + + "github.com/cockroachdb/errors" + "github.com/rcrowley/go-metrics" + + "github.com/ice-blockchain/subzero/storage/statistics/metadata" ) type ( diff --git a/storage/storage.go b/storage/storage.go index f6133eb..b1356ad 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -7,6 +7,7 @@ import ( "encoding/hex" "encoding/json" "io" + "log" "os" "path/filepath" "strconv" @@ -42,35 +43,36 @@ type ( DHT *dht.Node } headerData struct { - Master string `json:"m"` FileMetadata map[string]*FileMetaInput `json:"f"` FileHash map[string]string `json:"fh"` + Master string `json:"m"` } FileMetaInput struct { - Hash []byte `json:"h"` Caption string `json:"c"` Alt string `json:"a"` - CreatedAt uint64 `json:"cAt"` Owner string `json:"o"` + Hash []byte `json:"h"` + CreatedAt uint64 `json:"cAt"` } FileMetadata struct { *nip94.FileMetadata CreatedAt uint64 `json:"created_at"` } client struct { - conn *storage.Connector - db *leveldb.DB - server *storage.Server + stats statistics.Statistics progressStorage *db.Storage + server *storage.Server + conn *storage.Connector gateway *adnl.Gateway dht *dht.Client - rootStoragePath string newFiles map[string]map[string]*FileMetaInput newFilesMx *sync.RWMutex - stats statistics.Statistics + db *leveldb.DB downloadQueue chan queueItem activeDownloads map[string]bool activeDownloadsMx *sync.RWMutex + rootStoragePath string + debug bool } queueItem struct { tor *storage.Torrent @@ -86,6 +88,9 @@ var ( func (c *client) fileMeta(bag *storage.Torrent) (*headerData, error) { var desc headerData + if bag.Header == nil { + return nil, errors.Errorf("No header fetched yet for %v", hex.EncodeToString(bag.BagID)) + } hData := bag.Header.Data if len(hData) == 0 { hData = []byte("{}") @@ -238,3 +243,37 @@ func (c *client) Close() error { close(c.downloadQueue) return err.ErrorOrNil() } + +func (c *client) report(ctx context.Context) { + period := 1 * time.Hour + if c.debug { + period = 1 * time.Minute + } + for ctx.Err() == nil { + select { + case <-ctx.Done(): + return + case <-time.After(period): + activelyDownloading := 0 + activeUploading := 0 + notResolvedHeader := 0 + notResolvedInfo := 0 + all := c.progressStorage.GetAll() + for _, t := range all { + if t.IsDownloadAll() { + activelyDownloading++ + } + if _, upl := t.IsActive(); upl { + activeUploading++ + } + if t.Info == nil { + notResolvedInfo++ + } + if t.Header == nil { + notResolvedHeader++ + } + } + log.Printf("[STORAGE STATS] DEBUG: Q TO DOWNLOAD %v, DOWNLOADING %v, UPLOADING %v, RESOLVING INFO %v, RESOLVING HEADER %v TOTAL %v", len(c.downloadQueue), activelyDownloading, activeUploading, notResolvedInfo, notResolvedHeader, len(all)) + } + } +} diff --git a/storage/upload.go b/storage/upload.go index d608e89..41bc026 100644 --- a/storage/upload.go +++ b/storage/upload.go @@ -9,6 +9,7 @@ import ( "encoding/hex" "encoding/json" "fmt" + "log" "path/filepath" "sync" "time" @@ -59,6 +60,7 @@ func (c *client) StartUpload(ctx context.Context, userPubKey, masterPubKey, rela return "", "", false, errors.Wrapf(err, "failed to start upload of %v", relativePathToFileForUrl) } bagID = hex.EncodeToString(bag.BagID) + log.Printf("[STORAGE] INFO: new upload %v for user %v hash %v resulted in bag %v, total %v", relativePathToFileForUrl, masterPubKey, hash, bagID, bag.Header.FilesCount) if newFile != nil { uplFile, err := bag.GetFileOffsets(relativePathToFileForUrl) if err != nil { @@ -67,7 +69,6 @@ func (c *client) StartUpload(ctx context.Context, userPubKey, masterPubKey, rela fullFilePath := filepath.Join(c.rootStoragePath, masterPubKey, relativePathToFileForUrl) go c.stats.ProcessFile(fullFilePath, gomime.TypeByExtension(filepath.Ext(fullFilePath)), uplFile.Size) } - url, err = c.buildUrl(bagID, relativePathToFileForUrl, bs) if err != nil { return "", "", false, errors.Wrapf(err, "failed to build url for %v (bag %v)", relativePathToFileForUrl, bagID) From ec24d2f63d1e64cb6ab8b84a14dac62676af7a3c Mon Sep 17 00:00:00 2001 From: ice-cronus <105345303+ice-cronus@users.noreply.github.com> Date: Fri, 18 Oct 2024 14:49:59 +0300 Subject: [PATCH 14/18] fix updated go version --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 6101769..b662598 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/ice-blockchain/subzero -go 1.23.1 +go 1.23.2 replace ( filippo.io/mkcert => github.com/kixelated/mkcert v1.4.4-days From 705031cf3e632c909d836e60a46655e692848caf Mon Sep 17 00:00:00 2001 From: ice-cronus <105345303+ice-cronus@users.noreply.github.com> Date: Mon, 21 Oct 2024 11:13:26 +0300 Subject: [PATCH 15/18] 7 threads --- cmd/subzero/.testdata/regenerate.sh | 3 ++- go.mod | 2 +- go.sum | 4 ++-- storage/global.go | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/cmd/subzero/.testdata/regenerate.sh b/cmd/subzero/.testdata/regenerate.sh index 0a3b478..c2ede30 100755 --- a/cmd/subzero/.testdata/regenerate.sh +++ b/cmd/subzero/.testdata/regenerate.sh @@ -10,7 +10,8 @@ go run filippo.io/mkcert -ecdsa -install cp $(go run filippo.io/mkcert -CAROOT)/rootCA.pem ca.pem # Generate a new certificate for localhost -go run filippo.io/mkcert -ecdsa -days 13 -cert-file "$CRT" -key-file "$KEY" localhost $(hostname) 127.0.0.1 ::1 +NETWORK_IP=$(ifconfig | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1' | tail -1) +go run filippo.io/mkcert -ecdsa -days 13 -cert-file "$CRT" -key-file "$KEY" localhost $(hostname) 127.0.0.1 ::1 $NETWORK_IP # Compute the sha256 fingerprint of the certificate for WebTransport rm fingerprint.base64 || true diff --git a/go.mod b/go.mod index b662598..90ef315 100644 --- a/go.mod +++ b/go.mod @@ -47,7 +47,7 @@ require ( github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect github.com/bytedance/sonic v1.12.3 // indirect - github.com/bytedance/sonic/loader v0.2.0 // indirect + github.com/bytedance/sonic/loader v0.2.1 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect diff --git a/go.sum b/go.sum index 08b12ea..b4df5d4 100644 --- a/go.sum +++ b/go.sum @@ -28,8 +28,8 @@ github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtyd github.com/bytedance/sonic v1.12.3 h1:W2MGa7RCU1QTeYRTPE3+88mVC0yXmsRQRChiyVocVjU= github.com/bytedance/sonic v1.12.3/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= -github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM= -github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E= +github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM= github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY= github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= diff --git a/storage/global.go b/storage/global.go index 46789a9..c53aec0 100644 --- a/storage/global.go +++ b/storage/global.go @@ -39,7 +39,7 @@ const DefaultConfigUrl = "https://ton.org/global.config.json" var ConcurrentBagsDownloading = runtime.NumCPU() * 10 -const threadsPerBagForDownloading = 1 +const threadsPerBagForDownloading = 7 func Client() StorageClient { return globalClient From 5c4f47dd9e240e325378ea06b78600f74230aa23 Mon Sep 17 00:00:00 2001 From: ice-cronus <105345303+ice-cronus@users.noreply.github.com> Date: Mon, 21 Oct 2024 12:21:54 +0300 Subject: [PATCH 16/18] readme update --- README.md | 18 ++++++++++++++++++ server/http/auth_nip98.go | 6 +++--- server/http/storage_nip96.go | 12 ++++++------ 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 5b27f1a..6751d9c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,23 @@ # subzero +## Starting +```bash +subzero --port=9998 --cert=./cmd/subzero/.testdata/localhost.crt --key=./cmd/subzero/.testdata/localhost.key --adnl-external-ip=127.0.0.1 --adnl-port=11512 --storage-root=./../.uploads --adnl-node-key= [--global-config-url=file://path/to/global.json] +``` +Parameters: +* port - port to start https/ws server for user iteraction +* cert - tls cert for https / ws server +* key - tls key for https / ws server +* adnl-external-ip - node's external address (needed to serve storage uploads), other nodes connect to : +* adnl-port - port to start adnl / storage server +* storage-root - root storage to store files `//files_here.ext` +* adnl-node-key - adnl key for the node in hex form (length: 64 bytes, 128 in hex), i.e `6cc91d96a67bcae7a7a4df91f9c04469f652cf007b33460c60c0649f1777df5703bec10efbd4520126e53d0d70552f873ba843d54352d59fa28989bdf3925a7d` = random if not specified +```go +_, key ,_ := ed25519.GenerateKey(nil) +fmt.Println(hex.EncodeToString(key)) +``` +* global-config-url - url (supports file:// schema) of global config (to fetch initial DHT nodes for storage), by default = mainnet url +## NIPs NIPs | latest commit hash implemented | comments --- | --- | --- [01](https://github.com/nostr-protocol/nips/blob/master/01.md) | [9971db3](https://github.com/nostr-protocol/nips/commit/9971db355164815c986251f8f89d1c7c70ec9e53) diff --git a/server/http/auth_nip98.go b/server/http/auth_nip98.go index 3dca1ec..2769eeb 100644 --- a/server/http/auth_nip98.go +++ b/server/http/auth_nip98.go @@ -19,7 +19,7 @@ import ( type ( Token interface { PubKey() string - MasterKey() string + MasterPubKey() string ExpectedHash() string ValidateAttestation(ctx context.Context, kind int, now time.Time) error } @@ -106,7 +106,7 @@ func (a *authNostr) VerifyToken(gCtx *gin.Context, token string, now time.Time) func (t *nostrToken) PubKey() string { return t.ev.PubKey } -func (t *nostrToken) MasterKey() string { +func (t *nostrToken) MasterPubKey() string { return t.ev.GetMasterPublicKey() } func (t *nostrToken) ExpectedHash() string { @@ -114,7 +114,7 @@ func (t *nostrToken) ExpectedHash() string { } func (t *nostrToken) ValidateAttestation(ctx context.Context, kind int, now time.Time) error { - if t.ev.PubKey == t.MasterKey() { + if t.ev.PubKey == t.MasterPubKey() { return nil } attestationEvent := query.GetStoredEvents(ctx, &model.Subscription{model.Filters{model.Filter{ diff --git a/server/http/storage_nip96.go b/server/http/storage_nip96.go index 5f376db..efe94ae 100644 --- a/server/http/storage_nip96.go +++ b/server/http/storage_nip96.go @@ -130,7 +130,7 @@ func (s *storageHandler) Upload() gin.HandlerFunc { upload.ContentType = gomime.TypeByExtension(filepath.Ext(upload.File.Filename)) } - storagePath, _ := s.storageClient.BuildUserPath(token.MasterKey(), upload.ContentType) + storagePath, _ := s.storageClient.BuildUserPath(token.MasterPubKey(), upload.ContentType) uploadingFilePath := filepath.Join(storagePath, upload.File.Filename) relativePath := upload.File.Filename if err := os.MkdirAll(filepath.Dir(uploadingFilePath), 0o755); err != nil { @@ -171,7 +171,7 @@ func (s *storageHandler) Upload() gin.HandlerFunc { os.Remove(uploadingFilePath) return } - bagID, url, existed, err := s.storageClient.StartUpload(ctx, token.PubKey(), token.MasterKey(), relativePath, hex.EncodeToString(hash), &storage.FileMetaInput{ + bagID, url, existed, err := s.storageClient.StartUpload(ctx, token.PubKey(), token.MasterPubKey(), relativePath, hex.EncodeToString(hash), &storage.FileMetaInput{ Hash: hash, Caption: upload.Caption, Alt: upload.Alt, @@ -224,7 +224,7 @@ func (s *storageHandler) Download() gin.HandlerFunc { gCtx.JSON(http.StatusBadRequest, uploadErr("filename is required")) return } - url, err := s.storageClient.DownloadUrl(token.MasterKey(), file) + url, err := s.storageClient.DownloadUrl(token.MasterPubKey(), file) if err != nil { if errors.Is(err, storage.ErrNotFound) { gCtx.Status(http.StatusNotFound) @@ -260,7 +260,7 @@ func (s *storageHandler) Delete() gin.HandlerFunc { gCtx.JSON(http.StatusBadRequest, uploadErr("filehash is required")) return } - if err := s.storageClient.Delete(token.PubKey(), token.MasterKey(), file); err != nil { + if err := s.storageClient.Delete(token.PubKey(), token.MasterPubKey(), file); err != nil { log.Printf("ERROR: %v", errors.Wrap(err, "failed to delete file")) if errors.Is(err, storage.ErrNotFound) || errors.Is(err, storage.ErrForbidden) { gCtx.JSON(http.StatusForbidden, uploadErr("user do not own file")) @@ -295,9 +295,9 @@ func (s *storageHandler) ListFiles() gin.HandlerFunc { if params.Count == 0 { params.Count = 10 } - total, filesList, err := s.storageClient.ListFiles(token.MasterKey(), params.Page, params.Count) + total, filesList, err := s.storageClient.ListFiles(token.MasterPubKey(), params.Page, params.Count) if err != nil { - log.Printf("ERROR: %v", errors.Wrapf(err, "failed to list files for user %v", token.MasterKey())) + log.Printf("ERROR: %v", errors.Wrapf(err, "failed to list files for user %v", token.MasterPubKey())) gCtx.JSON(http.StatusInternalServerError, uploadErr("oops, error occured!")) return } From f649b138537b03ccac5691d418a3f2997fa42763 Mon Sep 17 00:00:00 2001 From: ice-cronus <105345303+ice-cronus@users.noreply.github.com> Date: Mon, 21 Oct 2024 14:21:07 +0300 Subject: [PATCH 17/18] fix comments --- server/http/auth_nip98.go | 15 +++++++++------ server/http/nip11_test.go | 7 ++++--- server/http/storage_nip96.go | 9 +++++---- storage/upload.go | 2 +- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/server/http/auth_nip98.go b/server/http/auth_nip98.go index 2769eeb..4a83ce7 100644 --- a/server/http/auth_nip98.go +++ b/server/http/auth_nip98.go @@ -6,12 +6,11 @@ import ( "context" "encoding/base64" "net/url" + "strings" "time" "github.com/cockroachdb/errors" "github.com/gin-gonic/gin" - "github.com/nbd-wtf/go-nostr" - "github.com/ice-blockchain/subzero/database/query" "github.com/ice-blockchain/subzero/model" ) @@ -49,12 +48,16 @@ func NewAuth() AuthClient { return &authNostr{} } +func getAuthHeader(gCtx *gin.Context) string { + return strings.TrimPrefix(gCtx.GetHeader("Authorization"), "Nostr ") +} + func (a *authNostr) VerifyToken(gCtx *gin.Context, token string, now time.Time) (Token, error) { bToken, err := base64.StdEncoding.DecodeString(token) if err != nil { return nil, errors.Wrap(err, "failed to unmarshal auth token: malformed base64") } - var event nostr.Event + var event model.Event if err = event.UnmarshalJSON(bToken); err != nil { return nil, errors.Wrap(err, "failed to unmarshal auth token: malformed event json") } @@ -101,7 +104,7 @@ func (a *authNostr) VerifyToken(gCtx *gin.Context, token string, now time.Time) if payloadTag := event.Tags.GetFirst([]string{"payload"}); payloadTag != nil && len(*payloadTag) > 1 { expectedHash = payloadTag.Value() } - return &nostrToken{ev: model.Event{event}, expectedHash: expectedHash}, nil + return &nostrToken{ev: event, expectedHash: expectedHash}, nil } func (t *nostrToken) PubKey() string { return t.ev.PubKey @@ -117,7 +120,7 @@ func (t *nostrToken) ValidateAttestation(ctx context.Context, kind int, now time if t.ev.PubKey == t.MasterPubKey() { return nil } - attestationEvent := query.GetStoredEvents(ctx, &model.Subscription{model.Filters{model.Filter{ + attestationEventIt := query.GetStoredEvents(ctx, &model.Subscription{model.Filters{model.Filter{ Kinds: []int{model.CustomIONKindAttestation}, Tags: model.TagMap{ "p": []string{t.PubKey()}, @@ -125,7 +128,7 @@ func (t *nostrToken) ValidateAttestation(ctx context.Context, kind int, now time }, }}) var allowed bool - for attestation, err := range attestationEvent { + for attestation, err := range attestationEventIt { if err != nil { return errors.Wrapf(err, "failed to get attestation event") } diff --git a/server/http/nip11_test.go b/server/http/nip11_test.go index d3636ba..2dd6f28 100644 --- a/server/http/nip11_test.go +++ b/server/http/nip11_test.go @@ -5,6 +5,7 @@ package http import ( "context" "fmt" + "github.com/ice-blockchain/subzero/database/query" "net/http" "os" "testing" @@ -16,7 +17,6 @@ import ( "github.com/stretchr/testify/require" "golang.org/x/net/http2" - "github.com/ice-blockchain/subzero/database/query" wsserver "github.com/ice-blockchain/subzero/server/ws" "github.com/ice-blockchain/subzero/server/ws/fixture" ) @@ -33,17 +33,18 @@ var pubsubServer *fixture.MockService func TestMain(m *testing.M) { serverCtx, serverCancel := context.WithTimeout(context.Background(), 10*time.Minute) defer serverCancel() + query.MustInit() initServer(serverCtx, serverCancel, 9997, storageRoot) http.DefaultClient.Transport = &http2.Transport{TLSClientConfig: fixture.LocalhostTLS()} - m.Run() + code := m.Run() serverCancel() + os.Exit(code) } func initServer(serverCtx context.Context, serverCancel context.CancelFunc, port uint16, storageRoot string) { wd, _ := os.Getwd() certFilePath := fmt.Sprintf(certPath, wd) keyFilePath := fmt.Sprintf(keyPath, wd) - query.MustInit() initStorage(serverCtx, storageRoot) uploader := NewUploadHandler(serverCtx) pubsubServer = fixture.NewTestServer(serverCtx, serverCancel, &wsserver.Config{ diff --git a/server/http/storage_nip96.go b/server/http/storage_nip96.go index efe94ae..c5409a5 100644 --- a/server/http/storage_nip96.go +++ b/server/http/storage_nip96.go @@ -94,7 +94,7 @@ func (s *storageHandler) Upload() gin.HandlerFunc { now := time.Now() ctx, cancel := context.WithTimeout(gCtx, mediaEndpointTimeout) defer cancel() - authHeader := strings.TrimPrefix(gCtx.GetHeader("Authorization"), "Nostr ") + authHeader := getAuthHeader(gCtx) token, authErr := s.auth.VerifyToken(gCtx, authHeader, now) if authErr != nil { log.Printf("ERROR: endpoint authentification failed: %v", errors.Wrap(authErr, "endpoint authentification failed")) @@ -181,6 +181,7 @@ func (s *storageHandler) Upload() gin.HandlerFunc { if err != nil { log.Printf("ERROR: failed to upload file: %v", errors.Wrap(err, "failed to upload file to ion storage")) gCtx.JSON(http.StatusInternalServerError, uploadErr("oops, error occured!")) + os.Remove(uploadingFilePath) return } resStatus := http.StatusCreated @@ -212,7 +213,7 @@ func (s *storageHandler) Upload() gin.HandlerFunc { func (s *storageHandler) Download() gin.HandlerFunc { return func(gCtx *gin.Context) { now := time.Now() - authHeader := strings.TrimPrefix(gCtx.GetHeader("Authorization"), "Nostr ") + authHeader := getAuthHeader(gCtx) token, authErr := s.auth.VerifyToken(gCtx, authHeader, now) if authErr != nil { log.Printf("ERROR: endpoint authentification failed: %v", errors.Wrap(authErr, "endpoint authentification failed")) @@ -242,7 +243,7 @@ func (s *storageHandler) Delete() gin.HandlerFunc { now := time.Now() ctx, cancel := context.WithTimeout(gCtx, mediaEndpointTimeout) defer cancel() - authHeader := strings.TrimPrefix(gCtx.GetHeader("Authorization"), "Nostr ") + authHeader := getAuthHeader(gCtx) token, authErr := s.auth.VerifyToken(gCtx, authHeader, now) if authErr != nil { log.Printf("ERROR: endpoint authentification failed: %v", errors.Wrap(authErr, "endpoint authentification failed")) @@ -276,7 +277,7 @@ func (s *storageHandler) Delete() gin.HandlerFunc { func (s *storageHandler) ListFiles() gin.HandlerFunc { return func(gCtx *gin.Context) { now := time.Now() - authHeader := strings.TrimPrefix(gCtx.GetHeader("Authorization"), "Nostr ") + authHeader := getAuthHeader(gCtx) token, authErr := s.auth.VerifyToken(gCtx, authHeader, now) if authErr != nil { log.Printf("ERROR: endpoint authentification failed: %v", errors.Wrap(authErr, "endpoint authentification failed")) diff --git a/storage/upload.go b/storage/upload.go index 41bc026..bf29782 100644 --- a/storage/upload.go +++ b/storage/upload.go @@ -61,7 +61,7 @@ func (c *client) StartUpload(ctx context.Context, userPubKey, masterPubKey, rela } bagID = hex.EncodeToString(bag.BagID) log.Printf("[STORAGE] INFO: new upload %v for user %v hash %v resulted in bag %v, total %v", relativePathToFileForUrl, masterPubKey, hash, bagID, bag.Header.FilesCount) - if newFile != nil { + if newFile != nil && c.debug { uplFile, err := bag.GetFileOffsets(relativePathToFileForUrl) if err != nil { return "", "", false, errors.Wrapf(err, "failed to get just created file from new bag") From 44ce3dffe840a8951a5d0a42b58887f5c851a98d Mon Sep 17 00:00:00 2001 From: ice-cronus <105345303+ice-cronus@users.noreply.github.com> Date: Mon, 21 Oct 2024 14:25:16 +0300 Subject: [PATCH 18/18] imports fix --- server/http/auth_nip98.go | 1 + server/http/nip11_test.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/server/http/auth_nip98.go b/server/http/auth_nip98.go index 4a83ce7..5adcdce 100644 --- a/server/http/auth_nip98.go +++ b/server/http/auth_nip98.go @@ -11,6 +11,7 @@ import ( "github.com/cockroachdb/errors" "github.com/gin-gonic/gin" + "github.com/ice-blockchain/subzero/database/query" "github.com/ice-blockchain/subzero/model" ) diff --git a/server/http/nip11_test.go b/server/http/nip11_test.go index 2dd6f28..193512a 100644 --- a/server/http/nip11_test.go +++ b/server/http/nip11_test.go @@ -5,7 +5,6 @@ package http import ( "context" "fmt" - "github.com/ice-blockchain/subzero/database/query" "net/http" "os" "testing" @@ -17,6 +16,7 @@ import ( "github.com/stretchr/testify/require" "golang.org/x/net/http2" + "github.com/ice-blockchain/subzero/database/query" wsserver "github.com/ice-blockchain/subzero/server/ws" "github.com/ice-blockchain/subzero/server/ws/fixture" )